
from logging import Logger
from logging import getLogger

from os import linesep as osLineSep

from xml.etree.ElementTree import Element
from xml.etree.ElementTree import SubElement

from pyutmodel.ModelTypes import ClassName
from pyutmodel.PyutActor import PyutActor
from pyutmodel.PyutClass import PyutClass
from pyutmodel.PyutClassCommon import PyutClassCommon
from pyutmodel.PyutField import PyutField
from pyutmodel.PyutInterface import PyutInterface
from pyutmodel.PyutLink import PyutLink
from pyutmodel.PyutMethod import PyutMethod
from pyutmodel.PyutMethod import SourceCode
from pyutmodel.PyutNote import PyutNote
from pyutmodel.PyutParameter import PyutParameter
from pyutmodel.PyutSDInstance import PyutSDInstance
from pyutmodel.PyutSDMessage import PyutSDMessage
from pyutmodel.PyutText import PyutText
from pyutmodel.PyutUseCase import PyutUseCase

from oglio.toXmlV11.BaseXml import BaseXml
from oglio.toXmlV11.InternalTypes import ElementAttributes
from oglio.toXmlV11.XmlConstants import XmlConstants


class PyutToXml(BaseXml):
    """
    Serializes Pyut Models classes to DOM
    """
    # https://www.codetable.net/hex/a
    END_OF_LINE_MARKER: str = '&#xA;'

    def __init__(self):
        super().__init__()
        self.logger: Logger = getLogger(__name__)

    def pyutClassToXml(self, pyutClass: PyutClass, graphicElement: Element) -> Element:
        """
        Exporting a PyutClass to a miniDom Element.

        Args:
            pyutClass:       The pyut class to save
            graphicElement:  The xml element to update

        Returns:
            A new updated element
        """

        commonAttributes = self._pyutClassCommonAttributes(pyutClass)
        attributes = {
            XmlConstants.ATTR_ID:                 str(pyutClass.id),
            XmlConstants.ATTR_NAME:               pyutClass.name,
            XmlConstants.ATTR_STEREOTYPE:         pyutClass.stereotype.value,
            XmlConstants.ATTR_DISPLAY_METHODS:    str(pyutClass.showMethods),
            XmlConstants.ATTR_DISPLAY_PARAMETERS: str(pyutClass.displayParameters),
            XmlConstants.ATTR_DISPLAY_FIELDS:     str(pyutClass.showFields),
            XmlConstants.ATTR_DISPLAY_STEREOTYPE: str(pyutClass.displayStereoType),
        }

        attributes = attributes | commonAttributes

        pyutClassElement: Element = SubElement(graphicElement, XmlConstants.ELEMENT_PYUT_CLASS, attrib=attributes)

        for method in pyutClass.methods:
            self._pyutMethodToXml(pyutMethod=method, pyutClassElement=pyutClassElement)

        for pyutField in pyutClass.fields:
            self._pyutFieldToXml(pyutField=pyutField, pyutClassElement=pyutClassElement)
        return pyutClassElement

    def pyutLinkToXml(self, pyutLink: PyutLink, oglLinkElement: Element) -> Element:
        """
        Exporting a PyutLink to an Element.

        Args:
            pyutLink:   Link to save
            oglLinkElement:     xml document

        Returns:
            A new minidom element
        """
        srcLinkId:  int = self._idFactory.getID(pyutLink.getSource())
        destLinkId: int = self._idFactory.getID(pyutLink.getDestination())

        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_NAME:                    pyutLink.name,
            XmlConstants.ATTR_TYPE:                    pyutLink.linkType.name,
            XmlConstants.ATTR_CARDINALITY_SOURCE:      pyutLink.sourceCardinality,
            XmlConstants.ATTR_CARDINALITY_DESTINATION: pyutLink.destinationCardinality,
            XmlConstants.ATTR_BIDIRECTIONAL:           str(pyutLink.getBidir()),
            XmlConstants.ATTR_SOURCE_ID:               str(srcLinkId),
            XmlConstants.ATTR_DESTINATION_ID:          str(destLinkId),
        })
        pyutLinkElement: Element = SubElement(oglLinkElement, XmlConstants.ELEMENT_PYUT_LINK, attrib=attributes)

        return pyutLinkElement

    def pyutInterfaceToXml(self, pyutInterface: PyutInterface, interface2Element: Element) -> Element:

        classId: int = self._idFactory.getID(pyutInterface)
        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_ID:          str(classId),
            XmlConstants.ATTR_NAME:        pyutInterface.name,
            XmlConstants.ATTR_DESCRIPTION: pyutInterface.description
        })
        pyutInterfaceElement: Element = SubElement(interface2Element, XmlConstants.ELEMENT_MODEL_INTERFACE, attrib=attributes)

        for method in pyutInterface.methods:
            self._pyutMethodToXml(pyutMethod=method, pyutClassElement=pyutInterfaceElement)

        for className in pyutInterface.implementors:
            self.logger.debug(f'implementing className: {className}')
            self._pyutImplementorToXml(className, pyutInterfaceElement)

        return pyutInterfaceElement

    def pyutNoteToXml(self, pyutNote: PyutNote, oglNoteElement: Element) -> Element:

        noteId:       int = self._idFactory.getID(pyutNote)
        content:      str = pyutNote.content
        fixedContent: str  = content.replace(osLineSep, PyutToXml.END_OF_LINE_MARKER)
        if pyutNote.fileName is None:
            pyutNote.fileName = ''

        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_ID:       str(noteId),
            XmlConstants.ATTR_CONTENT:  fixedContent,
            XmlConstants.ATTR_FILENAME: pyutNote.fileName,
        })
        pyutNoteElement: Element = SubElement(oglNoteElement, XmlConstants.ELEMENT_PYUT_NOTE, attrib=attributes)

        return pyutNoteElement

    def pyutTextToXml(self, pyutText: PyutText, oglTextElement: Element) -> Element:

        textId:       int = self._idFactory.getID(pyutText)
        content:      str = pyutText.content
        fixedContent: str  = content.replace(osLineSep, PyutToXml.END_OF_LINE_MARKER)

        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_ID:       str(textId),
            XmlConstants.ATTR_CONTENT:  fixedContent,
        })
        pyutTextElement: Element = SubElement(oglTextElement, XmlConstants.ELEMENT_PYUT_NOTE, attrib=attributes)

        return pyutTextElement

    def pyutActorToXml(self, pyutActor: PyutActor, oglActorElement: Element) -> Element:

        actorId:  int = self._idFactory.getID(pyutActor)
        fileName: str = pyutActor.fileName
        if fileName is None:
            fileName = ''

        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_ID:       str(actorId),
            XmlConstants.ATTR_NAME:     pyutActor.name,
            XmlConstants.ATTR_FILENAME: fileName,
        })
        pyutActorElement: Element = SubElement(oglActorElement, XmlConstants.ELEMENT_PYUT_ACTOR, attributes)

        return pyutActorElement

    def pyutUseCaseToXml(self, pyutUseCase: PyutUseCase, oglUseCaseElement: Element) -> Element:

        useCaseId: int = self._idFactory.getID(pyutUseCase)
        fileName:  str = pyutUseCase.fileName
        if fileName is None:
            fileName = ''

        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_ID:       str(useCaseId),
            XmlConstants.ATTR_NAME:     pyutUseCase.name,
            XmlConstants.ATTR_FILENAME: fileName
        })
        pyutUseCaseElement: Element = SubElement(oglUseCaseElement, XmlConstants.ELEMENT_PYUT_USE_CASE, attributes)

        return pyutUseCaseElement

    def pyutSDInstanceToXml(self, pyutSDInstance: PyutSDInstance, oglSDInstanceElement: Element) -> Element:

        sdInstanceId: int     = self._idFactory.getID(pyutSDInstance)
        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_ID:               str(sdInstanceId),
            XmlConstants.ATTR_INSTANCE_NAME:    pyutSDInstance.instanceName,
            XmlConstants.ATTR_LIFE_LINE_LENGTH: str(pyutSDInstance.instanceLifeLineLength),
        })

        pyutSDInstanceElement: Element = SubElement(oglSDInstanceElement, XmlConstants.ELEMENT_PYUT_SD_INSTANCE, attrib=attributes)

        return pyutSDInstanceElement

    def pyutSDMessageToXml(self, pyutSDMessage: PyutSDMessage, oglSDMessageElement: Element) -> Element:

        sdMessageId: int = self._idFactory.getID(pyutSDMessage)

        srcInstance: PyutSDInstance = pyutSDMessage.getSource()
        dstInstance: PyutSDInstance = pyutSDMessage.getDestination()

        idSrc: int = self._idFactory.getID(srcInstance)
        idDst: int = self._idFactory.getID(dstInstance)

        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_ID:                        str(sdMessageId),
            XmlConstants.ATTR_MESSAGE:                   pyutSDMessage.message,
            XmlConstants.ATTR_SOURCE_TIME:               str(pyutSDMessage.sourceY),
            XmlConstants.ATTR_DESTINATION_TIME:          str(pyutSDMessage.destinationY),
            XmlConstants.ATTR_SD_MESSAGE_SOURCE_ID:      str(idSrc),
            XmlConstants.ATTR_SD_MESSAGE_DESTINATION_ID: str(idDst),
        })

        pyutSDMessageElement: Element = SubElement(oglSDMessageElement, XmlConstants.ELEMENT_PYUT_SD_MESSAGE, attrib=attributes)

        return pyutSDMessageElement

    def _pyutMethodToXml(self, pyutMethod: PyutMethod, pyutClassElement: Element) -> Element:
        """
        Exporting a PyutMethod to an Element

        Args:
            pyutMethod:        Method to serialize
            pyutClassElement:  xml document

        Returns:
            The new updated element
        """
        attributes = {
            XmlConstants.ATTR_NAME:               pyutMethod.name,
            XmlConstants.ATTR_VISIBILITY:         pyutMethod.visibility.name,
            XmlConstants.ATTR_METHOD_RETURN_TYPE: pyutMethod.returnType.value,
        }
        pyutMethodElement: Element = SubElement(pyutClassElement, XmlConstants.ELEMENT_PYUT_METHOD, attrib=attributes)
        for modifier in pyutMethod.modifiers:
            attributes = {
                XmlConstants.ATTR_NAME: modifier.name,
            }
            SubElement(pyutMethodElement, XmlConstants.ELEMENT_MODEL_MODIFIER, attrib=attributes)
        self._pyutSourceCodeToXml(pyutMethod.sourceCode, pyutMethodElement)

        for pyutParameter in pyutMethod.parameters:
            self._pyutParameterToXml(pyutParameter, pyutMethodElement)
        # pyutMethodElement: Element = xmlDoc.createElement(XmlConstants.ELEMENT_MODEL_METHOD)
        #
        # pyutMethodElement.setAttribute(XmlConstants.ATTR_NAME, pyutMethod.name)
        #
        # visibility: PyutVisibilityEnum = pyutMethod.getVisibility()
        # visName:    str                = self.__safeVisibilityToName(visibility)
        #
        # if visibility is not None:
        #     pyutMethodElement.setAttribute(XmlConstants.ATTR_VISIBILITY, visName)
        #
        # for modifier in pyutMethod.modifiers:
        #     xmlModifier: Element = xmlDoc.createElement(XmlConstants.ELEMENT_MODEL_MODIFIER)
        #     xmlModifier.setAttribute(XmlConstants.ATTR_NAME, modifier.name)
        #     pyutMethodElement.appendChild(xmlModifier)
        #
        # if pyutMethod.returnType is not None:
        #     xmlReturnType: Element = xmlDoc.createElement(XmlConstants.ELEMENT_MODEL_RETURN)
        #     xmlReturnType.setAttribute(XmlConstants.ATTR_TYPE, str(pyutMethod.returnType))
        #     pyutMethodElement.appendChild(xmlReturnType)
        #
        # for param in pyutMethod.parameters:
        #     pyutMethodElement.appendChild(self._pyutParamToDom(param, xmlDoc))
        #
        # codeRoot: Element = self._pyutSourceCodeToDom(pyutMethod.sourceCode, xmlDoc)
        # pyutMethodElement.appendChild(codeRoot)
        # return pyutMethodElement
        return pyutMethodElement

    def _pyutClassCommonAttributes(self, classCommon: PyutClassCommon):

        attributes = {
            XmlConstants.ATTR_DESCRIPTION: classCommon.description
        }
        return attributes

    def _pyutSourceCodeToXml(self, sourceCode: SourceCode, pyutMethodElement: Element):

        codeRoot: Element = SubElement(pyutMethodElement, XmlConstants.ELEMENT_MODEL_SOURCE_CODE)

        for code in sourceCode:
            codeElement: Element = SubElement(codeRoot, XmlConstants.ELEMENT_MODEL_CODE)
            codeElement.text = code

        return codeRoot

    def _pyutParameterToXml(self, pyutParameter: PyutParameter, pyutMethodElement: Element) -> Element:

        attributes = {
            XmlConstants.ATTR_NAME:          pyutParameter.name,
            XmlConstants.ATTR_TYPE:          pyutParameter.type.value,
            XmlConstants.ATTR_DEFAULT_VALUE: pyutParameter.defaultValue,
        }
        pyutParameterElement: Element = SubElement(pyutMethodElement, XmlConstants.ELEMENT_MODEL_PYUT_PARAMETER, attrib=attributes)

        return pyutParameterElement

    def _pyutFieldToXml(self, pyutField: PyutField, pyutClassElement: Element) -> Element:
        """
        Serialize a PyutField to an Element

        Args:
            pyutField:         The PyutField to serialize
            pyutClassElement: The Pyut Class element to update

        Returns:
            The new updated element
        """
        attributes = {
            XmlConstants.ATTR_NAME:          pyutField.name,
            XmlConstants.ATTR_VISIBILITY:    pyutField.visibility.name,
            XmlConstants.ATTR_TYPE:          pyutField.type.value,
            XmlConstants.ATTR_DEFAULT_VALUE: pyutField.defaultValue,
        }
        pyutFieldElement: Element = SubElement(pyutClassElement, XmlConstants.ELEMENT_MODEL_PYUT_FIELD, attrib=attributes)

        return pyutFieldElement

    def _pyutImplementorToXml(self, className: ClassName, xmlDoc: Element) -> Element:

        # root: Element = xmlDoc.createElement(XmlConstants.ELEMENT_IMPLEMENTOR)
        # root.setAttribute()
        attributes: ElementAttributes = ElementAttributes({
            XmlConstants.ATTR_IMPLEMENTING_CLASS_NAME: className,
        })
        implementorElement: Element = SubElement(xmlDoc, XmlConstants.ELEMENT_IMPLEMENTOR, attrib=attributes)
        return implementorElement
