# Graphics support for Jets and Sharks network

#--------------------------------------------------------------------
from graphics import *
import time

from jetsNsharks import MINIMUM
from jetsNsharks import MAXIMUM

def sgn(value):
    if value < 0: return -1
    else: return +1

class JSNetwork(GraphWin):

    def __init__(self, allUnits):
        gangs = allUnits[0:2]
        ages = allUnits[2:5]
        eds = allUnits[5:8]
        maritals = allUnits[8:11]
        jobs = allUnits[11:14]
        names = allUnits[14:41]
        instances = allUnits[41:]
        layout = [gangs + [None] + ages + [None] + eds + [None] + maritals + [None] + jobs,
                  names[:15], names[15:], instances[:15], instances[15:]]
        width = 600
        height = 700
        GraphWin.__init__(self, "Jets and Sharks", width, height, autoflush=False)
        self.setBackground('white')
        self.allUnits = allUnits
        borderSize = 0.02
        spacing = 0.02
        textAdjust = 0.167
        self.fontSize = 9
        # set up permanent invisible Text object to be used to compute string widths
        self.widthText = Text(Point(0, 0), "")
        self.widthText.setSize(self.fontSize)
        self.widthText.setTextColor('white')
        self.widthText.draw(self)
        numColumns = len(layout)
        numRows = max([len(c) for c in layout]) + 1
        cellWidth = float(width) / numColumns
        cellHeight = float(height) / (numRows + 1)
        labelWidth = max([self.stringWidth(u.label.upper()) for u in self.allUnits])
        borderWidth = borderSize * cellWidth
        borderHeight = borderSize * cellHeight
        availableWidth = cellWidth - 2 * borderWidth
        availableHeight = cellHeight - 2 * borderHeight
        labelSpace = spacing * availableWidth
        maxDiameter = max(1, min(availableHeight, availableWidth - labelSpace - labelWidth))
        cyclesX = width / 2.0
        cyclesY = cellHeight / 2.0 + textAdjust * cellHeight
        self.cycles = 0
        self.cycleText = Text(Point(cyclesX, cyclesY), "Cycles: 0")
        self.cycleText.setSize(self.fontSize)
        self.cycleText.setTextColor('black')
        self.cycleText.draw(self)
        xOffset = borderWidth + maxDiameter / 2.0
        yOffset = borderHeight + maxDiameter / 2.0 + textAdjust * availableHeight
        labelXoffset = borderWidth + maxDiameter + labelSpace
        labelYoffset = borderHeight + maxDiameter / 2.0 + textAdjust * availableHeight
        # create a painter for each unit in layout
        for c in range(numColumns):
            columnUnits = layout[c]
            for r in range(len(columnUnits)):
                unit = columnUnits[r]
                if unit is not None:
                    x = c * cellWidth + xOffset
                    y = (r + 1) * cellHeight + yOffset
                    labelX = c * cellWidth + labelXoffset
                    labelY = (r + 1) * cellHeight + labelYoffset
                    unit.painter = Painter(unit, self, x, y, maxDiameter, labelX, labelY)

    def makeString(self, x, y, string, color):
        sw = self.stringWidth(string)
        t = Text(Point(x + sw / 2.0, y), string)
        t.setSize(self.fontSize)
        t.setFill(color)
        t.draw(self)
        return t

    def stringWidth(self, string):
        self.widthText.setText(string)
        bb = self.bbox(self.widthText.id)
        return bb[2] - bb[0]

    def makeCircle(self, x, y, diameter, color):
        c = Circle(Point(x, y), 1)
        c.setWidth(diameter)
        c.setFill(color)
        c.setOutline(color)
        c.draw(self)
        return c

    def redraw(self):
        for unit in self.allUnits:
            unit.painter.repaint()
        self.cycleText.setText("Cycles: %d" % self.cycles)

    def reset(self):
        self.cycles = 0
        self.redraw()

    def update(self):
        self.cycles = self.cycles + 1
        self.redraw()
        time.sleep(0.1)


class Painter:

    def __init__(self, unit, window, x, y, maxDiameter, labelX, labelY):
        self.unit = unit
        self.label = unit.label
        self.maxDiameter = maxDiameter
        self.clampColor = 'magenta'
        self.negativeColor = 'red'
        self.positiveColor = 'green'
        self.diameter = 0
        self.lastActivation = 0
        # create the label Text and the activation Circle
        self.labelText = window.makeString(labelX, labelY, self.label, 'black')
        self.circle = window.makeCircle(x, y, 0, 'black')
        self.lastExternalInput = None
        # draw the current node activation
        self.repaint()

    def repaint(self):
        # update label if necessary
        if self.unit.externalInput != self.lastExternalInput:
            if self.lastExternalInput == 0:
                self.labelText.setText(self.label.upper())
                self.labelText.setTextColor(self.clampColor)
            else:
                self.labelText.setText(self.label)
                self.labelText.setTextColor('black')
            self.lastExternalInput = self.unit.externalInput
        # update activation image
        if sgn(self.lastActivation) != sgn(self.unit.activation):
            self.lastActivation = self.unit.activation
            if self.unit.activation < 0:
                self.circle.setFill(self.negativeColor)
                self.circle.setOutline(self.negativeColor)
            else:
                self.circle.setFill(self.positiveColor)
                self.circle.setOutline(self.positiveColor)
        diameter = int(self.maxDiameter * abs(self.unit.activation) / max(abs(MINIMUM), abs(MAXIMUM)))
        if self.diameter != diameter:
            self.circle.setWidth(diameter)
            self.diameter = diameter
