use super::posts::parse::Tag;
use super::util::slugify::slugify;
use super::util::templates::{filters, TemplateCommon};
use super::util::{from_frontmatter, output_rendered_template, word_count};
use super::{content_path, markdown};
use askama::Template;
use chrono::{DateTime, FixedOffset};
use serde::Deserialize;
use std::{fs, io::Read, path::PathBuf};

pub fn generate() {
    let series = vec![
        generate_series("forge-modding-1102", "Forge Mods for 1.10.2"),
        generate_series("forge-modding-1112", "Forge Mods for 1.11.2"),
        generate_series("forge-modding-112", "Forge Mods for 1.12"),
    ];

    generate_index(&series);
}

fn generate_index(series: &[TutorialSeries]) {
    output_rendered_template(
        &TutorialsIndexTemplate { all_series: series },
        "/tutorials/index.html",
    )
    .unwrap();
}

fn generate_series(slug: &'static str, name: &'static str) -> TutorialSeries {
    let mut path = content_path("tutorials");
    path.push(slug);
    let posts = fs::read_dir(path)
        .unwrap()
        .map(|entry| {
            let entry = entry.unwrap();
            assert!(entry.file_type().unwrap().is_file());
            parse_post(entry.path(), slug, name)
        })
        .collect::<Vec<_>>();

    let series = TutorialSeries { slug, name, posts };

    for post in series.posts.iter() {
        generate_post(&series, post);
    }
    generate_series_index(&series);

    series
}

fn generate_post(series: &TutorialSeries, post: &TutorialPost) {
    output_rendered_template(
        &TutorialPostTemplate { series, post },
        format!("/tutorials/{}/{}/index.html", series.slug, post.slug),
    )
    .unwrap();
}

fn generate_series_index(series: &TutorialSeries) {
    output_rendered_template(
        &TutorialSeriesTemplate { series },
        format!("/tutorials/{}/index.html", series.slug),
    )
    .unwrap();
}

fn parse_post(path: PathBuf, series_slug: &str, series_name: &str) -> TutorialPost {
    let mut f = fs::File::open(&path).unwrap();
    let mut buffer = String::new();
    f.read_to_string(&mut buffer).unwrap();
    TutorialPost::new(path, &buffer, series_slug, series_name)
}

struct TutorialSeries {
    slug: &'static str,
    name: &'static str,
    posts: Vec<TutorialPost>,
}

impl TutorialSeries {
    fn last_updated(&self) -> &DateTime<FixedOffset> {
        &self
            .posts
            .iter()
            .max_by_key(|p| p.metadata.date)
            .unwrap()
            .metadata
            .date
    }
}

struct TutorialPost {
    content: String,
    slug: String,
    word_count: Option<u32>,
    metadata: TutorialPostMetadata,
}

impl TutorialPost {
    fn new(path: PathBuf, contents: &str, series_slug: &str, series_name: &str) -> Self {
        let (mut metadata, rest_contents) =
            from_frontmatter::<'_, TutorialPostMetadata>(contents).unwrap();

        metadata.tags = Some(vec![Tag {
            name: series_name.to_owned(),
            slug: format!("tutorials/{}", series_slug),
        }]);

        let slug = metadata
            .slug
            .clone()
            .unwrap_or_else(|| slugify(&metadata.title));

        assert!(path.extension().unwrap().eq_ignore_ascii_case("md"));

        let wc = word_count::markdown(rest_contents);

        let mut buf = vec![];
        markdown::render(rest_contents, &mut buf);
        let html = String::from_utf8(buf).unwrap();

        TutorialPost {
            content: html,
            slug,
            word_count: Some(wc),
            metadata,
        }
    }
}

#[derive(Deserialize)]
struct TutorialPostMetadata {
    title: String,
    date: DateTime<FixedOffset>,
    slug: Option<String>,
    tags: Option<Vec<Tag>>,
}

#[derive(Template)]
#[template(path = "layout/tutorial.html")]
struct TutorialPostTemplate<'a> {
    series: &'a TutorialSeries,
    post: &'a TutorialPost,
}

impl<'a> TemplateCommon for TutorialPostTemplate<'a> {}
impl<'a> TutorialPostTemplate<'a> {
    fn permalink(&self) -> String {
        format!("/tutorials/{}/{}/", self.series.slug, self.post.slug)
    }
}

#[derive(Template)]
#[template(path = "layout/tutorial-series.html")]
struct TutorialSeriesTemplate<'a> {
    series: &'a TutorialSeries,
}

impl<'a> TemplateCommon for TutorialSeriesTemplate<'a> {}
impl<'a> TutorialSeriesTemplate<'a> {
    fn permalink(&self) -> String {
        format!("/tutorials/{}/", self.series.slug)
    }
}

#[derive(Template)]
#[template(path = "tutorials.html")]
struct TutorialsIndexTemplate<'a> {
    all_series: &'a [TutorialSeries],
}

impl<'a> TemplateCommon for TutorialsIndexTemplate<'a> {}
impl<'a> TutorialsIndexTemplate<'a> {
    fn permalink(&self) -> &'static str {
        "/tutorials/"
    }
}
