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

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

impl<'a> Context<'a> {
    pub fn new( mut ll: Lexer<'a>, source: usize ) -> Self {
        let cur = [ll.next(), ll.next()];
        //println!(" - cur: {:?}, peek: {:?}", cur[0], cur[1] );
        Self {
            cur,
            icur: 0,
            ll,
            source,
        }
    }
    
    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 fn check_unwrapped<T: Fn( &SymbolType, &SymbolType ) -> bool>(&self, context: &str, f: T) -> CrimResult<bool> {
        if self.cur().is_none() || self.peek().is_none() {
            Err(CrimError::eos( context ))
        } else  {
            Ok(f( self.cur().unwrap().symbol(), self.peek().unwrap().symbol()))
        }
    }

    pub fn source(&self) -> usize {
        self.source
    }
}

trait SymbolHelper {
    fn is<T: Fn( &SymbolType ) -> bool>(&self, context: &str, f: T ) -> CrimResult<bool>;
    fn is_valid_tag_name(&self) -> CrimResult<bool>;
}

impl<'a> SymbolHelper for Option<Symbol<'a>> {
    fn is<T: Fn( &SymbolType ) -> bool>(&self, context: &str, f: T ) -> CrimResult<bool> {
        if let Some(st) = self {
            Ok(f( &st.symbol() ))
        } else {
            Err(CrimError::eos( context ))
        }
    }
    
    fn is_valid_tag_name(&self) -> CrimResult<bool> {
        if let Some(st) = self {
            Ok(match st.symbol() {
                SymbolType::Token(_) |
                SymbolType::View |
                SymbolType::Output |
                SymbolType::Loop |
                SymbolType::If |
                SymbolType::ElseIf |
                SymbolType::Else |
                SymbolType::Show => true,
                _ => false
            })
        } else {
            Err(CrimError::parse(
                Position::none(),
                "Unexpeceted end of stream.".to_string()
                ))
        }
    }
}

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: 'view' 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: 'view' literal props
 *         | 'section' literal
 *         | 'output' literal
 *         ;
 *
 * literal: '"' [^"]* '"'
 *        ;
 *
 * props: props token '=' literal
 *      |
 *      ;
 */
impl Parser {
    pub fn new() -> Self {
        Self {
        }
    }

    fn lex_error<T>(&self, error: &Symbol) -> CrimResult<T> {
        if let SymbolType::Error{what} = error.symbol() {
            Err(CrimError::parse( *error.start(),
                format!("What: {:?}", *what)
            ))
        } else {
            Err(CrimError::parse( Position::none(), "Not an error?".into()))
        }
    }

    pub fn parse(&self, src: &str, source: usize ) -> CrimResult<Vec<View>> {
        let ll = Lexer::new( src );
        let mut ctx = Context::new( ll, source );

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

    fn p_input(&self, ctx: &mut Context ) -> CrimResult<Vec<View>> {
        let mut children = Vec::new();
        loop {
            if ctx.cur().is_none() {
                break;
            }
            match ctx.cur().unwrap().symbol() {
                SymbolType::Text(_) => { /* Skip top level text */ }
                SymbolType::StartFlat => {
                    children.push( self.parse_tag_view( ctx )? );
                }
                SymbolType::Error{..} => {
                    self.lex_error( &ctx.cur().unwrap() )?;
                }
                _ => {

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

    fn parse_tag_view(&self, ctx: &mut Context) -> CrimResult<View> {
        let tb = self.parse_open_tag( ctx )?;
        if !tb.is_name( &SymbolType::View ) {
            return Err(CrimError::parse(tb.start_pos(),
                "Unexpected tag type, only frament allowed at root".into()
            ));
        }

        if tb.is_unary() {
            return tb.build_view(ctx, Vec::new());
        }
        let mut children = Vec::new();
        let mut outputs = Vec::new();

        //println!("--parse-tag-view-- tag parsed, next token: {:?}", ctx.cur() );
        loop {
            if ctx.cur().is_none() {
                return Err(CrimError::eos(format!("close tag for {:?}", tb.name()).as_str()))
            }
            match ctx.cur().unwrap().symbol() {
                SymbolType::Text(s) => {
                    children.push( Token::Text(s.to_string()) );
                    ctx.next();
                }
                SymbolType::StartFlat => {
                    let subtb = self.parse_tag_body( ctx )?;
                    if subtb.is_name(&SymbolType::Output) {
                        outputs.push(subtb.build_output()?);
                    } else {
                        children.push(subtb.build()?);
                    }
                }
                SymbolType::StartPoint => {
                    let end = self.parse_end_tag( ctx )?;
                    //println!("End tag: {:?}, open tag: {:?}", end, tb.name() );
                    if tb.is_name(end.symbol()) {
                        // They match, time to decide what we're doing.
                        let any_outputs = outputs.len() > 0;
                        let all_text = children.iter().all(
                            |t| matches!(t, Token::Text(..))
                            );
                        if !any_outputs {
                            // Create an implicit output called "content"
                            outputs.push(Output{
                                name: "content".to_string(),
                                code: children,
                            });
                        } else {
                            if !all_text {
                                return Err(CrimError::parse(
                                    *end.start(),
                                    "You cannot mix non-output and output tags in a view.".to_string()
                                ));
                            }
                        }
                        return tb.build_view(ctx,outputs);
                    } else {
                        // They don't match, complain.
                        return Err(CrimError::parse(
                            *end.start(),
                            "Mismatched open and closing tags.".to_string()
                        ));
                    }
                }
                SymbolType::Error{..} => {
                    return self.lex_error( &ctx.cur().unwrap() );
                }
                _ => {
                    return Err(CrimError::parse(
                        *ctx.cur().unwrap().start(),
                        format!("Unexpected tag in view root: {:?}", ctx.cur().unwrap().symbol())
                    ));
                }
            }
        }
    }

    fn parse_open_tag<'a>(&self, ctx: &mut Context<'a>) -> CrimResult<TagBuilder<'a>> {
        let start_sym = ctx.cur().unwrap();
        let mut tb = TagBuilder::new(&start_sym);

        if ctx.next().is_some() {
            match ctx.cur().unwrap().symbol() {
                SymbolType::View | SymbolType::Show | SymbolType::Loop |
                SymbolType::If | SymbolType::ElseIf | SymbolType::Else |
                SymbolType::Output => {
                    let name_sym = ctx.cur().unwrap();
                    ctx.next();
                    tb.set_name( name_sym );
                }
                _ => {
                    return Err(CrimError::parse(
                        *ctx.cur().unwrap().start(),
                        "Unexpected symbol".to_string()
                    ));
                }
            }
        } else {
            return Err(CrimError::eos("tag type"));
        }

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

        if let Some(end_sym) = ctx.cur() {
            match end_sym.symbol() {
                SymbolType::EndPoint => {
                    tb.set_type( TagType::MultinaryOpen );
                }
                SymbolType::EndFlat => {
                    tb.set_type( TagType::Unary );
                }
                _ => {
                    return Err(CrimError::parse(
                        *end_sym.start(),
                        format!("Unexpected symbol {:?} looking for end of tag.", end_sym.symbol())
                        ));
                }
            }
        }

        ctx.next();

        Ok(tb)
    }

    fn parse_end_tag<'a>(&self, ctx: &mut Context<'a> ) -> CrimResult<Symbol<'a>> {
        if !ctx.cur().is("end tag",|s| *s == SymbolType::StartPoint )? {
            return Err(CrimError::parse(
                *ctx.cur().unwrap().start(),
                "Invalid end tag?".to_string()
            ));
        }
        if !ctx.next().is_valid_tag_name()? {
            return Err(CrimError::parse(
                *ctx.cur().unwrap().start(),
                "Invalid tag name".to_string()
            ));
        }
        let name = ctx.cur().unwrap();
        if !ctx.next().is("end of end tag", |s| *s == SymbolType::EndFlat )? {
            return Err(CrimError::parse(
                *ctx.cur().unwrap().start(),
                "Tag should be <| |] style end tag.".to_string(),
            ));
        }
        ctx.next();
        Ok(name)
    }

    fn parse_tag_body<'a>(&self, ctx: &mut Context<'a> ) -> CrimResult<TagBuilder<'a>> {
        let mut tb = self.parse_open_tag( ctx )?;
        if tb.is_unary() {
            return Ok(tb);
        }

        loop {
            if ctx.cur().is_none() {
                return Err(CrimError::eos(format!("close tag for {:?}", tb.name()).as_str()));
            }
            match ctx.cur().unwrap().symbol() {
                SymbolType::Text(s) => {
                    tb.add_child(Token:: Text(s.to_string()));
                    ctx.next();
                }
                SymbolType::StartFlat => {
                    tb.add_child( self.parse_tag_body( ctx )?.build()? );
                }
                SymbolType::StartPoint => {
                    let end = self.parse_end_tag( ctx )?;
                    if tb.is_name(end.symbol()) {
                        // They match, end.
                        return Ok(tb);
                    } else {
                        // They don't match, complain.
                        return Err(CrimError::parse(
                            *end.start(),
                            "Mismatched open and closing tags.".to_string()
                        ));
                    }
                }
                SymbolType::Error{..} => {
                    return self.lex_error( &ctx.cur().unwrap() );
                }
                _ => {
                    return Err(CrimError::parse(
                        *ctx.cur().unwrap().start(),
                        format!("Unexpected token: {:?}", ctx.cur().unwrap().symbol()),
                    ));
                }
            }
        }
    }

    fn parse_tag_params(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> {
        loop {
            if ctx.cur().is_none() {
                return Err(CrimError::eos("tag parameters"));
            }
            match ctx.cur().unwrap().symbol() {
                SymbolType::Literal(s) => {
                    tb.add_param( ParamValue::Literal(s.to_string()) );
                    ctx.next();
                }
                SymbolType::Token(_) => {
                    if ctx.peek().is("tag data", |s| *s == SymbolType::Equals)? {
                        break;
                    }
                    tb.add_param( self.parse_identifier( ctx )? );
                }
                _ => {
                    break;
                }
            }
        }
        Ok(())
    }

    fn parse_identifier(&self, ctx: &mut Context ) -> CrimResult<ParamValue> {
        let mut id = Identifier::new();

        loop {
            if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Token(_) ))? {
                if let SymbolType::Token(s) = ctx.cur().unwrap().symbol() {
                    id.push( IdentifierValue::Name(s.to_string()) );
                }
            } else {
                return Err(CrimError::parse( ctx.cur().unwrap().start().clone(), format!("Expeceted identifier, found {:?}", ctx.cur().unwrap().symbol() ) ) );
            }

            if !ctx.next().is("identifier seperator",|s| *s == SymbolType::Period )? {
                break;
            }
            ctx.next();
        }

        Ok(ParamValue::Identifier(id))
    }
    
    fn parse_tag_props(&self, ctx: &mut Context, tb: &mut TagBuilder ) -> CrimResult<()> {
        loop {
            if ctx.cur().is_none() {
                return Err(CrimError::eos("tag properties"));
            }
            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(CrimError::parse(Position::none(),"Expected quoted literal string".to_string()));
                    }
                } else {
                    break;
                }
            } else {
                break;
            }
            ctx.next();
        }
        Ok(())
    }
}

#[derive(PartialEq,Copy,Clone,Debug)]
enum TagType {
    Unknown,
    Unary,
    MultinaryOpen,
    MultinaryMid,
}

struct TagBuilder<'a> {
    name: Option<Symbol<'a>>,
    params: Vec<ParamValue>,
    props: Properties,
    children: Vec<Token>,
    tag_type: TagType,
    start_pos: Position,
}

#[derive(Debug)]
enum ParamValue {
    Literal(String),
    Identifier(Identifier),
}

impl<'a> TagBuilder<'a> {
    pub fn new(start: &Symbol) -> TagBuilder<'a> {
        TagBuilder {
            name: None,
            params: Vec::new(),
            props: Properties::new(),
            children: Vec::new(),
            tag_type: TagType::Unknown,
            start_pos: *start.start(),
        }
    }

    pub fn start_pos(&self) -> Position {
        self.start_pos
    }

    pub fn is_unary(&self) -> bool {
        if self.tag_type == TagType::Unary {
            true
        } else {
            false
        }
    }

    pub fn can_have_children(&self) -> bool {
        if self.tag_type == TagType::MultinaryOpen ||
            self.tag_type == TagType::MultinaryMid {
            true
        } else {
            false
        }
    }

    pub fn is_name(&self, name: &SymbolType<'a>) -> bool {
        if let Some(a) = self.name {
            a.symbol() == name
        } else {
            false
        }
    }

    pub fn name(&self) -> Option<Symbol<'a>> {
        self.name
    }

    pub fn params(&self) -> &Vec<ParamValue> {
        &self.params
    }

    pub fn set_name(&mut self, name: Symbol<'a>) {
        self.name = Some(name);
    }

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

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

    pub fn add_child(&mut self, token: Token) {
        self.children.push( token );
    }

    pub fn append_children(&mut self, children: &mut Vec::<Token>) {
        self.children.append( children );
    }

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

    pub fn tag_type(&self) -> TagType {
        self.tag_type
    }

    pub fn build_view(mut self, ctx: &Context, outputs: Vec<Output>) -> CrimResult<View> {
        if let Some(sym) = self.name {
            if *sym.symbol() == SymbolType::View {
                let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) {
                    s.to_string()
                } else {
                    return Err(CrimError::parse(Position::none(), "Expected string literal for view name.".to_string()));
                };
                Ok(View {
                    name: name,
                    source: ctx.source(),
                    outputs: outputs,
                    //theme: Option<String>
                    layout: self.props.get("layout").cloned(),
                })
            } else {
                Err(CrimError::parse(self.start_pos, format!("Expected tag type view, found {:?}", sym.symbol())))
           }
        } else {
            Err(CrimError::parse(self.start_pos, "Broken Parser? No tag type found when building view.".to_string()))
        }
    }

    pub fn build_output(mut self) -> CrimResult<Output> {
        if let Some(sym) = self.name {
            if *sym.symbol() == SymbolType::Output {
                let name = if let ParamValue::Literal(s) = self.params.swap_remove(0) {
                    s.to_string()
                } else {
                    return Err(CrimError::parse(Position::none(), "Expected string literal for output name.".into()));
                };
                Ok(Output {
                    name: name,
                    code: self.children,
                })
            } else {
                Err(CrimError::parse(self.start_pos, format!("Expected tag type view, found {:?}", sym.symbol())))
            }
        } else {
            Err(CrimError::parse(self.start_pos, "Broken Parser? No tag type found when building view.".into()))
        }
    }

    pub fn build(mut self) -> CrimResult<Token> {
        if let Some(sym) = self.name {
            match sym.symbol() {
                SymbolType::Loop => {
                    let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) {
                        id
                    } else {
                        return Err(CrimError::parse(Position::none(), "Identifier for loop variable name.".into()));
                    };
                    Ok(Token::Loop(id, self.children))
                }
                SymbolType::Show => {
                    let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) {
                        id
                    } else {
                        return Err(CrimError::parse(Position::none(), "Identifier for show variable name.".into()));
                    };
                    Ok(Token::Show(id, self.props))
                }
                _ => {
                    Err(CrimError::parse( self.start_pos, "Bad tag type".into()))
                }
            }
        } else {
            Err(CrimError::parse(self.start_pos, "Bad tag type".into()))
        }
    }
}
