
from typing import cast

from logging import Logger
from logging import getLogger

from xml.dom.minidom import Document
from xml.dom.minidom import Element
from xml.dom.minidom import NodeList

from xml.dom.minidom import parseString

from pyumldiagrams.Definitions import ClassDefinition
from pyumldiagrams.Definitions import ClassDefinitions
from pyumldiagrams.Definitions import LinePositions
from pyumldiagrams.Definitions import LineType
from pyumldiagrams.Definitions import Position
from pyumldiagrams.Definitions import Size
from pyumldiagrams.Definitions import UmlLineDefinition
from pyumldiagrams.Definitions import UmlLineDefinitions

from pyumldiagrams.UnsupportedException import UnsupportedException

from pyumldiagrams.xmlsupport.XmlConstants import ATTR_HEIGHT
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_LINK_DESTINATION_ANCHOR_X
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_LINK_DESTINATION_ANCHOR_Y
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_LINK_SOURCE_ANCHOR_X
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_LINK_SOURCE_ANCHOR_Y
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_NAME
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_TYPE
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_WIDTH
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_X
from pyumldiagrams.xmlsupport.XmlConstants import ATTR_Y
from pyumldiagrams.xmlsupport.XmlConstants import ELEMENT_CONTROL_POINT
from pyumldiagrams.xmlsupport.XmlConstants import ELEMENT_GRAPHIC_CLASS
from pyumldiagrams.xmlsupport.XmlConstants import ELEMENT_GRAPHIC_LINK
from pyumldiagrams.xmlsupport.XmlConstants import ELEMENT_MODEL_CLASS
from pyumldiagrams.xmlsupport.XmlConstants import ELEMENT_MODEL_LINK


class ToClassDefinition:

    def __init__(self, fqFileName: str):

        self.logger: Logger = getLogger(__name__)

        self._xmlData:          str      = ''
        self._documentNode:     Document = None

        self._classDefinitions: ClassDefinitions   = []
        self._umlLineDefinitions:  UmlLineDefinitions = []

        with open(fqFileName) as xmlFile:
            self._xmlData = xmlFile.read()
            self._documentNode: Document = parseString(self._xmlData)

    def generateClassDefinitions(self):

        graphicClassNodes: NodeList = self._documentNode.getElementsByTagName(ELEMENT_GRAPHIC_CLASS)

        self.logger.debug(f'{graphicClassNodes=}')

        for xmlGraphicClass in graphicClassNodes:

            xmlGraphicClass: Element = cast(Element, xmlGraphicClass)

            height: float = float(xmlGraphicClass.getAttribute(ATTR_HEIGHT))
            width:  float = float(xmlGraphicClass.getAttribute(ATTR_WIDTH))
            x:      float = float(xmlGraphicClass.getAttribute(ATTR_X))
            y:      float = float(xmlGraphicClass.getAttribute(ATTR_Y))

            xmlClass:  Element = xmlGraphicClass.getElementsByTagName(ELEMENT_MODEL_CLASS)[0]
            className: str     = xmlClass.getAttribute(ATTR_NAME)

            classDef: ClassDefinition = ClassDefinition(name=className)

            classSize: Size = Size(width=width, height=height)
            classDef.size = classSize

            position: Position = Position(x=x, y=y)
            classDef.position = position

            self.logger.debug(f'{classDef=}')
            self._classDefinitions.append(classDef)

    def generateUmlLineDefinitions(self):

        graphicLinkNodes: NodeList = self._documentNode.getElementsByTagName(ELEMENT_GRAPHIC_LINK)

        for xmlGraphicLink in graphicLinkNodes:

            xmlGraphicLink: Element = cast(Element, xmlGraphicLink)

            xmlLink:       Element  = xmlGraphicLink.getElementsByTagName(ELEMENT_MODEL_LINK)[0]
            controlPoints: NodeList = xmlGraphicLink.getElementsByTagName(ELEMENT_CONTROL_POINT)

            srcX: float = float(xmlGraphicLink.getAttribute(ATTR_LINK_SOURCE_ANCHOR_X))
            srcY: float = float(xmlGraphicLink.getAttribute(ATTR_LINK_SOURCE_ANCHOR_Y))

            strType:  str      = xmlLink.getAttribute(ATTR_TYPE)
            lineType: LineType = LineType.toEnum(strType)

            srcPosition: Position = Position(x=srcX, y=srcY)
            linePositions: LinePositions = [srcPosition]
            umlLineDefinition: UmlLineDefinition = UmlLineDefinition(linePositions=linePositions, lineType=lineType)

            for controlPoint in controlPoints:

                controlPoint: Element = cast(Element, controlPoint)

                self.logger.debug(f'{controlPoint=}')
                x: float = float(controlPoint.getAttribute(ATTR_X))
                y: float = float(controlPoint.getAttribute(ATTR_Y))
                bendPosition: Position = Position(x=x, y=y)
                linePositions.append(bendPosition)

            destX: float = float(xmlGraphicLink.getAttribute(ATTR_LINK_DESTINATION_ANCHOR_X))
            destY: float = float(xmlGraphicLink.getAttribute(ATTR_LINK_DESTINATION_ANCHOR_Y))

            destPosition: Position = Position(x=destX, y=destY)

            linePositions.append(destPosition)
            self.logger.debug(f'{umlLineDefinition=}')
            self._umlLineDefinitions.append(umlLineDefinition)

    @property
    def classDefinitions(self) -> ClassDefinitions:
        return self._classDefinitions

    @classDefinitions.setter
    def classDefinitions(self, newDefinitions: ClassDefinitions):
        raise UnsupportedException('Class definitions are read-only')

    @property
    def umlLineDefinitions(self) -> UmlLineDefinitions:
        return self._umlLineDefinitions

    @umlLineDefinitions.setter
    def umlLineDefinitions(self, newDefinitions: UmlLineDefinitions):
        raise UnsupportedException('UML Line definitions are read-only')
