#![feature(plugin)]
#![allow(dead_code)]
#![allow(unused_variables)]

// error-chain can recurse deeply
#![recursion_limit = "1024"]

#[macro_use]
extern crate error_chain;
extern crate chrono;
extern crate markdown;
extern crate liquid;
extern crate walkdir;
extern crate rustc_serialize;
extern crate docopt;
extern crate inotify;
extern crate git2;
extern crate url;

use std::collections::HashMap;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::{self, File, OpenOptions};
use std::sync::mpsc::channel;
use std::thread;
use std::process::Command;
use std::default::Default;
use std::path::PathBuf;
use walkdir::WalkDir;
use liquid::Renderable;
use liquid::Context;
use liquid::Value;
use docopt::Docopt;

mod logger;
mod post;
mod site;
mod cmdline;
#[macro_use]
mod new_blog;
use logger::*;
use post::*;
use site::*;
use cmdline::{NOT_SILICA_SITE, USAGE};
use new_blog::{create_new_site, exists_config, create_new_post};

mod errors {
    // Create the Error, ErrorKind, ResultExt, and Result types
    error_chain! { }
}

use errors::*;

#[derive(Debug, RustcDecodable)]
struct Args {
    cmd_site: bool,
    cmd_post: bool,
    cmd_publish: bool,
    cmd_serve: bool,
    flag_draft: bool,
    flag_version: bool,
    flag_help: bool,
    arg_sname: Vec<String>,
    arg_pname: Vec<String>,
}

#[derive(Debug)]
    Metadata,
    Content,
    WeGood,
    MarkdownParseError,
    NotValidSite,
    IOError,
}

enum SilicaParseError {
    ConfigFile,
    NotASite,
    Other,
}

fn watch() {
    let site = Site::new();
    let (tx, rx) = channel();
    thread::spawn(move || {
        site.watch_changes(tx.clone());
    });
    println!("Watching for changes");
    match rx.recv().unwrap() {
        Events::Modified => {
            let _ = publish("./");
            serve()
        }
        _ => println!("I got nothing"),
    }
}

fn serve() {
    if !exists_config("./config.toml") {
        return log_err(NOT_SILICA_SITE);
    }
    Command::new("pkill")
        .arg("python")
        .output()
        .unwrap_or_else(|e| panic!("failed to run Web Server: {}", e));

    let mut child = Command::new("python")
                        .arg("-m")
                        .arg("http.server")
                        .spawn()
                        .unwrap_or_else(|e| panic!("failed to run Web Server: {}", e));

    // watch for file event changes
    // this calls the site's watch_changes method which spawns another thread and receives a
    // a signal of file changes, back to main thread, and on receiving the main thread
    // does a match and calls the serve publish command again, which restarts the server.
    watch();

    let ecode = child.wait()
                     .unwrap_or_else(|e| panic!("failed to wait on child: {}", e));
    println!("Child Exited with {:?}", ecode);
}

// TODO decouple roles here maybe ?
fn publish(base: &str) -> Result<()> {
    assert_valid_site!();
    let mut config_map = HashMap::new();
    let mut posts: Vec<Post> = vec![];
    let mut path_post = PathBuf::new();
    path_post.push(base);
    path_post.push("content");
    path_post.push("posts");
    let mut site = Site::new();

    let has_md_ext = |ext: &walkdir::DirEntry| ext.path().extension().unwrap() == "md";
    // TODO use this when we have better idea on basic properties of a Silica Site
    let _site_configs = || -> Result<HashMap<String, String>> {
        let lines = BufReader::new(File::open("./silica.toml").unwrap()).lines();
        for line in lines {
            let line_content = line.unwrap();
            let v: Vec<&str> = line_content.split("=").collect();
            if v.len() == 2 {
                config_map.insert(v[0].trim().to_owned(), v[1].trim().to_owned());
            }
        }
        Ok(config_map)
    };

    for entry in WalkDir::new(path_post) {
        let entry = entry.unwrap();
        if !entry.path().is_dir() && has_md_ext(&entry) {
            let current_file = entry.path().to_str().unwrap();
            match File::open(current_file) {
                Ok(ref mut f) => {
                    log_info(&format!("Processing {:?}", current_file));
                    let mut md_string = String::new();
                    let _ = f.read_to_string(&mut md_string);
                    match parse_md(md_string) {
                        Ok(post) => posts.push(post),
                        Err(_) => bail!("Markdown Parse Error")
                    }
                }
                Err(_) => bail!("Markdown Parse Error"),
            }

        } else {
            log_warn("Some non-markdown files discovered");
        }
    }
    site.posts(posts);

    match fs::metadata("./build") {
        Ok(_) => log_warn("Exists: Ignoring folder creation"),
        Err(_) => {let _ = fs::create_dir("./build");}
    }

    let mut index_page = String::new();
    index_page.push_str("./build/");
    index_page.push_str("index.html");
    match File::create(&index_page) {
        Ok(mut buffer) => {
            let mut site_template = OpenOptions::new().read(true).open("../base/base_theme.html").unwrap();
            let mut base_theme_buff: Vec<u8> = Vec::new();
            site_template.read_to_end(&mut base_theme_buff).unwrap();
            let base_theme_buff = String::from_utf8(base_theme_buff).unwrap();
            let _ = buffer.write_all((&base_theme_buff[..]).as_bytes());

            let mut liquid_posts = vec![];
            if let Some(ref posts) = *site.get_posts() {
                for p in posts {
                    liquid_posts.push(Value::Str(p.get_content()))
                }
            }

            let template = liquid::parse(&base_theme_buff, Default::default()).unwrap();
            let mut cx = Context::new();
            cx.set_val("posts", Value::Array(liquid_posts));
            let output = template.render(&mut cx).unwrap().unwrap();
            let mut output_slice = output.as_bytes();
            let _ = buffer.write_all(&mut output_slice);
        }
        Err(_) => bail!("Error transpiling markdown to HTML")
    }
    Ok(())
}

fn process_cmd(args: &Args) -> Result<()> {
    if args.cmd_site {
        create_new_site(args.arg_sname.first().unwrap())
    } else if args.cmd_post {
        let mut as_draft: bool = false;
        if args.flag_draft {
            as_draft = true;
        }
        let post_name = args.arg_pname[0].clone();
        let  _ = create_new_post(&post_name, as_draft);
    } else if args.cmd_publish {
        match publish("./") {
            Err(what) => bail!("Something's wrong: {:?}", what),
            Ok(_) => {
                log_info("Your shiny new site lives in `build` folder.\nHave a great day.");
            }
        }
    } else if args.cmd_serve {
        serve()
    } else if args.flag_help {
        println!("{}", USAGE);
    }
    Ok(())
}

// The entry point for silica. Parses command line arguments and acts accordingly
fn main() {
    let args = Docopt::new(USAGE)
                         .and_then(|d| d.decode())
                         .unwrap_or_else(|e| e.exit());
    if let Err(ref e) = process_cmd(&args) {
        use ::std::io::Write;
        let stderr = &mut ::std::io::stderr();
        let errmsg = "Error writing to stderr";

        writeln!(stderr, "error: {}", e).expect(errmsg);

        for e in e.iter().skip(1) {
            writeln!(stderr, "caused by: {}", e).expect(errmsg);
        }

        // The backtrace is not always generated. Try to run this example
        // with `RUST_BACKTRACE=1`.
        if let Some(backtrace) = e.backtrace() {
            writeln!(stderr, "backtrace: {:?}", backtrace).expect(errmsg);
        }

        ::std::process::exit(1);
}
}

#[cfg(test)]
mod test {
    use {bootstrap, init_post};
    #[test]
    fn test_create_site() {
        use std::path::Path;
        use std::fs;
        let site_dir = Path::new("./blog");
        let site_config_file = Path::new("./blog/config.toml");
        bootstrap("blog");
        assert_eq!(true, site_dir.is_dir());
        assert_eq!(true, site_config_file.exists());
        fs::remove_dir_all("./blog").unwrap();
    }

    #[test]
    fn test_init_post() {
        use std::path::Path;
        use std::fs;
        init_post("intro.md", false);
        let post_md = Path::new("./intro.md");
        assert_eq!(true, post_md.exists());
        fs::remove_file("./intro.md").unwrap();
    }
}
pub enum State {