# coding: utf-8

r"""Fixing methods for shapes, faces, tolerance, continuity and
curve resampling

"""

import logging
# import ast

from OCC.Core.GCPnts import GCPnts_UniformDeflection
from OCC.Core.GeomAbs import GeomAbs_C2
from OCC.Core.GeomAbs import GeomAbs_C0, GeomAbs_C1  # dynamically built code
from OCC.Core.GeomAPI import GeomAPI_PointsToBSpline
from OCC.Core.ShapeFix import ShapeFix_ShapeTolerance, ShapeFix_Shape, \
    ShapeFix_Face
from OCC.Core.ShapeUpgrade import ShapeUpgrade_ShapeDivideContinuity

from aocutils.tolerance import OCCUTILS_DEFAULT_TOLERANCE, \
    OCCUTILS_FIXING_TOLERANCE
from aocutils.common import AssertIsDone
from aocutils.collections import point_list_to_tcolgp_array1_of_pnt
from aocutils.geom.curve import Curve

logger = logging.getLogger(__name__)


def fix_shape(shp, tolerance=OCCUTILS_FIXING_TOLERANCE):
    r"""Fix a shape

    Parameters
    ----------
    shp : OCC.TopoDS.TopoDS_Shape
    tolerance : float

    Returns
    -------
    OCC.TopoDS.TopoDS_Shape

    """
    fix = ShapeFix_Shape(shp)

    # Returns (modifiable) the mode for applying fixes of ShapeFix_Shell,
    # by default True
    fix.SetFixFreeShellMode(True)

    sf = fix.FixShellTool().GetObject()
    sf.SetFixOrientationMode(True)
    fix.LimitTolerance(tolerance)
    fix.Perform()  # Iterates on sub- shape and performs fixes.
    return fix.Shape()


def fix_face(face, tolerance=OCCUTILS_FIXING_TOLERANCE):
    r"""Fix a face

    This operator allows to perform various fixes on face and its wires:
    - fixes provided by ShapeFix_Wire,
    - fixing orientation of wires,
    - addition of natural bounds,
    - fixing of missing seam edge,
    - detection and removal of null-area wires.

    Parameters
    ----------
    face : ShapeFix_Face
    tolerance : float

    Returns
    -------
    OCC.TopoDS.TopoDS_Face

    """
    fix = ShapeFix_Face(face)
    fix.SetMaxTolerance(tolerance)

    # Performs all the fixes, depending on modes
    # Function Status returns the status of last call to Perform()
    # ShapeExtend_OK : face was OK, nothing done
    # ShapeExtend_DONE1: some wires are fixed
    # ShapeExtend_DONE2: orientation of wires fixed
    # ShapeExtend_DONE3: missing seam added
    # ShapeExtend_DONE4: small area wire removed
    # ShapeExtend_DONE5: natural bounds added
    # ShapeExtend_FAIL1: some fails during fixing wires
    # ShapeExtend_FAIL2: cannot fix orientation of wires
    # ShapeExtend_FAIL3: cannot add missing seam
    # ShapeExtend_FAIL4: cannot remove small area wire.
    fix.Perform()
    return fix.Face()  # assumes no FixMissingSeam involved


def fix_tolerance(shape, tolerance=OCCUTILS_DEFAULT_TOLERANCE):
    r"""Sets (enforces) tolerances in a shape to the given value.

    Modifies tolerances of sub-shapes (vertices, edges, faces)

    Parameters
    ----------
    shape : OCC.TopoDS.TopoDS_Shape
    tolerance : float

    """
    # void 	SetTolerance (const TopoDS_Shape &shape, const Standard_Real preci,
    #                     const TopAbs_ShapeEnum styp=TopAbs_SHAPE) const
    ShapeFix_ShapeTolerance().SetTolerance(shape, tolerance)


def fix_continuity(edge, continuity=1):
    r"""Fix the continuity of an edge

    Parameters
    ----------
    edge : OCC.TopoDS.TopoDS_Edge
    continuity : int

    Returns
    -------
    OCC.TopoDS.TopoDS_Shape
        The upgrade resulting shape

    """
    # ShapeUpgrade_ShapeDivideContinuity :
    #     API Tool for converting shapes with C0 geometry into C1 ones
    shape_upgrade = ShapeUpgrade_ShapeDivideContinuity(edge)
    continuity_constant = eval('GeomAbs_C' + str(continuity))
    shape_upgrade.SetBoundaryCriterion(continuity_constant)
    shape_upgrade.Perform()
    return shape_upgrade.Result()


def resample_curve_uniform_deflection(curve,
                                      deflection=0.5,
                                      degree_min=3,
                                      degree_max=8,
                                      continuity=GeomAbs_C2,
                                      tolerance=OCCUTILS_DEFAULT_TOLERANCE):
    r"""Fits a bspline through the samples on curve

    Parameters
    ----------
    curve : OCC.TopoDS.TopoDS_Wire, OCC.TopoDS.TopoDS_Edge, curve
    deflection : float
    degree_min : int
    degree_max : int
    continuity : GeomAbs_C*
    tolerance : float

    Returns
    -------
    OCC.Geom.Geom_Curve
        The resampled curve

    """
    # crv = aocutils.convert.adapt.to_adaptor_3d(curve)
    crv = Curve(curve).to_adaptor_3d()
    defl = GCPnts_UniformDeflection(crv, deflection)
    with AssertIsDone(defl, 'failed to compute UniformDeflection'):
        logger.info('Number of points : %i' % defl.NbPoints())
    sampled_pnts = [defl.Value(i) for i in range(1, defl.NbPoints())]

    resampled_curve = GeomAPI_PointsToBSpline(
        point_list_to_tcolgp_array1_of_pnt(sampled_pnts),
        degree_min,
        degree_max,
        continuity,
        tolerance)

    return resampled_curve.Curve().GetObject()
