# shapes.py
from Processing import *
import math

window(500, 500)

# Further extend Ellipse with Circle
# Helper function
def triangleArea(x1, y1, x2, y2, x3, y3):
    return 0.5*math.fabs((x2-x1)*(y3-y1)-(y2-y1)*(x3-x1))

# Given shape points, compute bounding box
def boundingBox( pts ):
    minX, maxX = pts[0][0], pts[0][0]
    minY, maxY = pts[0][1], pts[0][1]
    for p in pts:
        if p[0] < minX:
            minX = p[0]
        elif p[0] > maxX:
            maxX = p[0]
        if p[1] < minY:
            minY = p[1]
        elif p[1] > maxY:
            maxY = p[1]
    return [minX, minY, maxX, maxY]

# Shared Shape class
class Shape:
    def __init__(self, pts):
        self.pts = pts
        self.strokeColor = color(32)
        self.fillColor = color(255, 128, 128)
        self.selected = False
        self.updateParameters()

    # Calculate a few parameters
    def updateParameters(self):
        self.bbox = boundingBox(self.pts)
        self.width = self.bbox[2] - self.bbox[0]
        self.height = self.bbox[3] - self.bbox[1]
        self.centerX = 0.5 * (self.bbox[2] + self.bbox[0])
        self.centerY = 0.5 * (self.bbox[3] + self.bbox[1])

    def draw(self):
        fill( self.fillColor )
        stroke( self.strokeColor )
        if self.selected == True:
            strokeWeight(5)
        else:
            strokeWeight(1)
        self.drawShape()

    def drawShape(self):
        pass

    # Default implementation of containsPoint checks bounding box
    def containsPoint(self, x, y):
        if x < self.bbox[0]: return False
        if x > self.bbox[2]: return False
        if y < self.bbox[1]: return False
        if y > self.bbox[3]: return False
        return True

    def mouseMoved(self):
        x, y = mouseX(), mouseY()
        if self.containsPoint(x, y):
            self.strokeColor = color(255)
        else:
            self.strokeColor = color(32)

    def mousePressed(self):
        x, y = mouseX(), mouseY()

        # Modify selection state
        if self.containsPoint(x, y):
            self.selected = True
        elif keyCode() != 65505:
            self.selected = False

        # Cache all points if selected
        if self.selected:
            self.startPts = [p[:] for p in self.pts]

    def mouseDragged(self):
        if self.selected:
            for i in range( len(self.pts) ):
                self.pts[i][0] = self.startPts[i][0] + deltaX
                self.pts[i][1] = self.startPts[i][1] + deltaY
            self.updateParameters()

# Rectangle Class
class Rectangle(Shape):
    def __init__(self, pts):
        Shape.__init__(self, pts)
        self.strokeColor = color(255, 0, 0)

    def drawShape(self):
        rectMode(CORNER)
        rect(self.bbox[0], self.bbox[1], self.width, self.height)

# Ellipse Class
class Ellipse(Shape):
    def __init__(self, pts):
        Shape.__init__(self, pts)
        self.fillColor = color(128, 128, 255)

    def drawShape(self):
        ellipseMode(CENTER)
        ellipse(self.centerX, self.centerY, self.width, self.height)

    def containsPoint(self, x, y):
        dx = x - self.centerX
        dy = y - self.centerY
        return 4.0*((dx*dx)/(self.width**2) + (dy*dy)/(self.height**2)) < 1.0

# Triangle Class
class Triangle(Shape):
    def __init__(self, pts):
        Shape.__init__(self, pts)
        self.fillColor = color(128, 255, 128)

    # Draw the triangle
    def drawShape(self):
        triangle(self.pts[0][0], self.pts[0][1], self.pts[1][0], self.pts[1][1], self.pts[2][0], self.pts[2][1])

    # A point is in a triangle if the sum of the areas of all
    # sub-triangles made with the point <= the area of the triangle itself
    def containsPoint(self, x, y):
        a1 = triangleArea(self.pts[0][0], self.pts[0][1], self.pts[1][0], self.pts[1][1], x, y)
        a2 = triangleArea(self.pts[0][0], self.pts[0][1], x, y, self.pts[2][0], self.pts[2][1])
        a3 = triangleArea(x, y, self.pts[1][0], self.pts[1][1], self.pts[2][0], self.pts[2][1])
        a = triangleArea(self.pts[0][0], self.pts[0][1], self.pts[1][0], self.pts[1][1], self.pts[2][0], self.pts[2][1])
        return (a1 + a2 + a3) <= a

# Event handlers
def mousePressed(o, e):
    global startX, startY
    startX, startY = mouseX(), mouseY()

    for s in shapes:
        s.mousePressed()

def mouseMoved(o, e):
    for s in shapes:
        s.mouseMoved()

def mouseDragged(o, e):
    global deltaX, deltaY
    deltaX = mouseX() - startX
    deltaY = mouseY() - startY
    for s in shapes:
        s.mouseDragged()

def draw(o, e):
  background(200)
  for s in shapes:
      s.draw()

onMouseMoved += mouseMoved
onMousePressed += mousePressed
onMouseDragged += mouseDragged

shapes = []
shapes.append( Rectangle ( [[100, 200], [200, 250]] ) )
shapes.append( Ellipse( [[200, 100], [270, 160]] ) )
shapes.append( Triangle( [[300, 300], [270, 370], [400, 400]] ) )

frameRate(20)
onLoop += draw
loop()

#r = Rectangle ( [[100, 200], [200, 250]] )
#print( isinstance( r, Rectangle ) )
#print( issubclass( Rectangle, Shape ) )