"""A module containing a core representation of IATI Schemas."""
from lxml import etree
import iati.core.codelists
import iati.core.exceptions
import iati.core.resources
import iati.core.utilities


class Schema(object):
    """Represenation of a Schema as defined within the IATI SSOT.

    Attributes:
        name (str): The name of the Schema.
        codelists (set): The Codelists asspciated with this Schema. This is a read-only attribute.

    Todo:
        Create a custom dictionary type that prevents overwriting values and only allows the correct types to be added.
    """

    def __init__(self, name=None):
        """Initialise a Schema.

        Args:
            name (str): The name of the schema being initialised.
                This name refers to a file contained within the core IATI resources folder.

        Raises:
            TypeError: The type of the provided name is incorrect.
            iati.core.exceptions.SchemaError: An error occurred during the creation of the Schema.

        Todo:
            Allow for generation of schemas outside the IATI SSOT.

            Better use the try-except pattern.

            Allow the base schema to be modified after initialisation.

            Create test instance where the SchemaError is raised.
        """
        self.name = name
        self._schema_base_tree = None
        self.codelists = set()

        if isinstance(name, str):
            path = iati.core.resources.path_schema(self.name)
            try:
                loaded_tree = iati.core.resources.load_as_tree(path)
            except (IOError, OSError):
                msg = "Failed to load tree at '{0}' when creating Schema.".format(path)
                iati.core.utilities.log_error(msg)
                raise iati.core.exceptions.SchemaError
            else:
                self._schema_base_tree = loaded_tree
        elif name is not None:
            msg = "The name of the Schema is an invalid type. Must be a string, though was a {0}.".format(type(name))
            iati.core.utilities.log_error(msg)
            raise TypeError(msg)

    def validator(self):
        """A schema that can be used for validation.

        Takes the base schema and dynamically injects elements for content checking.

        Returns:
            etree.XMLSchema: A schema that can be used for validation.

        Todo:
            Implement Codelist content checking.

            Implement Ruleset content checking.

            Add configuration parameters.

            Make the algorithm relating to mappings not-hideous.
        """
        # tree = copy.deepcopy(self._schema_base_tree)
        tree = self._schema_base_tree

        if len(self.codelists):
            mappings = iati.core.codelists.fetch_mappings()
            updated_xpaths = {}

            for xpath, (ref, _) in mappings.items():
                # the XPaths are for a data file rather than a Schema, so need formatting differently
                path_sections = xpath.split('/')
                try:
                    elements = path_sections[path_sections.index('iati-activity') + 1:-1]
                    # locate the relevant elements
                    xpath_sections = ['{http://www.w3.org/2001/XMLSchema}element[@name="' + el + '"]' for el in elements]
                    # locate the attribute on the final element
                    xpath_sections.append('{http://www.w3.org/2001/XMLSchema}attribute[@name="' + path_sections[-1:][0][1:] + '"]')

                    updated_xpaths[ref] = '//'.join(xpath_sections)
                except ValueError:
                    pass

            for codelist in self.codelists:
                if codelist.name in updated_xpaths:
                    thing_to_update = tree.getroot().find(updated_xpaths[codelist.name])
                    thing_to_update.attrib['type'] = codelist.name + '-type'
                tree.getroot().append(codelist.xsd_tree())

            try:
                a = iati.core.utilities.convert_tree_to_schema(tree)
                return a
            except etree.XMLSchemaParseError as err:
                iati.core.utilities.log_error(err)
        else:
            return iati.core.utilities.convert_tree_to_schema(tree)

@@ -996,32 +996,231 @@
 of the Schema.%0A%0A
+        Warning:%0A            The format of the constructor is likely to change. It needs to be less reliant on the name acting as a UID, and allow for other attributes to be provided at this point.%0A%0A
         Todo:%0A  
@@ -2540,32 +2540,204 @@
 or validation.%0A%0A
+        Warning:%0A            It is planned to change from Schema-based to Data-based Codelist validation. As such, it is highly likely that this function will be removed.%0A%0A
         Todo:%0A  

Add warnings to Schemas