/*
A Rust Site Engine
Copyright 2020-2021 Anthony Martinez

Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
http://opensource.org/licenses/MIT>, at your option. This file may not be
copied, modified, or distributed except according to those terms.
*/

use std::path::{Path, PathBuf};
use std::sync::Arc;

use super::config::AppConfig;
use super::common;
use super::{Context, Result};

use log::{debug, trace};
use pulldown_cmark::{Parser, html};
use tera::Tera;
use tera::Context as TemplateContext;

mod default;

#[derive(Debug)]
pub(crate) struct Engine {
    pub app: Arc<AppConfig>,
    pub topic_slug: String,
    pub template: String,
    pub instance: Tera
}

impl Engine {
    pub(crate) fn new(a: Arc<AppConfig>, ts: &str, tmpl: &str, inst: Tera) -> Engine {
	trace!("Loading rendering engine");
	Engine {
	    app: a,
	    topic_slug: ts.to_owned(),
	    template: tmpl.to_owned(),
	    instance: inst
	}
    }
}

pub(crate) fn load_default_template() -> Result<Tera> {
    trace!("Loading default rendering template");
    let mut tera = Tera::default();
    tera.add_raw_template("default.tmpl", default::TEMPLATE)
        .context("failure adding default template")?;
    Ok(tera)
}

pub(crate) fn render_topic(engine: Engine) -> Result<String> {
    debug!("Rendering topic: '{}'", &engine.topic_slug);
    let site = &engine.app.site;
    let topic_data = load_topic(&engine)?;
    let mut context = TemplateContext::new();
    context.insert("site", site);
    context.insert("posts", &topic_data);
    let output = engine.instance.render(&engine.template, &context)
        .with_context(|| format!("failed rendering topic: {}", &engine.topic_slug))?;

    trace!("Rendered content for topic: {}\n{}", &engine.topic_slug, output);
    Ok(output)
}

fn load_topic(engine: &Engine) -> Result<Vec<String>> {
    trace!("Loading topic content for '{}'", &engine.topic_slug);
    let topic_path = Path::new(&engine.app.docpaths.webroot).join(&engine.topic_slug).join("posts");
    let pat = format!("{}/*.md", topic_path.display());
    let paths = common::path_matches(&pat)?;
    read_to_html(paths)
}

fn read_to_html(paths: Vec<PathBuf>) -> Result<Vec<String>> {
    debug!("Rendering Markdown to HTML");
    let mut contents: Vec<String> = Vec::new();
    for path in paths {
	trace!("Rendering {} to HTML", &path.display());
	let buf = std::fs::read_to_string(&path)
	    .with_context(|| format!("failure reading '{}' to string", &path.display()))?;
	let parser = Parser::new(&buf);
	let mut html_output = String::new();
	html::push_html(&mut html_output, parser);
	contents.push(html_output);
    }

    Ok(contents)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::File;
    use std::io::prelude::*;

    #[test]
    fn check_default_template() {
	let tera = load_default_template();
	assert!(tera.is_ok())
    }

    #[test]
    fn check_render_topic() {
	let dir = tempfile::tempdir().unwrap();
	let mut src: &[u8] = b"Site Name\nAuthor Name\nOne, Two, Three, And More\nadmin\n";
	let instance = load_default_template().unwrap();
	let config = AppConfig::generate(&dir, &mut src).unwrap();
	let config = Arc::new(config);
	let engine = Engine::new(config, "one", "default.tmpl", instance);

	let post = r#"
### Something

Very cool, but maybe not super useful
"#;
	let post2 = r#"
### Something Again

Super Wow!
"#;
	
	let mut f = File::create(&dir.path().join("site/webroot/one/posts/post1.md")).unwrap();
	f.write_all(&post.as_bytes()).unwrap();

	let mut f = File::create(&dir.path().join("site/webroot/one/posts/post2.md")).unwrap();
	f.write_all(&post2.as_bytes()).unwrap();

	let page = render_topic(engine).unwrap();

	assert!(page.contains("super useful"));
	assert!(page.contains("Super Wow!"));
    }
}
