#![no_std]

#[macro_use]
extern crate bitflags;
extern crate cursebox;
extern crate libc;

use core::char;
use core::marker::PhantomData;
use core::mem;
use cursebox::*;
use libc::c_int;

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Key {
    Tab,
    Enter,
    Esc,
    Backspace,
    Right,
    Left,
    Up,
    Down,
    Delete,
    Insert,
    Home,
    End,
    PgUp,
    PgDn,
    Char(char),
    Ctrl(char),
    F(u16),
}

impl Key {
    pub fn from_code(code: u16) -> Option<Key> {
        use Key::*;
        match code {
              8 => Some(Backspace),
              9 => Some(Tab),
             13 => Some(Enter),
            1...26 => Some(Ctrl(unsafe { char::from_u32_unchecked('`' as u32 + code as u32) })),
             27 => Some(Esc),
             28 => Some(Ctrl('\\')),
             29 => Some(Ctrl(']')),
             30 => Some(Ctrl('6')),
             31 => Some(Ctrl('/')),
             32 => Some(Char(' ')),
            127 => Some(Backspace),
            TB_KEY_ARROW_LEFT  => Some(Left),
            TB_KEY_ARROW_RIGHT => Some(Right),
            TB_KEY_ARROW_UP    => Some(Up),
            TB_KEY_ARROW_DOWN  => Some(Down),
            TB_KEY_INSERT => Some(Insert),
            TB_KEY_DELETE => Some(Delete),
            TB_KEY_HOME => Some(Home),
            TB_KEY_END  => Some(End),
            TB_KEY_PGUP => Some(PgUp),
            TB_KEY_PGDN => Some(PgDn),
            TB_KEY_F1  => Some(F( 1)),
            TB_KEY_F2  => Some(F( 2)),
            TB_KEY_F3  => Some(F( 3)),
            TB_KEY_F4  => Some(F( 4)),
            TB_KEY_F5  => Some(F( 5)),
            TB_KEY_F6  => Some(F( 6)),
            TB_KEY_F7  => Some(F( 7)),
            TB_KEY_F8  => Some(F( 8)),
            TB_KEY_F9  => Some(F( 9)),
            TB_KEY_F10 => Some(F(10)),
            TB_KEY_F11 => Some(F(11)),
            TB_KEY_F12 => Some(F(12)),
            _ => None,
        }
    }
}

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[repr(C, u16)]
pub enum Color {
    Black   = 0x00,
    Red     = 0x01,
    Green   = 0x02,
    Yellow  = 0x03,
    Blue    = 0x04,
    Magenta = 0x05,
    Cyan    = 0x06,
    White   = 0x07,
    Default = 0x0F,
}

bitflags! {
    #[repr(C)]
    pub struct Face: u8 {
        const BOLD      = 0x10;
        const UNDERLINE = 0x20;
        const REVERSE   = 0x40;
    }
}

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Event {
    Key(Key),
    Resize(u32, u32),
}

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Failure {
    Unknown,
    UnsupportedTerminal,
    FailedToOpenTty,
    PipeTrapError,
}

#[derive(Debug)]
pub struct Term(PhantomData<*mut ()>);

impl Term {
    pub fn init() -> Result<Self, Failure> {
        let c = unsafe { tb_init() };
        if c >= 0 { Ok(Term(PhantomData)) } else {
            use Failure::*;
            Err(match c {
                    TB_EUNSUPPORTED_TERMINAL => UnsupportedTerminal,
                    TB_EFAILED_TO_OPEN_TTY => FailedToOpenTty,
                    TB_EPIPE_TRAP_ERROR => PipeTrapError,
                    _ => Unknown,
                })
        }
    }


    pub fn width(&self) -> usize { unsafe { tb_width() as usize } }
    pub fn height(&self) -> usize { unsafe { tb_height() as usize } }

    pub fn clear(&mut self) { unsafe { tb_clear() } }
    pub fn freshen(&mut self) { unsafe { tb_present() } }

    pub fn print_char(&mut self, x_pos: usize, y_pos: usize,
                      face: Face, fg: Color, bg: Color, x: char) {
        unsafe { tb_change_cell(x_pos as c_int, y_pos as c_int, x as u32,
                                fg as u16 | ((face.bits as u16) << 8), bg as u16) };
    }

    pub fn next_event(&mut self, timeout: Option<u32>) -> Result<Option<Event>, ()> { unsafe {
        let mut ev: RawEvent = mem::uninitialized();
        let c = match timeout {
            Some(t_msec) => tb_peek_event(&mut ev, t_msec as c_int),
            None => tb_poll_event(&mut ev),
        };
        if c < 0 { return Err(()) };
        match c as u8 {
            0 => Ok(None),
            TB_EVENT_KEY => Ok(if ev.key == 0 { char::from_u32(ev.ch).map(Key::Char) }
                               else { Key::from_code(ev.key) }.map(Event::Key)),
            TB_EVENT_RESIZE => Ok(Some(Event::Resize(ev.w as u32, ev.h as u32))),
            _ => Err(()),
        }
    } }

    pub fn set_cursor(&mut self, x: usize, y: usize) {
        unsafe { tb_set_cursor(x as c_int, y as c_int) }
    }
}

impl Drop for Term {
    #[inline] fn drop(&mut self) {
        unsafe { tb_shutdown() };
    }
}