import math

import FreeCAD
import Part

if FreeCAD.GuiUp:
    from PySide import QtCore
else:
    # \cond
    def translate(ctxt, txt):
        return txt


    def QT_TRANSLATE_NOOP(ctxt, txt):
        return txt
    # \endcond

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s



def VectorToPoint(vector):
    from shapely.geometry import Point
    if True:
        return Point(vector.x, vector.y)
    else:
        return Point(vector.x, vector.y, vector.z)

# buscar el camino más corto:
from collections import defaultdict
class Graph():
    def __init__(self):
        """
        self.edges is a dict of all possible next nodes
        e.g. {'X': ['A', 'B', 'C', 'E'], ...}
        self.weights has all the weights between two nodes,
        with the two nodes as a tuple as the key
        e.g. {('X', 'A'): 7, ('X', 'B'): 2, ...}
        """
        self.edges = defaultdict(list)
        self.weights = {}

    def add_edge(self, from_node, to_node, weight):
        # Note: assumes edges are bi-directional
        self.edges[from_node].append(to_node)
        self.edges[to_node].append(from_node)
        self.weights[(from_node, to_node)] = weight
        self.weights[(to_node, from_node)] = weight



def dijsktra(graph, initial, end):
    # shortest paths is a dict of nodes
    # whose value is a tuple of (previous node, weight)
    shortest_paths = {initial: (None, 0)}
    current_node = initial
    visited = set()

    while current_node != end:
        visited.add(current_node)
        destinations = graph.edges[current_node]
        weight_to_current_node = shortest_paths[current_node][1]

        for next_node in destinations:
            weight = graph.weights[(current_node, next_node)] + weight_to_current_node
            if next_node not in shortest_paths:
                shortest_paths[next_node] = (current_node, weight)
            else:
                current_shortest_weight = shortest_paths[next_node][1]
                if current_shortest_weight > weight:
                    shortest_paths[next_node] = (current_node, weight)

        next_destinations = {node: shortest_paths[node] for node in shortest_paths if node not in visited}
        if not next_destinations:
            return "Route Not Possible"
        # next node is the destination with the lowest weight
        current_node = min(next_destinations, key=lambda k: next_destinations[k][1])

    # Work back through destinations in shortest path
    path = []
    while current_node is not None:
        path.append(current_node)
        next_node = shortest_paths[current_node][0]
        current_node = next_node
    # Reverse path
    path = path[::-1]
    return path


'''
Flatten a wire to 2 axis: X - Y
'''
def FlattenWire(wire):
    pts = []
    xx = 0
    for i, ver in enumerate(wire.Vertexes):
        if i == 0:
            pts.append(FreeCAD.Vector(xx, ver.Point.z, 0))
        else:
            xx += (ver.Point - wire.Vertexes[i - 1].Point).Length
            pts.append(FreeCAD.Vector(xx, ver.Point.z, 0))
    return Part.makePolygon(pts)

def flattenWire(wire):
    pts = []
    for i, ver in enumerate(wire.Vertexes):
        point = FreeCAD.Vector(ver.Point)
        point.z = 0
        pts.append(point)
    return Part.makePolygon(pts)

def simplifyWire(wire):
    if not wire.isDerivedFrom("Part::Part2DObject"):
        wire = flattenWire(wire)
    pts = [ver.Point for ver in wire.Vertexes]
    for i in range(len(pts)-2):
        '''
        '''

    '''for i in range(len(wire.Edges) - 1):
        ed1 = wire.Edges[i]
        ed2 = wire.Edges[i + 1]
        vec1 = ed1.Vertexes[1].Point.sub(ed1.Vertexes[0].Point)
        vec2 = ed2.Vertexes[1].Point.sub(ed2.Vertexes[0].Point)
        angle = vec1.getAngle(vec2)
        if angle == 0:'''



def getPointsFromVertexes(Vertexes):
    return [ver.Point for ver in Vertexes]

def makeProfileFromTerrain(path):
    import PVPlantSite
    terrain = PVPlantSite.get().Terrain
    if terrain:
        return terrain.Shape.makeParallelProjection(path.Shape.Wires[0], FreeCAD.Vector(0, 0, 1))

def angleToPercent(deg):
    ''' ángulo en porcentaje = tan(ángulo en grados) * 100% '''

    return math.tan(math.radians(deg)) * 100

def PercentToAngle(percent):
    ''' ángulo en grados = arctan(ángulo en porcentaje / 100%) '''

    return math.degrees(math.atan(percent / 100))

def getEWAngle(frame1, frame2):
    vec = frame2.Placement.Base.sub(frame1.Placement.Base)
    rad = vec.getAngle(FreeCAD.Vector(1, 0, 0))
    deg = math.degrees(rad)
    if deg > 90:
        deg = 180 - deg
    return deg

def simplifyWire(wire):
    import math

    closed = False
    points = [ver.Point for ver in wire.Vertexes]
    if points[0] == points[-1]:
        points.pop(-1)
        closed = True

    if len(points) < 3:
        return wire

    '''cnt = 0
    while cnt < (len(points) - 2):
        p1 = cnt + 1
        if p1 == len(points)-2:
            p1 = 0
        elif p1 == len(points)-1:
            p1 = 1

        p2 = p1 + 1
        vec1 = points[p1].sub(points[cnt])
        vec2 = points[p2].sub(points[p1])
        angle = vec2.getAngle(vec1)
        # Edges have same direction
        if (round(angle, 2) == 0) or (round(angle - math.pi, 2) == 0):
            points.pop(cnt + 1)
        else:
            cnt += 1'''

    i = 0
    while i < len(points) - 2:
        p1 = points[(i + 1) % len(points)]
        p2 = points[(i + 2) % len(points)]
        vec1 = p1 - points[i]
        vec2 = p2 - p1
        angle = vec2.getAngle(vec1)
        # Agregar tolerancia para el ángulo
        if round(angle, 2) == 0 or round(angle - math.pi, 2) == 0:
            points.pop(i + 1)
        else:
            i += 1

    #if closed:
    points.append(FreeCAD.Vector(points[0]))

    pol = Part.makePolygon(points)
    return pol

def getProjected(shape, direction=FreeCAD.Vector(0, 0, 1)): # Based on Draft / shape2dview
    """ returns projected edges from a shape and a direction """

    import Part, TechDraw
    edges = []
    groups = TechDraw.projectEx(shape, direction)
    for g in groups[0:5]:
        if g:
            if len(g.Edges) > 0:
                edges.extend(g.Edges)
    if len(edges) > 1:
        ow = TechDraw.findOuterWire(edges)
    else:
        ow = Part.Wire(edges[0])
    return ow


'''def findObjects(classtype):
    objects = FreeCAD.ActiveDocument.Objects
    objlist = list()
    for object in objects:
        if hasattr(object, "Proxy"):
            if object.Proxy.Type == classtype:
                objlist.append(object)
    return objlist'''

def findObjects(classtype):
    return [obj for obj in FreeCAD.ActiveDocument.Objects
            if hasattr(obj, "Proxy")
            and hasattr(obj.Proxy, "Type")
            and obj.Proxy.Type == classtype]

def getClosePoints(sh1, angle):
    '''
    sh1= reference
            tag angle = height / offset
    - dato conocido: angle
    - si conocemos offset (= 1 mm) => height = offset * tag angle
    '''

    import Draft, math, Part
    import MeshPart as mp

    pts = []
    for ver in sh1.Vertexes:
        point = FreeCAD.Vector(ver.Point)
        point.z = 0
        pts.append([point, ver])
    minsh1 = min(pts, key=lambda x: x[1].Z)[1]

    projected = getProjected(max(sh1.Wires, key=lambda x: x.Length))
    sh2 = projected.makeOffset2D(1, 0, False, False, True)
    height = math.tan(math.radians(angle))
    sh2.Placement.Base.z = - height
    sh2pts = []
    for edge in sh2.Edges:
        count = 0
        if issubclass(type(edge.Curve), Part.Circle):
            v1 = edge.Vertexes[0].Point.sub(edge.Curve.Center)
            v2 = edge.Curve.Center.sub(edge.Vertexes[0].Point)
            count = int(v1.getAngle(v2) / math.radians(5))

        if count > 0:
            sh2pts.extend(edge.discretize(Number=count))
        else:
            sh2pts.append(edge.Vertexes[0].Point)

    def getline(real, pro):
        land = FreeCAD.ActiveDocument.Mesh002.Mesh
        vec = FreeCAD.Vector(real.Point)
        vec.z = 0
        vec = vec.sub(pro)
        vec.normalize()
        vec.Length = 10000
        vec = real.Point.sub(vec)
        line = Part.LineSegment(real.Point, vec)
        plane = Part.Plane(minsh1, FreeCAD.Vector(0, 0, 1))

        tmp = mp.projectPointsOnMesh([real.Point,], land, vec)
        if len(tmp) > 0:
            return tmp[0]

        #Draft.makeLine(real.Point, vec)
        #return vec

    lines = []
    if len(sh1.Vertexes) >= len(sh2pts):
        for ver in sh1.Vertexes:
            point = FreeCAD.Vector(ver.Point)
            point.z = 0
            mn = min(sh2.Vertexes, key=lambda x: x.Point.sub(point).Length)
            getpoints(ver, mn)
    else:
        from datetime import datetime
        starttime = datetime.now()
        for point in sh2pts:
            mn = min(pts, key=lambda x: x[0].sub(point).Length)
            lines.append(getline(mn[1], point))
        total_time = datetime.now() - starttime
        print(" -- Tiempo tardado en ajustar al terreno:", total_time)

    ret = [ver.Point for ver in sh1.Vertexes]
    ret.extend(lines)
    Draft.makeWire(lines, closed=True)
    import Mesh
    from MeshTools.Triangulation import Triangulate
    m = Triangulate(ret, MaxlengthLE=4000)
    Mesh.show(m)


import FreeCAD as App

def offset_wire(wire, distance):
    # Crear un tubo alrededor de la wire original
    tube = Part.makeTube(wire, distance)
    # Offsetear el tubo
    offset_wire = tube.makeOffsetShape(distance)
    return offset_wire