use super::css::StyleSheet;
use super::html::{
    replace_placeholders, HtmlDoc, HtmlElement, CSS_LINK_FRAGMENT, FAVICON_LINK_FRAGMENT,
    IMAGE_LINKS_FRAGMENT, SCRIPT_FRAGMENT,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Page {
    pub template_name: String,
    pub body: PageBody,
    pub metadata: PageMetadata,
    #[serde(default = "Vec::new")]
    pub sub_pages: Vec<Page>,
    #[serde(default = "HtmlDoc::new")]
    pub html: HtmlDoc,
    pub template: Option<PageTemplate>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PageTemplate {
    pub layout: StyleSheet,
    pub name: String,
    pub contents: Vec<HtmlElement>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PageBody(Vec<HtmlElement>);

impl std::fmt::Display for PageBody {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "{}",
            &self
                .0
                .iter()
                .map(|i| i.to_string())
                .collect::<Vec<String>>()
                .join("")
        )
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PageMetadata {
    pub title: String,
    pub lang: String,
    pub description: String,
    pub url_slug: String,
    #[serde(default = "CSSLinks::new")]
    pub css: CSSLinks,
    #[serde(default = "JSLinks::new")]
    pub js: JSLinks,
    #[serde(default = "FaviconLink::new")]
    pub favicon: FaviconLink,
    #[serde(default = "String::new")]
    pub author: String,
    #[serde(default = "ImageLinks::new")]
    pub image: ImageLinks,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FaviconLink(String);

impl FaviconLink {
    pub fn new() -> Self {
        FaviconLink(String::from("/static/default/favicon.ico"))
    }
}

impl std::fmt::Display for FaviconLink {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "{}",
            replace_placeholders(FAVICON_LINK_FRAGMENT, {
                let mut map = HashMap::new();
                map.insert("url".to_owned(), self.0.to_owned());
                map
            })
        )
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CSSLinks(Vec<String>);

impl CSSLinks {
    pub fn new() -> Self {
        CSSLinks(vec!["/static/default/style.css".to_owned()])
    }
}

impl std::fmt::Display for CSSLinks {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "{}",
            &self
                .0
                .iter()
                .map(|url| replace_placeholders(CSS_LINK_FRAGMENT, {
                    let mut map = HashMap::new();
                    map.insert("url".to_string(), url.to_owned());
                    map
                }))
                .collect::<Vec<String>>()
                .join("\n")
        )
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JSLinks(Vec<String>);

impl JSLinks {
    pub fn new() -> Self {
        JSLinks(vec!["/static/default/script.js".to_owned()])
    }
}

impl std::fmt::Display for JSLinks {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "{}",
            &self
                .0
                .iter()
                .map(|url| replace_placeholders(SCRIPT_FRAGMENT, {
                    let mut map = HashMap::new();
                    map.insert("url".to_string(), url.to_owned());
                    map
                }))
                .collect::<Vec<String>>()
                .join("\n")
        )
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ImageLinks(Vec<String>);

impl ImageLinks {
    pub fn new() -> Self {
        ImageLinks(vec![])
    }
}

impl std::fmt::Display for ImageLinks {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "{}",
            &self
                .0
                .iter()
                .map(|url| replace_placeholders(IMAGE_LINKS_FRAGMENT, {
                    let mut map = HashMap::new();
                    map.insert("url".to_string(), url.to_owned());
                    map
                }))
                .collect::<Vec<String>>()
                .join("\n")
        )
    }
}

impl Page {
    pub fn build_html(&mut self) {
        self.html = HtmlDoc::from_page(self);
    }

    pub fn build_with_template(&mut self, template: PageTemplate) {
        let mut complete_body = template.clone();
        self.template = Some(template);
        let err = "Couldn't find page-body placeholder";

        // Insert page body inside the template page-body container.
        complete_body
            .contents
            .iter_mut()
            .find(|el| el.attrs.get("id").unwrap_or(&String::new()).eq("page-body"))
            .expect(err)
            .set_contents(self.body.0.clone());
        // Then replace the page body by the complete template
        self.body.0 = complete_body.contents;
    }

    pub fn to_map(&self) -> HashMap<String, String> {
        let mut map = HashMap::new();
        map.insert("title".to_string(), self.metadata.title.to_owned());
        map.insert("lang".to_string(), self.metadata.lang.to_owned());
        map.insert(
            "description".to_string(),
            self.metadata.description.to_owned(),
        );
        map.insert("slug".to_string(), self.metadata.url_slug.to_owned());
        map.insert("body".to_string(), self.body.to_string());
        map.insert("css".to_string(), self.metadata.css.to_string());
        map.insert("js".to_string(), self.metadata.js.to_string());
        map.insert("author".to_string(), self.metadata.author.to_string());
        map.insert("image".to_string(), self.metadata.image.to_string());

        map
    }
}

#[cfg(test)]
mod test_pages {
    use super::*;
    use crate::website::html::{HtmlAttributes, HtmlElement};

    fn test_page_metadata() -> PageMetadata {
        PageMetadata {
            title: String::from("Test"),
            lang: String::from("en"),
            description: String::from("test descr"),
            url_slug: String::from("test-page"),
            css: CSSLinks(vec![
                "/static/source_code/mystyle.css".to_string(),
                "/static/source_code/mystyle2.css".to_string(),
            ]),
            js: JSLinks(vec![
                "/static/source_code/myscript.js".to_string(),
                "/static/source_code/myscript2.js".to_string(),
            ]),
            favicon: FaviconLink(String::from("/static/images/testicon.ico")),
            author: String::from("test author"),
            image: ImageLinks(vec![
                "/static/images/testimage.png".to_string(),
                "/static/images/testimage2.png".to_string(),
            ]),
        }
    }

    fn test_page_template() -> PageTemplate {
        PageTemplate {
            layout: StyleSheet(HashMap::new()),
            name: String::from("test template"),
            contents: vec![
                HtmlElement {
                    tag: String::from("nav"),
                    attrs: HtmlAttributes::new(),
                    contents: None,
                    text: Some(String::from("NAV")),
                },
                HtmlElement {
                    tag: String::from("div"),
                    attrs: {
                        let mut attrs = HtmlAttributes::new();
                        attrs
                            .0
                            .insert(String::from("id"), String::from("page-body"));
                        attrs
                    },
                    contents: None,
                    text: None,
                },
                HtmlElement {
                    tag: String::from("footer"),
                    attrs: HtmlAttributes::new(),
                    contents: None,
                    text: Some(String::from("FOOTER")),
                },
            ],
        }
    }

    fn test_page() -> Page {
        Page {
            template_name: String::from("test template"),
            body: PageBody(vec![HtmlElement {
                tag: "span".to_string(),
                text: Some("TEST".to_string()),
                contents: None,
                attrs: HtmlAttributes::new(),
            }]),
            metadata: test_page_metadata(),
            sub_pages: Vec::new(),
            html: HtmlDoc::new(),
            template: None,
        }
    }

    #[test]
    fn page_body_to_string() {
        let body = PageBody(vec![
            HtmlElement {
                tag: "span".to_string(),
                text: Some("Hello ".to_string()),
                contents: None,
                attrs: HtmlAttributes::new(),
            },
            HtmlElement {
                tag: "span".to_string(),
                text: Some("World!".to_string()),
                contents: None,
                attrs: HtmlAttributes::new(),
            },
        ]);

        assert_eq!(body.to_string(), "<span>Hello </span><span>World!</span>");
    }

    #[test]
    fn favicon_link_to_string() {
        let pmd = test_page_metadata();
        assert_eq!(
            pmd.favicon.to_string(),
            "<link rel='icon' type='image/*' href='/static/images/testicon.ico'/>"
        )
    }

    #[test]
    fn css_links_to_string() {
        let pmd = test_page_metadata();
        assert_eq!(
            pmd.css.to_string(),
            "<link rel='stylesheet' href='/static/source_code/mystyle.css'>
<link rel='stylesheet' href='/static/source_code/mystyle2.css'>"
        )
    }

    #[test]
    fn js_links_to_string() {
        let pmd = test_page_metadata();
        assert_eq!(
            pmd.js.to_string(),
            "<script src='/static/source_code/myscript.js'></script>
<script src='/static/source_code/myscript2.js'></script>"
        )
    }

    #[test]
    fn images_links_to_string() {
        let pmd = test_page_metadata();
        assert_eq!(
            pmd.image.to_string(),
            "<meta name='image' content='/static/images/testimage.png'/>
<meta property='og:image' content='/static/images/testimage.png'/>
<meta property='twitter:image' content='/static/images/testimage.png'/>
<meta name='image' content='/static/images/testimage2.png'/>
<meta property='og:image' content='/static/images/testimage2.png'/>
<meta property='twitter:image' content='/static/images/testimage2.png'/>"
        )
    }

    #[test]
    fn build_body_with_template() {
        let mut page = test_page();
        page.build_with_template(test_page_template());
        assert_eq!(
            page.body.to_string(),
            "<nav>NAV</nav><div id=\"page-body\"><span>TEST</span></div><footer>FOOTER</footer>"
        )
    }
}