use std::path::{PathBuf,Path};
use std::collections::HashMap;
use core::error::Error;
use std::time::SystemTime;

mod lexer;
mod parser;
mod position;

pub use parser::ParseError;
pub use position::*;

#[derive(Debug)]
pub struct Crimtag {
    views: HashMap<String, View>,
    sources: Vec<ViewSource>,
}

#[derive(PartialEq,Eq,Debug,Clone)]
pub enum ViewSource {
    File {
        path: PathBuf,
        loaded: SystemTime,
    },
    Static,
    External {
        key: String,
    },
}

#[derive(Debug)]
struct View {
    source: usize,
    outputs: Vec<Output>,
//    theme: Option<String>
    layout: Option<String>,
}

impl View {
    fn process(&self, input: &Value) -> Result<Value, ParseError> {
        let mut out_vars = HashMap::new();
        for output in &self.outputs {
            if let Token::Output(_,/*_,*/tokens) = &output.ast_root {
                let mut buf = String::new();
                exec( input, tokens, &mut buf )?;
                out_vars.insert( output.name.clone(), Value::String(buf) );
            }
        }
        Ok(Value::Dictionary(out_vars))
    }
}

#[derive(Debug)]
struct Output {
    name: String,
    ast_root: Token,
}

type Properties = HashMap<String, String>;

#[derive(Debug)]
enum Token {
    Root(Vec<Token>),
    View(String,Properties,Vec<Token>),
    Output(String,/*Properties,*/ Vec<Token>),
    Show(String,Properties),
    Text(String),
}

#[derive(Debug)]
pub enum Value {
    Dictionary(HashMap<String,Value>),
    List(Vec<Value>),
    String(String),
    Int(i64),
    Float(f64),
}

impl Crimtag {
    pub fn new() -> Self {
        Self {
            views: HashMap::new(),
            sources: vec![ViewSource::Static],
        }
    }

    fn id_source(&mut self, src: &ViewSource ) -> usize {
        for idx in 0..self.sources.len() {
            if self.sources[idx] == *src {
                return idx;
            }
        }

        // Nothing found, add a new one
        let idx = self.sources.len();
        self.sources.push( src.clone() );
        idx
    }

    pub fn load_file(&mut self, path: &Path) -> Result<(),Box::<dyn Error>> {
        let time = if let Ok(meta) = path.metadata() {
            if let Ok(t) = meta.modified() {
                t
            } else if let Ok(t) = meta.created() {
                t
            } else {
                SystemTime::now()
            }
        } else {
            SystemTime::now()
        };

        let p = parser::Parser::new();
        self.register_views(
            p.parse( &String::from_utf8( std::fs::read( path )? )? )?,
            ViewSource::File {
            path: path.to_path_buf(),
            loaded: time,
        })?;
        Ok(())
    }

    pub fn load_static(&mut self, data: &str) -> Result<(),ParseError> {
        let p = parser::Parser::new();
        self.register_views( p.parse( &data )?, ViewSource::Static )
    }

    pub fn load_external(&mut self, key: &str, data: &str) -> Result<(),ParseError> {
        let p = parser::Parser::new();
        self.register_views(
            p.parse( &data )?,
            ViewSource::External{ key: key.to_string()}
            )
    }

    fn register_views(&mut self, token: Token, source: ViewSource) -> Result<(),ParseError> {
        let source_id = self.id_source( &source );
        if let Token::Root(views) = token {
            for token in views {
                if let Token::View(name,props,output_tokens) = token {
                    let mut outputs = Vec::new();
                    for ot in output_tokens {
                        let output_name = if let Token::Output(on,..) = &ot {
                            on.clone()
                        } else {
                            return Err(ParseError::new(
                                Position::new(0,0),
                                "Broken parser? Item in view is not an output."
                            ));
                        };
                        outputs.push(
                            Output {
                                name: output_name,
                                ast_root: ot,
                            }
                        );
                    }

                    let layout = if let Some(l) = props.get("layout") {
                        Some(l.clone())
                    } else {
                        None
                    };

                    self.views.insert( name, View {
                        source: source_id,
                        outputs: outputs,
                        layout: layout,
                    });
                } else {
                    return Err(ParseError::new(
                        Position::new(0,0),
                        "Broken parser? Item in root is not a view."
                    ));
                }
            }
            Ok(())
        } else {
            Err(ParseError::new(
                Position::new(0,0),
                "Broken parser? Root is not a root token."
            ))
        }
    }

    pub fn get_view_source(&self, view: &str) -> Option<ViewSource> {
        if let Some(view) = self.views.get(view) {
            if view.source < self.sources.len() {
                Some(self.sources[view.source].clone())
            } else {
                None
            }
        } else {
            None
        }
    }

    pub fn view_partial(&self, view: &str, input: &Value ) -> Result<Value,ParseError> {
        if let Some(view) = self.views.get(view) {
            return view.process( input )
        } else {
            return Err(ParseError::new(Position::new(0,0),"No such view found"));
        }
    }

    pub fn view(&self, view: &str, input: &Value ) -> Result<String,ParseError> {
        if let Some(view) = self.views.get( view ) {
            let output = view.process( input )?;
            if let Some(l) = &view.layout {
                self.view( l, &output )
            } else {
                if let Value::Dictionary(mut d) = output &&
                    let Some(content) = d.remove("content") &&
                    let Value::String(s) = content {

                    Ok(s)
                } else {
                    Err(ParseError::new(Position::new(0,0),"No content found in root layout."))
                }
            }

        } else {
            Err(ParseError::new(Position::new(0,0),"No such view found"))
        }
    }
}

fn exec(input: &Value, tokens: &Vec<Token>, buf: &mut String) -> Result<(),ParseError> {
    for token in tokens {
        match token {
            Token::Root(..) | Token::View(..) | Token::Output(..) => {
                println!("Too high!?");
                // Error?
            }
            Token::Show(s,p) => {
                let format = if let Some(s) = p.get("format") {
                    s
                } else {
                    &"".to_string()
                };
                if let Value::Dictionary(d) = input &&
                    let Some(value) = d.get(s) {
                        match value {
                            Value::Dictionary(_) => {
                                buf.push_str("<dictionary>");
                            }
                            Value::List(_) => {
                                buf.push_str("<list>");
                            }
                            Value::String(s) => {
                                buf.push_str(s);
                            }
                            Value::Int(i) => {
                                buf.push_str(&i.to_string());
                            }
                            Value::Float(f) => {
                                if format == "" {
                                    buf.push_str(&f.to_string());
                                } else {
                                    buf.push_str(&f.to_string());
                                }
                            }
                        }
                }
            }
            Token::Text(s) => {
                buf.push_str( s );
            }
        }
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::lexer::*;

    #[test]
    fn lexing() {
        let data = r#"Leading comment: [|view "basic" something="yeup"|>Hello there <|view|] Trailing text"#;
        let ll = lexer::Lexer::new( &data );
        for sym in ll {
            if let SymbolType::Error{what} = sym.symbol() {
                println!("Error {}:{}: {:?}", sym.start().line, sym.start().column, what );
                break;
            } else {
                println!("Symbol: {:?}", sym );
            }
        }
    }

    #[test]
    fn parsing() {
        let mut ct = Crimtag::new();
        if let Err(e) = ct.load_static(r#"Here is a sample
[|view "index" theme="standard"|><html><body>Hi</body></html><|view|]
That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body [|show "content"|] and end<|view|] aoeu

[|view "index" layout="simple"|>Whatever man!<|view|]

Now with explicit outputs:
[|view "complex"|>
    [|output "content"|>
       Here's a [|show "name"|].
    <|output|]
    [|output "sidebar"|>
        What's up world?
    <|output|]
    [|output "footer"|>
        Same, I guess!
    <|output|]
<|view|]"#) {
            println!("Error: {:?}", e );
        } else {
            println!("It finished");
            if let Ok(v) = ct.view(
                "index",
                &Value::Dictionary(
                    HashMap::from([
                        ("hi".to_string(),Value::String("hi".to_string()))
                        ])
                    )
                ) {
                println!("View result: {:?}", v );
            } else {
                println!("Error?");
            }
        }
    }
}
