# from curve workbench

import FreeCAD
import FreeCADGui
import Part
import PySide.QtCore as QtCore
import PySide.QtGui as QtGui
from pivy import coin

from Utils import graphics


def parameterization(points, a, closed):
    """Computes a knot Sequence for a set of points
    fac (0-1) : parameterization factor
    fac=0 -> Uniform / fac=0.5 -> Centripetal / fac=1.0 -> Chord-Length"""
    pts = points.copy()
    if closed and pts[0].distanceToPoint(pts[-1]) > 1e-7:  # we need to add the first point as the end point
        pts.append(pts[0])
    params = [0]
    for i in range(1, len(pts)):
        p = pts[i] - pts[i - 1]
        if isinstance(p, FreeCAD.Vector):
            le = p.Length
        else:
            le = p.length()
        pl = pow(le, a)
        params.append(params[-1] + pl)
    return params

class ConnectionMarker(graphics.Marker):
    def __init__(self, points):
        super(ConnectionMarker, self).__init__(points, True)

class MarkerOnShape(graphics.Marker):
    def __init__(self, points, sh=None):
        super(MarkerOnShape, self).__init__(points, True)
        self._shape = None
        self._sublink = None
        self._tangent = None
        self._translate = coin.SoTranslation()
        self._text_font = coin.SoFont()
        self._text_font.name = "Arial:Bold"
        self._text_font.size = 13.0
        self._text = coin.SoText2()
        self._text_switch = coin.SoSwitch()
        self._text_switch.addChild(self._translate)
        self._text_switch.addChild(self._text_font)
        self._text_switch.addChild(self._text)
        self.on_drag_start.append(self.add_text)
        self.on_drag_release.append(self.remove_text)
        self.addChild(self._text_switch)
        if isinstance(sh, Part.Shape):
            self.snap_shape = sh
        elif isinstance(sh, (tuple, list)):
            self.sublink = sh

    def subshape_from_sublink(self, o):
        name = o[1][0]
        print(name, " selected")
        if 'Vertex' in name:
            n = eval(name.lstrip('Vertex'))
            return o[0].Shape.Vertexes[n - 1]
        elif 'Edge' in name:
            n = eval(name.lstrip('Edge'))
            return o[0].Shape.Edges[n - 1]
        elif 'Face' in name:
            n = eval(name.lstrip('Face'))
            return o[0].Shape.Faces[n - 1]

    def add_text(self):
        self._text_switch.whichChild = coin.SO_SWITCH_ALL
        self.on_drag.append(self.update_text)

    def remove_text(self):
        self._text_switch.whichChild = coin.SO_SWITCH_NONE
        self.on_drag.remove(self.update_text)

    def update_text(self):
        p = self.points[0]
        coords = ['{: 9.3f}'.format(p[0]), '{: 9.3f}'.format(p[1]), '{: 9.3f}'.format(p[2])]
        self._translate.translation = p
        self._text.string.setValues(0, 3, coords)

    @property
    def tangent(self):
        return self._tangent

    @tangent.setter
    def tangent(self, t):
        if isinstance(t, FreeCAD.Vector):
            if t.Length > 1e-7:
                self._tangent = t
                self._tangent.normalize()
                self.marker.markerIndex = coin.SoMarkerSet.DIAMOND_FILLED_9_9
            else:
                self._tangent = None
                self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9
        else:
            self._tangent = None
            self.marker.markerIndex = coin.SoMarkerSet.CIRCLE_FILLED_9_9

    @property
    def snap_shape(self):
        return self._shape

    @snap_shape.setter
    def snap_shape(self, sh):
        if isinstance(sh, Part.Shape):
            self._shape = sh
        else:
            self._shape = None
        self.alter_color()

    @property
    def sublink(self):
        return self._sublink

    @sublink.setter
    def sublink(self, sl):
        if isinstance(sl, (tuple, list)) and not (sl == self._sublink):
            self._shape = self.subshape_from_sublink(sl)
            self._sublink = sl
        else:
            self._shape = None
            self._sublink = None
        self.alter_color()

    def alter_color(self):
        if isinstance(self._shape, Part.Vertex):
            self.set_color("white")
        elif isinstance(self._shape, Part.Edge):
            self.set_color("cyan")
        elif isinstance(self._shape, Part.Face):
            self.set_color("magenta")
        else:
            self.set_color("black")

    def __repr__(self):
        return "MarkerOnShape({})".format(self._shape)

    def drag(self, mouse_coords, fact=1.):
        if self.enabled:
            pts = self.points
            for i, p in enumerate(pts):
                p[0] = mouse_coords[0] * fact + self._tmp_points[i][0]
                p[1] = mouse_coords[1] * fact + self._tmp_points[i][1]
                p[2] = mouse_coords[2] * fact + self._tmp_points[i][2]
                if self._shape:
                    v = Part.Vertex(p[0], p[1], p[2])
                    proj = v.distToShape(self._shape)[1][0][1]
                    # FreeCAD.Console.PrintMessage("%s -> %s\n"%(p.getValue(), proj))
                    p[0] = proj.x
                    p[1] = proj.y
                    p[2] = proj.z
            self.points = pts
            for foo in self.on_drag:
                foo()


class ConnectionPolygon(graphics.Polygon):
    std_col = "green"

    def __init__(self, markers):
        super(ConnectionPolygon, self).__init__(
            sum([m.points for m in markers], []), True)
        self.markers = markers

        for m in self.markers:
            m.on_drag.append(self.updatePolygon)

    def updatePolygon(self):
        self.points = sum([m.points for m in self.markers], [])

    @property
    def drag_objects(self):
        return self.markers

    def check_dependency(self):
        if any([m._delete for m in self.markers]):
            self.delete()

class ConnectionLine(graphics.Line):
    def __init__(self, markers):
        super(ConnectionLine, self).__init__(
            sum([m.points for m in markers], []), True)
        self.markers = markers
        self._linear = False
        for m in self.markers:
            m.on_drag.append(self.updateLine)

    def updateLine(self):
        self.points = sum([m.points for m in self.markers], [])
        if self._linear:
            p1 = self.markers[0].points[0]
            p2 = self.markers[-1].points[0]
            t = p2 - p1
            tan = FreeCAD.Vector(t[0], t[1], t[2])
            for m in self.markers:
                m.tangent = tan

    @property
    def linear(self):
        return self._linear

    @linear.setter
    def linear(self, b):
        self._linear = bool(b)

    @property
    def drag_objects(self):
        return self.markers

    def check_dependency(self):
        if any([m._delete for m in self.markers]):
            self.delete()


class Edit(object):

    def __init__(self, obj=None):
        self.obj = obj
        self.root_inserted = False
        self.root = None

        self.editing = None

        # event callbacks
        self.selection_callback = None
        self._keyPressedCB = None
        self._mouseMovedCB = None
        self._mousePressedCB = None

        #  Setup coin objects
        self.guidoc = self.obj.ViewObject.Document
        self.view = self.guidoc.ActiveView
        self.rm = self.view.getViewer().getSoRenderManager()
        self.sg = self.view.getSceneGraph()
        self.setupInteractionSeparator()

        #  Callbacks
        self.unregister_editing_callbacks()
        self.register_editing_callbacks()

    def setupInteractionSeparator(self):
        if self.root_inserted:
            self.sg.removeChild(self.root)
        self.root = graphics.InteractionSeparator(self.rm)
        self.root.setName("InteractionSeparator")
        self.root.pick_radius = 40

        #  Populate root node
        #self.root += self.points
        #self.build_lines()
        #self.root += self.lines

        #  set FreeCAD color scheme
        '''for o in self.points + self.lines:
            o.ovr_col = "yellow"
            o.sel_col = "green"'''

        self.root.register()
        self.sg.addChild(self.root)
        self.root_inserted = True
        self.root.selected_objects = list()

    def build_lines(self):
        for i in range(len(self.points) - 1):
            line = ConnectionLine([self.points[i], self.points[i + 1]])
            line.set_color("blue")
            self.lines.append(line)

    # -------------------------------------------------------------------------
    # SCENE EVENTS CALLBACKS
    # -------------------------------------------------------------------------

    def register_editing_callbacks(self):
        """ Register editing callbacks (former action function) """

        print("Registering callbacks")

        if self._keyPressedCB is None:
            self._keyPressedCB = self.root.events.addEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self.keyPressed)

        if self._mousePressedCB is None:
            self._mousePressedCB = self.root.events.addEventCallback(coin.SoMouseButtonEvent.getClassTypeId(), self.mousePressed)

        if self._mouseMovedCB is None:
            self._mouseMovedCB = self.root.events.addEventCallback(coin.SoLocation2Event.getClassTypeId(), self.mouseMoved)

    def unregister_editing_callbacks(self):
        """ Remove callbacks used during editing if they exist """

        print("Unregistering")

        if self._keyPressedCB:
            self.root.events.removeEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self._keyPressedCB)
            self._keyPressedCB = None

        if self._mousePressedCB:
            self.root.events.removeEventCallback(coin.SoMouseButtonEvent.getClassTypeId(), self._mousePressedCB)
            self._mousePressedCB = None

        if self._mouseMovedCB:
            self.root.events.removeEventCallback(coin.SoLocation2Event.getClassTypeId(), self._mouseMovedCB)
            self._mouseMovedCB = None

    # -------------------------------------------------------------------------
    # SCENE EVENT HANDLERS
    # -------------------------------------------------------------------------

    def keyPressed(self, attr, event_callback):
        event = event_callback.getEvent()
        print(event.getKey())
        if event.getState() == event.UP:
            #FreeCAD.Console.PrintMessage("Key pressed : %s\n"%event.getKey())
            if event.getKey() == ord("i"):
                self.subdivide()
            elif event.getKey() == ord("q"):# or event.getKey() == ord(65307):
                if self.obj:
                    self.obj.ViewObject.Proxy.doubleClicked(self.obj.ViewObject)
                else:
                    self.quit()
            elif event.getKey() == ord("s"):
                sel = FreeCADGui.Selection.getSelectionEx()
                tup = None
                if len(sel) == 1:
                    tup = (sel[0].Object, sel[0].SubElementNames)
                for i in range(len(self.root.selected_objects)):
                    if isinstance(self.root.selected_objects[i], MarkerOnShape):
                        self.root.selected_objects[i].sublink = tup
                        #FreeCAD.Console.PrintMessage("Snapped to {}\n".format(str(self.root.selected_objects[i].sublink)))
                        self.root.selected_objects[i].drag_start()
                        self.root.selected_objects[i].drag((0, 0, 0.))
                        self.root.selected_objects[i].drag_release()
            elif (event.getKey() == 65535) or (event.getKey() == 65288):  # Suppr or Backspace
                # FreeCAD.Console.PrintMessage("Some objects have been deleted\n")
                pts = list()
                for o in self.root.dynamic_objects:
                    if isinstance(o, MarkerOnShape):
                        pts.append(o)
                self.points = pts
                self.setupInteractionSeparator()
            elif event.getKey() == 65307:
                self.quit()

    def mousePressed(self, attr, event_callback):
        """ Mouse button event handler, calls: startEditing, endEditing, addPoint, delPoint """
        event = event_callback.getEvent()
        if (event.getState() == coin.SoMouseButtonEvent.DOWN) and (event.getButton() == event.BUTTON1):  # left click
            print("Mouse button down and mouse button 1")
            if not event.wasAltDown():
                if self.editing is None:
                    ''' do something'''
                else:
                    self.endEditing(self.obj, self.editing)

            elif event.wasAltDown():  # left click with ctrl down
                self.display_tracker_menu(event)

        elif (event.getState() == coin.SoMouseButtonEvent.DOWN) and (event.getButton() == event.BUTTON2):  # right click
            self.display_tracker_menu(event)

    def mouseMoved(self, attr, event_callback):
        """ Execute as callback for mouse movement. Update tracker position and update preview ghost. """

        event = event_callback.getEvent()
        pos = event.getPosition()
        listObjects = FreeCADGui.ActiveDocument.ActiveView.getObjectsInfo((int(pos[0]), int(pos[1])))
        if listObjects:
            #{'x': 483239.09375, 'y': 783855.8125, 'z': 797695.0, 'Document': 'Salinas_II_GRG___v3',
            # 'Object': 'Wire023', 'Component': 'Edge30'}]
            for obj in listObjects:
                print(listObjects)
                if obj.Object == self.obj.Name:
                    print(listObjects)

        '''
        if self.editing is not None:
            self.updateTrackerAndGhost(event)
        else:
            # look for a node in mouse position and highlight it
            pos = event.getPosition()
            node = self.getEditNode(pos)
            ep = self.getEditNodeIndex(node)
            if ep is not None:
                if self.overNode is not None:
                    self.overNode.setColor(COLORS["default"])
                self.trackers[str(node.objectName.getValue())][ep].setColor(COLORS["red"])
                self.overNode = self.trackers[str(node.objectName.getValue())][ep]
                print("show menu")
                # self.display_tracker_menu(event)
            else:
                if self.overNode is not None:
                    self.overNode.setColor(COLORS["default"])
                    self.overNode = None
        '''

    def endEditing(self, obj, nodeIndex=None, v=None):
        self.editing = None


    # ------------------------------------------------------------------------
    # DRAFT EDIT Context menu
    # ------------------------------------------------------------------------

    def display_tracker_menu(self, event):
        self.tracker_menu = QtGui.QMenu()
        self.event = event
        actions = None
        actions = ["add point"]

        '''
        if self.overNode:
            # if user is over a node
            doc = self.overNode.get_doc_name()
            obj = App.getDocument(doc).getObject(self.overNode.get_obj_name())
            ep = self.overNode.get_subelement_index()

            obj_gui_tools = self.get_obj_gui_tools(obj)
            if obj_gui_tools:
                actions = obj_gui_tools.get_edit_point_context_menu(obj, ep)

        else:
            # try if user is over an edited object
            pos = self.event.getPosition()
            obj = self.get_selected_obj_at_position(pos)
            if utils.get_type(obj) in ["Line", "Wire", "BSpline", "BezCurve"]:
                actions = ["add point"]
            elif utils.get_type(obj) in ["Circle"] and obj.FirstAngle != obj.LastAngle:
                actions = ["invert arc"]

        if actions is None:
            return
        '''
        for a in actions:
            self.tracker_menu.addAction(a)

        self.tracker_menu.popup(FreeCADGui.getMainWindow().cursor().pos())
        QtCore.QObject.connect(self.tracker_menu,
                               QtCore.SIGNAL("triggered(QAction *)"),
                               self.evaluate_menu_action)

    def evaluate_menu_action(self, labelname):
        action_label = str(labelname.text())

        doc = None
        obj = None
        idx = None

        if action_label == "add point":
            self.addPoint(self.event)

        del self.event

    # -------------------------------------------------------------------------
    # EDIT functions
    # -------------------------------------------------------------------------

    def addPoint(self, event):
        ''' add point to the end '''
        pos = event.getPosition()
        pts = self.points.copy()
        new_select = list()
        point = FreeCAD.Vector(pos)
        mark = MarkerOnShape([point])
        pts.append(mark)
        new_select.append(mark)
        self.points = pts
        self.setupInteractionSeparator()
        self.root.selected_objects = new_select

    def subdivide(self):
        #  get selected lines and subdivide them
        pts = list()
        new_select = list()
        for o in self.lines:
            #FreeCAD.Console.PrintMessage("object %s\n"%str(o))
            if isinstance(o, ConnectionLine):
                pts.append(o.markers[0])
                if o in self.root.selected_objects:
                    #idx = self.lines.index(o)
                    #FreeCAD.Console.PrintMessage("Subdividing line  #{}\n".format(idx))
                    p1 = o.markers[0].points[0]
                    p2 = o.markers[1].points[0]
                    midpar = (FreeCAD.Vector(p1) + FreeCAD.Vector(p2)) / 2.0
                    mark = MarkerOnShape([midpar])
                    pts.append(mark)
                    new_select.append(mark)
        pts.append(self.points[-1])
        self.points = pts
        self.setupInteractionSeparator()
        self.root.selected_objects = new_select
        return True

    def quit(self):
        print("Quit")
        self.unregister_editing_callbacks()
        self.root.unregister()
        #self.sg.removeChild(self.root)
        self.root_inserted = False