import FreeCAD
import FreeCADGui
import Part

#import _utils

#import sys
#from PySide2.QtWidgets import QApplication
#from PySide2.QtGui import QColor
from pivy import coin
from pivy import graphics

import sys
if sys.version_info.major >= 3:
    from importlib import reload

def midpoint(e):
    p = e.FirstParameter + 0.5 * (e.LastParameter - e.FirstParameter)
    return(e.valueAt(p))

def subshape_from_sublink(o):
    name = o[1][0]
    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])

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

#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 MarkerOnShape(graphics.Marker):
    def __init__(self, points, sh=None):
        super(MarkerOnShape, self).__init__(points, True)
        self.shape = None
        self.sublink = None
        if isinstance(sh,Part.Shape):
            self.shape = sh
        elif isinstance(sh,(tuple,list)):
            self.set_sublink(sh)

    def set_sublink(self,sl):
        if isinstance(sl,(tuple,list)):
            self.shape = subshape_from_sublink(sl)
            self.sublink = sl
        elif sl is None:
            self.shape = None
            self.sublink = None

    def __repr__(self):
        return("MarkerOnShape(%s)"%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 ConnectionLine(graphics.Line):
    def __init__(self, markers, dynamic=True):
        super(ConnectionLine, self).__init__(
            sum([m.points for m in markers], []), dynamic)
        self.markers = markers
        for m in self.markers:
            m.on_drag.append(self.updateLine)

    def updateLine(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 LineManip(ConnectionLine):
    def __init__(self, face, edge, par=0.0, scale=1.0):
        super(LineManip, self).__init__([], True)
        self.face = face
        self.edge = edge
        self.tangent = None
        self.p1 = MarkerOnShape([(0,0,0)],edge)
        self.p2 = MarkerOnShape([(1,0,0)])
        self._par = 0
        self._scale = 1
        self.param = par
        self.scale = scale

    @property
    def param(self):
        return self._par

    @param.setter
    def param(self, par):
        if (par >= self.edge.FirstParameter) and  (par <= self.edge.LastParameter):
            self._par = par
            self.p1 = MarkerOnShape([self.edge.valueAt(self._par)],edge)
            tangent = self.edge.tangentAt(self._par) # TODO change tangent to cross-tangent
            tangent.normalize()
            tangent.multiply(1e12)
            l = Part.LineSegment(self.edge.valueAt(self._par).sub(tangent),self.edge.valueAt(self._par).add(tangent))
            self.tangent = l.toShape()
            self.p2 = MarkerOnShape([self.tangent.valueAt(self._scale)],self.tangent)
        else:
            FreeCAD.Console.PrintError("Bad parameter value, setting to middle of edge")
            self._par = self.edge.FirstParameter + 0.5 * (self.edge.LastParameter - self.edge.FirstParameter)

    @property
    def scale(self):
        return self._scale

    @scale.setter
    def scale(self, sc):
        if abs(sc) >= 1e-7:
            self._scale = sc
            self.p2 = MarkerOnShape([self.tangent.valueAt(self._scale)],self.tangent)
        else:
            FreeCAD.Console.PrintError("Scale value too small")
            self._scale = 1e-7


class BlendSurfEditor(object):
    """BlendSurfEditor free-hand editor
    my_editor = InterpoCurveEditor([points],obj)
    obj is the FreeCAD object that will receive
    the curve shape at the end of editing.
    points can be :
    - Vector (free point)
    - (Vector, shape) (point on shape)"""
    def __init__(self, seg=[]):
        self.segments = list()
        #self.curve = Part.BSplineCurve()
        self.root_inserted = False
        #self.support = None # Not yet implemented
        for p in seg:
            if isinstance(p,FreeCAD.Vector):
                self.points.append(MarkerOnShape([p]))
            elif isinstance(p,(tuple,list)):
                self.points.append(MarkerOnShape([p[0]],p[1]))
            elif isinstance(p,(MarkerOnShape, ConnectionMarker)):
                self.points.append(p)
            else:
                FreeCAD.Console.PrintError("InterpoCurveEditor : bad input")
        # Setup coin objects
        if self.fp:
            self.guidoc = self.fp.ViewObject.Document
        else:
            if not FreeCADGui.ActiveDocument:
                appdoc = FreeCAD.newDocument("New")
        self.guidoc = FreeCADGui.ActiveDocument
        self.view = self.guidoc.ActiveView
        self.rm = self.view.getViewer().getSoRenderManager()
        self.sg = self.view.getSceneGraph()
        self.setup_InteractionSeparator()
        self.update_curve()

    def setup_InteractionSeparator(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
        self.root.on_drag.append(self.update_curve)
        # Keyboard callback
        self._controlCB = self.root.events.addEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self.controlCB)
        # populate root node
        self.root += self.points
        self.build_lines()
        self.root += self.lines
        self.root.register()
        self.sg.addChild(self.root)
        self.root_inserted = True

    def compute_tangents(self):
        tans = list()
        flags = list()
        for i in range(len(self.points)):
            if isinstance(self.points[i].shape,Part.Face):
                for vec in self.points[i].points:
                    u,v = self.points[i].shape.Surface.parameter(FreeCAD.Vector(vec))
                    norm = self.points[i].shape.normalAt(u,v)
                    cp = self.curve.parameter(FreeCAD.Vector(vec))
                    t = self.curve.tangent(cp)[0]
                    pl = Part.Plane(FreeCAD.Vector(),norm)
                    ci = Part.Geom2d.Circle2d()
                    ci.Radius = t.Length * 2
                    w = Part.Wire([ci.toShape(pl)])
                    f = Part.Face(w)
                    #proj = f.project([Part.Vertex(t)])
                    proj = Part.Vertex(t).distToShape(f)[1][0][1]
                    #pt = proj.Vertexes[0].Point
                    #FreeCAD.Console.PrintMessage("Projection %s -> %s\n"%(t,proj))
                    if proj.Length > 1e-7:
                        tans.append(proj)
                        flags.append(True)
                    else:
                        tans.append(FreeCAD.Vector(1,0,0))
                        flags.append(False)
            else:
                for j in range(len(self.points[i].points)):
                    tans.append(FreeCAD.Vector(1,0,0))
                    flags.append(False)
        return(tans,flags)

    def update_curve(self):
        pts = list()
        for p in self.points:
            pts += p.points
        #FreeCAD.Console.PrintMessage("pts :\n%s\n"%str(pts))
        if len(pts) > 1:
            self.curve.interpolate(pts)
            tans, flags = self.compute_tangents()
            if (len(tans) == len(pts)) and (len(flags) == len(pts)):
                self.curve.interpolate(Points=pts, Tangents=tans, TangentFlags=flags)
            if self.fp:
                self.fp.Shape = self.curve.toShape()

    def build_lines(self):
        self.lines = list()
        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)
    
    def controlCB(self, attr, event_callback):
        event = event_callback.getEvent()
        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"):
                if self.fp:
                    self.fp.ViewObject.Proxy.doubleClicked(self.fp.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].set_sublink(tup)
                        FreeCAD.Console.PrintMessage("Snapped to %s\n"%str(self.root.selected_objects[i].sublink))
                        self.root.selected_objects[i].drag_start()
                        self.root.selected_objects[i].drag((0,0,0))
                self.update_curve()
            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.setup_InteractionSeparator()
                self.update_curve()
    
    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 #%d\n"%idx)
                    p1 = o.markers[0].points[0]
                    p2 = o.markers[1].points[0]
                    par1 = self.curve.parameter(FreeCAD.Vector(p1))
                    par2 = self.curve.parameter(FreeCAD.Vector(p2))
                    midpar = (par1+par2)/2.0
                    mark = MarkerOnShape([self.curve.value(midpar)])
                    pts.append(mark)
                    new_select.append(mark)
        pts.append(self.points[-1])
        self.points = pts
        self.setup_InteractionSeparator()
        self.root.selected_objects = new_select
        self.update_curve()
        return(True)
    
    def quit(self):
        self.root.events.removeEventCallback(coin.SoKeyboardEvent.getClassTypeId(), self._controlCB)
        self.root.unregister()
        self.sg.removeChild(self.root)
        self.root_inserted = False
            



def get_guide_params():
    sel = FreeCADGui.Selection.getSelectionEx()
    pts = list()
    for s in sel:
        pts.extend(list(zip(s.PickedPoints,s.SubObjects)))
    return(pts)

def main():
    #obj = FreeCAD.ActiveDocument.addObject("Part::Spline","profile")
    #tups = get_guide_params()
    sel = FreeCADGui.Selection.getSelection()
    ip = BlendSurfEditor(sel)
    FreeCAD.ActiveDocument.recompute()

if __name__ == '__main__':
    main()