'''
Created on 1 mai 2021

@author: CNRA, arxit
'''
from __future__ import absolute_import

from builtins import object
import os.path
import xml.etree.ElementTree as ET
from qgis.PyQt.QtCore import QFile, QIODevice, QVariant
from qgis.core import *

from . import main

class GeometryType(object):
    '''
    Geometry types
    '''

    POINT='POINT'
    POLYLINE='LINESTRING'
    POLYGON='POLYGON'

class DataType(object):
    '''
    Data types
    '''

    STRING='string'
    INTEGER='integer'
    DOUBLE='double'
    DATE='date'

XSD_GEOMETRY_TYPES = {'LUREF':GeometryType.POINT,
                      'gml:CurvePropertyType':GeometryType.POLYLINE,
                      'gml:SurfacePropertyType':GeometryType.POLYGON}

XSD_QGIS_DATATYPE_MAP = {DataType.STRING:QVariant.String,
                         DataType.INTEGER:QVariant.LongLong,
                         DataType.DOUBLE:QVariant.Double,
                         DataType.DATE:QVariant.String}

XSD_QGIS_GEOMETRYTYPE_MAP = {GeometryType.POINT:QgsWkbTypes.Point,
                             GeometryType.POLYLINE:QgsWkbTypes.LineString,
                             GeometryType.POLYGON:QgsWkbTypes.Polygon}

class ZOASchema(object):
    '''
    The ZOA schema parsed from the XSD
    '''

    def __init__(self):
        '''
        Constructor
        '''
        self.parseXSD()

    def parseXSD(self):
        '''
        Parses the XSD
        '''

        xsd_path = os.path.join(
            main.plugin_dir,
            'assets',
            'ZOASchema.xsd')

        # Parse as QgsGmlSchema
        file = QFile(xsd_path)
        file.open(QIODevice.ReadOnly)
        xsdcontent = file.readAll()

        schema = QgsGmlSchema()
        schema.parseXSD(xsdcontent)

        #Parse as XML
        ns = {'xsd': 'http://www.w3.org/2001/XMLSchema'} # XSD namespace
        xsd = ET.parse(xsd_path)

        topics = list() # Topics : ZOA_TA, GESTION
        concrete_typenames = list()

        # Loop GML schema type names
        for typename in schema.typeNames():
            # Remove GEOMETRIE types
            if '.' in typename:
                continue

            # Topics ILI
            if len(schema.fields(typename)) == 1 and schema.fields(typename)[0].name() == 'member':
                topics.append((typename, self._getTopicMembers(schema.fields(typename)[0].typeName(), xsd.getroot(), ns)))
                continue

            # Concretes types
            concrete_typenames.append(typename)

        self.types = list()

        # Loop concrete type names and parse to ZOAType
        for typename in concrete_typenames:
            xml_element = xsd.getroot().find('xsd:complexType[@name="{}Type"]'.format(typename),ns)
            zoa_type = ZOAType()
            zoa_type.parse(typename, xml_element, ns)
            self.types.append(zoa_type)
            
        # Add topic to types
        for topic, members in topics:
            for member in members:
                for type in self.types:
                    if type.name == member:
                        type.name = '{}.{}'.format(topic, member)
                        break

    def getType(self, typename):
        '''
        Get a type from the name

        :param typename: The type name (ex : BIOTOPE_LIGNE)
        :type typename: str, QString
        '''

        for type in self.types:
            if type.friendlyName() == typename:
                return type

        return None

    def getTypeFromTableName(self, tablename):
        '''
        Get a type from the table name

        :param tablename: The table name (ex :  ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE)
        :type tablename: str, QString
        '''

        for type in self.types:
            if type.name == tablename:
                return type

        return None

    def _getTopicMembers(self, typename, xml_root, ns):
        '''
        Parse XML node

        :param typename: The type name of the topic (ex : ZOAMemberType)
        :type typename: str, QString

        :param ns: XSD namespaces
        :type ns: dict
        '''

        xml_element = xml_root.find('xsd:complexType[@name="{}"]'.format(typename), ns)
        members_elements = xml_element.findall('.//xsd:choice/xsd:element', ns)

        members = list()

        for element in members_elements:
            name = element.get('ref')
            if '.' not in name: # Remove geometrie type
                members.append(name)

        return members

class ZOAType(object):
    '''
    A ZOA XSD type
    '''

    def __init__(self):
        '''
        Constructor
        '''

        self.geometry_type = None
        self.fields = list()

    def parse(self, name, xml_element, ns):
        '''
        Parse XML node

        :param name: The name of the type
        :type name: str, QString

        :param xml_element: The root XML node of the type (xsd:complexType)
        :type xml_element: Element

        :param ns: XSD namespaces
        :type ns: dict
        '''

        # Type name
        self.name = name
        self.geometry_type = None
        self.geometry_fieldname = None

        self.fields = list()
        self.ordered_field_names = list()

        sequence = xml_element.find('.//xsd:sequence',ns)
        elements = sequence.findall('xsd:element',ns)

        for element in elements:
            # Process geometry
            element_type = element.get('type')
            if element_type in XSD_GEOMETRY_TYPES:
                self.geometry_type = XSD_GEOMETRY_TYPES[element_type]
                self.geometry_fieldname = element.get('name')

            # Process field
            else:
                zoa_field = ZOAField()
                zoa_field.parse(element, ns)
                self.fields.append(zoa_field)

            self.ordered_field_names.append(element.get('name'))

    def friendlyName(self):
        '''
        Gets the friendly name of the type aka table name
        E.g. ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE for ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE
        '''

        if self.name is None:
            return ''

        split = self.name.split('.')

        return split[-1]

    def topic(self):
        '''
        Gets the topic of the type
        E.g. ZOA_TA for ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE
        '''

        if self.name is None:
            return ''

        split = self.name.split('.')

        return split[0]

    def getField(self, fieldname):
        '''
        Get a field from the name

        :param fieldname: The field name (ex : GENRE)
        :type fieldname: str, QString
        '''

        for field in self.fields:
            if field.name == fieldname:
                return field

        return None

class ZOAField(object):
    '''
    A ZOA XSD field
    '''

    def __init__(self):
        '''
        Constructor
        '''
        self.nullable = True
        self.type = DataType.STRING
        self.length = None
        self.minvalue = None
        self.maxvalue = None
        self.listofvalues = None

    def parse(self, xml_element, ns):
        '''
        Parse XML node

        :param xml_element: The root XML node of the field (xsd:element)
        :type xml_element: Element

        :param ns: XSD namespaces
        :type ns: dict
        '''

        # Field name
        self.name = xml_element.get('name')

        # Field is nullable
        self.nullable = xml_element.get('minOccurs') is not None and xml_element.get('minOccurs')=='0'

        type = self._getDataType(xml_element, ns)

        # Data type
        self.type = type[0]

        # Text length
        self.length = type[1]

        # Numeric or date min value
        self.minvalue = type[2]

        # Numeric or date max value
        self.maxvalue = type[3]

        # List of possible values, enumeration
        self.listofvalues = type[4]

    def _getDataType(self, xml_element, ns):
        '''
        Returns the datatype of the field

        :param xml_element: The root XML node of the data type (xsd:element)
        :type xml_element: Element

        :param ns: XSD namespaces
        :type ns: dict
        '''

        # Dictionary of data types
        datatypes = {'xsd:string':DataType.STRING,
                     'xsd:normalizedString':DataType.STRING,
                     'xsd:integer':DataType.INTEGER,
                     'xsd:double':DataType.DOUBLE,
                     'xsd:decimal':DataType.DOUBLE,
                     'xsd:date':DataType.DATE}

        # XSD restriction element
        restriction = xml_element.find('.//xsd:restriction',ns)

        # Data type
        type = datatypes[restriction.get('base')]

        # Length
        length_element = restriction.find('xsd:maxLength',ns)
        length = length_element.get('value') if length_element is not None else None

        # Minimum inclusive value
        minvalue_element = restriction.find('xsd:minInclusive',ns)
        minvalue = minvalue_element.get('value') if minvalue_element is not None else None

        # Maximum inclusive value
        maxvalue_element = restriction.find('xsd:maxInclusive',ns)
        maxvalue = maxvalue_element.get('value') if maxvalue_element is not None else None

        # Enumeration
        enumeration_elements = restriction.findall('xsd:enumeration',ns)
        enumeration = list()
        for enumeration_element in enumeration_elements:
            enumeration.append(enumeration_element.get('value'))

        return type, length, minvalue, maxvalue, enumeration if len(enumeration)>0 else None

    def getEnumerationMap(self):
        map = dict()

        for element in self.listofvalues:
            map[element]=element

        return map
'''
Created on 1 mai 2021

@author: CNRA, arxit
'''
from __future__ import absolute_import

from builtins import object
import os.path
import xml.etree.ElementTree as ET
from qgis.PyQt.QtCore import QFile, QIODevice, QVariant
from qgis.core import *

from . import main

class GeometryType(object):
    '''
    Geometry types
    '''

    POINT='POINT'
    POLYLINE='LINESTRING'
    POLYGON='POLYGON'

class DataType(object):
    '''
    Data types
    '''

    STRING='string'
    INTEGER='integer'
    DOUBLE='double'
    DATE='date'

XSD_GEOMETRY_TYPES = {'LUREF':GeometryType.POINT,
                      'gml:CurvePropertyType':GeometryType.POLYLINE,
                      'gml:SurfacePropertyType':GeometryType.POLYGON}

XSD_QGIS_DATATYPE_MAP = {DataType.STRING:QVariant.String,
                         DataType.INTEGER:QVariant.LongLong,
                         DataType.DOUBLE:QVariant.Double,
                         DataType.DATE:QVariant.String}

XSD_QGIS_GEOMETRYTYPE_MAP = {GeometryType.POINT:QgsWkbTypes.Point,
                             GeometryType.POLYLINE:QgsWkbTypes.LineString,
                             GeometryType.POLYGON:QgsWkbTypes.Polygon}

class ZOASchema(object):
    '''
    The ZOA schema parsed from the XSD
    '''

    def __init__(self):
        '''
        Constructor
        '''
        self.parseXSD()

    def parseXSD(self):
        '''
        Parses the XSD
        '''

        xsd_path = os.path.join(
            main.plugin_dir,
            'assets',
            'ZOASchema.xsd')

        # Parse as QgsGmlSchema
        file = QFile(xsd_path)
        file.open(QIODevice.ReadOnly)
        xsdcontent = file.readAll()

        schema = QgsGmlSchema()
        schema.parseXSD(xsdcontent)

        #Parse as XML
        ns = {'xsd': 'http://www.w3.org/2001/XMLSchema'} # XSD namespace
        xsd = ET.parse(xsd_path)

        topics = list() # Topics : ZOA_TA, GESTION
        concrete_typenames = list()

        # Loop GML schema type names
        for typename in schema.typeNames():
            # Remove GEOMETRIE types
            if '.' in typename:
                continue

            # Topics ILI
            if len(schema.fields(typename)) == 1 and schema.fields(typename)[0].name() == 'member':
                topics.append((typename, self._getTopicMembers(schema.fields(typename)[0].typeName(), xsd.getroot(), ns)))
                continue

            # Concretes types
            concrete_typenames.append(typename)

        self.types = list()

        # Loop concrete type names and parse to ZOAType
        for typename in concrete_typenames:
            xml_element = xsd.getroot().find('xsd:complexType[@name="{}Type"]'.format(typename),ns)
            zoa_type = ZOAType()
            zoa_type.parse(typename, xml_element, ns)
            self.types.append(zoa_type)
            
        # Add topic to types
        for topic, members in topics:
            for member in members:
                for type in self.types:
                    if type.name == member:
                        type.name = '{}.{}'.format(topic, member)
                        break

    def getType(self, typename):
        '''
        Get a type from the name

        :param typename: The type name (ex : BIOTOPE_LIGNE)
        :type typename: str, QString
        '''

        for type in self.types:
            if type.friendlyName() == typename:
                return type

        return None

    def getTypeFromTableName(self, tablename):
        '''
        Get a type from the table name

        :param tablename: The table name (ex :  ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE)
        :type tablename: str, QString
        '''

        for type in self.types:
            if type.name == tablename:
                return type

        return None

    def _getTopicMembers(self, typename, xml_root, ns):
        '''
        Parse XML node

        :param typename: The type name of the topic (ex : ZOAMemberType)
        :type typename: str, QString

        :param ns: XSD namespaces
        :type ns: dict
        '''

        xml_element = xml_root.find('xsd:complexType[@name="{}"]'.format(typename), ns)
        members_elements = xml_element.findall('.//xsd:choice/xsd:element', ns)

        members = list()

        for element in members_elements:
            name = element.get('ref')
            if '.' not in name: # Remove geometrie type
                members.append(name)

        return members

class ZOAType(object):
    '''
    A ZOA XSD type
    '''

    def __init__(self):
        '''
        Constructor
        '''

        self.geometry_type = None
        self.fields = list()

    def parse(self, name, xml_element, ns):
        '''
        Parse XML node

        :param name: The name of the type
        :type name: str, QString

        :param xml_element: The root XML node of the type (xsd:complexType)
        :type xml_element: Element

        :param ns: XSD namespaces
        :type ns: dict
        '''

        # Type name
        self.name = name
        self.geometry_type = None
        self.geometry_fieldname = None

        self.fields = list()
        self.ordered_field_names = list()

        sequence = xml_element.find('.//xsd:sequence',ns)
        elements = sequence.findall('xsd:element',ns)

        for element in elements:
            # Process geometry
            element_type = element.get('type')
            if element_type in XSD_GEOMETRY_TYPES:
                self.geometry_type = XSD_GEOMETRY_TYPES[element_type]
                self.geometry_fieldname = element.get('name')

            # Process field
            else:
                zoa_field = ZOAField()
                zoa_field.parse(element, ns)
                self.fields.append(zoa_field)

            self.ordered_field_names.append(element.get('name'))

    def friendlyName(self):
        '''
        Gets the friendly name of the type aka table name
        E.g. ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE for ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE
        '''

        if self.name is None:
            return ''

        split = self.name.split('.')

        return split[-1]

    def topic(self):
        '''
        Gets the topic of the type
        E.g. ZOA_TA for ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE
        '''

        if self.name is None:
            return ''

        split = self.name.split('.')

        return split[0]

    def getField(self, fieldname):
        '''
        Get a field from the name

        :param fieldname: The field name (ex : GENRE)
        :type fieldname: str, QString
        '''

        for field in self.fields:
            if field.name == fieldname:
                return field

        return None

class ZOAField(object):
    '''
    A ZOA XSD field
    '''

    def __init__(self):
        '''
        Constructor
        '''
        self.nullable = True
        self.type = DataType.STRING
        self.length = None
        self.minvalue = None
        self.maxvalue = None
        self.listofvalues = None

    def parse(self, xml_element, ns):
        '''
        Parse XML node

        :param xml_element: The root XML node of the field (xsd:element)
        :type xml_element: Element

        :param ns: XSD namespaces
        :type ns: dict
        '''

        # Field name
        self.name = xml_element.get('name')

        # Field is nullable
        self.nullable = xml_element.get('minOccurs') is not None and xml_element.get('minOccurs')=='0'

        type = self._getDataType(xml_element, ns)

        # Data type
        self.type = type[0]

        # Text length
        self.length = type[1]

        # Numeric or date min value
        self.minvalue = type[2]

        # Numeric or date max value
        self.maxvalue = type[3]

        # List of possible values, enumeration
        self.listofvalues = type[4]

    def _getDataType(self, xml_element, ns):
        '''
        Returns the datatype of the field

        :param xml_element: The root XML node of the data type (xsd:element)
        :type xml_element: Element

        :param ns: XSD namespaces
        :type ns: dict
        '''

        # Dictionary of data types
        datatypes = {'xsd:string':DataType.STRING,
                     'xsd:normalizedString':DataType.STRING,
                     'xsd:integer':DataType.INTEGER,
                     'xsd:double':DataType.DOUBLE,
                     'xsd:decimal':DataType.DOUBLE,
                     'xsd:date':DataType.DATE}

        # XSD restriction element
        restriction = xml_element.find('.//xsd:restriction',ns)

        # Data type
        type = datatypes[restriction.get('base')]

        # Length
        length_element = restriction.find('xsd:maxLength',ns)
        length = length_element.get('value') if length_element is not None else None

        # Minimum inclusive value
        minvalue_element = restriction.find('xsd:minInclusive',ns)
        minvalue = minvalue_element.get('value') if minvalue_element is not None else None

        # Maximum inclusive value
        maxvalue_element = restriction.find('xsd:maxInclusive',ns)
        maxvalue = maxvalue_element.get('value') if maxvalue_element is not None else None

        # Enumeration
        enumeration_elements = restriction.findall('xsd:enumeration',ns)
        enumeration = list()
        for enumeration_element in enumeration_elements:
            enumeration.append(enumeration_element.get('value'))

        return type, length, minvalue, maxvalue, enumeration if len(enumeration)>0 else None

    def getEnumerationMap(self):
        map = dict()

        for element in self.listofvalues:
            map[element]=element

        return map
Created on 1 mai 2021  @author: CNRA, arxit Geometry types Data types The ZOA schema parsed from the XSD Constructor Parses the XSD # Parse as QgsGmlSchema #Parse as XML # XSD namespace # Topics : ZOA_TA, GESTION # Loop GML schema type names # Remove GEOMETRIE types # Topics ILI # Concretes types # Loop concrete type names and parse to ZOAType # Add topic to types Get a type from the name          :param typename: The type name (ex : BIOTOPE_LIGNE)         :type typename: str, QString Get a type from the table name          :param tablename: The table name (ex :  ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE)         :type tablename: str, QString Parse XML node          :param typename: The type name of the topic (ex : ZOAMemberType)         :type typename: str, QString          :param ns: XSD namespaces         :type ns: dict # Remove geometrie type A ZOA XSD type Constructor Parse XML node          :param name: The name of the type         :type name: str, QString          :param xml_element: The root XML node of the type (xsd:complexType)         :type xml_element: Element          :param ns: XSD namespaces         :type ns: dict # Type name # Process geometry # Process field Gets the friendly name of the type aka table name         E.g. ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE for ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE Gets the topic of the type         E.g. ZOA_TA for ZOA_TA.ZOA_TERRAIN_AMENAGE_LIGNE Get a field from the name          :param fieldname: The field name (ex : GENRE)         :type fieldname: str, QString A ZOA XSD field Constructor Parse XML node          :param xml_element: The root XML node of the field (xsd:element)         :type xml_element: Element          :param ns: XSD namespaces         :type ns: dict # Field name # Field is nullable # Data type # Text length # Numeric or date min value # Numeric or date max value # List of possible values, enumeration Returns the datatype of the field          :param xml_element: The root XML node of the data type (xsd:element)         :type xml_element: Element          :param ns: XSD namespaces         :type ns: dict # Dictionary of data types # XSD restriction element # Data type # Length # Minimum inclusive value # Maximum inclusive value # Enumeration