# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the NiBabel package for the
#   copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Thin layer around xml.etree.ElementTree, to abstract nibabel xml support"""

from io import BytesIO
from xml.etree.ElementTree import Element, SubElement, tostring  # noqa: F401
from xml.parsers.expat import ParserCreate

from .filebasedimages import FileBasedHeader


class XmlSerializable:
    """Basic interface for serializing an object to XML"""

    def _to_xml_element(self) -> Element:
        """Output should be a xml.etree.ElementTree.Element"""
        raise NotImplementedError

    def to_xml(self, enc='utf-8', **kwargs) -> bytes:
        r"""Generate an XML bytestring with a given encoding.

        Parameters
        ----------
        enc : :class:`string`
            Encoding to use for the generated bytestring. Default: 'utf-8'
        \*\*kwargs : :class:`dict`
            Additional keyword arguments to :func:`xml.etree.ElementTree.tostring`.
        """
        ele = self._to_xml_element()
        return tostring(ele, enc, **kwargs)


class XmlBasedHeader(FileBasedHeader, XmlSerializable):
    """Basic wrapper around FileBasedHeader and XmlSerializable."""


class XmlParser:
    """Base class for defining how to parse xml-based image snippets.

    Image-specific parsers should define:
        StartElementHandler
        EndElementHandler
        CharacterDataHandler
    """

    HANDLER_NAMES = ['StartElementHandler', 'EndElementHandler', 'CharacterDataHandler']

    def __init__(self, encoding='utf-8', buffer_size=35000000, verbose=0):
        """
        Parameters
        ----------
        encoding : str
            string containing xml document

        buffer_size: None or int, optional
            size of read buffer. None uses default buffer_size
            from xml.parsers.expat.

        verbose : int, optional
            amount of output during parsing (0=silent, by default).
        """
        self.encoding = encoding
        self.buffer_size = buffer_size
        self.verbose = verbose
        self.fname = None  # set on calls to parse

    def _create_parser(self):
        """Internal function that allows subclasses to mess
        with the underlying parser, if desired."""

        parser = ParserCreate(encoding=self.encoding)  # from xml package
        parser.buffer_text = True
        if self.buffer_size is not None:
            parser.buffer_size = self.buffer_size
        return parser

    def parse(self, string=None, fname=None, fptr=None):
        """
        Parameters
        ----------
        string : bytes
            string (as a bytes object) containing xml document

        fname : str
            file name of an xml document.

        fptr : file pointer
            open file pointer to an xml documents
        """
        if int(string is not None) + int(fptr is not None) + int(fname is not None) != 1:
            raise ValueError('Exactly one of fptr, fname, string must be specified.')

        if string is not None:
            fptr = BytesIO(string)
        elif fname is not None:
            fptr = open(fname)

        # store the name of the xml file in case it is needed during parsing
        self.fname = getattr(fptr, 'name', None)
        parser = self._create_parser()
        for name in self.HANDLER_NAMES:
            setattr(parser, name, getattr(self, name))
        parser.ParseFile(fptr)

    def StartElementHandler(self, name, attrs):
        raise NotImplementedError

    def EndElementHandler(self, name):
        raise NotImplementedError

    def CharacterDataHandler(self, data):
        raise NotImplementedError
