use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;

use pathdiff;

use crate::config::Config;
use crate::error::Error;
use crate::file_writer;
use crate::logger::{LogLevel, Logger};
use crate::options::{BuildOpt, Opt};
use crate::renderer;
use crate::template::Template;

const DEFAULT_CSS: &'static str = include_str!("templates/default-css.css");
const DEFAULT_JS: &'static str = include_str!("templates/default-js.js");
const PAGE_TEMPLATE: &'static str = include_str!("templates/page-template.html");
const NAVBAR_ITEM: &'static str = include_str!("templates/navbar/item-template.html");
const NAVBAR_IMAGE_ITEM: &'static str = include_str!("templates/navbar/image-item-template.html");
const CSS_TAG: &'static str = include_str!("templates/meta_tags/css_tag.html");
const JS_TAG: &'static str = include_str!("templates/meta_tags/js_tag.html");

fn fetch_config() -> Result<Config, Error> {
    match Config::new() {
        Ok(config) => Ok(config),
        Err(err) => Err(Error::from(err)),
    }
}

pub fn build(logger: &Logger, opt: &Opt, _: &BuildOpt) -> Result<(), Error> {
    logger.log(LogLevel::INFO, "Starting build");
    let config = fetch_config()?;

    if config.global_config.website.use_default_css {
        let css_path = file_writer::add_to_beginning(
            PathBuf::from("css/default.css"),
            config
                .clone()
                .global_config
                .website
                .output
                .unwrap_or("public".to_owned()),
        );
        logger.log(LogLevel::DETAIL, format!("Adding {:?}", css_path));
        file_writer::write_file(&css_path, DEFAULT_CSS, opt.overwrite)?;
    }

    if config.global_config.website.use_default_js {
        let js_path = file_writer::add_to_beginning(
            PathBuf::from("js/default.js"),
            config
                .clone()
                .global_config
                .website
                .output
                .unwrap_or("public".to_owned()),
        );
        logger.log(LogLevel::DETAIL, format!("Adding {:?}", js_path));
        file_writer::write_file(&js_path, DEFAULT_JS, opt.overwrite)?;
    }

    logger.log(LogLevel::INFO, "Generating page templates");
    let page_template = Template::new(PAGE_TEMPLATE);
    let css_tag_template = Template::new(CSS_TAG);
    let js_tag_template = Template::new(JS_TAG);

    let mut navbar_content = String::new();
    if config.global_config.navbar.is_some() {
        let navbar_item_template = Template::new(NAVBAR_ITEM);
        let navbar_image_item_template = Template::new(NAVBAR_IMAGE_ITEM);
        logger.log(LogLevel::DETAIL, "Rendering Navbar");
        navbar_content =
            renderer::render_navbar(&config, navbar_item_template, navbar_image_item_template)?;
    }

    logger.log(LogLevel::INFO, "Rendering");
    let configs = config.get_configs()?;
    let mut renders = Vec::new();
    for config in configs.clone() {
        logger.log(
            LogLevel::DETAILER,
            format!("Setting up to render {}", config.page_config.page.html_path),
        );

        // Generate CSS tagstags
        logger.log(LogLevel::DETAILER, "Generating CSS tags");
        let mut css_string = String::new();
        let css_list = config.global_config.website.css.clone();

        for item in css_list {
            let data = Template::css_tag_data_from(item, false);
            css_string += &*css_tag_template.render(&data);
        }

        if let Some(list) = config.page_config.page.css.clone() {
            for item in list {
                let data = Template::css_tag_data_from(item, true);
                css_string += &*css_tag_template.render(&data);
            }
        }

        // Generate JS tags
        logger.log(LogLevel::DETAILER, "Generating JS tags");
        let mut js_string = String::new();
        let js_list = config.global_config.website.javascript.clone();

        for item in js_list {
            let data = Template::css_tag_data_from(item, false);
            js_string += &*js_tag_template.render(&data);
        }

        if let Some(list) = config.page_config.page.javascript.clone() {
            for item in list {
                let data = Template::css_tag_data_from(item, true);
                js_string += &*js_tag_template.render(&data);
            }
        }

        // Generate and render Injections
        logger.log(LogLevel::DETAILER, "Rendering injections.");
        let (before_navbar, before_content, after_content) =
            renderer::render_injections(&logger, &config)?;

        logger.log(LogLevel::DETAILER, "Rendering");

        let markdown = renderer::render_markdown_content(&config)?;
        let markdown = renderer::render_custom_markdown(markdown)?;
        let data = Template::page_data_from(
            config.clone(),
            navbar_content.clone(),
            before_navbar,
            before_content,
            after_content,
            css_string,
            js_string,
            markdown,
        );
        let rendered = page_template.render(&data);
        renders.push(rendered);

        logger.log(
            LogLevel::DETAIL,
            format!("Rendered {}", config.page_config.page.html_path),
        );
    }

    logger.log(LogLevel::INFO, "Writing");
    for (idx, config) in configs.clone().iter().enumerate() {
        let html_path = config.clone().page_config.page.html_path;
        let html_path = file_writer::add_to_beginning(
            PathBuf::from(html_path),
            config
                .clone()
                .global_config
                .website
                .output
                .unwrap_or("public".to_owned()),
        );
        logger.log(LogLevel::DETAIL, format!("Writing {:?}", html_path));
        if let Some(render) = renders.get(idx) {
            file_writer::write_file(&html_path, render.clone(), opt.overwrite)?;
        } else {
            return Err(Error::new(
                LogLevel::SEVERE,
                format!("Internal error; Render not found for file: {:?}", html_path),
            ));
        }
    }

    logger.log(LogLevel::INFO, "Copying resources");
    if let Some(resources) = config.global_config.resources.clone() {
        for resource in resources.values() {
            let path = Path::new(&resource.source);
            if path.exists() {
                let dest = file_writer::add_to_beginning(
                    PathBuf::from(resource.destination.clone()),
                    config
                        .clone()
                        .global_config
                        .website
                        .output
                        .unwrap_or("public".to_owned()),
                );
                match write_recursive_resource(
                    path.to_path_buf(),
                    PathBuf::from(resource.source.clone()),
                    dest,
                ) {
                    Ok(_) => logger.log(LogLevel::DETAIL, "Resource successfully copied."),
                    Err(err) => return Err(err),
                }
            } else {
                logger.log(
                    LogLevel::WARNING,
                    format!("Resource does not exist: {:?}", path),
                );
            }
        }
    }

    logger.log(LogLevel::INFO, "Done!");

    Ok(())
}

fn write_recursive_resource(
    resource_path: PathBuf,
    source: PathBuf,
    destination: PathBuf,
) -> Result<(), Error> {
    if let Some(relative) = pathdiff::diff_paths(&resource_path, &source) {
        if resource_path.is_dir() {
            for item in resource_path.read_dir().unwrap() {
                match item {
                    Ok(item) => {
                        match write_recursive_resource(
                            item.path(),
                            source.clone(),
                            destination.clone(),
                        ) {
                            Ok(_) => {}
                            Err(err) => return Err(err),
                        };
                    }
                    Err(error) => return Err(Error::from(error)),
                }
            }
            Ok(())
        } else {
            match File::open(resource_path.clone()) {
                Ok(read_file) => {
                    let bytes: Vec<u8> = match read_file.bytes().collect() {
                        Ok(bytes) => bytes,
                        Err(_) => Vec::new(),
                    };

                    let mut dest_path = destination.clone();

                    if source.is_dir() {
                        dest_path = dest_path.join(relative.clone());
                        dest_path.set_file_name(resource_path.file_name().unwrap());
                    }
                    file_writer::write_bytes(&dest_path, &bytes, true)?;

                    Ok(())
                }
                Err(err) => Err(Error::new(
                    LogLevel::SEVERE,
                    format!("Failed to open read file {:?}: {}", resource_path, err),
                )),
            }
        }
    } else {
        Err(Error::new(
            LogLevel::SEVERE,
            format!(
                "Internal Error: resource relative path invalid: {:?} to {:?}",
                &resource_path, &source
            ),
        ))
    }
}