import Part, Draft, math
from PySide import QtGui, QtCore

# ---------------------------------------------------------------------------------------------------------------
# WALL GEOMETRY OBJECT AND VIEWPROVIDER
# ---------------------------------------------------------------------------------------------------------------

class Wall(object):

    def __init__(self, obj=None):
        print("runing wall object init method\n")
        self.Type = 'Arch_Wall'

        if obj:
            print("runing obj init method")

            obj.Proxy = self
            self.Object = obj
            self.attach(obj)
            self.Execute(obj)


    def init_properties(self, obj):
        obj.addProperty('App::PropertyString', 'Description', 'Base', 'Wall description').Description = ""

        # LEVEL Properties
        obj.addProperty('App::PropertyBool', 'BaseConstrain', 'Level properties', 'Constrain the wall base to the parent level').BaseConstrain = True
        obj.addProperty('App::PropertyLength', 'BaseOffset', 'Level properties', 'If the wall base is constrained to the parent level, set Z offset').BaseOffset = '0'
        obj.addProperty('App::PropertyBool', 'TopConstrain', 'Level properties', 'Constrain the wall top to the upper level').TopConstrain = True
        obj.addProperty('App::PropertyLength', 'TopOffset', 'Level properties', 'If the wall top is constrained to the parent level, set Z offset').TopOffset = '0'

        # CHILDREN Properties
        obj.addProperty('App::PropertyLinkChild', 'BaseGeometry', 'Children', 'Link to the child representing the base geometry of the wall shape')
        obj.addProperty('App::PropertyLinkListChild', 'Openings', 'Children', 'Link to the children that will be subtracted from the wall shape')
        obj.addProperty('App::PropertyLinkListChild', 'Fusions', 'Children', 'Link to the children that will be fused with the wall shape')
        obj.addProperty('App::PropertyLinkListChild', 'Components', 'Children', 'Link to the structural children members of the wall')
        obj.addProperty('App::PropertyLinkListChild', 'Windows', 'Children', 'Link to the windows inserted into the wall ')

        # WALL ENDS Properties
        obj.addProperty('App::PropertyBool', 'JoinFirstEnd', 'Wall connections', "Allow automatic compute of first end").JoinFirstEnd = False
        obj.addProperty('App::PropertyString', 'JoinFirstEndTo', 'Wall connections', "Name of the object to join wall's first end").JoinFirstEndTo = ''
        obj.addProperty('App::PropertyBool', 'JoinLastEnd', 'Wall connections', "Allow automatic compute of last end").JoinLastEnd = False
        obj.addProperty('App::PropertyString', 'JoinLastEndTo', 'Wall connections', "Name of the object to join wall's last end").JoinLastEndTo = ''
        obj.addProperty('App::PropertyStringList', 'IncomingTJoins', 'Wall connections', "Names of the objects that target current wall").IncomingTJoins = []


    def attach(self,obj):

        print("running" + obj.Name + "attach() method\n")
        obj.addExtension('App::OriginGroupExtensionPython', None)
        obj.Origin = FreeCAD.ActiveDocument.addObject('App::Origin','Origin')
        self.init_properties(obj)


    def execute(self,obj):

        print("running " + obj.Name + " execute() method\n")

        import Part
        
        wall_shape = None
        subtractive_geometries = []

        if hasattr(obj, "BaseGeometry"):
            if obj.BaseGeometry:
                if hasattr(obj.BaseGeometry, "Shape"):
                    wall_shape = obj.BaseGeometry.Shape

        if wall_shape is None:
            return

        # perform boolean cuts of Wall Openings and collect subtractive_geometries
        if hasattr(obj,"Openings"):
            if obj.Openings is not None and obj.Openings != []:
                if len(obj.Openings) > 0:
                    for o in obj.Openings:
                        subtractive_geometries.append(o)
                        ns = wall_shape.cut(o.Shape)
                        wall_shape = ns
        # perform boolean cut of Windows openings (to be implemented)

        # collect additive_geometries
        additive_geometries = [wall_shape]
        for child in obj.Group:
            if hasattr(child, "Shape") and child != obj.BaseGeometry and not child in subtractive_geometries:
                additive_geometries.append(child.Shape)

        # Make a compound out of the wall BaseGeometry and other additive_geometries
        obj.Shape = Part.Compound(additive_geometries)


    def onBeforeChange(self, obj, prop):
        """this method is activated before a property changes"""
        # WALL ENDS properties
        if (hasattr(obj, "JoinFirstEndTo") and hasattr(obj, "JoinLastEndTo") and
            hasattr(obj, "JoinFirstEnd")and hasattr(obj, "JoinLastEnd")):

            if prop == "JoinFirstEndTo" and obj.JoinFirstEnd:
                target = App.ActiveDocument.getObject(obj.JoinFirstEndTo)
                if hasattr(target, "IncomingTJoins"):
                    list = target.IncomingTJoins
                    if obj.Name in list:
                        list.remove(obj.Name)
                        target.IncomingTJoins = list

            elif prop == "JoinLastEndTo" and obj.JoinLastEnd:
                target = App.ActiveDocument.getObject(obj.JoinFirstEndTo)
                if hasattr(target, "IncomingTJoins"):
                    list = target.IncomingTJoins
                    if obj.Name in list:
                        list.remove(obj.Name)
                        target.IncomingTJoins = list


    def onChanged(self, obj, prop):
        """this method is activated when a property changes"""
        if prop == "Placement":
            if hasattr(obj, "Placement"):
                # TODO: update calculation of end joints
                self.recompute_ends(obj, 0)
                self.recompute_ends(obj, 1)
                for t_name in obj.IncomingTJoins:
                    t = App.ActiveDocument.getObject(t_name)
                    t.Proxy.recompute_ends(t, 0)
                    t.Proxy.recompute_ends(t, 1)

        # CHILDREN properties
        if prop == "BaseGeometry":
            if hasattr(obj, "BaseGeometry"):
                self.format_base_geometry_object(obj, obj.BaseGeometry)

        # WALL ENDS properties
        if (hasattr(obj, "JoinFirstEndTo") and hasattr(obj, "JoinLastEndTo") and
            hasattr(obj, "JoinFirstEnd")and hasattr(obj, "JoinLastEnd")):

            if prop == "JoinFirstEndTo" and obj.JoinFirstEnd:
                self.recompute_ends(obj, 0)

            elif prop == "JoinLastEndTo" and obj.JoinLastEnd:
                self.recompute_ends(obj, 1)


    def recompute_ends(self, obj, end_idx):
        """
        This method auto recompute the first or the last wall end joint
        If the obj and the target objects are both joinable it recompute the
        joints, if not it resets the corresponding wall end to 90 deg.

        Parameters
        -----
        obj         wall object
        end_idx     None or 0 or 1
                    the wall end index:
                    0 for first end
                    1 for last end
                    2 for both ends
        """
        if end_idx == 0:
            target = App.ActiveDocument.getObject(obj.JoinFirstEndTo)
            if target == obj:
                return
            if self.is_wall_joinable(obj):
                if self.is_wall_joinable(target):
                    self.join_end(obj, target, 0)
                else:
                    self.reset_end(obj, 0)

        if end_idx == 1:
            target = App.ActiveDocument.getObject(obj.JoinLastEndTo)
            if target == obj:
                return
            if self.is_wall_joinable(obj):
                if self.is_wall_joinable(target):
                    self.join_end(obj, target, 1)
                else:
                    self.reset_end(obj, 1)


    def is_wall_joinable(self, obj):
        """
        Returns True if the given object type is 'Arch_Wall' and if its
        BaseGeometry is an 'Arch_WallSegment' object.
        in every other case returns False.
        """

        if Draft.get_type(obj) != "Arch_Wall":
            #print("Wall Joining only works on valid Arch_Wall objects")
            return False
        if Draft.get_type(obj.BaseGeometry) != 'Arch_WallSegment':
            #print("Wall Joining only works if Wall base geometry is an Arch_WallSegment object")
            return False
        return True


    def reset_end(self, obj, idx):
        """
        Reset given wall object end joints.

        Parameters
        -----
        obj         wall object
        end_idx     the wall end index to reset
                    0 for first end
                    1 for last end
        """
        print("running reset_end() "+obj.Name+"_"+str(idx)+"\n")
        if idx == 0:
            obj.BaseGeometry.FirstCoreInnerAngle = 90
            obj.BaseGeometry.FirstCoreOuterAngle = 90
        elif idx == 1:
            obj.BaseGeometry.LastCoreInnerAngle = 90
            obj.BaseGeometry.LastCoreOuterAngle = 90


    def remove_linked_walls_references(self, obj):
        """ 
        Removes the reference to given wall to all the other 
        walls that target it to join
        """
        print("REMOVE ALL REFERENCES on DELETING")
        references = obj.IncomingTJoins
        references.append(obj.JoinFirstEndTo)
        references.append(obj.JoinLastEndTo)
    
        for link in references:
            o = App.ActiveDocument.getObject(link)

            if o:
                if hasattr(o, "JoinFirstEndTo"):
                    if o.JoinFirstEndTo == obj.Name:
                        o.JoinFirstEndTo = ""
                if hasattr(o, "JoinLastEndTo"):
                    if o.JoinLastEndTo == obj.Name:
                        o.JoinLastEndTo = ""
                if hasattr(o, "IncomingTJoins"):
                    if obj.Name in o.IncomingTJoins:
                        target_list = o.IncomingTJoins
                        target_list.remove(obj.Name)
                        o.IncomingTJoins = target_list


    def join_end(self, obj, target, end_idx):
        """ Join the wall to the target wall """
        # calculate which type of joining
        join_type, target_idx = self.guess_join_type(obj, target)

        if join_type == "T":
            w_ext = self.extend(obj, target, end_idx)
            if w_ext:
                self.T_join(obj, target, end_idx)
            else:
                return False

        elif join_type == "L":
            w_ext = self.extend(obj, target, end_idx)
            t_ext = self.extend(target, obj, target_idx)
            if w_ext and t_ext:
                self.L_join(obj, target, end_idx, target_idx)
                self.L_join(target, obj, target_idx, end_idx)
            else:
                return False
   
        return True 
    

    def guess_join_type(self, obj, target):
        """ Guess which kind of joint to apply to the given wall """
        print("running guess_join_type()\n")
        if target.JoinFirstEndTo == obj.Name and target.JoinFirstEnd:
            return "L", 0
        elif target.JoinLastEndTo == obj.Name and target.JoinLastEnd:
            return "L", 1
        else:
            return "T", None


    def extend(self, wall, target, idx):
        """ Extend the given wall to the target wall """
        print("--------\n"+"Extend "+wall.Name + " to " +target.Name+ "\n")

        wall_core_axis = wall.Proxy.get_core_axis(wall)#.toShape()
        target_core_axis = target.Proxy.get_core_axis(target)#.toShape()
        if wall_core_axis is None or target_core_axis is None:
            print("Failed to get wall core axis")
            return False

        int_pts = wall_core_axis.intersect(target_core_axis)
        if len(int_pts) == 1:
            int_p = int_pts[0]        
            intersection = App.Vector(int_p.X,int_p.Y,int_p.Z)
        else:
            print("No intersection point found, or too many intersection points found")
            return False

        if idx == 0:
            wall.BaseGeometry.Proxy.set_first_point(wall.BaseGeometry, intersection)
            return True
        elif idx == 1:
            wall.BaseGeometry.Proxy.set_last_point(wall.BaseGeometry, intersection)
            return True


    def T_join(self, wall, target, idx): 
        """ Compute wall angles according to given parameters """
        print("--------\n"+"T_Join "+wall.Name + " with " +target.Name+ "\n")

        if idx == 0:
            w1 = wall.Proxy.get_first_point(wall)
            w2 = wall.Proxy.get_last_point(wall)
        elif idx == 1:
            w1 = wall.Proxy.get_last_point(wall)
            w2 = wall.Proxy.get_first_point(wall)

        t1 = target.Proxy.get_first_point(target)
        t2 = target.Proxy.get_last_point(target)

        angle = math.degrees(Draft.DraftVecUtils.angle(w2-w1,t2-t1))
        print(angle)

        # identify if the function have to join the first or the end of the wall
        if idx == 0:
            wall.BaseGeometry.FirstCoreInnerAngle = angle
            wall.BaseGeometry.FirstCoreOuterAngle = -angle
        elif idx == 1:
            wall.BaseGeometry.LastCoreInnerAngle = -angle
            wall.BaseGeometry.LastCoreOuterAngle = angle

        if not wall.Name in target.IncomingTJoins:
            target_list = target.IncomingTJoins
            target_list.append(wall.Name)
            target.IncomingTJoins = target_list


    def L_join(self, wall, target, idx , target_idx):
        """ Compute wall angles according to given parameters 

                      /    wall
                     /     /     / .
                    /     /     /   angle   
                   /     /_wi__/______.__________________________         
                  /     /    ./       .
                 /  ti /  c. /        .
                /     /  .  /ti       . target.BaseGeometry.Width        
               /     / .   /          .
              /____ /_____/_____ _____._____ _____ _____ _____ __
             /    ./  wi /                    target
            /   . /     /
           /  .  /     /
          / .w_angle  /
         /.____/_____/___________________________________________          

        """
        # TODO: correct the bug of two different size walls with big angle in between
        print("--------\n"+"L_Join "+wall.Name+"_"+str(idx) + " with " +target.Name+"_"+str(target_idx) + "\n")
        
        if idx == 0:
            w1 = wall.Proxy.get_first_point(wall)
            w2 = wall.Proxy.get_last_point(wall)
        elif idx == 1:
            w1 = wall.Proxy.get_last_point(wall)
            w2 = wall.Proxy.get_first_point(wall)

        if target_idx == 0:
            t1 = target.Proxy.get_first_point(target)
            t2 = target.Proxy.get_last_point(target)
        elif target_idx == 1:
            t1 = target.Proxy.get_last_point(target)
            t2 = target.Proxy.get_first_point(target)

        angle = Draft.DraftVecUtils.angle(w2-w1,t2-t1)

        print("angle between walls: " + str(math.degrees(angle)) + "\n")

        if angle > 0:
            w_i = wall.BaseGeometry.Width * math.cos(math.pi/2-angle)
            t_i = target.BaseGeometry.Width * math.cos(math.pi/2-angle)
        if angle < 0:
            w_i = wall.BaseGeometry.Width * math.cos(-math.pi/2-angle)
            t_i = target.BaseGeometry.Width * math.cos(-math.pi/2-angle)

        c = math.sqrt( w_i**2 + t_i**2 - 2 * abs(w_i) * t_i * math.cos(math.pi-angle) )
        w_angle = math.asin( w_i / c * math.sin(math.pi-angle))
        
        print("w_i: " + str(w_i) + "\n")
        print("c: " + str(c) + "\n")
        print("flipping parameter: " + str(c) + "\n")    
        print("cut angle: " + str(math.degrees(w_angle)) + "\n")

        '''if angle < 0:
            if  w_i * c < 0 :
                w_angle *= -1'''


        '''if angle >= -math.pi and angle <= math.pi:
            pass
        elif angle > math.pi:
            if w_i * t_i < 0 :
                w_angle *= -1
        elif angle < 0:
            elif w_i * t_i < 0 :
                w_angle *= -1'''


        # assign the angles to the correct wall end
        w_angle = math.degrees( w_angle )
        if idx == 0:
            wall.BaseGeometry.FirstCoreInnerAngle = w_angle
            wall.BaseGeometry.FirstCoreOuterAngle = -w_angle
        elif idx == 1:
            wall.BaseGeometry.LastCoreInnerAngle = -w_angle
            wall.BaseGeometry.LastCoreOuterAngle = +w_angle


    def get_core_axis(self, obj):
        """ returns a part line representing the core axis of the wall """
        return obj.BaseGeometry.Proxy.get_core_axis(obj.BaseGeometry)

    def get_first_point(self, obj):
        """returns a part line representing the core axis of the wall"""
        return obj.BaseGeometry.Proxy.get_first_point(obj.BaseGeometry)

    def get_last_point(self, obj):
        """returns a part line representing the core axis of the wall"""
        return obj.BaseGeometry.Proxy.get_last_point(obj.BaseGeometry)

    def flip_wall(self, obj):
        """
        describe
        """
        #TODO: verify if it's needed to swap FirstEndJoinTo and LastEndJoinTo
        #TODO: check what happens if the base geometry is far from origin
        obj.Placement.Rotation.Angle += math.pi
        obj.JoinFirstEndTo, obj.JoinLastEndTo = obj.JoinLastEndTo, obj.JoinFirstEndTo

    def flip_wall(self, obj):
        """
        describe
        """
        #TODO: verify if it's needed to swap FirstEndJoinTo and LastEndJoinTo
        #TODO: check what happens if the base geometry is far from origin
        obj.Placement.Rotation.Angle += math.pi
        obj.JoinFirstEndTo, obj.JoinLastEndTo = obj.JoinLastEndTo, obj.JoinFirstEndTo

    def format_base_geometry_object(self, obj, base_geometry):
        """
        this method is called when a BaseGeometry object is assigned to the wall
        """
        if Draft.get_type(base_geometry) == 'Arch_WallSegment':
            # if base_geometry is default Arch_WallSegment object, 
            # enable auto computing of ends joints
            obj.JoinFirstEnd = True
            obj.JoinLastEnd = True
        else:
            # else disable it
            obj.JoinFirstEnd = False
            obj.JoinLastEnd = False


        if hasattr(base_geometry, "ViewObject"):
            # format given object to wall base geometry visual settings
            if hasattr(base_geometry.ViewObject, "Transparency"):
                base_geometry.ViewObject.Transparency = 90
            if hasattr(base_geometry.ViewObject, "DrawStyle"):
                base_geometry.ViewObject.DrawStyle = "Dashed"
            if hasattr(base_geometry.ViewObject, "LineWidth"):
                base_geometry.ViewObject.LineWidth = 1


    def onDocumentRestored(self, obj):
        self.Object = obj


    def __getstate__(self):
        return


    def __setstate__(self,_state):
        return


class ViewProviderWall(object):

    def __init__(self,vobj=None):

        if vobj:
            vobj.Proxy = self
            self.attach(vobj)
        else:
            self.ViewObject = None


    def updateData(self, obj, prop):

        return


    def attach(self,vobj):

        vobj.addExtension('Gui::ViewProviderOriginGroupExtensionPython', None)
        self.ViewObject = vobj
        print("running  " + vobj.Object.Name + " ViewObject attach() method\n")


    def onDelete(self, vobj, subelements): # subelements is a tuple of strings
        """
        Activated when object is deleted
        """
        # TODO: this method implementation removed the dialog asking to delete
        #       Group items. It should be implemented again i think.

        vobj.Object.Proxy.remove_linked_walls_references(vobj.Object)
        return True # If False is returned the object won't be deleted


    def __getstate__(self):
        """ describe """        
        return None


    def __setstate__(self, _state):
        """ describe """        
        return None


    def getDefaultDisplayMode(self):
        """
        Return the name of the default display mode. 
        It must be defined in getDisplayModes.
        """
        return "Flat Lines"


    def toggle_display_mode(self, vobj):
        """
        describe
        """
        if self.ViewObject.DisplayMode == "Group":
            self.ViewObject.DisplayMode = "Flat Lines"
        elif self.ViewObject.DisplayMode == "Flat Lines":
            self.ViewObject.DisplayMode = "Group"


    def setupContextMenu(self, vobj, menu):
        from PySide import QtCore,QtGui
        action1 = QtGui.QAction("Toggle display mode", menu)
        QtCore.QObject.connect(action1,QtCore.SIGNAL("triggered()"),lambda f=self.toggle_display_mode, arg=vobj:f(arg))
        menu.addAction(action1)
        action2 = QtGui.QAction("Flip wall", menu)
        QtCore.QObject.connect(action2,QtCore.SIGNAL("triggered()"),lambda f=vobj.Object.Proxy.flip_wall, arg=vobj.Object:f(arg))
        menu.addAction(action2)


# ---------------------------------------------------------------------------------------------------------------
# WALL GEOMETRY OBJECT AND VIEWPROVIDER
# ---------------------------------------------------------------------------------------------------------------


class WallGeometry():
    
    def __init__(self, obj):
        """
        Default Constructor
        """
        
        self.Type = 'Arch_WallSegment'
        self.ratios = None
        
        self.set_properties(obj)
        obj.Proxy = self
        self.Object = obj
    
    def set_properties(self, obj):

        # base
        obj.addProperty('App::PropertyString', 'Description', 'Base', 'Wall description').Description = ""

        # Base line : define wall core axis
        obj.addProperty('App::PropertyVector', 'FirstPoint', 'BaseLine', 'Start point of wall core axis').FirstPoint = App.Vector(0,0,0)
        obj.addProperty('App::PropertyVector', 'LastPoint', 'BaseLine', 'End point of wall core axis').LastPoint = App.Vector(4000,0,0)
        obj.addProperty('App::PropertyBool', 'ConstrainToXZPlane', 'BaseLine', 'Constrain to wall XZ axis').ConstrainToXZPlane = True

        # dimensions
        #obj.addProperty('App::PropertyLength', 'LengthForward', 'Dimensions', 'Wall length').LengthForward = '2 m'
        #obj.addProperty('App::PropertyLength', 'LengthBackward', 'Dimensions', 'Wall length').LengthBackward = '2 m'
        obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Wall length',1).Length = '4 m'
        obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Wall width').Width = '35 cm'
        obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Wall height').Height = '2.7 m'

        # wall joining
        obj.addProperty('App::PropertyAngle', 'FirstInnerLayerAngle', 'Wall Ends', 'Angular cut of first wall end (to be implemented)',4).FirstInnerLayerAngle = '90 deg'
        obj.addProperty('App::PropertyAngle', 'FirstOuterLayerAngle', 'Wall Ends', 'Angular cut of first wall end (to be implemented)',4).FirstOuterLayerAngle = '90 deg'
        obj.addProperty('App::PropertyAngle', 'FirstCoreInnerAngle', 'Wall Ends', 'Angular cut of first wall end (to be implemented)',4).FirstCoreInnerAngle = '90 deg'
        obj.addProperty('App::PropertyAngle', 'FirstCoreOuterAngle', 'Wall Ends', 'Angular cut of last wall end (to be implemented)',4).FirstCoreOuterAngle = '90 deg'

        obj.addProperty('App::PropertyAngle', 'LastInnerLayerAngle', 'Wall Ends', 'Angular cut of first wall end (to be implemented)',4).LastInnerLayerAngle = '90 deg'
        obj.addProperty('App::PropertyAngle', 'LastOuterLayerAngle', 'Wall Ends', 'Angular cut of first wall end (to be implemented)',4).LastOuterLayerAngle = '90 deg'
        obj.addProperty('App::PropertyAngle', 'LastCoreInnerAngle', 'Wall Ends', 'Angular cut of first wall end (to be implemented)',4).LastCoreInnerAngle = '90 deg'
        obj.addProperty('App::PropertyAngle', 'LastCoreOuterAngle', 'Wall Ends', 'Angular cut of last wall end (to be implemented)',4).LastCoreOuterAngle = '90 deg'


    def __getstate__(self):
        return self.Type
    
    def __setstate__(self, state):
        if state:
            self.Type = state
    
    def execute(self, obj):
        """
        The wall shape is defined as 2 Part Wedge solids, fused together;
        splays are controlled by obj.FirstCoreOuterAngle, obj.LastCoreOuterAngle
                                 obj.FirstCoreInnerAngle, obj.LastCoreInnerAngle

                 <--> first_splay                <--> last_splay                                   
                 ---------------------------------  outer surface
                  \         Part Wedge 1          \ 
                   \           core axis           \    
        first_point o-------------------------------o  last_point  
                     \                               \
                      \       Part Wedge 2            \
                       ---------------------------------  inner surface
                    <--> first_splay                <--> last_splay                                   
        """
        import Draft, Part, math
        
        if hasattr(obj,"FirstPoint") and hasattr(obj,"LastPoint") and hasattr(obj,"ConstrainToXZPlane") and hasattr(obj,"Width") and hasattr(obj,"Height"):
            length = obj.Length

            # swap first point and last point to have them in the right order
            if obj.FirstPoint.x < obj.LastPoint.x:
                first_point = obj.FirstPoint
            elif obj.FirstPoint.x > obj.LastPoint.x:
                first_point = obj.LastPoint
            elif obj.FirstPoint.x == obj.LastPoint.x:
                return
            
            if obj.ConstrainToXZPlane:
                first_splay = obj.Width/2 * math.tan(math.pi/2-math.radians(obj.FirstCoreInnerAngle))
                last_splay = obj.Width/2 * math.tan(math.pi/2-math.radians(obj.LastCoreInnerAngle))
                
                Xmin = 0
                Ymin = 0
                Zmin = 0
                Z2min = 0
                X2min = first_splay
                Xmax = length
                Ymax = obj.Width/2
                Zmax = obj.Height
                Z2max = obj.Height
                X2max = length - last_splay

                # checking conditions that will break Part.makeWedge()
                if first_splay >= length:
                    print("Wall is too short compared to the first splay: removing angles of outer core layer\n")
                    X2min = 0
                if last_splay >= length:
                    print("Wall is too short compared to the last splay: removing angles of outer core layer\n")
                    X2max = length
                if ( first_splay + last_splay ) >= length:
                    print("Wall is too short compared to the splays: removing angles of inner core layer\n")
                    X2min = 0
                    X2max = length

                inner_core = Part.makeWedge( Xmin, Ymin, Zmin, Z2min, X2min,
                                             Xmax, Ymax, Zmax, Z2max, X2max)#, obj.FirstPoint, obj.LastPoint )
                inner_core.Placement.Base.x = first_point.x

                first_splay = obj.Width/2 * math.tan(math.pi/2-math.radians(obj.FirstCoreOuterAngle))
                last_splay = obj.Width/2 * math.tan(math.pi/2-math.radians(obj.LastCoreOuterAngle))          
                
                Xmin = first_splay
                Ymin = 0
                Zmin = 0
                Z2min = 0
                X2min = 0
                Xmax = length - last_splay
                Ymax = obj.Width/2
                Zmax = obj.Height
                Z2max = obj.Height
                X2max = length

                # checking conditions that will break Part.makeWedge()
                if first_splay >= length:
                    print("Wall is too short compared to the first splay: removing angles of outer core layer\n")
                    Xmin = 0
                if last_splay >= length:
                    print("Wall is too short compared to the last splay: removing angles of outer core layer\n")
                    Xmax = length
                if ( first_splay + last_splay ) >= length:
                    print("Wall is too short compared to the splays: removing angles of outer core layer\n")
                    Xmin = 0
                    Xmax = length

                outer_core = Part.makeWedge( Xmin, Ymin, Zmin, Z2min, X2min,
                                             Xmax, Ymax, Zmax, Z2max, X2max)#, obj.Start, obj.End)
                       
                outer_core.Placement.Base = App.Vector(first_point.x, - obj.Width/2)

            else:
                print("ConstrainToXZPlane is set to false: Not implemented yet")
            
            core_layer = inner_core.fuse(outer_core)
            
            obj.Shape = core_layer


    def onChanged(self, obj, prop):
        """this method is activated when a property changes"""
        if prop == "FirstPoint" or prop == "LastPoint":
            if hasattr(obj, "FirstPoint") and hasattr(obj, "LastPoint"):
                #if obj.FirstPoint.x > obj.LastPoint.x:   circular
                #    obj.FirstPoint, obj.LastPoint = obj.LastPoint, obj.FirstPoint
                if hasattr(obj, "Length"):
                    obj.Length = abs(obj.LastPoint.x - obj.FirstPoint.x)

    def set_first_point(self, obj, first_point, local=False):
        """returns a part line representing the core axis of the wall"""
        if first_point != obj.LastPoint:
            self.set_point(obj, first_point, 0, local)
            return True
        else:
            print("You are trying to set the first point equal to the last point, this is not allowed.\n")
            return False

    def set_last_point(self, obj, last_point, local=False):
        """returns a part line representing the core axis of the wall"""
        if last_point != obj.FirstPoint:
            self.set_point(obj, last_point, 1, local)
            return True
        else:
            print("You are trying to set the last point equal to the first point, this is not allowed.\n")
            return False

    def set_point(self, obj, point, point_idx, local=False):
        """returns a part line representing the core axis of the wall"""
        if local:
            np = point
        else:
            np = obj.getGlobalPlacement().inverse().multVec(point)

        # assign the np to the first or end point of the wall
        if point_idx == 0:
            obj.FirstPoint = np
        elif point_idx == 1:
            obj.LastPoint = np

    def get_core_axis(self, obj):
        """returns a part line representing the core axis of the wall"""
        p1 = self.get_first_point(obj)
        p2 = self.get_last_point(obj)
        if p1 == p2:
            print("Points are equal, cannot get the axis")
            return None
        else:
            core_axis= Part.Line(p1, p2)
            return core_axis

    def get_first_point(self, obj):
        """returns a part line representing the core axis of the wall"""
        p1 = obj.getGlobalPlacement().multVec(obj.FirstPoint)
        return p1

    def get_last_point(self, obj):
        """returns a part line representing the core axis of the wall"""
        p2 = obj.getGlobalPlacement().multVec(obj.LastPoint)
        return p2


class ViewProviderWallGeometry:
    def __init__(self, vobj):
        """
        Set this object to the proxy object of the actual view provider
        """
        self.set_properties(vobj)
        vobj.Proxy = self

    def set_properties(self, vobj):
        pass


# ---------------------------------------------------------------------------------------------------------------
# MAKE FUNCTIONS
# ---------------------------------------------------------------------------------------------------------------


def make_wall(p1 = None, p2 = None, align = "core_axis"): #Add a Wall object to the document
    if App.ActiveDocument is None:
        return
    obj = App.ActiveDocument.addObject('Part::FeaturePython', 'Wall', Wall(), ViewProviderWall(), True)
    
    #Add a WallShape object to the document
    shp = App.ActiveDocument.addObject('Part::FeaturePython', obj.Name + 'Segment')
    WallGeometry(shp)
    ViewProviderWallGeometry(shp.ViewObject)
    
    #Add the WallShape as the Wall BaseGeometry
    obj.addObject(shp)
    obj.BaseGeometry = shp
    
    obj.Placement.Base = p1
    length = p1.distanceToPoint(p2)
    angle = Draft.DraftVecUtils.angle(p2-p1,App.Vector(1,0,0))
    obj.Placement.Rotation.Angle = -angle
    obj.BaseGeometry.LastPoint = App.Vector(length,0,0)
    
    #obj.set_center()
    #obj.align_first_point()
    #obj.set_first_point()
    App.ActiveDocument.recompute()
    return obj
'''
p1 = App.Vector(-2000,0,0)
p2 = App.Vector(2000,0,0)

a = make_wall(p1,p2)
b = make_wall(p1,p2)
'''
#corner joint
'''
a.JoinFirstEndTo = "Wall001"
a.BaseGeometry.Width = "0.10m"

b.JoinLastEndTo = "Wall"'''

# ---------------------------------------------------------------------------------------------------------------
# CODE TO BE REUSED
# ---------------------------------------------------------------------------------------------------------------

#calculates wall joining trough angles
#Draft.DraftGeomUtils.findIntersection(p1,p2,p3,p4)
'''if obj.FirstJoin:
p1,p2 = self.get_wall_points(obj)
p3,p4 = self.get_wall_points(obj.FirstJoin)
base_line_1 = Part.Line(p1,p2)
base_line_2 = Part.Line(p3,p4)
intersection = Draft.DraftGeomUtils.findIntersection(base_line_1.toShape(),base_line_2.toShape())
print(p1,p2,p3,p4,intersection[0])
angle = Draft.DraftVecUtils.angle(p2-p1,p4-p3)
obj.FirstAngle = math.degrees(angle)'''

# ---------------------------------------------------------------------------------------------------------------
# WALL COMMAND
# ---------------------------------------------------------------------------------------------------------------


class _CommandWall:

    "the Arch Wall command definition"

    def GetResources(self):

        return {'Pixmap'  : 'Arch_Wall',
                'MenuText': "Wall",
                'Accel': "W, A",
                'ToolTip': "Creates a wall object from scratch or from a selected object (wire, face or solid)"}

    def IsActive(self):

        return not FreeCAD.ActiveDocument is None

    def Activated(self):
        self.first_end = None
        self.last_end = None
        self.points = []

        FreeCADGui.Snapper.getPoint(callback=self.getPoint,extradlg=self.taskbox(),title="First point of wall"+":")
    def taskbox(self):

        "sets up a taskbox widget"

        w = QtGui.QWidget()
        ui = FreeCADGui.UiLoader()
        w.setWindowTitle("Wall options")
        grid = QtGui.QGridLayout(w)

        matCombo = QtGui.QComboBox()
        matCombo.addItem("Wall Presets...")
        matCombo.setToolTip("This list shows all the MultiMaterials objects of this document. Create some to define wall types.")
        self.multimats = []
        self.MultiMat = None
        for o in FreeCAD.ActiveDocument.Objects:
            if Draft.getType(o) == "MultiMaterial":
                self.multimats.append(o)
                matCombo.addItem(o.Label)
        if hasattr(FreeCAD,"LastArchMultiMaterial"):
            for i,o in enumerate(self.multimats):
                if o.Name == FreeCAD.LastArchMultiMaterial:
                    matCombo.setCurrentIndex(i+1)
                    self.MultiMat = o
        grid.addWidget(matCombo,0,0,1,2)

        label5 = QtGui.QLabel("Length")
        self.Length = ui.createWidget("Gui::InputField")
        self.Length.setText("0.00 mm")
        grid.addWidget(label5,1,0,1,1)
        grid.addWidget(self.Length,1,1,1,1)
        return w

    def getPoint(self,point=None,obj=None):

        "this function is called by the snapper when it has a 3D point"

        if len(self.points) == 0:
            if obj:
                print("-------\n"+obj.Name+"\n-------")
                self.first_end = obj.Name

            self.points.append(point)
            FreeCADGui.Snapper.getPoint(last=self.points[0],callback=self.getPoint,movecallback=None,extradlg=self.taskbox(),title="Next point"+":",mode="line")
        elif len(self.points) == 1:
            if obj:
                self.last_end = obj.Name
            self.points.append(point) 
            wall = make_wall(self.points[0], self.points[1])
            if self.first_end is not None:
                wall.JoinFirstEndTo = self.first_end
            if self.last_end is not None:
                wall.JoinLastEndTo = self.last_end


FreeCADGui.addCommand('muro',_CommandWall())
