#[cfg(test)]
pub mod tests;

use std::io::Write;

use anyhow::Error;

// use crate::url::Url;
use document_tree::{
    attribute_types as at, element_categories as c, elements as e, extra_attributes as a, Document,
    Element, ExtraAttributes, HasChildren,
};

// static FOOTNOTE_SYMBOLS: [char; 10] = ['*', '†', '‡', '§', '¶', '#', '♠', '♥', '♦', '♣'];

pub fn render_html<W>(document: &Document, stream: W, standalone: bool) -> Result<(), Error>
where
    W: Write,
{
    let mut renderer = HTMLRenderer { stream, level: 0 };
    if standalone {
        document.render_html(&mut renderer)
    } else {
        for c in document.children() {
            (*c).render_html(&mut renderer)?;
            writeln!(renderer.stream)?;
        }
        Ok(())
    }
}

fn escape_html(text: &str) -> String {
    text.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
}

struct HTMLRenderer<W>
where
    W: Write,
{
    stream: W,
    level: u8,
}

trait HTMLRender {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write;
}

macro_rules! impl_html_render_cat {($cat:ident { $($member:ident),+ }) => {
    impl HTMLRender for c::$cat {
        fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write {
            match self {$(
                c::$cat::$member(elem) => (**elem).render_html(renderer),
            )+}
        }
    }
}}

macro_rules! impl_html_render_simple {
    (
        $type1:ident => $tag1:ident $( [$($post1:tt)+] )?,
        $( $type:ident => $tag:ident $( [$($post:tt)+] )? ),+
    ) => {
        impl_html_render_simple!($type1 => $tag1 $([$($post1)+])?);
        $( impl_html_render_simple!($type => $tag $([$($post)+])?); )+
    };
    ( $type:ident => $tag:ident ) => {
        impl_html_render_simple!($type => $tag[""]);
    };
    ( $type:ident => $tag:ident [ $post:expr ] ) => {
        impl HTMLRender for e::$type {
            fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write {
                let multiple_children = self.children().len() > 1;
                write!(renderer.stream, "<{}", stringify!($tag))?;
                if self.classes().len() > 0 {
                    write!(renderer.stream, " class=\"{}\"", self.classes().join(" "))?;
                }
                write!(renderer.stream, ">")?;
                if multiple_children { write!(renderer.stream, $post)?; }
                for c in self.children() {
                    (*c).render_html(renderer)?;
                    if multiple_children { write!(renderer.stream, $post)?; }
                }
                write!(renderer.stream, "</{}>", stringify!($tag))?;
                Ok(())
            }
        }
    };
}

macro_rules! impl_html_render_simple_nochildren {( $($type:ident => $tag:ident),+ ) => { $(
    impl HTMLRender for e::$type {
        fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error> where W: Write {
            write!(renderer.stream, "<{0}></{0}>", stringify!($tag))?;
            Ok(())
        }
    }
)+ }}

// Impl

impl HTMLRender for Document {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        writeln!(renderer.stream, "<!doctype html><html>")?;
        for c in self.children() {
            (*c).render_html(renderer)?;
            writeln!(renderer.stream)?;
        }
        writeln!(renderer.stream, "</html>")?;
        Ok(())
    }
}

impl_html_render_cat!(StructuralSubElement {
    Title,
    Subtitle,
    Decoration,
    Docinfo,
    SubStructure
});
impl_html_render_simple!(Subtitle => h2);

impl HTMLRender for e::Title {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        let level = if renderer.level > 6 {
            6
        } else {
            renderer.level
        };
        write!(renderer.stream, "<h{0}>", level)?;
        for c in self.children() {
            (*c).render_html(renderer)?;
        }
        write!(renderer.stream, "</h{0}>", level)?;
        Ok(())
    }
}

impl HTMLRender for e::Docinfo {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // Like “YAML frontmatter” in Markdown
        unimplemented!();
    }
}

impl HTMLRender for e::Decoration {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // Header or footer
        unimplemented!();
    }
}

impl_html_render_cat!(SubStructure {
    Topic,
    Sidebar,
    Transition,
    Section,
    BodyElement
});
impl_html_render_simple!(Sidebar => aside);

impl HTMLRender for e::Section {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        renderer.level += 1;
        writeln!(renderer.stream, "<section id=\"{0}\">", self.ids()[0].0)?;
        for c in self.children() {
            (*c).render_html(renderer)?;
            writeln!(renderer.stream)?;
        }
        write!(renderer.stream, "</section>")?;
        renderer.level -= 1;
        Ok(())
    }
}

impl HTMLRender for e::Transition {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        write!(renderer.stream, "<hr/>")?;
        Ok(())
    }
}

impl HTMLRender for e::Topic {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // A mini section with title
        unimplemented!();
    }
}

impl_html_render_cat!(BodyElement {
    Paragraph,
    LiteralBlock,
    DoctestBlock,
    MathBlock,
    Rubric,
    SubstitutionDefinition,
    Comment,
    Pending,
    Target,
    Raw,
    Image,
    Compound,
    Container,
    BulletList,
    EnumeratedList,
    DefinitionList,
    FieldList,
    OptionList,
    LineBlock,
    BlockQuote,
    Admonition,
    Attention,
    Hint,
    Note,
    Caution,
    Danger,
    Error,
    Important,
    Tip,
    Warning,
    Footnote,
    Citation,
    SystemMessage,
    Figure,
    Table
});
impl_html_render_simple!(Paragraph => p, MathBlock => math, Rubric => a, Compound => p, Container => div, BulletList => ul["\n"], EnumeratedList => ol["\n"], DefinitionList => dl["\n"], FieldList => dl["\n"], OptionList => pre, LineBlock => div["\n"], BlockQuote => blockquote, Admonition => aside, Attention => aside, Hint => aside, Note => aside, Caution => aside, Danger => aside, Error => aside, Important => aside, Tip => aside, Warning => aside, Figure => figure);
impl_html_render_simple_nochildren!(Table => table); //TODO: after implementing the table, move it to elems with children

// circumvent E0119
trait IMark {}
impl IMark for e::Image {}
impl IMark for e::ImageInline {}
impl<I> HTMLRender for I
where
    I: e::Element + a::ExtraAttributes<a::Image> + IMark,
{
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        let extra = self.extra();
        if let Some(ref target) = extra.target {
            write!(
                renderer.stream,
                "<a href=\"{}\">",
                escape_html(target.as_str())
            )?;
        }
        write!(renderer.stream, "<img")?;
        if let Some(ref alt) = extra.alt {
            write!(renderer.stream, " alt=\"{}\"", escape_html(alt))?;
        }
        // TODO: align: Option<AlignHV>
        // TODO: height: Option<Measure>
        // TODO: width: Option<Measure>
        // TODO: scale: Option<u8>
        write!(
            renderer.stream,
            " src=\"{}\" />",
            escape_html(extra.uri.as_str())
        )?;
        if extra.target.is_some() {
            write!(renderer.stream, "</a>")?;
        }
        Ok(())
    }
}

impl HTMLRender for e::LiteralBlock {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        let mut cls_iter = self.classes().iter();
        let is_code = cls_iter.next() == Some(&"code".to_owned());
        write!(renderer.stream, "<pre>")?;
        if is_code {
            // TODO: support those classes not being at the start
            if let Some(lang) = cls_iter.next() {
                write!(renderer.stream, "<code class=\"language-{}\">", lang)?;
            } else {
                write!(renderer.stream, "<code>")?;
            }
        }
        for c in self.children() {
            (*c).render_html(renderer)?;
        }
        if is_code {
            write!(renderer.stream, "</code>")?;
        }
        write!(renderer.stream, "</pre>")?;
        Ok(())
    }
}

impl HTMLRender for e::DoctestBlock {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // TODO
        unimplemented!();
    }
}

impl HTMLRender for e::SubstitutionDefinition {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // TODO: Should those be removed after resolving them
        Ok(())
    }
}

impl HTMLRender for e::Comment {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        write!(renderer.stream, "<!--")?;
        for c in self.children() {
            (*c).render_html(renderer)?;
        }
        write!(renderer.stream, "-->")?;
        Ok(())
    }
}

impl HTMLRender for e::Pending {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // Will those be resolved by the time we get here?
        unimplemented!();
    }
}

impl HTMLRender for e::Target {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // Should be resolved by now
        Ok(())
    }
}

impl HTMLRender for e::Raw {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        let extra = self.extra();
        if extra.format.contains(&at::NameToken("html".to_owned())) {
            for c in self.children() {
                write!(renderer.stream, "{}", c)?;
            }
        }
        Ok(())
    }
}

impl HTMLRender for e::Footnote {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        unimplemented!();
    }
}

impl HTMLRender for e::Citation {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        unimplemented!();
    }
}

impl HTMLRender for e::SystemMessage {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        write!(renderer.stream, "<figure><caption>System Message</caption>")?;
        for c in self.children() {
            (*c).render_html(renderer)?;
        }
        write!(renderer.stream, "</figure>")?;
        Ok(())
    }
}

impl_html_render_cat!(TextOrInlineElement {
    String,
    Emphasis,
    Strong,
    Literal,
    Reference,
    FootnoteReference,
    CitationReference,
    SubstitutionReference,
    TitleReference,
    Abbreviation,
    Acronym,
    Superscript,
    Subscript,
    Inline,
    Problematic,
    Generated,
    Math,
    TargetInline,
    RawInline,
    ImageInline
});
impl_html_render_simple!(Emphasis => em, Strong => strong, Literal => code, FootnoteReference => a, CitationReference => a, TitleReference => a, Abbreviation => abbr, Acronym => acronym, Superscript => sup, Subscript => sub, Inline => span, Math => math, TargetInline => a);

impl HTMLRender for String {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        write!(renderer.stream, "{}", escape_html(self))?;
        Ok(())
    }
}

impl HTMLRender for e::Reference {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        let extra = self.extra();
        write!(renderer.stream, "<a")?;
        if let Some(ref target) = extra.refuri {
            write!(
                renderer.stream,
                " href=\"{}\"",
                escape_html(target.as_str())
            )?;
        }
        /*
        if let Some(ref name) = extra.name {
            write!(renderer.stream, " title=\"{}\"", escape_html(&name.0))?;
        }
        */
        write!(renderer.stream, ">")?;
        for c in self.children() {
            (*c).render_html(renderer)?;
        }
        write!(renderer.stream, "</a>")?;
        Ok(())
    }
}

impl HTMLRender for e::SubstitutionReference {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // Will those be resolved by the time we get here?
        unimplemented!();
    }
}

impl HTMLRender for e::Problematic {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // Broken inline markup leads to insertion of this in docutils
        unimplemented!();
    }
}

impl HTMLRender for e::Generated {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // Section numbers and so on
        unimplemented!();
    }
}

impl HTMLRender for e::RawInline {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        for c in self.children() {
            write!(renderer.stream, "{}", c)?;
        }
        Ok(())
    }
}

//--------------\\
//Content Models\\
//--------------\\

impl_html_render_cat!(SubTopic { Title, BodyElement });
impl_html_render_cat!(SubSidebar {
    Topic,
    Title,
    Subtitle,
    BodyElement
});
impl_html_render_simple!(ListItem => li);

impl HTMLRender for e::DefinitionListItem {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // Term→dt, Definition→dd, Classifier→???
        unimplemented!();
    }
}

impl HTMLRender for e::Field {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // FieldName→dt, FieldBody→dd
        unimplemented!();
    }
}

impl HTMLRender for e::OptionListItem {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        // OptionGroup→dt(s), Description→dd
        unimplemented!();
    }
}

impl_html_render_cat!(SubLineBlock { LineBlock, Line });

impl HTMLRender for e::Line {
    fn render_html<W>(&self, renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        for c in self.children() {
            (*c).render_html(renderer)?;
        }
        write!(renderer.stream, "<br>")?;
        Ok(())
    }
}

impl_html_render_cat!(SubBlockQuote {
    Attribution,
    BodyElement
});
impl_html_render_simple!(Attribution => cite); //TODO: correct?

impl_html_render_cat!(SubFigure {
    Caption,
    Legend,
    BodyElement
});
impl_html_render_simple!(Caption => caption);

impl HTMLRender for e::Legend {
    fn render_html<W>(&self, _renderer: &mut HTMLRenderer<W>) -> Result<(), Error>
    where
        W: Write,
    {
        unimplemented!();
    }
}

//------------\\
//Things to do\\
//------------\\

//TODO: prettyprint option list
//TODO: render admonitions: Admonition, Attention, Hint, Note, Caution, Danger, Error, Important, Tip, Warning
//TODO: properly render tables

//TODO: add reference target: FootnoteReference, CitationReference, TitleReference
//TODO: add title: Abbr, Acronym
//TODO: convert math, set display attr
//TODO: add id: Rubric, Target, TargetInline
