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 {
    name: String,
    source: usize,
    outputs: Vec<Output>,
//    theme: Option<String>
    layout: Option<String>,
}

impl View {
    fn process(&self, input: &impl MappedStructure) -> Result<Context, ParseError> {
        let mut out_vars = Context::new();
        for output in &self.outputs {
            let mut buf = String::new();
            exec( input, &output.code, &mut buf )?;
            out_vars.insert( output.name.clone(), Value::String(buf) );
        }
        Ok(out_vars)
    }
}

#[derive(Debug)]
struct Output {
    name: String,
    code: Vec<Token>,
}

type Properties = HashMap<String, String>;

#[derive(Debug)]
enum Token {
    Loop(Identifier, Vec<Token>),
    Show(Identifier,Properties),
    Text(String),
}

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

#[derive(Debug,Clone)]
pub enum IdentifierValue {
    Name(String),
    Index(usize),
}

pub type Identifier = Vec<IdentifierValue>;
pub type Context = HashMap<String,Value>;

pub trait MappedStructure {
    fn get_value(&self, id: &Identifier) -> Option<Value>;
}

impl MappedStructure for Context {
    fn get_value(&self, id: &Identifier) -> Option<Value> {
        if id.len() == 0 {
            return None;
        }
        let mut cur_val : Option<&Value> = if let IdentifierValue::Name(s) = &id[0] {
            self.get(s)
        } else {
            return None;
        };
        if cur_val.is_none() {
            return None;
        }

        for idx in 1..id.len() {
            if let IdentifierValue::Name(s) = &id[idx] &&
                let Some(Value::Dictionary(d)) = &cur_val {
                cur_val.replace( if let Some(v) = d.get(s) {
                    v
                } else {
                    return None;
                });
            }
        }

        return cur_val.cloned();
    }
}

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 source_id = self.id_source(
            &ViewSource::File {
                path: path.to_path_buf(),
                loaded: time,
                }
            );
        let p = parser::Parser::new();
        self.register_views(
            p.parse(
                &String::from_utf8( std::fs::read( path )? )?,
                source_id
                )?,
            )?;
        Ok(())
    }

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

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

    fn register_views(&mut self, views: Vec<View>) -> Result<(),ParseError> {
        for view in views {
            self.views.insert( view.name.clone(), view );
        }
        Ok(())
    }

    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 render_partial(&self, view: &str, input: &impl MappedStructure ) -> Result<Context,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 render(&self, view: &str, input: &impl MappedStructure ) -> Result<String,ParseError> {
        if let Some(view) = self.views.get( view ) {
            let mut output = view.process( input )?;
            if let Some(l) = &view.layout {
                self.render( l, &output )
            } else {
                if let Some(content) = output.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: &impl MappedStructure, tokens: &Vec<Token>, buf: &mut String) -> Result<(),ParseError> {
    for token in tokens {
        match token {
            Token::Loop(ident,code) => {
                if let Some(value) = input.get_value( &ident ) &&
                    let Value::List(list) = value {
                    for rec in &list {
                        if let Value::Dictionary(d) = rec {
                            exec( d, code, buf )?
                        }
                    }
                }
            }
            Token::Show(ident,p) => {
                let format = if let Some(s) = p.get("format") {
                    s
                } else {
                    &"".to_string()
                };
                if let Some(value) = input.get_value( &ident ) {
                    match value {
                        Value::Dictionary(_) => {
                            buf.push_str("<dictionary>");
                        }
                        Value::List(_) => {
                            buf.push_str("<list>");
                        }
                        Value::String(s) => {
                            buf.push_str(s.as_str());
                        }
                        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());
                            }
                        }
                        Value::Identifier(_) => {
                            // ???
                        }
                    }
                }
            }
            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 "person" layout="simple"|>Welcome [|show person.last_name|], [|show person.first_name|]! It's lovely to see you again.<|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.render(
                "person",
                &Context::from([
                    ("hi".to_string(),Value::String("hi".to_string())),
                    ("person".to_string(),Value::Dictionary(HashMap::from([
                        ("first_name".to_string(), Value::String("Bob".to_string())),
                        ("last_name".to_string(), Value::String("Smith".to_string())),
                    ]))),
                    ])
            ) {
                println!("View result: {:?}", v );
            } else {
                println!("Error?");
            }
        }
    }

    #[test]
    fn loops() {
        let mut ct = Crimtag::new();
        if let Err(e) = ct.load_static(r#"Looping code:
[|view "index"|>We will enumerate people here:[|loop people|>
 - [|show last_name|], [|show first_name|]: [|show title|]<|loop|]
<|view|]
"#) {
        println!("Error: {:?}", e );
        return;
        }

        let ctx = Context::from([
            ("people".to_string(), crate::Value::List(vec![
                crate::Value::Dictionary(Context::from([
                    ("first_name".to_string(), Value::String("Joe".to_string())),
                    ("last_name".to_string(), Value::String("Smith".to_string())),
                    ("title".to_string(), Value::String("CEO".to_string())),
                ])),
                crate::Value::Dictionary(Context::from([
                    ("first_name".to_string(), Value::String("Chris".to_string())),
                    ("last_name".to_string(), Value::String("Perkens".to_string())),
                    ("title".to_string(), Value::String("Baconeer".to_string())),
                ])),
                crate::Value::Dictionary(Context::from([
                    ("first_name".to_string(), Value::String("Will".to_string())),
                    ("last_name".to_string(), Value::String("Power".to_string())),
                    ("title".to_string(), Value::String("CFO".to_string())),
                ])),
                crate::Value::Dictionary(Context::from([
                    ("first_name".to_string(), Value::String("Justin".to_string())),
                    ("last_name".to_string(), Value::String("Time".to_string())),
                    ("title".to_string(), Value::String("CIO".to_string())),
                ])),
            ])),
        ]);
        match ct.render("index", &ctx) {
            Ok(s) => println!("View result: {}", s ),
            Err(e) => println!("Error: {:?}", e ),
        }
    }
}
