use std::str;
use std::fmt;
use std::iter;
use std::convert::AsRef;

pub fn tokenize(s: &str) -> Result<Vec<Token>, SyntaxError> {
    Lexer::tokenize(s)
}

#[derive(PartialEq, Debug)]
// assigning tokens
pub enum Token {
    Lparen, //OpenParen as (,
    Rparen,//CloseParen as ),
    Comma,//,
    Minus,//-
    Plus,//+
    Assign,//=
    IsZero,
    If,
    Then,
    Else,
    Let,
    In,
    Proc,
    Letrec,
    Identifier(String),
    Integer(i32),
    Boolean(bool),
}

//to show the syntax error
pub struct SyntaxError {
    message: String,
    line: u32,
    column: u32,
}

impl fmt::Display for SyntaxError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "SyntaxError: {} (line: {}, column: {})", self.message, self.line, self.column)
    }
}
impl fmt::Debug for SyntaxError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "SyntaxError: {} (line: {}, column: {})", self.message, self.line, self.column)
    }
}

macro_rules! syntax_error {
    ($lexer:ident, $($arg:tt)*) => (
        return Err(SyntaxError { message: format!($($arg)*), line: $lexer.line, column: $lexer.column })
    )
}
struct Lexer<'a> {
    chars: iter::Peekable<str::Chars<'a>>,
    current: Option<char>,
    tokens: Vec<Token>,
    line: u32,
    column: u32,

}


impl<'a> Lexer<'a> {



    fn tokenize(s: &str) -> Result<Vec<Token>, SyntaxError> {
        let mut lexer = Lexer { chars: s.chars().peekable(), current: None, tokens: Vec::new(), line: 1, column: 0 };
        try!(lexer.run());
        Ok(lexer.tokens)
    }

     fn current(&self) -> Option<char> {
        self.current
    }

    fn advance(&mut self) {
        if self.current() == Some('\x0a') {
            self.line += 1;
            self.column = 1;
        } else {
            self.column += 1;
        }
        self.current = self.chars.next();
    }

    fn peek(&mut self) -> Option<char> {
        match self.chars.peek() {
            Some(c) => Some(*c),
            None => None
        }
    }

 fn run(&mut self) -> Result<(), SyntaxError> {
        self.advance();
        loop {
            match self.current() {
                Some(c) => {
                    match c {
                        _ if c.is_whitespace() => {
                            self.advance();
                        },
                        '\n' => {
                                        self.advance();
                                },
                        '(' => {
                            self.tokens.push(Token::Lparen);
                            self.advance();
                        },
                        ')' => {
                            self.tokens.push(Token::Rparen);
                            self.advance();
                        },
                        ',' => {
                            self.tokens.push(Token::Comma);
                            self.advance();
                        },
                        '-' => {
                            self.tokens.push(Token::Minus);
                            self.advance();
                            },
                        '+' => {
                            self.tokens.push(Token::Plus);
                            self.advance();
                        },
                        '=' => {
                            self.tokens.push(Token::Assign);
                            self.advance();
                        },
   
                        '0'...'9' => {
                            // don't advance -- let parse_number advance as needed
                            let val = try!(self.parse_number());
                            self.tokens.push(Token::Integer(val as i32));//change the type to i32 from i64 or other
                            try!(self.parse_delimiter());
                        },
                        '[' | ']' | '{' | '}' | '|' | '\\' | '/' | '/'=> {
                            syntax_error!(self, "Unexpected character: {}", c);
                        },
                        _ => {
                            let val = try!(self.parse_identifier());
                            let iszero_str = String::from("iszero");
                            let if_str = String::from("if");
                            let then_str = String::from("then");
                            let else_str = String::from("else");                            
                            let let_str = String::from("let");
                            let in_str = String::from("in");
                            let proc_str = String::from("proc");
                            let letrec_str = String::from("letrec");
                            if   if_str == val {self.tokens.push(Token::If);}
                            else if then_str  == val {self.tokens.push(Token::Then);}
                            else if else_str  == val {self.tokens.push(Token::Else);}
                            else if iszero_str == val {self.tokens.push(Token::IsZero);}
                            else if let_str  == val {self.tokens.push(Token::Let);}
                            else if in_str  == val {self.tokens.push(Token::In);}
                            else if proc_str  == val {self.tokens.push(Token::Proc);}
                            else if letrec_str  == val {self.tokens.push(Token::Letrec);}
                            else{ self.tokens.push(Token::Identifier(val));
                                     try!(self.parse_delimiter());
                                                        }                       

                            }
                        
                    }
                },
                None => break
            }
        };
        Ok(())
    }

     fn parse_number(&mut self) -> Result<i64, SyntaxError> {
        let mut s = String::new();
        loop {
            match self.current() {
                Some(c) => {
                    match c {
                        '0'...'9' => {
                            s.push(c);
                            self.advance();
                        },
                        _ => break
                    }
                },
                None => break
            }
        }
        match s.parse() {
            Ok(value) => Ok(value),
            Err(_) => { syntax_error!(self, "Not a number: {}", self.current().unwrap()); },
        }
    }

      fn parse_identifier(&mut self) -> Result<String, SyntaxError> {
        let mut s = String::new();
        loop {
            match self.current() {
                Some(c) => {
                    match c {
                        _ if c.is_whitespace() => {
                            break;
                        },
                        '(' | ')' | '[' | ']' | '{' | '}' | '\"' | ',' | '\'' | '`' | ';' | '|' | '\\' => {
                            break;
                        },
                        _ => {
                            s.push(c);
                            self.advance();
                        },
                    }
                },
                None => break
            }
        }
        Ok(s)
    }
 
  
    fn parse_delimiter(&mut self) -> Result<(), SyntaxError> {
        match self.current() {
            Some(c) => {
                match c {
                    _ if c.is_whitespace() => (),
                   '(' => {
                        self.tokens.push(Token::Lparen);
                        self.advance();
                        },
                    ',' => {
                        self.tokens.push(Token::Comma);
                        self.advance();
                        },
                    ')' => {
                        self.tokens.push(Token::Rparen);
                        self.advance();
                    },
                    _ => syntax_error!(self, "Unexpected character when looking for a delimiter: {}", c),
                }
            },
            None => ()
        };
        Ok(())
    }

}
#[test]
fn test_lexer_simple_lexing() {
    assert_eq!(tokenize("(+ 2 3)").unwrap(),
               vec![Token::OpenParen, Token::Identifier("+".to_string()), Token::Integer(2), Token::Integer(3), Token::CloseParen]);
}


