extern crate std;
extern crate nix;

use std::fmt;
use std::time::Duration;
use std::io::prelude::*;
use std::io::{Error, ErrorKind, SeekFrom};
use std::fs::File;
use std::path::Path;

use std::os::unix::io::RawFd;

#[cfg(feature = "measure")]
use measure::Measure;

fn duration_to_ms(duration: Duration) -> u64 {
    duration.as_secs() * 1_000u64 + duration.subsec_nanos() as u64 / 1_000_000u64
}

#[derive(Clone, Copy, Debug)]
pub enum Direction {
    Out,
    In,
}
impl Direction {
    pub fn from_str(s: &str) -> Option<Direction> {
        match s {
            "out" => Some(Direction::Out),
            "in" => Some(Direction::In),
            _ => None,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match *self {
            Direction::Out => "out",
            Direction::In => "in",
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Edge {
    None,
    Rising,
    Falling,
    Both,
}
impl Edge {
    pub fn from_str(s: &str) -> Option<Edge> {
        match s {
            "none" => Some(Edge::None),
            "rising" => Some(Edge::Rising),
            "falling" => Some(Edge::Falling),
            "both" => Some(Edge::Both),
            _ => None,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match *self {
            Edge::None => "none",
            Edge::Rising => "rising",
            Edge::Falling => "falling",
            Edge::Both => "both",
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Value {
    High,
    Low,
}
impl Value {
    pub fn from_str(s: &str) -> Option<Value> {
        match s {
            "1" => Some(Value::High),
            "0" => Some(Value::Low),
            _ => None,
        }
    }

    pub fn from_buffer(b: &[u8; 1]) -> Option<Self> {
        Value::from_char(b[0])
    }

    pub fn from_char(c: u8) -> Option<Self> {
        match c {
            48 => Some(Value::Low), // '0'
            49 => Some(Value::High), // '1'
            _ => None,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match *self {
            Value::High => "1",
            Value::Low => "0",
        }
    }
}

#[derive(Debug)]
pub struct Port {
    pub number: u8,
    pub direction: Direction,
}

#[derive(Debug)]
pub struct SyncPort {
    pub port: Port,
    file: File,
    buffer: [u8; 1],
}
pub struct AsyncPort {
    pub port: Port,
    pub edge: Edge,
    file: RawFd,
    fds: [nix::poll::PollFd; 1],
    buffer: [u8; 1],
}

impl fmt::Debug for AsyncPort {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "AsyncPort {{ port: {:?}, edge: {:?}, file: {:?}, fds: [?], buffer: {:?} }}",
               self.port,
               self.edge,
               self.file,
               self.buffer)
    }
}

impl Port {
    pub fn new(number: u8, direction: Direction) -> std::io::Result<Port> {
        let port = Port {
            number: number,
            direction: direction,
        };

        port.init()?;
        Ok(port)
    }

    fn init(&self) -> std::io::Result<()> {
        self.export().ok();
        self.set_direction()?;

        Ok(())
    }

    pub fn drop(&mut self) {
        self.unexport().ok();
    }

    fn write_path(path: &str, value: &str) -> std::io::Result<()> {
        File::create(Path::new(path))?
            .write_all(value.as_bytes())
    }

    fn export(&self) -> std::io::Result<()> {
        Port::write_path("/sys/class/gpio/export", self.number.to_string().as_str())
    }
    fn unexport(&self) -> std::io::Result<()> {
        Port::write_path("/sys/class/gpio/unexport", self.number.to_string().as_str())
    }

    fn set_direction(&self) -> std::io::Result<()> {
        Port::write_path(format!("/sys/class/gpio/gpio{}/direction", self.number).as_str(),
                         self.direction.as_str())
    }
}

impl SyncPort {
    pub fn new(number: u8, direction: Direction) -> std::io::Result<SyncPort> {
        Ok(SyncPort {
               port: Port::new(number, direction)?,
               file: SyncPort::open(number, direction)?,
               buffer: [0; 1],
           })
    }

    fn open(number: u8, direction: Direction) -> std::io::Result<File> {
        let path = format!("/sys/class/gpio/gpio{}/value", number);
        let path = Path::new(path.as_str());

        Ok(match direction {
               Direction::Out => File::create(path)?,
               Direction::In => File::open(path)?,
           })
    }

    pub fn read(&mut self) -> std::io::Result<Value> {
        self.file.seek(SeekFrom::Start(0))?;
        self.file.read_exact(&mut self.buffer)?;

        Value::from_buffer(&self.buffer).ok_or(Error::new(ErrorKind::InvalidData,
                                                          "Unrecognized GPIO Value"))
    }

    pub fn write(&mut self, value: Value) -> std::io::Result<()> {
        self.file.write_all(value.as_str().as_bytes())
    }
}

impl AsyncPort {
    pub fn new(number: u8, edge: Edge) -> std::io::Result<AsyncPort> {
        let port = Port::new(number, Direction::In)?;
        let file = AsyncPort::open(number)?;
        let port = AsyncPort {
            port: port,
            edge: edge,
            file: file,
            fds: [nix::poll::PollFd::new(file, nix::poll::POLLPRI, nix::poll::EventFlags::empty())],
            buffer: [0; 1],
        };

        port.init()?;
        Ok(port)
    }
    fn init(&self) -> std::io::Result<()> {
        self.set_edge()?;

        Ok(())
    }

    fn open(number: u8) -> std::io::Result<RawFd> {
        nix::fcntl::open(format!("/sys/class/gpio/gpio{}/value", number).as_str(),
                         nix::fcntl::O_RDONLY,
                         nix::sys::stat::Mode::empty())
                .or(Err(Error::new(ErrorKind::Other, "open failed")))
    }

    fn get_timeout(timeout: Option<Duration>) -> i32 {
        match timeout {
            None => -1,
            Some(t) => duration_to_ms(t) as i32,
        }
    }

    fn nix_poll(&mut self, timeout: i32) -> nix::Result<i32> {
        nix::poll::poll(&mut self.fds, timeout)
    }

    fn poll_read(&mut self, poll: nix::Result<i32>) -> std::io::Result<Option<Value>> {
        let poll = poll.or(Err(Error::new(ErrorKind::Other, "poll failed")))?;

        if poll == 0 {
            return Ok(None);
        }

        nix::unistd::lseek(self.file, 0, nix::unistd::Whence::SeekSet)
            .or(Err(Error::new(ErrorKind::Other, "lseek failed")))?;

        nix::unistd::read(self.file, &mut self.buffer)
            .or(Err(Error::new(ErrorKind::Other, "read failed")))?;

        Value::from_buffer(&self.buffer).map_or(Err(Error::new(ErrorKind::InvalidData,
                                                               "Unrecognized GPIO Value")),
                                                |v| Ok(Some(v)))
    }

    pub fn poll(&mut self, timeout: Option<Duration>) -> std::io::Result<Option<Value>> {
        let poll = self.nix_poll(Self::get_timeout(timeout));

        self.poll_read(poll)
    }

    #[cfg(feature = "measure")]
    pub fn poll_measure(&mut self,
                        timeout: Option<Duration>,
                        measure: &mut Measure)
                        -> std::io::Result<Option<Value>> {
        let timeout = Self::get_timeout(timeout);

        measure.pause();
        let poll = self.nix_poll(timeout);
        measure.start();

        self.poll_read(poll)
    }

    fn set_edge(&self) -> std::io::Result<()> {
        Port::write_path(format!("/sys/class/gpio/gpio{}/edge", self.port.number).as_str(),
                         self.edge.as_str())
    }
}
