use super::util::from_frontmatter;
use super::util::output_rendered_template;
use super::util::templates::filters;
use super::util::templates::TemplateCommon;
use super::{content_path, markdown};
use askama::Template;
use chrono::NaiveDate;
use once_cell::sync::Lazy;
use pulldown_cmark::{html, Event};
use regex::Regex;
use serde::Deserialize;
use std::fs;
use std::io::Read;
use std::path::PathBuf;

pub fn generate() {
    let mut shows = fs::read_dir(content_path("tv"))
        .unwrap()
        .map(|entry| {
            let entry = entry.unwrap();
            if !entry.file_type().unwrap().is_file() {
                return None;
            }
            let path = entry.path();
            let mut f = fs::File::open(&path).unwrap();
            let mut buf = String::new();
            f.read_to_string(&mut buf).unwrap();
            Show::new(path, &buf).ok()
        })
        .flatten()
        .collect::<Vec<_>>();
    shows.sort_by(|a, b| a.last_updated().cmp(b.last_updated()).reverse());

    for show in shows.iter() {
        output_rendered_template(
            &ShowTemplate { show },
            format!("/tv/{}/index.html", show.slug),
        )
        .unwrap();
    }

    output_rendered_template(&TvTemplate { shows: &shows }, "/tv/index.html").unwrap();
}

#[derive(Template)]
#[template(path = "show.html")]
struct ShowTemplate<'a> {
    show: &'a Show,
}

impl<'a> TemplateCommon for ShowTemplate<'a> {}

impl<'a> ShowTemplate<'a> {
    fn permalink(&self) -> String {
        format!("/tv/{}/", self.show.slug)
    }
}

#[derive(Template)]
#[template(path = "tv.html")]
struct TvTemplate<'a> {
    shows: &'a [Show],
}

impl<'a> TemplateCommon for TvTemplate<'a> {}

impl<'a> TvTemplate<'a> {
    fn permalink(&self) -> &'static str {
        "/tv/"
    }
}

struct Show {
    pub metadata: ShowMetadata,
    pub slug: String,
    pub episodes: Vec<Episode>,
}

impl Show {
    fn new(path: PathBuf, contents: &str) -> anyhow::Result<Self> {
        let (metadata, rest_contents) = match from_frontmatter::<'_, ShowMetadata>(contents) {
            Ok(res) => res,
            Err(e) => return Err(e),
        };

        let slug = path.file_stem().unwrap().to_str().unwrap().to_owned();

        let episodes = parse_episodes(rest_contents);

        Ok(Self {
            metadata,
            slug,
            episodes,
        })
    }

    fn last_updated(&self) -> &NaiveDate {
        &self.episodes.iter().max_by_key(|ep| ep.date).unwrap().date
    }
}

fn parse_episodes(contents: &str) -> Vec<Episode> {
    Episodes {
        iter: markdown::parse(contents),
        buf: vec![],
        cur_episode_meta: None,
        finished: false,
    }
    .collect()
}

#[derive(Debug, Deserialize)]
struct ShowMetadata {
    pub title: String,
}

#[derive(Debug)]
struct Episode {
    title: String,
    date: NaiveDate,
    content: String,
}

struct Episodes<'a, I: Iterator<Item = Event<'a>>> {
    iter: I,
    buf: Vec<Event<'a>>,
    cur_episode_meta: Option<(String, NaiveDate)>,
    finished: bool,
}

impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Episodes<'a, I> {
    type Item = Episode;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            match self.iter.next() {
                Some(Event::Html(ref s)) if is_ep_comment(s) => {
                    if self.cur_episode_meta.is_some() {
                        let ep = self.create_episode();
                        self.cur_episode_meta = Some(parse_meta(s));
                        return Some(ep);
                    } else {
                        self.cur_episode_meta = Some(parse_meta(s));
                    }
                }
                Some(e) => {
                    self.buf.push(e);
                }
                None => {
                    if self.finished {
                        return None;
                    } else {
                        self.finished = true;
                        return Some(self.create_episode());
                    }
                }
            }
        }
    }
}

impl<'a, I: Iterator<Item = Event<'a>>> Episodes<'a, I> {
    fn create_episode(&mut self) -> Episode {
        let meta = self.cur_episode_meta.take().unwrap();

        let mut content = String::new();
        let events = std::mem::take(&mut self.buf);
        html::push_html(&mut content, events.into_iter());
        Episode {
            title: meta.0,
            date: meta.1,
            content,
        }
    }
}

static EPISODE_SEPARATOR: Lazy<Regex> =
    Lazy::new(|| Regex::new(r#"^\s*<!-- episode "(.+)" (.*) -->\s*$"#).unwrap());

fn is_ep_comment(s: impl AsRef<str>) -> bool {
    EPISODE_SEPARATOR.is_match(s.as_ref())
}

fn parse_meta(s: impl AsRef<str>) -> (String, NaiveDate) {
    let captures = EPISODE_SEPARATOR.captures(s.as_ref()).unwrap();
    let title = &captures[1];
    let date_str = &captures[2];
    (
        title.into(),
        NaiveDate::parse_from_str(date_str, "%Y-%m-%d").expect("episode entry date"),
    )
}
