use crate::lexer::*;
use crate::*;

use core::error::Error;
use std::fmt;

#[derive(Debug)]
struct ParseError {
    position: Position,
    what: String,
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error parsing input: {}", "yeah")
    }
}

impl Error for ParseError { }

struct Context<'a> {
    cur: [Option<Symbol<'a>>;2],
    icur: usize,
    ll: Lexer<'a>,
}

impl<'a> Context<'a> {
    pub fn new( mut ll: Lexer<'a> ) -> Self {
        let cur = [ll.next(), ll.next()];
        Self {
            cur,
            icur: 0,
            ll
        }
    }
    
    pub fn next(&mut self) -> Option<Symbol<'a>> {
        self.cur[self.icur] = self.ll.next();
        self.icur = (self.icur+1)%2;
        println!(" - cur: {:?}, peek: {:?}", self.cur(), self.peek() );
        self.cur[self.icur]
    }

    pub fn cur(&self) -> Option<Symbol<'a>> {
        self.cur[self.icur]
    }

    pub fn peek(&self) -> Option<Symbol<'a>> {
        self.cur[(self.icur+1)%2]
    }
}

pub struct Parser { 
}

/**
 * input: input tag
 *      | input tag_pair
 *      | input text
 *      |
 *      ;
 *
 * tag: '[|' tag_guts '|]'
 *    ;
 *
 * tag_pair: open_tag tag_body close_tag
 *         ;
 *
 * tag_body: tag_body tag
 *         | tag_body tag_pair
 *         | tag_body text
 *         |
 *         ;
 *
 * open_tag: '[|' tag_guts '|>'
 *         ;
 *
 * close_tag: '<|' token '|]'
 *          ;
 *
 * tag_guts: 'fragment' literal props
 *         | 'section' literal
 *         | 'output' literal
 *         ;
 *
 * literal: '"' [^"]* '"'
 *        ;
 *
 * props: props token '=' literal
 *      |
 *      ;
 *
 * disambiguate (tm):
 *
 * input: input tag
 *      | input text
 *      |
 *      ;
 *
 * tag: open_tag_base unary_tag
 *    | open_tag_base binary_tag
 *    ;
 *
 * open_tag_base: '[|' tag_guts
 *              ;
 *
 * unary_tag: '|]'
 *          ;
 *
 * binary_tag: '|>' tag_body close_tag
 *           ;
 *
 * tag_body: tag_body tag
 *         | tag_body text
 *         |
 *         ;
 *
 * close_tag: '<|' token '|]'
 *          ;
 *
 * tag_guts: 'fragment' literal props
 *         | 'section' literal
 *         | 'output' literal
 *         ;
 *
 * literal: '"' [^"]* '"'
 *        ;
 *
 * props: props token '=' literal
 *      |
 *      ;
 */
impl Parser {
    pub fn new() -> Self {
        Self {
        }
    }

    fn lex_error(&self, error: &Symbol) -> Result<(),Box<dyn Error>> {
        if let SymbolType::Error{what} = error.symbol() {
            Err(Box::new(ParseError {
                position: error.start().clone(),
                what: format!("What: {:?}", *what)
            }))
        } else {
            Err(Box::new(ParseError {
                position: Position::new(0,0),
                what: "Not an error?".to_string(),
            }))
        }
    }

    pub fn parse(&mut self, crim: &mut Crimtag, src: &str ) -> Result<(),Box<dyn Error>> {
        let mut ll = Lexer::new( src );
        let mut ctx = Context::new( ll );

        // Parse the root of the file, the input context
        self.p_input( &mut ctx )?;

        Ok(())
    }

    fn p_input(&mut self, ctx: &mut Context ) -> Result<(), Box<dyn Error>> {
        loop {
            if ctx.cur().is_none() {
                break;
            }
            match ctx.cur().unwrap().symbol() {
                SymbolType::Text(_) => { /* Skip top level text */ }
                SymbolType::StartFlat => {
                    self.parse_tag( ctx );
                }
                SymbolType::Error{..} => {
                    return self.lex_error( &ctx.cur().unwrap() );
                }
                _ => {

                }
            }
            ctx.next();
        }
        Ok(())
    }

    fn parse_tag(&mut self, ctx: &mut Context) -> Result<(), Box<dyn Error>> {
        let mut tb = TagBuilder::new();

        let start_sym = ctx.cur().unwrap();

        if ctx.next().is_some() {
            if let SymbolType::Token(s) = ctx.next().unwrap().symbol() {
                tb.set_name( s.to_string() );
            } else {
                return Err(Box::new(ParseError{position: ctx.cur().unwrap().start().clone(), what: "Unexpecetd symbol".to_string()}));
            }
        } else {
            return Err(Box::new(ParseError{position: Position::new(0,0), what: "Unexpecetd end of stream".to_string()}));
        }

        self.parse_tag_params( ctx, &mut tb )?;
        self.parse_tag_props( ctx, &mut tb )?;

        Ok(())
    }

    fn parse_tag_params(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box<dyn Error>> {
        loop {
            if ctx.cur().is_none() {

                return Err(Box::new(ParseError{position: Position::new(0,0), what: "Unexpecetd end of stream".to_string()}));
            }
            if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() {
                if let Some(p) = ctx.peek() {
                    if p.check_type(|t| matches!(t, SymbolType::Equals)) {
                        break;
                    }
                }
                else {
                    tb.add_param( s.to_string() );
                }
            }
            ctx.next();
        }
        Ok(())
    }
    
    fn parse_tag_props(&mut self, ctx: &mut Context, tb: &mut TagBuilder ) -> Result<(), Box<dyn Error>> {
        loop {
            if ctx.cur().is_none() {
                return Err(Box::new(ParseError{position: Position::new(0,0), what: "Unexpecetd end of stream".to_string()}));
            }
            if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() {
                if ctx.peek().is_some_and(|s|s.check_type(|t| matches!(t,SymbolType::Equals))) {
                    ctx.next();
                    if let Some(sym) = ctx.next() &&
                        let SymbolType::Literal(lv) = sym.symbol() {
                        tb.add_prop( s.to_string(), lv.to_string() );
                    } else {
                        return Err(Box::new(ParseError{position: Position::new(0,0), what: "Expected quoted literal string".to_string()}));
                    }
                } else {
                    return Err(Box::new(ParseError{position:Position::new(0,0), what: "Expected = ".to_string()}));
                }
            } else {
                break;
            }
            ctx.next();
        }
        Ok(())
    }
}

enum TagType {
    Unknown,
    Unary,
    BinaryOpen,
    BinaryClose,
}

struct TagBuilder {
    name: String,
    params: Vec<String>,
    props: Vec<(String,String)>,
    tag_type: TagType,
}

impl TagBuilder {
    pub fn new() -> TagBuilder {
        TagBuilder {
            name: String::new(),
            params: Vec::new(),
            props: Vec::new(),
            tag_type: TagType::Unknown,
        }
    }

    pub fn set_name(&mut self, name: String) {
        self.name = name;
    }

    pub fn add_param(&mut self, param: String) {
        self.params.push( param );
    }

    pub fn add_prop(&mut self, key: String, value: String) {
        self.props.push( (key, value) );
    }

    pub fn set_type(&mut self, tag_type: TagType) {
        self.tag_type = tag_type;
    }
}
