'''
Created on 30-Jun-2020

@author: bthakur

'''

import re
import pathlib
from pathlib import Path
import os
import sys
import time
import random
import traceback
import csv
import inspect
import matplotlib as mpl
import matplotlib.pyplot as plt
from celluloid import Camera
from xvgui import _Base
from enum import Enum     
from abc import ABC, abstractmethod

class _Shapehelper:
    
    def move(self, item, deltaX = 0, deltaY = 0):
        for i in range(len(item.X)):
            item.X[i] = item.X[i] + deltaX
            item.Y[i] = item.Y[i] + deltaY
            pass
        pass
    #def move(self, item, deltaX = 0, deltaY = 0):
        
    
    def _initDraw(self, **args):
        fig = args.get('fig', None)
        ax = args.get('ax', None)
        
        if not ('fig' in args and 'ax' in args):
            fig, ax = plt.subplots()
        
        return fig, ax
        pass
    #def _initDraw(self, **args):

    
    def parseInput(self, varName, defaultValue, dataType = None, **args):
        if not varName in args:
            return defaultValue
        
        varValue = args.get(varName, None)
        
        if varValue: 
            if dataType:
                if type(varValue).__name__ != dataType:
                    raise Exception(varName, "must have a boolean value")
            return varValue
        
        return defaultValue
        pass
    #def parseInput(self, varName, defaultValue, dataType = None, **args):

    
    def parseBoolInput(self, varName, defaultValue, **args):
        if not varName in args:
            return defaultValue
        
        varValue = args.get(varName, None)
        
        if type(varValue).__name__ != 'bool':
            raise Exception(varName, "must have a boolean value")
        
        if varValue: 
            return True
        
        return False
        pass
    #def parseBoolInput(self, varName, defaultValue, **args):
    pass
#class _Shapehelper():

class ShapeContainer(_Base):
    def __init__(self, **args):
        super().__init__(**args)
        self._items = []

        pass
    #def __init__(self, verbose = False):
    
    def append(self, shape):
        self._items.append(shape)
        pass
    
    def insert(self, index, shape):
        self._items.insert(index, shape)
        pass
    
    def remove(self, shape):
        self._items.remove(shape)
        pass
    
    def delete(self, index):
        del self._items[index]
        pass
    
    def draw(self, **args):
        fig, ax = _Shapehelper()._initDraw(**args)
        
        for item in self._items:
            item.draw(fig = fig, ax = ax)
            pass
        
        ax.axis('off')
        fig.canvas.set_window_title(type(self).__name__)
        return self
        pass
    #def draw(self, **args):


    def show(self, **args):
        plt.show()
        pass
    #def draw(self, **args):


    def animate(self, frames = 20, deltaX = 0.1, deltaY = 0.1, **args):
        helper = _Shapehelper()
        fig, ax = helper._initDraw(**args)
        camera = Camera(fig)
        
        for frameCount in range(frames):
            
            for item in self._items:
                
                helper.move(item, deltaX = deltaX, deltaY = deltaY)
                item.draw(fig = fig, ax = ax)
                
                pass
            #for item in self._items:

            camera.snap()
            print("Processed frameCount:", frameCount)
            pass
        #for i in range(200):

        animation = camera.animate()
        return animation
        pass
    #def animate(self):
    
    
    pass
# class Container(_Base):

class Shape(_Base, ABC):
    class Type(Enum):
        POINT = "Point"
        LINESEGMENT = "LineSegment"
        POLYGON = "Polygon"
        TRIANGLE = "Triangle"
        QUADRILATERAL = "Quadrilateral"
    
    def __init__(self, **args):
        super().__init__(**args)
        self.X = []
        self.Y = []
        self.validate()
        self.initXY()
        
        pass
    #def __init__(self, verbose = False):

    @abstractmethod
    def validate(self):
        pass
    
    @abstractmethod
    def initXY(self):
        pass
    
    def _plot(self, ax):
        if len(self.X) == 1:
            ax.plot(self.X[0], self.Y[0], 'bo')
            pass
        
        for i in range(len(self.X) - 1):
            x = [self.X[i], self.X[i+1]]
            y = [self.Y[i], self.Y[i+1]]
            ax.plot(x, y)
        
        pass
    #def _plot(self, **args):

    def draw(self, **args):
        fig, ax = _Shapehelper()._initDraw(**args)
        self._plot(ax)
        return plt
        pass
    #def draw(self, **args):

    def show(self, **args):
        return plt.show()
        pass
    #def draw(self, **args):

    def animate(self, frames = 20, deltaX = 0.1, deltaY = 0.1, **args):
        helper = _Shapehelper()
        fig, ax = helper._initDraw(**args)
        camera = Camera(fig)
        
        for frameCount in range(frames):
            
            helper.move(self, deltaX = deltaX, deltaY = deltaY)
            self.draw(fig = fig, ax = ax)

            camera.snap()
            print("Processed frameCount:", frameCount)
            pass
        #for i in range(200):

        animation = camera.animate()
        return animation
        pass
    #def animate(self):
    
    
    pass
#class Shape(ABC):

class Polygon(Shape):
    
    def validate(self):
        if not "x" in self.keys:
            raise Exception("x is missing.")
        
        if not "y" in self.keys:
            raise Exception("y is missing.")
        
        type_x = type(self.x).__name__
        type_y = type(self.y).__name__
        
        if type_x  not in ['list', 'tuple']:
            raise Exception("x must be a list or a tuple.")
        
        if type_y  not in ['list', 'tuple']:
            raise Exception("y must be a list or a tuple.")

        if len(self.x) != len(self.y):
            raise Exception("x and y must have same length.")

        if len(self.x) < 3:
            raise Exception("x and y must have at least three values.")
        
        return True
        pass
    #def validate(self):

    def initXY(self):
        self.X = list(self.x) 
        self.X.append(self.X[0])
        
        self.Y = list(self.y) 
        self.Y.append(self.Y[0])
        pass
    #def initXY(self):
    
    pass
#class Polygon(Shape):

class Point(Shape):
    def validate(self):
        if not "x" in self.keys:
            raise Exception("x is missing.")
        
        if not "y" in self.keys:
            raise Exception("y is missing.")
        
        return True
        pass
    
    def initXY(self):
        self.X = [self.x]
        self.Y = [self.y]
        pass
    #def initXY(self):
    
    pass
#class Point(Shape):
    
class LineSegment(Shape):
    def validate(self):
       
        if not "x1" in self.keys:
            raise Exception("x1 is missing.")

        if not "y1" in self.keys:
            raise Exception("y1 is missing.")
        
        if not "x2" in self.keys:
            raise Exception("x2 is missing.")
        
        if not "y2" in self.keys:
            raise Exception("y2 is missing.")
        
        
        pass
    
    def initXY(self):
        self.X = [self.x1, self.x2]
        self.Y = [self.y1, self.y2]
        pass
    #def initXY(self):
    

    pass
#sclass LineSegment(Shape):
    
class Triangle(Polygon):
    def initXY(self):
        super().initXY()
        
        if not (len(self.X) == 4 or len(self.Y)  == 4):
            raise Exception("The x and y should have exactly three coordinates each.")
        pass
    #def initXY(self):
    
    pass
#class Triangle(Polygon):
    
class Quadrilateral(Polygon):
    def initXY(self):
        super().initXY()
        
        if not (len(self.X) == 5 or len(self.Y)  == 5):
            raise Exception("The x and y should have exactly four coordinates each.")
        pass
    #def initXY(self):
    pass

class ShapeFactory():
    @classmethod
    def get(cls, shapeType = None,  **args):
        
        if shapeType:
            if type(shapeType).__name__ != "Type":
                raise Exception("Invalid shapeType {}".format(type(shapeType)))
                
            print("You want a shape of {} ".format(shapeType.value))
        
            shapeClass = getattr(sys.modules[__name__], shapeType.value)
            
            print(shapeClass)
            
            return shapeClass(**args)
        
        return Polygon(**args)
        pass
    
    pass
#class ShapeFactory():

class _TEST_ShapeManager:

    @classmethod
    def test_POINT(cls):
        print("\nFunction {} start\n#############\n".format(inspect.stack()[0][3]))
        
        sf = ShapeFactory.get(shapeType = Shape.Type.POINT,
                              x = 10,
                              y = 7
                            )
        
        
        sf.draw()
        #sf.animate()
        #Point(x = 2, y = 3).draw()
        
        print("\nFunction {} over\n#############\n".format(inspect.stack()[0][3]))
        pass
    #def test_POINT():


    @classmethod
    def test_LINESEGMENT(cls):
        print("\nFunction {} start\n#############\n".format(inspect.stack()[0][3]))
        
        sf = ShapeFactory.get(shapeType = Shape.Type.LINESEGMENT,
                              x1 = 10,
                              y1 = 7,
                              x2 = 15,
                              y2 = 10
                            )
        
        sf.draw()
        
        print("\nFunction {} over\n#############\n".format(inspect.stack()[0][3]))
        pass
    #def test_LINESEGMENT():
    
    @classmethod
    def test_POLYGON(cls):
        print("\nFunction {} start\n#############\n".format(inspect.stack()[0][3]))
        
        x = [1, 2, 3, 4, 3]
        y = [1, 3, 2, 1, 7]
        
        sf = ShapeFactory.get(shapeType = Shape.Type.POLYGON,
                              x = x,
                              y = y
                            )
        
        sf.draw()
        
        print("\nFunction {} over\n#############\n".format(inspect.stack()[0][3]))
        pass
    #def test_POLYGON():

    @classmethod
    def test_TRIANGLE(cls):
        print("\nFunction {} start\n#############\n".format(inspect.stack()[0][3]))
        
        x = [1, 2, 3]
        y = [1, 3, 2]
        
        sf = ShapeFactory.get(shapeType = Shape.Type.TRIANGLE,
                              x = x,
                              y = y
                            )
        
        sf.draw()
        
        print("\nFunction {} over\n#############\n".format(inspect.stack()[0][3]))
        pass
    #def test_TRIANGLE():
    
    @classmethod
    def test_QUADRILATERAL(cls):
        print("\nFunction {} start\n#############\n".format(inspect.stack()[0][3]))
        
        x = [1, 2, 3, 2]
        y = [1, 3, 2, -1]
        
        sf = ShapeFactory.get(shapeType = Shape.Type.QUADRILATERAL,
                              x = x,
                              y = y
                            )
        
        sf.draw()
        sf.animate(frames = 40, deltaX = 0.2, deltaY = 0.1).save(
            "h:/test/q.mp4", fps=60)
        
        #sf.animate(frames = 40, deltaX = 0.2, deltaY = 0.1).save(
        #    "h:/test/q.gif", writer='imagemagick', fps=60)
        
        print("\nFunction {} over\n#############\n".format(inspect.stack()[0][3]))
        pass
    #def test_QUADRILATERAL():
    
    @classmethod
    def test_ShapeContainer(cls):
        print("\nFunction {} start\n#############\n".format(inspect.stack()[0][3]))
        
        container = ShapeContainer()
        #eyebrows
        container.append(LineSegment(x1 = -2, y1 = 6, x2 = -0.5, y2 = 6))
        container.append(LineSegment(x1 =  2, y1 = 6, x2 =  0.5, y2 = 6))

        #eyes
        container.append(LineSegment(x1 = -1.5, y1 = 5, x2 = -0.5, y2 = 5))
        container.append(LineSegment(x1 =  1.5, y1 = 5, x2 = 0.5 , y2 = 5))
        #pupils
        container.append(Point(x = -1, y = 4.5)) 
        container.append(Point(x =  1, y = 4.5))

        #nose triangle
        container.append(Triangle(x = [-0.7, 0.7, 0], y = [-0.2, -0.2, 6]))
        
        #mouth
        container.append(LineSegment(x1 = -1, y1 = -1.5, x2 = 1, y2 = -1.5))
        container.append(LineSegment(x1 = -.4, y1 = -2, x2 = .4, y2 = -2))
        
        
        #face outline
        container.append(Quadrilateral(x = [-2.5, 2.5, 2.5, -2.5], y = [-5, -5, 12, 12]))
        
        #container.draw().show()
        
        filePath = 'h:/test/container.mp4'
        container.animate(frames = 30, deltaX = 0.1, deltaY = 0.2).save(filePath)
        print("created", filePath)   

        
        print("\nFunction {} over\n#############\n".format(inspect.stack()[0][3]))
        pass
    #def test_ShapeContainer():
    pass
#class _TEST_ShapeManager:

if __name__ == "__main__":
    #print(type(Shape.Type.TRIANGLE))
    
    #_TEST_ShapeManager.test_POINT()
    #_TEST_ShapeManager.test_LINESEGMENT()
    #_TEST_ShapeManager.test_TRIANGLE()
    #_TEST_ShapeManager.test_QUADRILATERAL()
    #_TEST_ShapeManager.test_POLYGON()
    _TEST_ShapeManager.test_ShapeContainer()
    
    #Point(x = 10, y =2).animate()
    #l1 = LineSegment(x1 = 10, y1 =2, x2 = 5, y2 = 3).draw().show()
    
    #Triangle(x = [1, 2, 5], y = [-1, 2, 3]).draw().show()
    #Polygon(x = [1, 2, 5], y = [-1, 2, 3]).draw()
    #Quadrilateral(x = [1, 2, 3, 5], y = [-1, 2, 3, 6]).draw()
    #Polygon(x = [1, 2, 5, 5], y = [-1, 2, 3, 6]).draw()
    #Polygon(x = [1, 2, -3, 5, -3], y = [-1, 2, 3, 6, -3]).draw()
    
    
    pass










