#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
Widget to display simulation data of a CSDF graph.

author: Sander Giesselink

"""

import sys
from PyQt5.QtWidgets import QWidget, QGraphicsItem, QPushButton, QVBoxLayout, QMenu, QAction, QInputDialog, QMessageBox
from PyQt5.QtCore import QRectF, QRect, QPointF, QPoint, Qt, QVariant
from PyQt5.QtGui import QColor, QPainter, QBrush, QPainterPath, QLinearGradient, QFont, QContextMenuEvent
from collections import Counter
import schemastyle

class Node(QGraphicsItem):

    def __init__(self, widget, view, nodeName):
        super().__init__()
        
        self.ioWidth = 10
        self.ioHeight = 10
        self.ioHeightDifference = 10
        self.nodeBodyWidth = 80
        self.maxNameLength = 6        
        self.calculateNodeColors(QColor(Qt.red))
        self.lastPos = QPointF(0, 0)
        self.yTranslationLeftIO = 0
        self.yTranslationRightIO = 0
        self.snappingIsOn = True
        self.showNeutralIO = False
        self.nodeFunction = 'func'
        self.clashCode = 'ccode'

        self.widget = widget
        self.view = view
        self.nodeName = nodeName
        self.nodeNameDisplayed = ''

        self.edgeList = []
        self.ioList = []
        #Add 2x IO ('left' = left, 'right' = right /,/ 0 = neutral, 1 = input, 2 is output)
        self.addNewIO('left', 0)
        self.addNewIO('right', 0)

        #Set flags for selecting, moving and enabling a position change event
        self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsGeometryChanges)
        self.setAcceptHoverEvents(True)
        self.hover = False

    def boundingRect(self):
        #Used for collision detection and repaint
        return QRectF(0, 0, self.nodeBodyWidth + 3, self.nodeBodyHeight + 3)

    def shape(self):
        # Determines the collision area
        path = QPainterPath()
        path.addRect(0, 0, self.nodeBodyWidth, self.nodeBodyHeight)
        return path
    
    def paint(self, painter, option, widget):
        lod = option.levelOfDetailFromTransform(painter.worldTransform())
        self.paintNodeBody(painter, lod)
        if lod > 0.2:
            self.paintNodeIO(painter, lod)
        if lod > 0.4:
            self.paintNodeName(painter)

    def paintNodeBody(self, painter, lod):
        painter.setPen(schemastyle.NODE_BACKGROUND_COLOR)
        brush = QBrush(schemastyle.NODE_BACKGROUND_COLOR)

        if self.hover:
            brush = QBrush(schemastyle.NODE_BACKGROUND_COLOR)

        if QGraphicsItem.isSelected(self):
            brush = QBrush(schemastyle.NODE_BACKGROUND_COLOR)

        painter.setBrush(brush)

        if lod > 0.1:
            painter.setPen(schemastyle.NODE_SHADOW_COLOR)
            painter.setBrush(QBrush(schemastyle.NODE_SHADOW_COLOR))
            painter.drawRoundedRect(3, 3, self.nodeBodyWidth, self.nodeBodyHeight, 5, 5)
            painter.setPen(schemastyle.NODE_BACKGROUND_COLOR)
            painter.setBrush(QBrush(schemastyle.NODE_BACKGROUND_COLOR))
            painter.drawRoundedRect(0, 0, self.nodeBodyWidth, self.nodeBodyHeight, 5, 5)
        else:
        	painter.drawRect(0, 0, self.nodeBodyWidth, self.nodeBodyHeight)


    def paintNodeIO(self, painter, lod):
        #Draw all IO
        for i in range(0, len(self.ioList)):
            #Center io if one side contains less io
            yTranslation = 0
            if self.ioList[i][3] == 'left':
                yTranslation = self.yTranslationLeftIO
            else:
                yTranslation = self.yTranslationRightIO

            # self.ioList[i][4] is state of socket: 0 neutral 1 input 2 output
            # self.ioList[i][5]: is hover
            painter.setPen(schemastyle.SOCKET_NEUTRAL_COLOR)
            brush = QBrush(schemastyle.SOCKET_NEUTRAL_COLOR)
            painter.setBrush(brush)

            #Don't paint neutral IO if disabled
            if self.ioList[i][4] != 0:
                if self.ioList[i][3] == 'left':
                    rect = QRect(self.ioList[i][0] + 5, self.ioList[i][1] + yTranslation + 1, self.ioWidth-2, self.ioHeight-2)
                else:
                    rect = QRect(self.ioList[i][0] - 5, self.ioList[i][1] + yTranslation + 1, self.ioWidth-2, self.ioHeight-2)
                painter.drawEllipse(rect)

                #Paint IO name
                if lod > 0.4:
                    painter.setFont(QFont("Arial", 6))
                    if self.ioList[i][3] == 'left':
                        painter.drawText(self.getIONameRect(i, yTranslation, self.ioList[i][3]), Qt.AlignLeft, str(self.ioList[i][6]))
                    else:
                        painter.drawText(self.getIONameRect(i, yTranslation, self.ioList[i][3]), Qt.AlignRight, str(self.ioList[i][6]))

        painter.setPen(Qt.black)

    def paintNodeName(self, painter):
        if self.nodeNameDisplayed == '':
            self.setNodeName()
        
        font = QFont("Arial", 12)
        font.setItalic(True)
        painter.setFont(font)
        rect = QRectF(0, 0, self.nodeBodyWidth, self.nodeBodyHeight)
        painter.setPen(schemastyle.NODE_TEXT_COLOR)
        painter.drawText(rect, Qt.AlignCenter, self.nodeNameDisplayed)
        
    def mousePressEvent(self, event):
        self.mouseIsOnIO(event.pos(), True)     

        super().mousePressEvent(event)
        self.update()

        #Must be done after super().mousePressEvent(event) in order to
        #flag the node again after clicking on an input/output
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)

    def mouseMoveEvent(self, event):
        #Code for ShiftModifier goes here

        self.update()
        super().mouseMoveEvent(event)

    def itemChange(self, change, value):
        # Move selected nodes and edges to the front, untill unselected
        if change == QGraphicsItem.ItemSelectedChange:
            if QGraphicsItem.isSelected(self):
            	#Unselected (since the flag is not updated yet)
                self.setZValue(0)
                self.setZValueEdges(1)
            else:
            	#Selected
                self.setZValue(4)
                self.setZValueEdges(5)

        #If the position of the node changes -> calculate position change
        #and move edges with the node
        newPos = value

        if change == QGraphicsItem.ItemPositionChange:
            if self.snappingIsOn:
                n
ewPos = self.snapToGrid(newPos)
            posChange = newPos - self.lastPos
            # Due to the grid snapping, only process when node actually moved 
            if not posChange.isNull():
                self.moveEdges(po
sChange)
                self.lastPos = newPos

        return super(Node, self).itemChange(change, newPos)

    def snapToGrid(self, position):
        #Return position of closest grid point
        gridSizeX = 40
        gridSizeY = 10
        curPos = QPoint(position.x(), position.y())
        gridPos = QPoint(round(curPos.x() / gridSizeX) * gridSizeX, round(curPos.y() / gridSizeY) * gridSizeY)

        return gridPos

    def mouseReleaseEvent(self, event):
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)

        super().mouseReleaseEvent(event)
        self.update()

    def hoverMoveEvent(self, event):
        #Don't execute when the nodeBody is selected in order to prevent unselecting the nodeBody
        if not QGraphicsItem.isSelected(self):
            self.mouseIsOnIO(event.pos())

        super().hoverMoveEvent(event)
        self.update()

        #Must be done after super().mousePressEvent(event) in order to
        #flag the node again after clicking on an input/output
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)

    def hoverEnterEvent(self, event):
        self.setCursor(Qt.Pointin