##Copyright 2008-2015 Jelle Feringa (jelleferinga@gmail.com)
##
##This file is part of pythonOCC.
##
##pythonOCC is free software: you can redistribute it and/or modify
##it under the terms of the GNU Lesser General Public License as published by
##the Free Software Foundation, either version 3 of the License, or
##(at your option) any later version.
##
##pythonOCC is distributed in the hope that it will be useful,
##but WITHOUT ANY WARRANTY; without even the implied warranty of
##MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##GNU Lesser General Public License for more details.
##
##You should have received a copy of the GNU Lesser General Public License
##along with pythonOCC.  If not, see <http://www.gnu.org/licenses/>

from OCC.Core.BRepCheck import *
from OCC.Core.GeomAbs import *
from OCC.Core.TopoDS import topods, TopoDS_Shape
from OCC.Core.BRep import BRep_Tool_Surface
from OCC.Core.TopAbs import *
from OCC.Core.Geom import Handle_Geom_CylindricalSurface, Handle_Geom_Plane


class ShapeToTopology(object):
    '''
    looks up the topology type and returns the corresponding topological entity
    '''
    def __init__(self):
        self.topoTypes = {TopAbs_VERTEX:      topods.Vertex,
                          TopAbs_EDGE:        topods.Edge,
                          TopAbs_FACE:        topods.Face,
                          TopAbs_WIRE:        topods.Wire,
                          TopAbs_SHELL:       topods.Shell,
                          TopAbs_SOLID:       topods.Solid,
                          TopAbs_COMPOUND:    topods.Compound,
                          TopAbs_COMPSOLID:   topods.CompSolid,
                          }

    def __call__(self, shape):
        if isinstance(shape, TopoDS_Shape):
            return self.topoTypes[shape.ShapeType()](shape)
        else:
            raise AttributeError('shape has not method `ShapeType`')

    def __getitem__(self, item):
        return self(item)


class EnumLookup(object):
    """
    perform bi-directional lookup of Enums'...
    """
    def __init__(self, li_in, li_out):
        self.d = {}
        for a, b in zip(li_in, li_out):
            self.d[a] = b
            self.d[b] = a

    def __getitem__(self, item):
        return self.d[item]


_curve_typesA = (GeomAbs_Line, GeomAbs_Circle, GeomAbs_Ellipse,
                 GeomAbs_Hyperbola, GeomAbs_Parabola,
                 GeomAbs_BezierCurve, GeomAbs_BSplineCurve, GeomAbs_OtherCurve)
_curve_typesB = ('line', 'circle', 'ellipse', 'hyperbola', 'parabola',
                 'bezier', 'spline', 'other')
_surface_typesA = (GeomAbs_Plane, GeomAbs_Cylinder, GeomAbs_Cone,
                   GeomAbs_Sphere, GeomAbs_Torus, GeomAbs_BezierSurface,
                   GeomAbs_BSplineSurface, GeomAbs_SurfaceOfRevolution,
                   GeomAbs_SurfaceOfExtrusion,
                   GeomAbs_OffsetSurface, GeomAbs_OtherSurface)
_surface_typesB = ('plane', 'cylinder', 'cone', 'sphere', 'torus', 'bezier',
                   'spline', 'revolution', 'extrusion', 'offset', 'other')


_stateA = ('in', 'out', 'on', 'unknown')
_stateB = (TopAbs_IN, TopAbs_OUT, TopAbs_ON, TopAbs_UNKNOWN)


_orientA = ['TopAbs_FORWARD', 'TopAbs_REVERSED', 'TopAbs_INTERNAL',
            'TopAbs_EXTERNAL']
_orientB = [TopAbs_FORWARD, TopAbs_REVERSED, TopAbs_INTERNAL,
            TopAbs_EXTERNAL]


_topoTypesA = ['vertex', 'edge', 'wire', 'face', 'shell',
               'solid', 'compsolid', 'compound', 'shape']
_topoTypesB = [TopAbs_VERTEX, TopAbs_EDGE, TopAbs_WIRE, TopAbs_FACE,
               TopAbs_SHELL, TopAbs_SOLID,
               TopAbs_COMPSOLID, TopAbs_COMPOUND, TopAbs_SHAPE]


_geom_types_a = ['line', 'circle', 'ellipse', 'hyperbola', 'parabola',
                 'beziercurve', 'bsplinecurve', 'othercurve']
_geom_types_b = [GeomAbs_Line, GeomAbs_Circle, GeomAbs_Ellipse,
                 GeomAbs_Hyperbola, GeomAbs_Parabola, GeomAbs_BezierCurve,
                 GeomAbs_BSplineCurve, GeomAbs_OtherCurve]


# TODO: make a function that generalizes this, there is absolutely
# no need for 2 lists to define an EnumLookup

def fix_formatting(_str):
    return [i.strip() for i in _str.split(',')]

_brep_check_a = fix_formatting("NoError, InvalidPointOnCurve,\
InvalidPointOnCurveOnSurface, InvalidPointOnSurface,\
No3DCurve, Multiple3DCurve, Invalid3DCurve, NoCurveOnSurface,\
InvalidCurveOnSurface, InvalidCurveOnClosedSurface, InvalidSameRangeFlag,\
InvalidSameParameterFlag,\
InvalidDegeneratedFlag, FreeEdge, InvalidMultiConnexity, InvalidRange,\
EmptyWire, RedundantEdge, SelfIntersectingWire, NoSurface,\
InvalidWire, RedundantWire, IntersectingWires, InvalidImbricationOfWires,\
EmptyShell, RedundantFace, UnorientableShape, NotClosed,\
NotConnected, SubshapeNotInShape, BadOrientation, BadOrientationOfSubshape,\
InvalidToleranceValue, CheckFail")

_brep_check_b = [BRepCheck_NoError, BRepCheck_InvalidPointOnCurve,
                 BRepCheck_InvalidPointOnCurveOnSurface,
                 BRepCheck_InvalidPointOnSurface,
                 BRepCheck_No3DCurve, BRepCheck_Multiple3DCurve,
                 BRepCheck_Invalid3DCurve, BRepCheck_NoCurveOnSurface,
                 BRepCheck_InvalidCurveOnSurface,
                 BRepCheck_InvalidCurveOnClosedSurface,
                 BRepCheck_InvalidSameRangeFlag,
                 BRepCheck_InvalidSameParameterFlag,
                 BRepCheck_InvalidDegeneratedFlag, BRepCheck_FreeEdge,
                 BRepCheck_InvalidMultiConnexity, BRepCheck_InvalidRange,
                 BRepCheck_EmptyWire, BRepCheck_RedundantEdge,
                 BRepCheck_SelfIntersectingWire, BRepCheck_NoSurface,
                 BRepCheck_InvalidWire, BRepCheck_RedundantWire,
                 BRepCheck_IntersectingWires,
                 BRepCheck_InvalidImbricationOfWires,
                 BRepCheck_EmptyShell, BRepCheck_RedundantFace,
                 BRepCheck_UnorientableShape, BRepCheck_NotClosed,
                 BRepCheck_NotConnected, BRepCheck_SubshapeNotInShape,
                 BRepCheck_BadOrientation, BRepCheck_BadOrientationOfSubshape,
                 BRepCheck_InvalidToleranceValue, BRepCheck_CheckFail]

brepcheck_lut = EnumLookup(_brep_check_a, _brep_check_b)
curve_lut = EnumLookup(_curve_typesA, _curve_typesB)
surface_lut = EnumLookup(_surface_typesA, _surface_typesB)
state_lut = EnumLookup(_stateA, _stateB)
orient_lut = EnumLookup(_orientA, _orientB)
topo_lut = EnumLookup(_topoTypesA, _topoTypesB)
shape_lut = ShapeToTopology()
geom_lut = EnumLookup(_geom_types_a, _geom_types_b)

# todo: refactor, these classes have been moved from the "Topology" directory
# which had too many overlapping methods & classes, that are
# now part of the KBE module...
# still need to think what to do with these...
# what_is_face should surely become a lut [ geom_lut? ]
# i'm not sure whether casting to a gp_* is useful...

classes = dir()
geom_classes = []
for elem in classes:
    if elem.startswith('Geom') and not 'swig' in elem:
        geom_classes.append(elem)


def what_is_face(face):
    ''' Returns all class names for which this class can be downcasted
    '''
    if not face.ShapeType() == TopAbs_FACE:
        print('%s is not a TopAbs_FACE. Conversion impossible')
        return None
    hs = BRep_Tool_Surface(face)
    obj = hs.GetObject()
    result = []
    for elem in classes:
        if (elem.startswith('Geom') and not 'swig' in elem):
            geom_classes.append(elem)
    # Run the test for each class
    for geom_class in geom_classes:
        if obj.IsKind(geom_class) and not geom_class in result:
            result.append(geom_class)
    return result


def face_is_plane(face):
    ''' Returns True if the TopoDS_Shape is a plane, False otherwise
    '''
    hs = BRep_Tool_Surface(face)
    downcast_result = Handle_Geom_Plane().DownCast(hs)
    # the handle is null if downcast failed or is not possible,
    # that is to say the face is not a plane
    if downcast_result.IsNull():
        return False
    else:
        return True


def shape_is_cylinder(face):
    ''' Returns True is the TopoDS_Shape is a cylinder, False otherwise
    '''
    hs = BRep_Tool_Surface(face)
    downcast_result = Handle_Geom_CylindricalSurface().DownCast(hs)
    if downcast_result.IsNull():
        return False
    else:
        return True
