use crate::elements::accent::{Color, ExpressionAccent, GenericAccent, OverSet, UnderSet};
use crate::elements::group::{
    Abs, Angles, Braces, Brackets, Ceil, Floor, Group, Matrix, NonEnclosed, Norm, Parentheses,
    Vector, XGroup,
};
use crate::elements::literal::{Literal, Number, PlainText, Symbol};
use crate::elements::special::{
    Expression, Frac, Integral, OIntegral, Pow, Prod, Root, Special, Sqrt, Sub, Sum,
};
use crate::elements::Element;
use crate::tokens::{
    Accent, Arrow, FontCommand, Function, Greek, Logical, Misc, Operation, Relation,
};
use htmlescape::{encode_attribute, encode_minimal};
pub trait ToMathML {
    fn to_mathml(&self) -> String;
}
impl ToMathML for Literal {
    fn to_mathml(&self) -> String {
        match self {
            Literal::Text(t) => t.to_mathml(),
            Literal::Symbol(s) => s.to_mathml(),
            Literal::Number(n) => n.to_mathml(),
            Literal::Greek(g) => g.to_mathml(),
            Literal::FontCommand(f) => f.to_mathml(),
            Literal::Relation(r) => r.to_mathml(),
            Literal::Function(f) => f.to_mathml(),
            Literal::Logical(l) => l.to_mathml(),
            Literal::Arrow(a) => a.to_mathml(),
            Literal::Misc(m) => m.to_mathml(),
            Literal::Operation(o) => o.to_mathml(),
            Literal::NewLine => "<mspace linebreak='newline' />".to_string(),
        }
    }
}
impl ToMathML for Greek {
    fn to_mathml(&self) -> String {
        let inner = match self {
            Greek::Alpha => "α",
            Greek::Beta => "β",
            Greek::Gamma => "γ",
            Greek::BigGamma => "Γ",
            Greek::Delta => "δ",
            Greek::BigDelta => "Δ",
            Greek::Epsilon => "ε",
            Greek::VarEpsilon => "ε",
            Greek::Zeta => "ζ",
            Greek::Eta => "η",
            Greek::Theta => "θ",
            Greek::BigTheta => "Θ",
            Greek::VarTheta => "θ",
            Greek::Iota => "ι",
            Greek::Kappa => "κ",
            Greek::Lambda => "λ",
            Greek::BigLambda => "Λ",
            Greek::Mu => "μ",
            Greek::Nu => "ν",
            Greek::Xi => "ξ",
            Greek::BigXi => "Ξ",
            Greek::Pi => "π",
            Greek::BigPi => "Π",
            Greek::Rho => "ρ",
            Greek::Sigma => "σ",
            Greek::BigSigma => "Σ",
            Greek::Tau => "τ",
            Greek::Upsilon => "υ",
            Greek::Phi => "φ",
            Greek::BigPhi => "Φ",
            Greek::VarPhi => "φ",
            Greek::Chi => "χ",
            Greek::Psi => "ψ",
            Greek::BigPsi => "Ψ",
            Greek::Omega => "ω",
            Greek::BigOmega => "Ω",
        };
        format!("<mi>{}</mi>", inner)
    }
}
impl ToMathML for PlainText {
    fn to_mathml(&self) -> String {
        if let Some(formatting) = &self.formatting {
            format!(
                "<mtext mathvariant='{}'>{}</mtext>",
                formatting.to_mathml(),
                encode_minimal(self.text.as_str())
            )
        } else {
            format!("<mtext>{}</mtext>", encode_minimal(self.text.as_str()))
        }
    }
}
impl ToMathML for FontCommand {
    fn to_mathml(&self) -> String {
        match self {
            FontCommand::Big => "bold".to_string(),
            FontCommand::BigOutline => "double-struck".to_string(),
            FontCommand::Cursive => "italic".to_string(),
            FontCommand::TText => "script".to_string(),
            FontCommand::Fr => "bold-fraktur".to_string(),
            FontCommand::SansSerif => "sans-serif".to_string(),
        }
    }
}
impl ToMathML for Symbol {
    fn to_mathml(&self) -> String {
        format!("<mi>{}</mi>", encode_minimal(self.symbol.as_str()))
    }
}
impl ToMathML for Number {
    fn to_mathml(&self) -> String {
        format!("<mn>{}</mn>", encode_minimal(self.number.as_str()))
    }
}
impl ToMathML for Relation {
    fn to_mathml(&self) -> String {
        let inner = match self {
            Relation::Eq => "=",
            Relation::Ne => "≠",
            Relation::Lt => "<",
            Relation::Gt => ">",
            Relation::Le => "≤",
            Relation::Ge => "≥",
            Relation::Prec => "≺",
            Relation::Succ => "≻",
            Relation::PrecEq => "≼",
            Relation::SuccEq => "≽",
            Relation::In => "∈",
            Relation::NotIn => "∉",
            Relation::SubSet => "⊂",
            Relation::SupSet => "⊃",
            Relation::SubSetEq => "⊆",
            Relation::SupSetEq => "⊇",
            Relation::Equiv => "≡",
            Relation::Cong => "≅",
            Relation::Approx => "≈",
            Relation::PropTo => "∝",
        };
        format!("<mo>{}</mo>", inner)
    }
}
impl ToMathML for Function {
    fn to_mathml(&self) -> String {
        let inner = match self {
            Function::Exp => "exp",
            Function::Sin => "sin",
            Function::Max => "max",
            Function::Min => "min",
            Function::Glb => "glb",
            Function::G => "g",
            Function::Lub => "lub",
            Function::Lcm => "lcm",
            Function::Gcd => "gcd",
            Function::Mod => "mod",
            Function::Dim => "dim",
            Function::Det => "det",
            Function::Ln => "ln",
            Function::Log => "log",
            Function::Cot => "cot",
            Function::Csc => "csc",
            Function::Sech => "sech",
            Function::Tanh => "tanh",
            Function::Cosh => "cosh",
            Function::ArcSin => "arcsin",
            Function::ArcCos => "arccos",
            Function::ArcTan => "arctan",
            Function::Tan => "tan",
            Function::Cos => "cos",
            Function::F => "f",
            Function::Sec => "sec",
            Function::Sinh => "sinh",
            Function::Csch => "csch",
            Function::Coth => "coth",
        };
        format!("<mi>{}</mi>", inner)
    }
}
impl ToMathML for Logical {
    fn to_mathml(&self) -> String {
        let inner = match self {
            Logical::And => "and",
            Logical::Or => "or",
            Logical::Not => "¬",
            Logical::Implies => "⇒",
            Logical::If => "if",
            Logical::Iff => "⇔",
            Logical::ForAll => "∀",
            Logical::Exists => "exists;",
            Logical::Bot => "⊥",
            Logical::Top => "⊤",
            Logical::VDash => "⊢",
            Logical::Models => "⊨",
        };
        format!("<mo>{}</mo>", inner)
    }
}
impl ToMathML for Arrow {
    fn to_mathml(&self) -> String {
        let inner = match self {
            Arrow::UpArrow => "↑",
            Arrow::DownArrow => "↓",
            Arrow::RightArrow => "→",
            Arrow::To => "→",
            Arrow::RightArrowTail => "↣",
            Arrow::TwoHeadRightArrow => "↠",
            Arrow::TwoHeadRightArrowTail => "⤖",
            Arrow::MapsTo => "↦",
            Arrow::LeftArrow => "←",
            Arrow::LeftRightArrow => "⟷",
            Arrow::BigRightArrow => "⇨",
            Arrow::BigLeftArrow => "⇦",
            Arrow::BigLeftRightArrow => "⬄",
        };
        format!("<mo>{}</mo>", inner)
    }
}
impl ToMathML for Misc {
    fn to_mathml(&self) -> String {
        let inner = match self {
            Misc::Del => "∂",
            Misc::Grad => "∇",
            Misc::PlusMinus => "±",
            Misc::EmptySet => "∅",
            Misc::Infty => "∞",
            Misc::Aleph => "ℵ",
            Misc::Therefore => "∴",
            Misc::Because => "∵",
            Misc::PLDots => "|…|",
            Misc::PCDots => "|···|",
            Misc::VDots => "︙",
            Misc::DDots => "⋱",
            Misc::EPipes => "||",
            Misc::EQuad => "| |",
            Misc::Angle => "∠",
            Misc::Frown => "⌢",
            Misc::Triangle => "△",
            Misc::Diamond => "⋄",
            Misc::Square => "□",
            Misc::LFloor => "⌊",
            Misc::RFloor => "⌋",
            Misc::LCeiling => "⌈",
            Misc::RCeiling => "⌉",
            Misc::Complex => "ℂ",
            Misc::Natural => "ℕ",
            Misc::Rational => "ℚ",
            Misc::Real => "ℝ",
            Misc::Integer => "ℤ",
            _ => "",
        };
        format!("<mi>{}</mi>", inner)
    }
}
impl ToMathML for Operation {
    fn to_mathml(&self) -> String {
        let inner = match self {
            Operation::Plus => "+",
            Operation::Minus => "−",
            Operation::CDot => "⋅",
            Operation::Ast => "∗",
            Operation::Star => "⋆",
            Operation::Slash => "/",
            Operation::Backslash => "∖",
            Operation::Times => "×",
            Operation::Div => "÷",
            Operation::LTimes => "⋉",
            Operation::RTimes => "⋊",
            Operation::Bowtie => "⋈",
            Operation::Circ => "∘",
            Operation::OPlus => "⊕",
            Operation::OTimes => "⊗",
            Operation::ODot => "⊙",
            Operation::Wedge => "∧",
            Operation::BidWedge => "⋀",
            Operation::Vee => "∨",
            Operation::BigVee => "⋁",
            Operation::Cap => "∩",
            Operation::BigCap => "⋂",
            Operation::Cup => "∪",
            Operation::BigCup => "⋃",
            _ => "",
        };
        format!("<mo>{}</mo>", inner)
    }
}
impl ToMathML for Accent {
    fn to_mathml(&self) -> String {
        match self {
            Accent::Hat => "ˆ".to_string(),
            Accent::Overline => "¯".to_string(),
            Accent::Underline => "–".to_string(),
            Accent::Vec => "→".to_string(),
            Accent::Dot => ".".to_string(),
            Accent::DDot => "..".to_string(),
            Accent::UnderBrace => "⏟".to_string(),
            Accent::OverBrace => "⏞".to_string(),
            Accent::Cancel => "⟋".to_string(),
            _ => "".to_string(),
        }
    }
}
impl ToMathML for OverSet {
    fn to_mathml(&self) -> String {
        format!(
            "<mover accentover='true'><mrow>{}</mrow><mo>{}</mo>",
            self.bottom.to_mathml(),
            self.top.to_mathml()
        )
    }
}
impl ToMathML for UnderSet {
    fn to_mathml(&self) -> String {
        format!(
            "<munder accentunder='true'><mrow>{}</mrow><mo>{}</mo>",
            self.top.to_mathml(),
            self.bottom.to_mathml(),
        )
    }
}
impl ToMathML for Color {
    fn to_mathml(&self) -> String {
        format!(
            "<mstyle mathcolor='{}'>{}</mstyle>",
            encode_attribute(self.color.as_str()),
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for GenericAccent {
    fn to_mathml(&self) -> String {
        match self.accent {
            Accent::Hat
            | Accent::Overline
            | Accent::Vec
            | Accent::Dot
            | Accent::DDot
            | Accent::OverBrace => format!(
                "<mover accentover='true'><mrow>{}</mrow><mo>{}</mo></mover>",
                self.inner.to_mathml(),
                self.accent.to_mathml()
            ),
            Accent::Underline | Accent::UnderBrace => format!(
                "<munder accentunder='true'><mrow>{}</mrow><mo>{}</mo></mover>",
                self.inner.to_mathml(),
                self.accent.to_mathml()
            ),
            _ => self.inner.to_mathml(),
        }
    }
}
impl ToMathML for Group {
    fn to_mathml(&self) -> String {
        match self {
            Group::Vector(v) => v.to_mathml(),
            Group::MSep => ",".to_string(),
            Group::Parentheses(p) => p.to_mathml(),
            Group::Brackets(b) => b.to_mathml(),
            Group::Braces(b) => b.to_mathml(),
            Group::Angles(a) => a.to_mathml(),
            Group::XGroup(x) => x.to_mathml(),
            Group::Abs(a) => a.to_mathml(),
            Group::Floor(f) => f.to_mathml(),
            Group::Ceil(c) => c.to_mathml(),
            Group::Norm(n) => n.to_mathml(),
            Group::Matrix(m) => m.to_mathml(),
            Group::NonEnclosed(ne) => ne.to_mathml(),
        }
    }
}
impl ToMathML for Parentheses {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>(</mo>{}<mo>)</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Brackets {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>[</mo>{}<mo>]</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Braces {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>{</mo>{}<mo>}</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Angles {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>⟨</mo>{}<mo>⟩</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for XGroup {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>(x</mo>{}<mo>x)</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Abs {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>|</mo>{}<mo>|</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Floor {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>⌊</mo>{}<mo>⌋</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Ceil {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>⌈</mo>{}<mo>⌉</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Norm {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>||</mo>{}<mo>||</mo></mrow>",
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Matrix {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>[</mo><mtable>{}</mtable><mo>]</mo></mrow>",
            self.inner.iter().fold("".to_string(), |a, b| format!(
                "{}<mtr>{}</mtr>",
                a,
                b.iter().fold("".to_string(), |a, b| format!(
                    "{}<mtd>{}</mtd>",
                    a,
                    b.to_mathml()
                ))
            ))
        )
    }
}
impl ToMathML for Vector {
    fn to_mathml(&self) -> String {
        format!(
            "<mrow><mo>(</mo><mtable>{}</mtable><mo>)</mo></mrow>",
            self.inner.iter().fold("".to_string(), |a, b| format!(
                "{}<mtr>{}</mtr>",
                a,
                b.iter().fold("".to_string(), |a, b| format!(
                    "{}<mtd>{}</mtd>",
                    a,
                    b.to_mathml()
                ))
            ))
        )
    }
}
impl ToMathML for NonEnclosed {
    fn to_mathml(&self) -> String {
        format!("<mrow>{}</mrow>", self.inner.to_mathml())
    }
}
impl ToMathML for Special {
    fn to_mathml(&self) -> String {
        match self {
            Special::Sum(s) => s.to_mathml(),
            Special::Prod(p) => p.to_mathml(),
            Special::Frac(f) => f.to_mathml(),
            Special::Pow(p) => p.to_mathml(),
            Special::Sub(s) => s.to_mathml(),
            Special::Sqrt(s) => s.to_mathml(),
            Special::Root(r) => r.to_mathml(),
            Special::Integral(i) => i.to_mathml(),
            Special::OIntegral(i) => i.to_mathml(),
        }
    }
}
impl ToMathML for Sum {
    fn to_mathml(&self) -> String {
        if let Some(bottom) = &self.bottom {
            if let Some(top) = &self.top {
                format!(
                    "<munderover><mi>∑</mi>{}{}</munderover>",
                    bottom.to_mathml(),
                    top.to_mathml()
                )
            } else {
                format!("<munder><mi>∑</mi>{}</munder>", bottom.to_mathml())
            }
        } else if let Some(top) = &self.top {
            format!("<mover><mi>∑<mi>{}</mover>", top.to_mathml())
        } else {
            format!("<mi>∑</mi>")
        }
    }
}
impl ToMathML for Prod {
    fn to_mathml(&self) -> String {
        if let Some(bottom) = &self.bottom {
            if let Some(top) = &self.top {
                format!(
                    "<munderover><mi>∏</mi>{}{}</munderover>",
                    bottom.to_mathml(),
                    top.to_mathml()
                )
            } else {
                format!("<munder><mi>∏</mi>{}</munder>", bottom.to_mathml())
            }
        } else if let Some(top) = &self.top {
            format!("<mover><mi>∏<mi>{}</mover>", top.to_mathml())
        } else {
            format!("<mi>∏</mi>")
        }
    }
}
impl ToMathML for Frac {
    fn to_mathml(&self) -> String {
        format!(
            "<mfrac>{}{}</mfrac>",
            self.top.to_mathml(),
            self.bottom.to_mathml()
        )
    }
}
impl ToMathML for Sqrt {
    fn to_mathml(&self) -> String {
        format!("<msqrt>{}</msqrt>", self.inner.to_mathml())
    }
}
impl ToMathML for Root {
    fn to_mathml(&self) -> String {
        format!(
            "<mroot>{}{}</mroot>",
            self.base.to_mathml(),
            self.inner.to_mathml()
        )
    }
}
impl ToMathML for Pow {
    fn to_mathml(&self) -> String {
        format!(
            "<msup>{}{}</msup>",
            self.base.to_mathml(),
            self.exp.to_mathml()
        )
    }
}
impl ToMathML for Sub {
    fn to_mathml(&self) -> String {
        format!(
            "<msub>{}{}</msub>",
            self.base.to_mathml(),
            self.lower.to_mathml()
        )
    }
}
impl ToMathML for Integral {
    fn to_mathml(&self) -> String {
        if let Some(bottom) = &self.bottom {
            if let Some(top) = &self.top {
                format!(
                    "<munderover><mi>∫</mi>{}{}</munderover>",
                    bottom.to_mathml(),
                    top.to_mathml()
                )
            } else {
                format!("<munder><mi>∫</mi>{}</munder>", bottom.to_mathml())
            }
        } else if let Some(top) = &self.top {
            format!("<mover><mi>∫<mi>{}</mover>", top.to_mathml())
        } else {
            format!("<mi>∫</mi>")
        }
    }
}
impl ToMathML for OIntegral {
    fn to_mathml(&self) -> String {
        if let Some(bottom) = &self.bottom {
            if let Some(top) = &self.top {
                format!(
                    "<munderover><mi>∮</mi>{}{}</munderover>",
                    bottom.to_mathml(),
                    top.to_mathml()
                )
            } else {
                format!("<munder><mi>∮</mi>{}</munder>", bottom.to_mathml())
            }
        } else if let Some(top) = &self.top {
            format!("<mover><mi>∮<mi>{}</mover>", top.to_mathml())
        } else {
            format!("<mi>∮</mi>")
        }
    }
}
impl ToMathML for ExpressionAccent {
    fn to_mathml(&self) -> String {
        match self {
            ExpressionAccent::Generic(g) => g.to_mathml(),
            ExpressionAccent::OverSet(o) => o.to_mathml(),
            ExpressionAccent::UnderSet(u) => u.to_mathml(),
            ExpressionAccent::Color(c) => c.to_mathml(),
        }
    }
}
impl ToMathML for Expression {
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    fn to_mathml(&self) -> String {
        format!(
            "<mrow>{}</mrow>",
            self.children
                .iter()
                .fold("".to_string(), |a, b| format!("{}{}", a, b.to_mathml()))
        )
    }
}
impl ToMathML for Element {
    fn to_mathml(&self) -> String {
        match self {
            Element::Special(s) => s.to_mathml(),
            Element::Literal(l) => l.to_mathml(),
            Element::Group(g) => g.to_mathml(),
            Element::Accent(a) => a.to_mathml(),
        }
    }
}