"""
A MathML printer.
"""

from __future__ import print_function, division

from sympy import sympify, S, Mul
from sympy.core.compatibility import range, string_types, default_sort_key
from sympy.core.function import _coeff_isneg
from sympy.printing.conventions import split_super_sub, requires_partial
from sympy.printing.precedence import \
    precedence_traditional, PRECEDENCE, PRECEDENCE_TRADITIONAL
from sympy.printing.pretty.pretty_symbology import greek_unicode
from sympy.printing.printer import Printer

import mpmath.libmp as mlib
from mpmath.libmp import prec_to_dps


class MathMLPrinterBase(Printer):
    """Contains common code required for MathMLContentPrinter and
    MathMLPresentationPrinter.
    """

    _default_settings = {
        "order": None,
        "encoding": "utf-8",
        "fold_frac_powers": False,
        "fold_func_brackets": False,
        "fold_short_frac": None,
        "inv_trig_style": "abbreviated",
        "ln_notation": False,
        "long_frac_ratio": None,
        "mat_delim": "[",
        "mat_symbol_style": "plain",
        "mul_symbol": None,
        "root_notation": True,
        "symbol_names": {},
        "mul_symbol_mathml_numbers": '&#xB7;',
    }

    def __init__(self, settings=None):
        Printer.__init__(self, settings)
        from xml.dom.minidom import Document, Text

        self.dom = Document()

        # Workaround to allow strings to remain unescaped
        # Based on
        # https://stackoverflow.com/questions/38015864/python-xml-dom-minidom-\
        #                              please-dont-escape-my-strings/38041194
        class RawText(Text):
            def writexml(self, writer, indent='', addindent='', newl=''):
                if self.data:
                    writer.write(u'{}{}{}'.format(indent, self.data, newl))

        def createRawTextNode(data):
            r = RawText()
            r.data = data
            r.ownerDocument = self.dom
            return r

        self.dom.createTextNode = createRawTextNode

    def doprint(self, expr):
        """
        Prints the expression as MathML.
        """
        mathML = Printer._print(self, expr)
        unistr = mathML.toxml()
        xmlbstr = unistr.encode('ascii', 'xmlcharrefreplace')
        res = xmlbstr.decode()
        return res

    def apply_patch(self):
        # Applying the patch of xml.dom.minidom bug
        # Date: 2011-11-18
        # Description: http://ronrothman.com/public/leftbraned/xml-dom-minidom\
        #                   -toprettyxml-and-silly-whitespace/#best-solution
        # Issue: http://bugs.python.org/issue4147
        # Patch: http://hg.python.org/cpython/rev/7262f8f276ff/

        from xml.dom.minidom import Element, Text, Node, _write_data

        def writexml(self, writer, indent="", addindent="", newl=""):
            # indent = current indentation
            # addindent = indentation to add to higher levels
            # newl = newline string
            writer.write(indent + "<" + self.tagName)

            attrs = self._get_attributes()
            a_names = list(attrs.keys())
            a_names.sort()

            for a_name in a_names:
                writer.write(" %s=\"" % a_name)
                _write_data(writer, attrs[a_name].value)
                writer.write("\"")
            if self.childNodes:
                writer.write(">")
                if (len(self.childNodes) == 1 and
                        self.childNodes[0].nodeType == Node.TEXT_NODE):
                    self.childNodes[0].writexml(writer, '', '', '')
                else:
                    writer.write(newl)
                    for node in self.childNodes:
                        node.writexml(
                            writer, indent + addindent, addindent, newl)
                    writer.write(indent)
                writer.write("</%s>%s" % (self.tagName, newl))
            else:
                writer.write("/>%s" % (newl))
        self._Element_writexml_old = Element.writexml
        Element.writexml = writexml

        def writexml(self, writer, indent="", addindent="", newl=""):
            _write_data(writer, "%s%s%s" % (indent, self.data, newl))
        self._Text_writexml_old = Text.writexml
        Text.writexml = writexml

    def restore_patch(self):
        from xml.dom.minidom import Element, Text
        Element.writexml = self._Element_writexml_old
        Text.writexml = self._Text_writexml_old


class MathMLContentPrinter(MathMLPrinterBase):
    """Prints an expression to the Content MathML markup language.

    References: https://www.w3.org/TR/MathML2/chapter4.html
    """
    printmethod = "_mathml_content"

    def mathml_tag(self, e):
        """Returns the MathML tag for an expression."""
        translate = {
            'Add': 'plus',
            'Mul': 'times',
            'Derivative': 'diff',
            'Number': 'cn',
            'int': 'cn',
            'Pow': 'power',
            'Max': 'max',
            'Min': 'min',
            'Abs': 'abs',
            'And': 'and',
            'Or': 'or',
            'Xor': 'xor',
            'Not': 'not',
            'Implies': 'implies',
            'Symbol': 'ci',
            'MatrixSymbol': 'ci',
            'RandomSymbol': 'ci',
            'Integral': 'int',
            'Sum': 'sum',
            'sin': 'sin',
            'cos': 'cos',
            'tan': 'tan',
            'cot': 'cot',
            'csc': 'csc',
            'sec': 'sec',
            'sinh': 'sinh',
            'cosh': 'cosh',
            'tanh': 'tanh',
            'coth': 'coth',
            'csch': 'csch',
            'sech': 'sech',
            'asin': 'arcsin',
            'asinh': 'arcsinh',
            'acos': 'arccos',
            'acosh': 'arccosh',
            'atan': 'arctan',
            'atanh': 'arctanh',
            'atan2': 'arctan',
            'acot': 'arccot',
            'acoth': 'arccoth',
            'asec': 'arcsec',
            'asech': 'arcsech',
            'acsc': 'arccsc',
            'acsch': 'arccsch',
            'log': 'ln',
            'Equality': 'eq',
            'Unequality': 'neq',
            'GreaterThan': 'geq',
            'LessThan': 'leq',
            'StrictGreaterThan': 'gt',
            'StrictLessThan': 'lt',
        }

        for cls in e.__class__.__mro__:
            n = cls.__name__
            if n in translate:
                return translate[n]
        # Not found in the MRO set
        n = e.__class__.__name__
        return n.lower()

    def _print_Mul(self, expr):

        if _coeff_isneg(expr):
            x = self.dom.createElement('apply')
            x.appendChild(self.dom.createElement('minus'))
            x.appendChild(self._print_Mul(-expr))
            return x

        from sympy.simplify import fraction
        numer, denom = fraction(expr)

        if denom is not S.One: