# SPDX-FileCopyrightText: 2014-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later

"""
This module contains functions operating on vertices (0D elements) and
polylines (1D elements).  The module is also intended to be a
collection of examples for function definition in Python.

User-defined functions inherit one of the following base classes,
depending on the object type (0D or 1D) to operate on and the return
value type:

- :class:`freestyle.types.UnaryFunction0DDouble`
- :class:`freestyle.types.UnaryFunction0DEdgeNature`
- :class:`freestyle.types.UnaryFunction0DFloat`
- :class:`freestyle.types.UnaryFunction0DId`
- :class:`freestyle.types.UnaryFunction0DMaterial`
- :class:`freestyle.types.UnaryFunction0DUnsigned`
- :class:`freestyle.types.UnaryFunction0DVec2f`
- :class:`freestyle.types.UnaryFunction0DVec3f`
- :class:`freestyle.types.UnaryFunction0DVectorViewShape`
- :class:`freestyle.types.UnaryFunction0DViewShape`
- :class:`freestyle.types.UnaryFunction1DDouble`
- :class:`freestyle.types.UnaryFunction1DEdgeNature`
- :class:`freestyle.types.UnaryFunction1DFloat`
- :class:`freestyle.types.UnaryFunction1DUnsigned`
- :class:`freestyle.types.UnaryFunction1DVec2f`
- :class:`freestyle.types.UnaryFunction1DVec3f`
- :class:`freestyle.types.UnaryFunction1DVectorViewShape`
- :class:`freestyle.types.UnaryFunction1DVoid`
"""

__all__ = (
    "ChainingTimeStampF1D",
    "Curvature2DAngleF0D",
    "Curvature2DAngleF1D",
    "CurveMaterialF0D",
    "CurveNatureF0D",
    "CurveNatureF1D",
    "DensityF0D",
    "DensityF1D",
    "GetCompleteViewMapDensityF1D",
    "GetCurvilinearAbscissaF0D",
    "GetDirectionalViewMapDensityF1D",
    "GetOccludeeF0D",
    "GetOccludeeF1D",
    "GetOccludersF0D",
    "GetOccludersF1D",
    "GetParameterF0D",
    "GetProjectedXF0D",
    "GetProjectedXF1D",
    "GetProjectedYF0D",
    "GetProjectedYF1D",
    "GetProjectedZF0D",
    "GetProjectedZF1D",
    "GetShapeF0D",
    "GetShapeF1D",
    "GetSteerableViewMapDensityF1D",
    "GetViewMapGradientNormF0D",
    "GetViewMapGradientNormF1D",
    "GetXF0D",
    "GetXF1D",
    "GetYF0D",
    "GetYF1D",
    "GetZF0D",
    "GetZF1D",
    "IncrementChainingTimeStampF1D",
    "LocalAverageDepthF0D",
    "LocalAverageDepthF1D",
    "MaterialF0D",
    "Normal2DF0D",
    "Normal2DF1D",
    "Orientation2DF1D",
    "Orientation3DF1D",
    "QuantitativeInvisibilityF0D",
    "QuantitativeInvisibilityF1D",
    "ReadCompleteViewMapPixelF0D",
    "ReadMapPixelF0D",
    "ReadSteerableViewMapPixelF0D",
    "ShapeIdF0D",
    "TimeStampF1D",
    "VertexOrientation2DF0D",
    "VertexOrientation3DF0D",
    "ZDiscontinuityF0D",
    "ZDiscontinuityF1D",
    "pyCurvilinearLengthF0D",
    "pyDensityAnisotropyF0D",
    "pyDensityAnisotropyF1D",
    "pyGetInverseProjectedZF1D",
    "pyGetSquareInverseProjectedZF1D",
    "pyInverseCurvature2DAngleF0D",
    "pyViewMapGradientNormF0D",
    "pyViewMapGradientNormF1D",
    "pyViewMapGradientVectorF0D",
)


# module members
from _freestyle import (
    ChainingTimeStampF1D,
    Curvature2DAngleF0D,
    Curvature2DAngleF1D,
    CurveNatureF0D,
    CurveNatureF1D,
    DensityF0D,
    DensityF1D,
    GetCompleteViewMapDensityF1D,
    GetCurvilinearAbscissaF0D,
    GetDirectionalViewMapDensityF1D,
    GetOccludeeF0D,
    GetOccludeeF1D,
    GetOccludersF0D,
    GetOccludersF1D,
    GetParameterF0D,
    GetProjectedXF0D,
    GetProjectedXF1D,
    GetProjectedYF0D,
    GetProjectedYF1D,
    GetProjectedZF0D,
    GetProjectedZF1D,
    GetShapeF0D,
    GetShapeF1D,
    GetSteerableViewMapDensityF1D,
    GetViewMapGradientNormF0D,
    GetViewMapGradientNormF1D,
    GetXF0D,
    GetXF1D,
    GetYF0D,
    GetYF1D,
    GetZF0D,
    GetZF1D,
    IncrementChainingTimeStampF1D,
    LocalAverageDepthF0D,
    LocalAverageDepthF1D,
    MaterialF0D,
    Normal2DF0D,
    Normal2DF1D,
    Orientation2DF1D,
    Orientation3DF1D,
    QuantitativeInvisibilityF0D,
    QuantitativeInvisibilityF1D,
    ReadCompleteViewMapPixelF0D,
    ReadMapPixelF0D,
    ReadSteerableViewMapPixelF0D,
    ShapeIdF0D,
    TimeStampF1D,
    VertexOrientation2DF0D,
    VertexOrientation3DF0D,
    ZDiscontinuityF0D,
    ZDiscontinuityF1D,
)

# constructs for function definition in Python
from freestyle.types import (
    CurvePoint,
    IntegrationType,
    UnaryFunction0DDouble,
    UnaryFunction0DMaterial,
    UnaryFunction0DVec2f,
    UnaryFunction1DDouble,
)
from freestyle.utils import ContextFunctions as CF
from freestyle.utils import integrate

from mathutils import Vector

# -- Functions for 0D elements (vertices) -- #


class CurveMaterialF0D(UnaryFunction0DMaterial):
    """
    A replacement of the built-in MaterialF0D for stroke creation.
    MaterialF0D does not work with Curves and Strokes.  Line color
    priority is used to pick one of the two materials at material
    boundaries.

    Notes: expects instances of CurvePoint to be iterated over
           can return None if no fedge can be found
    """

    def __call__(self, inter):
        fe = inter.object.fedge
        if fe is None:
            return None
        if fe.is_smooth:
            return fe.material
        else:
            right, left = fe.material_right, fe.material_left
            return right if (right.priority > left.priority) else left


class pyInverseCurvature2DAngleF0D(UnaryFunction0DDouble):
    def __call__(self, inter):
        func = Curvature2DAngleF0D()
        c = func(inter)
        return (3.1415 - c)


class pyCurvilinearLengthF0D(UnaryFunction0DDouble):
    def __call__(self, inter):
        cp = inter.object
        assert isinstance(cp, CurvePoint)
        return cp.t2d


class pyDensityAnisotropyF0D(UnaryFunction0DDouble):
    """Estimates the anisotropy of density."""

    def __init__(self, level):
        UnaryFunction0DDouble.__init__(self)
        self.IsoDensity = ReadCompleteViewMapPixelF0D(level)
        self.d0Density = ReadSteerableViewMapPixelF0D(0, level)
        self.d1Density = ReadSteerableViewMapPixelF0D(1, level)
        self.d2Density = ReadSteerableViewMapPixelF0D(2, level)
        self.d3Density = ReadSteerableViewMapPixelF0D(3, level)

    def __call__(self, inter):
        c_iso = self.IsoDensity(inter)
        c_0 = self.d0Density(inter)
        c_1 = self.d1Density(inter)
        c_2 = self.d2Density(inter)
        c_3 = self.d3Density(inter)
        cMax = max(max(c_0, c_1), max(c_2, c_3))
        cMin = min(min(c_0, c_1), min(c_2, c_3))
        return 0 if (c_iso == 0) else (cMax - cMin) / c_iso


class pyViewMapGradientVectorF0D(UnaryFunction0DVec2f):
    """
    Returns the gradient vector for a pixel.

    .. method:: __init__(self, level)

       Builds a pyViewMapGradientVectorF0D object.

       :param level: the level at which to compute the gradient
       :type level: int
    """

    def __init__(self, level):
        UnaryFunction0DVec2f.__init__(self)
        self._l = level
        self._step = pow(2, self._l)

    def __call__(self, iter):
        p = iter.object.point_2d
        gx = CF.read_complete_view_map_pixel(self._l, int(p.x + self._step), int(p.y)) - \
            CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
        gy = CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y + self._step)) - \
            CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
        return Vector((gx, gy))


class pyViewMapGradientNormF0D(UnaryFunction0DDouble):
    def __init__(self, l):
        UnaryFunction0DDouble.__init__(self)
        self._l = l
        self._step = pow(2, self._l)

    def __call__(self, iter):
        p = iter.object.point_2d
        gx = CF.read_complete_view_map_pixel(self._l, int(p.x + self._step), int(p.y)) - \
            CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
        gy = CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y + self._step)) - \
            CF.read_complete_view_map_pixel(self._l, int(p.x), int(p.y))
        return Vector((gx, gy)).length

# -- Functions for 1D elements (curves) -- #


class pyGetInverseProjectedZF1D(UnaryFunction1DDouble):
    def __call__(self, inter):
        func = GetProjectedZF1D()
        z = func(inter)
        return (1.0 - z)


class pyGetSquareInverseProjectedZF1D(UnaryFunction1DDouble):
    def __call__(self, inter):
        func = GetProjectedZF1D()
        z = func(inter)
        return (1.0 - pow(z, 2))


class pyDensityAnisotropyF1D(UnaryFunction1DDouble):
    def __init__(self, level, integrationType=IntegrationType.MEAN, sampling=2.0):
        UnaryFunction1DDouble.__init__(self, integrationType)
        self._func = pyDensityAnisotropyF0D(level)
        self._integration = integrationType
        self._sampling = sampling

    def __call__(self, inter):
        v = integrate(
            self._func, inter.points_begin(
                self._sampling), inter.points_end(
                self._sampling), self._integration)
        return v


class pyViewMapGradientNormF1D(UnaryFunction1DDouble):
    def __init__(self, l, integrationType, sampling=2.0):
        UnaryFunction1DDouble.__init__(self, integrationType)
        self._func = pyViewMapGradientNormF0D(l)
        self._integration = integrationType
        self._sampling = sampling

    def __call__(self, inter):
        v = integrate(
            self._func, inter.points_begin(
                self._sampling), inter.points_end(
                self._sampling), self._integration)
        return v
