# -*- coding: utf-8 -*-

__title__ = "Curves workbench utilities"
__author__ = "Christophe Grellier (Chris_G)"
__license__ = "LGPL 2.1"
__doc__ = "Curves workbench utilities common to all tools."

import FreeCAD

def setIconsPath(path):
    global icons_path
    icons_path = path
    return True

def iconsPath():
    global icons_path
    return icons_path

def info(string):
    FreeCAD.Console.PrintMessage("%s\n"%string)
    
def warn(string):
    FreeCAD.Console.PrintWarning("%s\n"%string)

def error(string):
    FreeCAD.Console.PrintError("%s\n"%string)

def debug(string):
    FreeCAD.Console.PrintMessage("%s\n"%string)

def doNothing(string):
    return None

def setEditorMode(fp, group, mode):
    """set the editor mode of a group of properties"""
    for prop in group:
        fp.setEditorMode(prop, mode)

def getSubShape(shape, shape_type, n):
    if shape_type == "Vertex" and len(shape.Vertexes) >= n:
        return shape.Vertexes[n-1]
    elif shape_type == "Edge" and len(shape.Edges) >= n:
        return shape.Edges[n-1]
    elif shape_type == "Face" and len(shape.Faces) >= n:
        return shape.Faces[n-1]
    else:
        return None

def getShape(obj, prop, shape_type):
    if hasattr(obj, prop) and obj.getPropertyByName(prop):
        if obj.getTypeIdOfProperty(prop) == "App::PropertyLinkSub":
            n = eval(obj.getPropertyByName(prop)[1][0].lstrip(shape_type))
            sh = obj.getPropertyByName(prop)[0].Shape.copy()
            if sh and hasattr(obj.getPropertyByName(prop)[0], "getGlobalPlacement"):
                pl = obj.getPropertyByName(prop)[0].getGlobalPlacement()
                sh.Placement = pl
            return getSubShape(sh, shape_type, n)
        elif obj.getTypeIdOfProperty(prop) == "App::PropertyLinkSubList":
            res = []
            for tup in obj.getPropertyByName(prop):
                for ss in tup[1]:
                    n = eval(ss.lstrip(shape_type))
                    sh = tup[0].Shape.copy()
                    if sh and hasattr(tup[0], "getGlobalPlacement"):
                        pl = tup[0].getGlobalPlacement()
                        sh.Placement = pl
                    res.append(getSubShape(sh, shape_type, n))
            return res
        else:
            FreeCAD.Console.PrintError("CurvesWB._utils.getShape: wrong property type.\n")
            return None
    else:
        FreeCAD.Console.PrintError("CurvesWB._utils.getShape: %r has no property %r\n"%(obj, prop))
        return None

def same_direction(e1, e2, num=10):
    """bool = same_direction(e1, e2, num=10)
    Check if the 2 entities have same direction,
    by comparing them on 'num' samples.
    Entities can be : edges, wires or curves
    """
    v1 = []
    v2 = []
    pts1 = e1.discretize(num)
    pts2 = e2.discretize(num)
    for i in range(num):
        v1.append(pts1[i].distanceToPoint(pts2[i]))
        v2.append(pts1[i].distanceToPoint(pts2[num-1-i]))
    if sum(v1) < sum(v2):
        return True
    else:
        return False

def info_subshapes(shape):
    """Print the list of subshapes of a shape in FreeCAD console.
    info_subshapes(my_shape)
    """
    sh = ["Solids",
          "Compounds",
          "CompSolids",
          "Shells",
          "Faces",
          "Wires",
          "Edges",
          "Vertexes"]
    info("-> Content of {}".format(shape.ShapeType))
    for s in sh:
        subs = shape.__getattribute__(s)
        if subs:
            if (len(subs) == 1) and (subs[0].isEqual(shape)):
                pass # hide self
            else:
                info("{}: {}".format(s, len(subs)))

def ancestors(shape, sub):
    '''list_of_shapes = ancestors(shape, sub)
    Returns the closest ancestors of "sub" in "shape"'''
    def cleanup(shape):
        s = str(shape)
        ss = s.split()[0]
        return ss.split('<')[1]
    shd = (Part.Vertex,
           Part.Edge,
           Part.Wire,
           Part.Face,
           Part.Shell,
           Part.Solid,
           Part.CompSolid,
           Part.Compound)
    for i in range(len(shd)-1):
        if isinstance(sub, shd[i]):
            for j in range(i+1,len(shd)):
                manc = shape.ancestorsOfType(sub, shd[j])
                if manc:
                    print("{} belongs to {} {}.".format(cleanup(sub), len(manc), cleanup(manc[0])))
                    return manc

def rootNode(shape, mode=2, deviation=0.3, angle=0.4):
    buf = shape.writeInventor(mode, deviation, angle)
    from pivy import coin
    inp = coin.SoInput()
    inp.setBuffer(buf)
    node = coin.SoDB.readAll(inp)
    return node

def ruled_surface(e1,e2):
    """ creates a ruled surface between 2 edges, with automatic orientation."""
    import Part
    if not same_direction(e1,e2):
        e = e2.copy()
        e.reverse()
        return Part.makeRuledSurface(e1,e)
    else:
        return Part.makeRuledSurface(e1,e2)

def anim(obj, path, on_path=False, reverse=False, duration=1.0, samples=100):
    """
    Animate obj along path

    anim(obj, path, on_path=False, duration=1.0, samples=100)

    path must be an edge or a wire

    if on_path is True, the animation path is absolute
    else, the animation path is relative to current obj placement

    reverse : reverse path direction

    duration : animation duration in seconds

    samples : number of animation samples
    """
    from time import sleep
    pts = path.discretize(samples)
    if reverse:
        pts.reverse()
    rpts = [p-pts[0] for p in pts]
    if not on_path:
        origin = obj.Placement.Base
    else:
        origin = pts[0]
    for p in rpts:
        obj.Placement.Base = origin + p
        Gui.ActiveDocument.update()
        sleep(float(duration) / samples)




class SilentFPO:
    '''Fake FeaturePython object that has no interaction with other FreeCAD objects.
    It is used as a temporary FPO during editing'''
    def __init__(self):
        self.Proxy = None
        self.Shape = None
        self.props = []
    def addProperty(ptype, pname, pgroup="", pdoc=""):
        setattr(self, pname, None)
        self.props.append(pname)
        return self
    def get_data(self, realfpo):
        for prop in self.props:
            setattr(self, pname, getattr(realfpo, prop))
    def set_data(self, realfpo):
        for prop in self.props:
            setattr(realfpo, pname, getattr(self, prop))
    def status(self):
        for prop in self.props:
            print("%s = %s"%(prop, str(getattr(self, prop))))



class EasyProxy(object):
    def __init__(self, fp):
        self.document_restored = True
        self.ep_add_properties(fp)
        fp.Proxy = self
        self.ep_init(fp)

    def execute(self, fp):
        if not self.document_restored:
            debug("Skipping %s.execute() ..."%fp.Label)
            return False
        else:
            self.ep_execute(fp)

    def onChanged(self, fp, prop):
        if not self.document_restored:
            debug("Skipping %s.onChanged(%s) ..."%(fp.Label,prop))
            return False
        else:
            self.ep_prop_changed(fp, prop)

    def onBeforeChange(self, fp, prop):
        if prop == "Proxy":
            return False
        if not self.document_restored:
            debug("Skipping %s.onBeforeChange(%s) ..."%(fp.Label,prop))
            return False
        else:
            self.ep_before_prop_change(fp, prop)

    def onDocumentRestored(self, fp):
        self.document_restored = True
        debug("%s restored !"%fp.Label)
        self.ep_init(fp)

    def __getstate__(self):
        debug("EasyProxy.__getstate__")
        state = self.ep_on_save()
        # add additional instance variables
        # state["variable"] = self.variable
        return state

    def __setstate__(self,state):
        debug("EasyProxy.__setstate__")
        self.document_restored = False
        self.ep_on_restore(state)
        # restore additional instance variables
        # self.variable = state["variable"]
        return None

    def ep_add_properties(self, fp):
        #fp.addProperty("App::PropertyInteger", "myprop", "Test", "a property").myprop = 1
        return None

    def ep_init(self, fp):
        return None

    def ep_execute(self, fp):
        return None

    def ep_prop_changed(self, fp, prop):
        return None

    def ep_before_prop_change(self, fp, prop):
        return None

    def ep_on_save(self):
        return dict()

    def ep_on_restore(self, state):
        return None



class MyProxy(EasyProxy):

    def ep_add_properties(self, fp):
        debug("---MyProxy.ep_add_properties")
        fp.addProperty("App::PropertyVector", "position", "Test", "a property")
        fp.addProperty("App::PropertyVector", "direction", "Test", "a property")
        fp.addProperty("App::PropertyFloat", "Length", "Test", "a property").Length = 10.0
        fp.addProperty("App::PropertyFloat", "Width", "Test", "a property").Width = 5.0
        fp.addProperty("App::PropertyFloat", "Height", "Test", "a property").Height = 1.0

    def ep_init(self, fp):
        debug("---MyProxy.ep_init(%s)"%fp.Label)

    def ep_execute(self, fp):
        debug("---MyProxy.ep_execute(%s)"%fp.Label)

    def ep_prop_changed(self, fp, prop):
        debug("---MyProxy.ep_prop_changed: %s(%s)"%(fp.Label,prop))

    def ep_before_prop_change(self, fp, prop):
        debug("---MyProxy.ep_before_prop_change: %s(%s)"%(fp.Label,prop))

    def ep_on_save(self):
        debug("---MyProxy.ep_on_save")
        return None

    def ep_on_restore(self,state):
        debug("---MyProxy.ep_on_restore")
        return None