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::ElIf |
                SymbolType::Else |
                SymbolType::Show => true,
                _ => false
            })
        } else {
            Err(CrimError::parse(
                Position::none(),
                "Unexpeceted end of stream.".to_string()
                ))
        }
    }
}

pub struct Parser { 
}

/**
 * input: input complete_tag
 *      | input text
 *      |
 *      ;
 *
 * tag: unary_tag
 *    | multinary_open_tag
 *    | multinary_mid_tag
 *    | multinary_close_tag
 *    ;
 *
 * unary_tag: '[|' tag_guts '|]'
 *          ;
 *
 *    | '[|' tag_guts '|>'
 *    | '<|' tag_guts '|>'
 *    | '<|' 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 tags = Vec::new();
        loop {
            if ctx.cur().is_none() {
                break;
            }
            match ctx.cur().unwrap().symbol() {
                SymbolType::Text(_) => { /* Skip top level text */ }
                SymbolType::StartFlat => {
                    let tb = self.parse_tag( ctx )?;
                    let tb = self.parse_tag_set( ctx, tb )?;
                    tags.push( tb );
                }
                SymbolType::Error{..} => {
                    self.lex_error( &ctx.cur().unwrap() )?;
                }
                _ => {
                    return Err(CrimError::parse(
                        *ctx.cur().unwrap().start(),
                        format!("Unexpected symbol {:?} found looking for tags.", ctx.cur().unwrap().symbol() )
                    ));
                }
            }
            ctx.next();
        }

        let mut views = Vec::new();
        for tag in tags {
            let start_pos = tag.start_pos();
            let name = tag.name().clone();
            if tag.is_name(&SymbolType::View) &&
                let BuildResult::View(view) = tag.build( ctx )? {
                    views.push( view );
            } else {
                return Err(CrimError::parse(
                    start_pos,
                    format!("Expected view at root, found {:?}", name )
                    ));
            }
        }
        Ok(views)
    }

    fn parse_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::ElIf | 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"));
        }

        if tb.can_have_params() || tb.can_have_expr() {
            self.parse_tag_params( ctx, &mut tb )?;
        }
        if tb.can_have_props() {
            self.parse_tag_props( ctx, &mut tb )?;
        }

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

        ctx.next();

        Ok(tb)
    }

    fn parse_tag_set<'a>(&self, ctx: &mut Context<'a>, mut base: TagBuilder<'a>) -> CrimResult<TagBuilder<'a>> {
        if base.is_unary() {
            return Ok(base);
        }

        loop {
            if ctx.cur().is_none() {
                return Err(CrimError::eos(format!("close tag for {:?}", base.name()).as_str()));
            }
            match ctx.cur().unwrap().symbol() {
                SymbolType::Text(s) => {
                    base.add_child(Entry::Token(Token::Text(s.to_string())));
                    ctx.next();
                }
                SymbolType::StartFlat | SymbolType::StartPoint => {
                    let tb = self.parse_tag( ctx )?;

                    match tb.tag_type() {
                        TagType::MultinaryOpen => {
                            base.add_child(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?));
                        }
                        TagType::MultinaryMid => {
                            base.add_chain(Entry::TagBuilder(self.parse_tag_set( ctx, tb )?));
                            return Ok(base);
                        }
                        TagType::MultinaryClose => {
                            if (tb.is_name(&SymbolType::If) &&
                                (base.is_name(&SymbolType::ElIf) ||
                                 base.is_name(&SymbolType::Else))) ||
                               tb.is_name(base.name().unwrap().symbol()) {
                                   return Ok(base);
                            } else {
                                return Err(CrimError::parse(
                                    *tb.name().unwrap().start(),
                                    format!("Found {:?} looking for close of {:?}", tb.name().unwrap().symbol(), base.name().unwrap().symbol())
                                ));
                            }
                        }
                        TagType::Unary => {
                            base.add_child(Entry::TagBuilder(tb));
                        }
                        TagType::Unknown => {
                            return Err(CrimError::broken(base.start_pos(), "Found unknown tag when looking for something else."));
                        }
                    }
                }
                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(_) | SymbolType::Sharp => {
                    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();

        if ctx.cur().is("identifier",|s| matches!( s, SymbolType::Sharp))? {
            id.push( IdentifierValue::Root );
            ctx.next();
        }
        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,
    MultinaryClose,
}

#[derive(Debug)]
enum Entry<'a>{
    Token(Token),
    TagBuilder(TagBuilder<'a>),
}

type EntryList<'a> = Vec<Entry<'a>>;

trait EntryListConverter {
    fn has_outputs(&self) -> bool;
    fn to_outputs(&mut self, ctx: &mut Context ) -> CrimResult<Vec<Output>>;
    fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult<Vec<Token>>;
}

impl<'a> EntryListConverter for EntryList<'a> {
    fn has_outputs(&self) -> bool {
        self.iter().any(|e|
            if let Entry::TagBuilder(tb) = e
              && tb.is_name(&SymbolType::Output) {
                  true
              } else {
                  false
            }
            )
    }

    fn to_outputs(&mut self, ctx: &mut Context) -> CrimResult<Vec<Output>> {
        let mut outputs = Vec::new();
            
        if self.has_outputs() {
            // We have an explicit output, we cant have anything else
            for e in self.drain(..) {
                let tb = if let Entry::TagBuilder(tb) = e {
                    tb
                } else {
                    continue;
                };
                if let BuildResult::Output(out) = tb.build(ctx)? {
                    outputs.push( out );
                }
            }
        } else {
            // No outputs, so we create one implicit output
            outputs.push(Output {
                name: "content".into(),
                code: self.to_tokens(ctx)?
            });
        }

        Ok(outputs)
    }

    fn to_tokens(&mut self, ctx: &mut Context) -> CrimResult<Vec<Token>> {
        let mut tokens : Vec<Token> = Vec::new();
        for e in self.drain(..) {
            match e {
                Entry::Token(token) => {
                    tokens.push( token );
                }
                Entry::TagBuilder(tb) => {
                    if let BuildResult::Token(token) = tb.build(ctx)? {
                        tokens.push( token );
                    } else {
                        return Err(CrimError::broken( Position::none(), "Non-token result built."));
                    }
                }
            }
        }
        Ok(tokens)
    }
}

#[derive(Debug)]
struct TagBuilder<'a> {
    name: Option<Symbol<'a>>,
    params: Vec<ParamValue>,
    props: Properties,
    children: Vec<Entry<'a>>,
    tag_type: TagType,
    start: Symbol<'a>,
    chain: Vec<Entry<'a>>,
}

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

enum BuildResult {
    View(View),
    Output(Output),
    Token(Token),
}

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

    pub fn set_end(&mut self, end: &Symbol) -> CrimResult<()> {
        if *self.start.symbol() == SymbolType::StartFlat {
            if *end.symbol() == SymbolType::EndFlat {
                self.tag_type = TagType::Unary;
            } else if *end.symbol() == SymbolType::EndPoint {
                self.tag_type = TagType::MultinaryOpen;
            } else {
                return Err(CrimError::broken( *end.start(), "Invalid bracket token type."));
            }
        } else if *self.start.symbol() == SymbolType::StartPoint {
            if *end.symbol() == SymbolType::EndFlat {
                self.tag_type = TagType::MultinaryClose;
            } else if *end.symbol() == SymbolType::EndPoint {
                self.tag_type = TagType::MultinaryMid;
            } else {
                return Err(CrimError::broken( *end.start(), "Invalid bracket token type."));
            }
        } else {
                return Err(CrimError::broken( *self.start.start(), "Invalid bracket token type."));
        }
        Ok(())
    }

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

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

    pub fn is_multinary_open(&self) -> bool {
        self.tag_type == TagType::MultinaryOpen
    }

    pub fn can_have_params(&self) -> bool {
        match self.name.unwrap().symbol() {
            SymbolType::View | SymbolType::Output | SymbolType::Loop => true,
            SymbolType::Show | SymbolType::If | SymbolType::ElIf |
            SymbolType::Else => false,
            _ => false,
        }
    }

    pub fn can_have_expr(&self) -> bool {
        match self.name.unwrap().symbol() {
            SymbolType::View | SymbolType::Output | SymbolType::Loop => false,
            SymbolType::Show | SymbolType::If | SymbolType::ElIf |
            SymbolType::Else => true,
            _ => false,
        }
    }

    pub fn can_have_props(&self) -> bool {
        true
    }

    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, tb: Entry<'a>) {
        self.children.push( tb );
    }

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

    pub fn add_chain(&mut self, tb: Entry<'a>) {
        self.chain.push( tb );
    }

    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(mut self, ctx: &mut Context ) -> CrimResult<BuildResult> {
        if let Some(sym) = self.name {
            match 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(BuildResult::View(View {
                        name: name,
                        source: ctx.source(),
                        outputs: self.children.to_outputs(ctx)?,
                        //theme: Option<String>
                        layout: self.props.get("layout").cloned(),
                    }))
                }
                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(BuildResult::Output(Output {
                        name: name,
                        code: self.children.to_tokens(ctx)?,
                    }))
                }
                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(BuildResult::Token(
                        Token::Loop(id, self.children.to_tokens(ctx)?)
                        ))
                }
                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(BuildResult::Token(Token::Show(id, self.props)))
                }
                SymbolType::If => {
                    let id = if let ParamValue::Identifier(id)
                        = self.params.swap_remove(0) {
                        id
                    } else {
                        return Err(CrimError::parse(
                            Position::none(),
                            "Identifier for if variable name.".into())
                            );
                    };
                    
                    let is_else = if self.chain.len() == 1 &&
                        let Entry::TagBuilder(tb) = &self.chain[0] &&
                        tb.is_name(&SymbolType::Else) &&
                        tb.params.len() == 0 {
                            true
                    } else {
                        false
                    };

                    if is_else &&
                        let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) {
                        self.chain.clear();
                        self.chain.append(&mut tb.children);
                    }
                    Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?)))
                }
                SymbolType::ElIf => {
                    let id = if let ParamValue::Identifier(id) = self.params.swap_remove(0) {
                        id
                    } else {
                        return Err(CrimError::parse(
                            Position::none(),
                            "Identifier for elif variable name.".into())
                            );
                    };

                    // this is copied from if, since they're the same this
                    // should be encapsulated and moved into a function...
                    // ...but I can't do that right now.
                    let is_else = if self.chain.len() == 1 &&
                        let Entry::TagBuilder(tb) = &self.chain[0] &&
                        tb.is_name(&SymbolType::Else) &&
                        tb.params.len() == 0 {
                            true
                    } else {
                        false
                    };

                    if is_else &&
                        let Entry::TagBuilder(mut tb) = self.chain.swap_remove(0) {
                        self.chain.clear();
                        self.chain.append(&mut tb.children);
                    }


                    Ok(BuildResult::Token(Token::If(id, self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?)))
                }
                SymbolType::Else => {
                    Ok(BuildResult::Token(Token::If(Identifier::new(), self.children.to_tokens(ctx)?, self.chain.to_tokens(ctx)?)))
                }
                _ => {
                    Err(CrimError::parse( self.start_pos(), "Bad tag type".into()))
                }
            }
        } else {
            Err(CrimError::parse(self.start_pos(), "Bad tag type".into()))
        }
    }
}
