#
# Copyright 2008-2009,2011 Zuza Software Foundation
#
# This file is part of the Translate Toolkit.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

from copy import copy
from typing import Never

from lxml import etree

from translate.misc.xml_helpers import (
    get_safe_xml_parser,
    getXMLspace,
    normalize_xml_space,
)
from translate.storage.placeables import StringElem, base, xliff
from translate.storage.xml_extract import misc

__all__ = ("strelem_to_xml", "xml_to_strelem")
# Use the above functions as entry points into this module. The rest are
# used by these functions.


def make_empty_replacement_placeable(klass, node, xml_space="preserve"):
    try:
        return klass(
            id=node.attrib["id"],
            rid=node.attrib.get("rid", None),
            xid=node.attrib.get("xid", None),
            xml_attrib=node.attrib,
        )
    except KeyError:
        pass
    return klass()


def make_g_placeable(klass, node, xml_space="default"):
    return klass(
        id=node.attrib["id"],
        sub=xml_to_strelem(node, xml_space).sub,
        xml_attrib=node.attrib,
    )


def not_yet_implemented(klass, node, xml_space="preserve") -> Never:
    raise NotImplementedError


def make_unknown(klass, node, xml_space="preserve"):
    assert klass is xliff.UnknownXML

    sub = xml_to_strelem(node, xml_space).sub
    id = node.get("id", None)
    rid = node.get("rid", None)
    xid = node.get("xid", None)

    return klass(sub=sub, id=id, rid=rid, xid=xid, xml_node=node)


_class_dictionary = {
    #'bpt': (xliff.Bpt, not_yet_implemented),
    "bx": (xliff.Bx, make_empty_replacement_placeable),
    #'ept': (xliff.Ept, not_yet_implemented),
    "ex": (xliff.Ex, make_empty_replacement_placeable),
    "g": (xliff.G, make_g_placeable),
    #'it': (xliff.It, not_yet_implemented),
    #'ph': (xliff.Ph, not_yet_implemented),
    #'sub': (xliff.Sub, not_yet_implemented),
    "x": (xliff.X, make_empty_replacement_placeable),
}


def make_placeable(node, xml_space):
    _namespace, tag = misc.parse_tag(node.tag)
    klass, maker = _class_dictionary.get(tag, (xliff.UnknownXML, make_unknown))
    return maker(klass, node, xml_space)


def as_unicode(string):
    if isinstance(string, str):
        return string
    if isinstance(string, StringElem):
        return str(string)
    return string.decode("utf-8")


def xml_to_strelem(dom_node, xml_space="preserve"):
    if dom_node is None:
        return StringElem()
    if isinstance(dom_node, str):
        parser = get_safe_xml_parser()
        dom_node = etree.fromstring(dom_node, parser)
    xml_space = getXMLspace(dom_node) or xml_space
    normalize_xml_space(dom_node, xml_space, remove_start=True)
    result = StringElem()
    sub = result.sub  # just an optimisation
    for child_dom_node in dom_node:
        if child_dom_node.tag is etree.Comment:
            continue
        sub.append(make_placeable(child_dom_node, xml_space))
        if child_dom_node.tail:
            sub.append(StringElem(str(child_dom_node.tail)))

    # This is just a strange way of inserting the first text and avoiding a
    # call to .prune() which is very expensive. We assume the tree is optimal.
    node_text = dom_node.text
    if sub and node_text:
        sub.insert(0, StringElem(str(node_text)))
    elif node_text:
        sub.append(str(node_text))
    return result


# ==========================================================


def placeable_as_dom_node(placeable, tagname):
    dom_node = etree.Element(tagname)
    if placeable.id is not None:
        dom_node.attrib["id"] = placeable.id
    if placeable.xid is not None:
        dom_node.attrib["xid"] = placeable.xid
    if placeable.rid is not None:
        dom_node.attrib["rid"] = placeable.rid

    if hasattr(placeable, "xml_attrib"):
        for attrib, value in placeable.xml_attrib.items():
            dom_node.set(attrib, value)

    return dom_node


def unknown_placeable_as_dom_node(placeable):
    assert type(placeable) is xliff.UnknownXML

    node = copy(placeable.xml_node)
    for _i in range(len(node)):
        del node[0]
    node.tail = None
    node.text = None

    return node


_placeable_dictionary = {
    xliff.Bpt: lambda placeable: placeable_as_dom_node(placeable, "bpt"),
    xliff.Bx: lambda placeable: placeable_as_dom_node(placeable, "bx"),
    xliff.Ept: lambda placeable: placeable_as_dom_node(placeable, "ept"),
    xliff.Ex: lambda placeable: placeable_as_dom_node(placeable, "ex"),
    xliff.G: lambda placeable: placeable_as_dom_node(placeable, "g"),
    xliff.It: lambda placeable: placeable_as_dom_node(placeable, "it"),
    xliff.Ph: lambda placeable: placeable_as_dom_node(placeable, "ph"),
    xliff.Sub: lambda placeable: placeable_as_dom_node(placeable, "sub"),
    xliff.X: lambda placeable: placeable_as_dom_node(placeable, "x"),
    xliff.UnknownXML: unknown_placeable_as_dom_node,
    base.Bpt: lambda placeable: placeable_as_dom_node(placeable, "bpt"),
    base.Bx: lambda placeable: placeable_as_dom_node(placeable, "bx"),
    base.Ept: lambda placeable: placeable_as_dom_node(placeable, "ept"),
    base.Ex: lambda placeable: placeable_as_dom_node(placeable, "ex"),
    base.G: lambda placeable: placeable_as_dom_node(placeable, "g"),
    base.It: lambda placeable: placeable_as_dom_node(placeable, "it"),
    base.Ph: lambda placeable: placeable_as_dom_node(placeable, "ph"),
    base.Sub: lambda placeable: placeable_as_dom_node(placeable, "sub"),
    base.X: lambda placeable: placeable_as_dom_node(placeable, "x"),
}


def xml_append_string(node, string):
    if not len(node):
        if not node.text:
            node.text = str(string)
        else:
            node.text += str(string)
    else:
        lastchild = node.getchildren()[-1]
        if lastchild.tail is None:
            lastchild.tail = ""
        lastchild.tail += str(string)
    return node


def strelem_to_xml(parent_node, elem):
    if isinstance(elem, str):
        return xml_append_string(parent_node, elem)
    if not isinstance(elem, StringElem):
        return parent_node

    if type(elem) is StringElem and elem.isleaf():
        return xml_append_string(parent_node, elem)

    if elem.__class__ in _placeable_dictionary:
        node = _placeable_dictionary[elem.__class__](elem)
        parent_node.append(node)
    else:
        node = parent_node

    for sub in elem.sub:
        strelem_to_xml(node, sub)

    return parent_node


def parse_xliff(pstr):
    parser = get_safe_xml_parser()
    return xml_to_strelem(etree.fromstring(f"<source>{pstr}</source>", parser))


xliff.parsers = [parse_xliff]
