use lexer::*;
use std::rc::Rc;
use std::fmt;
use std::slice;

pub fn parse(tokens: &Vec<Token>) -> Result<Option<ProcLangExp>,ParseError> {
    Parser::parse(tokens)
}


#[derive (Debug,Clone)]
pub enum ProcLangExp{
    ConstExp(i32),
    Boolean(bool),
    DiffExp(Rc<ProcLangExp>,Rc<ProcLangExp>),
    IsZeroExp(Rc<ProcLangExp>),
    IfExp(Rc<ProcLangExp>,Rc<ProcLangExp>,Rc<ProcLangExp>),
    VarExp(String),
    LetExp(String,Rc<ProcLangExp>,Rc<ProcLangExp>),
    LetRecExp(String,String,Rc<ProcLangExp>,Rc<ProcLangExp>),
    PlusExp(Rc<ProcLangExp>,Rc<ProcLangExp>),
    ProcExp(String,Rc<ProcLangExp>),
    CallExp(Rc<ProcLangExp>,Rc<ProcLangExp>),
}
impl ProcLangExp {
    // a string representation, to be used by the formatter, for each type of LetLangExp
    pub fn to_string(&self) -> String {
        match self.clone() {
            ProcLangExp::ConstExp(int)       => int.to_string(),
            ProcLangExp::Boolean(bool)       => bool.to_string(),
            ProcLangExp::DiffExp(e1, e2)     => {let mut temp: String = "-(".to_string();
                                                temp.push_str(&(e1.to_string()));
                                                temp.push_str(&(", ".to_string()));
                                                temp.push_str(&(e2.to_string()));
                                                temp.push_str(&(")".to_string()));
                                                temp}
			ProcLangExp::PlusExp(e1, e2)     => {let mut temp: String = "+(".to_string();
                                                temp.push_str(&(e1.to_string()));
                                                temp.push_str(&(", ".to_string()));
                                                temp.push_str(&(e2.to_string()));
                                                temp.push_str(&(")".to_string()));
                                                temp}
            ProcLangExp::IsZeroExp(e)        => {let mut temp = "iszero(".to_string();
                                                temp.push_str(&(e.to_string()));
                                                temp.push_str(&(")".to_string()));
                                                temp}
            ProcLangExp::IfExp(e1, e2, e3)   => {let mut temp = "if ".to_string();
                                                temp.push_str(&(e1.to_string()));
                                                temp.push_str(&(" then ".to_string()));
                                                temp.push_str(&(e2.to_string()));
                                                temp.push_str(&(" else ".to_string()));
                                                temp.push_str(&(e3.to_string()));
                                                temp}
            ProcLangExp::VarExp(var)         => var,
            ProcLangExp::LetExp(v, e1, e2)   => {let mut temp = "let ".to_string();
                                                temp.push_str(&(v.to_string()));
                                                temp.push_str(&(" = ".to_string()));
                                                temp.push_str(&(e1.to_string()));
                                                temp.push_str(&(" in ".to_string()));
                                                temp.push_str(&(e2.to_string()));
                                                temp}
			ProcLangExp::LetRecExp(v1,v2,e1,e2)=> {let mut temp = "letrec ".to_string();
                                                temp.push_str(&(v1.to_string()));
												temp.push_str(&(v2.to_string()));
                                                temp.push_str(&(" = ".to_string()));
                                                temp.push_str(&(e1.to_string()));
                                                temp.push_str(&(" in ".to_string()));
                                                temp.push_str(&(e2.to_string()));
                                                temp}
			ProcLangExp::ProcExp(v1,e1)=>{let mut temp = "letproc ".to_string();
                                                temp.push_str(&(v1.to_string()));
                                                temp.push_str(&(" = ".to_string()));
                                                temp.push_str(&(e1.to_string()));
                                                temp}
			ProcLangExp::CallExp(e1,e2)  => {let mut temp = "call ".to_string();
                                                temp.push_str(&(e1.to_string()));
                                                temp.push_str(&(e2.to_string()));
                                                temp}
        }}
}

impl fmt::Display for ProcLangExp { // do not change this code
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut s = format!("");
        let s1 = self.to_string();
        s.push_str(&s1);
        write!(f, "{}", s)
    }}
pub struct ParseError {
    message: String,
}

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

macro_rules! parse_error {
    ($($arg:tt)*) => (
        return Err(ParseError { message: format!($($arg)*)})
    )
}
pub fn show_parse(s: &String)->Result<ProcLangExp,ParseError>{
    println!("Parsing: {}",s);
    let tok_result=tokenize(s);
    match tok_result{
        Ok(v) => {
            let ast = try!(parse(&v));
            println!("AST: \n{:?}\n",ast);
            // println!("Stringified AST: ()\n",ast);
            Ok(ast.unwrap())},
            //  Ok(ast)},
        Err(_e) => parse_error!("Error in building AST"),
        }
    }
struct Parser<'a> {
    tokens: slice::Iter<'a, Token>,
}

impl<'a> Parser<'a> {
    fn parse(tokens: &Vec<Token>) -> Result<Option<ProcLangExp>, ParseError> {
        let mut parser = Parser { tokens: tokens.iter() };
        parser.parse_ProcLangExps()
    }

    fn parse_ProcLangExps(&mut self)->Result<Option<ProcLangExp>,ParseError>{
        let temp = self.parse_ProcLangExp_token();
        Ok(temp.unwrap())
    }
  fn change_option_to_Proc_expression(&mut self,exp: Option<ProcLangExp>)->Result<ProcLangExp, ParseError>{
    match exp {
        Some(v) =>{ Ok(v)},
        None =>{parse_error!("Missing expected expression") },
    }
    }

    fn IsZerochk(&mut self)->Result<Option<ProcLangExp>, ParseError>{
        //let e =self.LetLangExp_loop(exp);
        //println!("IsZeroExp(");
        self.tokens.next();//to skip Lparen
        let exp_get = self.parse_ProcLangExp_token();
        let exp = exp_get.unwrap().take();
        self.tokens.next();//to skip Rparen
        let e =ProcLangExp::IsZeroExp(Rc::new(exp.unwrap()));
        Ok(Some(e))
    }
    fn Diffexpchk(&mut self)->Result<Option<ProcLangExp>, ParseError>{
        //let e =self.LetLangExp_loop(exp);
        self.tokens.next();//For Lparen
        let exp_get1 = self.parse_ProcLangExp_token();
        let exp_1 = exp_get1.unwrap().take();
        self.tokens.next();//to skip comma
        let exp_get2 = self.parse_ProcLangExp_token();
        let exp_2 = exp_get2.unwrap().take();
        self.tokens.next();//to skip Rparen
        let e =ProcLangExp::DiffExp(Rc::new(exp_1.unwrap()),Rc::new(exp_2.unwrap()));
        Ok(Some(e))
    }
    
    fn Plusexpchk(&mut self)->Result<Option<ProcLangExp>, ParseError>{
        //let e =self.LetLangExp_loop(exp);
        self.tokens.next();//For Lparen
        let exp_get1 = self.parse_ProcLangExp_token();
        let exp_1 = exp_get1.unwrap().take();
        self.tokens.next();//to skip comma
        let exp_get2 = self.parse_ProcLangExp_token();
        let exp_2 = exp_get2.unwrap().take();
        self.tokens.next();//to skip Rparen
        let e =ProcLangExp::PlusExp(Rc::new(exp_1.unwrap()),Rc::new(exp_2.unwrap()));
        Ok(Some(e))
    }

    fn Ifexpchk(&mut self)->Result<Option<ProcLangExp>, ParseError>{
        //let e =self.LetLangExp_loop(exp);
        let exp_get1 = self.parse_ProcLangExp_token();
        let exp_1 = exp_get1.unwrap().take();
        self.tokens.next();//to skip then
        let exp_get2 = self.parse_ProcLangExp_token();
        let exp_2 = exp_get2.unwrap().take();
        self.tokens.next();//to skip else
        let exp_get3 = self.parse_ProcLangExp_token();
        let exp_3 = exp_get3.unwrap().take();
        let e =ProcLangExp::IfExp(Rc::new(exp_1.unwrap()),Rc::new(exp_2.unwrap()),Rc::new(exp_3.unwrap()));
        Ok(Some(e))
    }
    fn Callexpchk(&mut self)->Result<Option<ProcLangExp>, ParseError>{
        //let e =self.LetLangExp_loop(exp);
        let exp_get1 = self.parse_ProcLangExp_token();
        let exp_1 = exp_get1.unwrap().take();
        //self.tokens.next();//to skip comma
        let exp_get2 = self.parse_ProcLangExp_token();
        let exp_2 = exp_get2.unwrap().take();
        self.tokens.next();//to skip Rparen
        let e =ProcLangExp::CallExp(Rc::new(exp_1.unwrap()),Rc::new(exp_2.unwrap()));
        Ok(Some(e))
    }

    fn Letexpchk(&mut self)->Result<Option<ProcLangExp>, ParseError>{
        //let e =self.LetLangExp_loop(exp);
        //to find the identifier
        /*self.tokens.next(){
        let exp_get1 = self.Identifierchk(token);
        let exp_1 = exp_get1.unwrap().take();
        }*/
               /* let exp_1 = exp_get1.unwrap().take();
         let exp_1_str = match exp_1{
            Some(x) =>  {let m= Rc::try_unwrap(x);
                        match m.unwrap(){
                            LetLangExp::VarExp(e) => {e},
                            _ => {"Error"},
                        }
                            },
            _ => (),
        };
        */
        let ident_get1 = self.parse_ProcLangExp_token();
        let ident_1_str = self.RC_exp_to_string(ident_get1.unwrap());
        self.tokens.next();//to avoid the assigner
        let exp_get2 = self.parse_ProcLangExp_token();
        let exp_2 = exp_get2.unwrap().take();
        self.tokens.next();//to skip in
        let exp_get3 = self.parse_ProcLangExp_token();
        let exp_3 = exp_get3.unwrap().take();
        let e =ProcLangExp::LetExp(ident_1_str.unwrap(),Rc::new(exp_2.unwrap()),Rc::new(exp_3.unwrap()));
        Ok(Some(e))
    }

    fn Letrecexpchk(&mut self)->Result<Option<ProcLangExp>, ParseError>{
        let ident_get1 = self.parse_ProcLangExp_token();//identifier
        let ident_1_str = self.RC_exp_to_string(ident_get1.unwrap());
        self.tokens.next();//to avoid the Left paren
        let ident_get2 = self.parse_ProcLangExp_token();//identifier
        let ident_2_str = self.RC_exp_to_string(ident_get2.unwrap());
        self.tokens.next();//to avoid the right paren
        self.tokens.next();//to avoid the assigner

        let exp_get3 = self.parse_ProcLangExp_token();
        let exp_3 = exp_get3.unwrap().take();
        self.tokens.next();//to skip in
        let exp_get4 = self.parse_ProcLangExp_token();
        let exp_4 = exp_get4.unwrap().take();

        let e = ProcLangExp::LetRecExp(ident_1_str.unwrap(),ident_2_str.unwrap(),Rc::new(exp_3.unwrap()),Rc::new(exp_4.unwrap()));
        Ok(Some(e))
    }

    fn Procexpchk(&mut self)->Result<Option<ProcLangExp>, ParseError>{
        self.tokens.next();//to avoid the Left paren
        let ident_get1 = self.parse_ProcLangExp_token();//identifier
        let ident_1_str = self.RC_exp_to_string(ident_get1.unwrap());
        self.tokens.next();//to avoid the right paren

        let exp_get2 = self.parse_ProcLangExp_token();
        let exp_2 = exp_get2.unwrap().take();

        let e =ProcLangExp::ProcExp(ident_1_str.unwrap(),Rc::new(exp_2.unwrap()));
        Ok(Some(e))
    }

    fn RC_exp_to_string(&mut self,exp :Option<ProcLangExp>)-> Result<String, ParseError>{
        match exp{
            Some(x) =>  {let m= x;
                        match m{
                            ProcLangExp::VarExp(e) => {Ok(e)}, 
                            _ => {parse_error!("Identifier should be after Let_expression-----------------111111111111111")},
                        }
                            },
            _ => {parse_error!("Identifier should be after Let_expression-------------------2222222222222222")},
        }
        
    }
    
     fn parse_ProcLangExp_token(&mut self) -> Result<Option<ProcLangExp>, ParseError> {
        match self.tokens.next() {
                Some(token) => {
                match *token {
                       Token::IsZero => {                         
                                        let e = self.IsZerochk();
                                        Ok(e.unwrap())
                       },
                       Token::Lparen=>{let e = self.Callexpchk();
                                        Ok(e.unwrap())},
                       Token::Rparen=>{Ok(None)},
                       Token::Comma=>{Ok(None)},
                       Token::Assign=>{Ok(None)},
                       Token::Minus=>{
                           let e = self.Diffexpchk();
                           Ok(e.unwrap())
                       },
                       Token::Plus=>{
                           let e = self.Plusexpchk();
                           Ok(e.unwrap())
                       },
                       Token::If=>{
                           let e = self.Ifexpchk();
                           Ok(e.unwrap())
                       },                     
                    Token::Let=>{
                        let e = self.Letexpchk();
                        Ok(e.unwrap())
                    },
                    Token::Letrec=>{
                        let e = self.Letrecexpchk();
                        Ok(e.unwrap())
                    },
                     Token::Proc=>{
                        let e = self.Procexpchk();
                        Ok(e.unwrap())
                    },
                     Token::Identifier(ref val) => {
                         let e = ProcLangExp::VarExp(val.clone());
                         //println!("chk identifier part {}",val);
                         Ok(Some(e))
                    },
                    Token::Else=>{Ok(None)},
                    Token::Then=>{Ok(None)},
                    Token::In=>{Ok(None)},
                    Token::Integer(val)=>{let e = ProcLangExp::ConstExp(val);
                        Ok(Some(e))}
                    Token::Boolean(val)=>{let e = ProcLangExp::Boolean(val);
                        Ok(Some(e))}   
                                       }
                }
                None=>{
                    Ok(None)
                }
            }
        }
    }


        /*let exp_1 = match self.tokens.next() {
                Some(token) => {
                match *token {
                Token::Identifier(ref val) => {
                            Ok(val.clone())
                            },
                            _=>{
                                Ok()
                            }
                        } 
        } };*/