# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

"""
Predicates operating on vertices (0D elements) and polylines (1D
elements).  Also intended to be a collection of examples for predicate
definition in Python
"""

# module members
from _freestyle import (
    ContourUP1D,
    DensityLowerThanUP1D,
    EqualToChainingTimeStampUP1D,
    EqualToTimeStampUP1D,
    ExternalContourUP1D,
    FalseBP1D,
    FalseUP0D,
    FalseUP1D,
    Length2DBP1D,
    QuantitativeInvisibilityUP1D,
    SameShapeIdBP1D,
    ShapeUP1D,
    TrueBP1D,
    TrueUP0D,
    TrueUP1D,
    ViewMapGradientNormBP1D,
    WithinImageBoundaryUP1D,
    )

# constructs for predicate definition in Python
from freestyle.types import (
    BinaryPredicate1D,
    IntegrationType,
    Nature,
    TVertex,
    UnaryPredicate0D,
    UnaryPredicate1D,
    )
from freestyle.functions import (
    Curvature2DAngleF0D,
    CurveNatureF1D,
    DensityF1D,
    GetCompleteViewMapDensityF1D,
    GetCurvilinearAbscissaF0D,
    GetDirectionalViewMapDensityF1D,
    GetOccludersF1D,
    GetProjectedZF1D,
    GetShapeF1D,
    GetSteerableViewMapDensityF1D,
    GetZF1D,
    QuantitativeInvisibilityF0D,
    ZDiscontinuityF1D,
    pyCurvilinearLengthF0D,
    pyDensityAnisotropyF1D,
    pyViewMapGradientNormF1D,
    )
import random


## Unary predicates for 0D elements (vertices)
##############################################

class pyHigherCurvature2DAngleUP0D(UnaryPredicate0D):
    def __init__(self,a):
        UnaryPredicate0D.__init__(self)
        self._a = a

    def __call__(self, inter):
        func = Curvature2DAngleF0D()
        a = func(inter)
        return (a > self._a)


class pyUEqualsUP0D(UnaryPredicate0D):
    def __init__(self,u, w):
        UnaryPredicate0D.__init__(self)
        self._u = u
        self._w = w

    def __call__(self, inter):
        func = pyCurvilinearLengthF0D()
        u = func(inter)
        return (u > (self._u-self._w)) and (u < (self._u+self._w))


class pyVertexNatureUP0D(UnaryPredicate0D):
    def __init__(self,nature):
        UnaryPredicate0D.__init__(self)
        self._nature = nature

    def __call__(self, inter):
        v = inter.object
        return (v.nature & self._nature) != 0


## check whether an Interface0DIterator
## is a TVertex and is the one that is
## hidden (inferred from the context)
class pyBackTVertexUP0D(UnaryPredicate0D):
    def __init__(self):
        UnaryPredicate0D.__init__(self)
        self._getQI = QuantitativeInvisibilityF0D()

    def __call__(self, iter):
        if (iter.object.nature & Nature.T_VERTEX) == 0:
            return False
        if iter.is_end:
            return False
        if self._getQI(iter) != 0:
            return True
        return False


class pyParameterUP0DGoodOne(UnaryPredicate0D):
    def __init__(self,pmin,pmax):
        UnaryPredicate0D.__init__(self)
        self._m = pmin
        self._M = pmax

    def __call__(self, inter):
        u = inter.u
        return ((u>=self._m) and (u<=self._M))


class pyParameterUP0D(UnaryPredicate0D):
    def __init__(self,pmin,pmax):
        UnaryPredicate0D.__init__(self)
        self._m = pmin
        self._M = pmax

    def __call__(self, inter):
        func = Curvature2DAngleF0D()
        c = func(inter)
        b1 = (c>0.1)
        u = inter.u
        b = ((u>=self._m) and (u<=self._M))
        return b and b1

## Unary predicates for 1D elements (curves)
############################################

class AndUP1D(UnaryPredicate1D):
    def __init__(self, pred1, pred2):
        UnaryPredicate1D.__init__(self)
        self.__pred1 = pred1
        self.__pred2 = pred2

    def __call__(self, inter):
        return self.__pred1(inter) and self.__pred2(inter)


class OrUP1D(UnaryPredicate1D):
    def __init__(self, pred1, pred2):
        UnaryPredicate1D.__init__(self)
        self.__pred1 = pred1
        self.__pred2 = pred2

    def __call__(self, inter):
        return self.__pred1(inter) or self.__pred2(inter)


class NotUP1D(UnaryPredicate1D):
    def __init__(self, pred):
        UnaryPredicate1D.__init__(self)
        self.__pred = pred

    def __call__(self, inter):
        return not self.__pred(inter)


class pyNFirstUP1D(UnaryPredicate1D):
    def __init__(self, n):
        UnaryPredicate1D.__init__(self)
        self.__n = n
        self.__count = 0

    def __call__(self, inter):
        self.__count = self.__count + 1
        if self.__count <= self.__n:
            return True
        return False


class pyHigherLengthUP1D(UnaryPredicate1D):
    def __init__(self,l):
        UnaryPredicate1D.__init__(self)
        self._l = l

    def __call__(self, inter):
        return (inter.length_2d > self._l)


class pyNatureUP1D(UnaryPredicate1D):
    def __init__(self,nature):
        UnaryPredicate1D.__init__(self)
        self._nature = nature
        self._getNature = CurveNatureF1D()

    def __call__(self, inter):
        if(self._getNature(inter) & self._nature):
            return True
        return False


class pyHigherNumberOfTurnsUP1D(UnaryPredicate1D):
    def __init__(self,n,a):
        UnaryPredicate1D.__init__(self)
        self._n = n
        self._a = a

    def __call__(self, inter):
        count = 0
        func = Curvature2DAngleF0D()
        it = inter.vertices_begin()
        while not it.is_end:
            if func(it) > self._a:
                count = count+1
            if count > self._n:
                return True
            it.increment()
        return False


class pyDensityUP1D(UnaryPredicate1D):
    def __init__(self, wsize, threshold, integration=IntegrationType.MEAN, sampling=2.0):
        UnaryPredicate1D.__init__(self)
        self._wsize = wsize
        self._threshold = threshold
        self._integration = integration
        self._func = DensityF1D(self._wsize, self._integration, sampling)

    def __call__(self, inter):
        return (self._func(inter) < self._threshold)


class pyLowSteerableViewMapDensityUP1D(UnaryPredicate1D):
    def __init__(self, threshold, level, integration=IntegrationType.MEAN):
        UnaryPredicate1D.__init__(self)
        self._threshold = threshold
        self._level = level
        self._integration = integration

    def __call__(self, inter):
        func = GetSteerableViewMapDensityF1D(self._level, self._integration)
        return (func(inter) < self._threshold)


class pyLowDirectionalViewMapDensityUP1D(UnaryPredicate1D):
    def __init__(self, threshold, orientation, level, integration=IntegrationType.MEAN):
        UnaryPredicate1D.__init__(self)
        self._threshold = threshold
        self._orientation = orientation
        self._level = level
        self._integration = integration

    def __call__(self, inter):
        func = GetDirectionalViewMapDensityF1D(self._orientation, self._level, self._integration)
        return (func(inter) < self._threshold)


class pyHighSteerableViewMapDensityUP1D(UnaryPredicate1D):
    def __init__(self, threshold, level, integration=IntegrationType.MEAN):
        UnaryPredicate1D.__init__(self)
        self._threshold = threshold
        self._level = level
        self._integration = integration
        self._func = GetSteerableViewMapDensityF1D(self._level, self._integration)

    def __call__(self, inter):
        return (self._func(inter) > self._threshold)


class pyHighDirectionalViewMapDensityUP1D(UnaryPredicate1D):
    def __init__(self, threshold, orientation, level, integration=IntegrationType.MEAN, sampling=2.0):
        UnaryPredicate1D.__init__(self)
        self._threshold = threshold
        self._orientation = orientation
        self._level = level
        self._integration = integration
        self._sampling = sampling

    def __call__(self, inter):
        func = GetDirectionalViewMapDensityF1D(self._orientation, self._level, self._integration, self._sampling)
        return (func(inter) > self._threshold)


class pyHighViewMapDensityUP1D(UnaryPredicate1D):
    def __init__(self, threshold, level, integration=IntegrationType.MEAN, sampling=2.0):
        UnaryPredicate1D.__init__(self)
        self._threshold = threshold
        self._level = level
        self._integration = integration
        self._sampling = sampling
        self._func = GetCompleteViewMapDensityF1D(self._level, self._integration, self._sampling) # 2.0 is the smpling

    def __call__(self, inter):
        return (self._func(inter) > self._threshold)


class pyDensityFunctorUP1D(UnaryPredicate1D):
    def __init__(self, wsize, threshold, functor, funcmin=0.0, funcmax=1.0, integration=IntegrationType.MEAN):
        UnaryPredicate1D.__init__(self)
        self._wsize = wsize
        self._threshold = float(threshold)
        self._functor = functor
        self._funcmin = float(funcmin)
        self._funcmax = float(funcmax)
        self._integration = integration

    def __call__(self, inter):
        func = DensityF1D(self._wsize, self._integration)
        res = self._functor(inter)
        k = (res-self._funcmin)/(self._funcmax-self._funcmin)
        return (func(inter) < (self._threshold * k))


class pyZSmallerUP1D(UnaryPredicate1D):
    def __init__(self,z, integration=IntegrationType.MEAN):
        UnaryPredicate1D.__init__(self)
        self._z = z
        self._integration = integration

    def __call__(self, inter):
        func = GetProjectedZF1D(self._integration)
        return (func(inter) < self._z)


class pyIsOccludedByUP1D(UnaryPredicate1D):
    def __init__(self,id):
        UnaryPredicate1D.__init__(self)
        self._id = id

    def __call__(self, inter):
        func = GetShapeF1D()
        shapes = func(inter)
        for s in shapes:
            if(s.id == self._id):
                return False
        it = inter.vertices_begin()
        itlast = inter.vertices_end()
        itlast.decrement()
        v = it.object
        vlast = itlast.object
        tvertex = v.viewvertex
        if type(tvertex) is TVertex:
            #print("TVertex: [ ", tvertex.id.first, ",",  tvertex.id.second," ]")
            eit = tvertex.edges_begin()
            while not eit.is_end:
                ve, incoming = eit.object
                if ve.id == self._id:
                    return True
                #print("-------", ve.id.first, "-", ve.id.second)
                eit.increment()
        tvertex = vlast.viewvertex
        if type(tvertex) is TVertex:
            #print("TVertex: [ ", tvertex.id.first, ",",  tvertex.id.second," ]")
            eit = tvertex.edges_begin()
            while not eit.is_end:
                ve, incoming = eit.object
                if ve.id == self._id:
                    return True
                #print("-------", ve.id.first, "-", ve.id.second)
                eit.increment()
        return False


class pyIsInOccludersListUP1D(UnaryPredicate1D):
    def __init__(self,id):
        UnaryPredicate1D.__init__(self)
        self._id = id

    def __call__(self, inter):
        func = GetOccludersF1D()
        occluders = func(inter)
        for a in occluders:
            if a.id == self._id:
                return True
        return False


class pyIsOccludedByItselfUP1D(UnaryPredicate1D):
    def __init__(self):
        UnaryPredicate1D.__init__(self)
        self.__func1 = GetOccludersF1D()
        self.__func2 = GetShapeF1D()

    def __call__(self, inter):
        lst1 = self.__func1(inter)
        lst2 = self.__func2(inter)
        for vs1 in lst1:
            for vs2 in lst2:
                if vs1.id == vs2.id:
                    return True
        return False


class pyIsOccludedByIdListUP1D(UnaryPredicate1D):
    def __init__(self, idlist):
        UnaryPredicate1D.__init__(self)
        self._idlist = idlist
        self.__func1 = GetOccludersF1D()

    def __call__(self, inter):
        lst1 = self.__func1(inter)
        for vs1 in lst1:
            for _id in self._idlist:
                if vs1.id == _id:
                    return True
        return False


class pyShapeIdListUP1D(UnaryPredicate1D):
    def __init__(self,idlist):
        UnaryPredicate1D.__init__(self)
        self._idlist = idlist
        self._funcs = []
        for _id in idlist:
            self._funcs.append(ShapeUP1D(_id.first, _id.second))

    def __call__(self, inter):
        for func in self._funcs:
            if func(inter) == 1:
                return True
        return False


## deprecated
class pyShapeIdUP1D(UnaryPredicate1D):
    def __init__(self, _id):
        UnaryPredicate1D.__init__(self)
        self._id = _id

    def __call__(self, inter):
        func = GetShapeF1D()
        shapes = func(inter)
        for a in shapes:
            if a.id == self._id:
                return True
        return False


class pyHighDensityAnisotropyUP1D(UnaryPredicate1D):
    def __init__(self,threshold, level, sampling=2.0):
        UnaryPredicate1D.__init__(self)
        self._l = threshold
        self.func = pyDensityAnisotropyF1D(level, IntegrationType.MEAN, sampling)

    def __call__(self, inter):
        return (self.func(inter) > self._l)


class pyHighViewMapGradientNormUP1D(UnaryPredicate1D):
    def __init__(self,threshold, l, sampling=2.0):
        UnaryPredicate1D.__init__(self)
        self._threshold = threshold
        self._GetGradient = pyViewMapGradientNormF1D(l, IntegrationType.MEAN)

    def __call__(self, inter):
        gn = self._GetGradient(inter)
        #print(gn)
        return (gn > self._threshold)


class pyDensityVariableSigmaUP1D(UnaryPredicate1D):
    def __init__(self, functor, sigmaMin, sigmaMax, lmin, lmax, tmin, tmax, integration=IntegrationType.MEAN, sampling=2.0):
        UnaryPredicate1D.__init__(self)
        self._functor = functor
        self._sigmaMin = float(sigmaMin)
        self._sigmaMax = float(sigmaMax)
        self._lmin = float(lmin)
        self._lmax = float(lmax)
        self._tmin = tmin
        self._tmax = tmax
        self._integration = integration
        self._sampling = sampling

    def __call__(self, inter):
        sigma = (self._sigmaMax-self._sigmaMin)/(self._lmax-self._lmin)*(self._functor(inter)-self._lmin) + self._sigmaMin
        t = (self._tmax-self._tmin)/(self._lmax-self._lmin)*(self._functor(inter)-self._lmin) + self._tmin
        sigma = max(sigma, self._sigmaMin)
        self._func = DensityF1D(sigma, self._integration, self._sampling)
        return (self._func(inter) < t)


class pyClosedCurveUP1D(UnaryPredicate1D):
    def __call__(self, inter):
        it = inter.vertices_begin()
        itlast = inter.vertices_end()
        itlast.decrement()
        vlast = itlast.object
        v = it.object
        #print(v.id.first, v.id.second)
        #print(vlast.id.first, vlast.id.second)
        if v.id == vlast.id:
            return True
        return False

## Binary predicates for 1D elements (curves)
#############################################

class AndBP1D(BinaryPredicate1D):
    def __init__(self, pred1, pred2):
        BinaryPredicate1D.__init__(self)
        self.__pred1 = pred1
        self.__pred2 = pred2

    def __call__(self, inter1, inter2):
        return self.__pred1(inter1, inter2) and self.__pred2(inter1, inter2)


class OrBP1D(BinaryPredicate1D):
    def __init__(self, pred1, pred2):
        BinaryPredicate1D.__init__(self)
        self.__pred1 = pred1
        self.__pred2 = pred2

    def __call__(self, inter1, inter2):
        return self.__pred1(inter1, inter2) or self.__pred2(inter1, inter2)


class NotBP1D(BinaryPredicate1D):
    def __init__(self, pred):
        BinaryPredicate1D.__init__(self)
        self.__pred = pred

    def __call__(self, inter1, inter2):
        return not self.__pred(inter1, inter2)


class pyZBP1D(BinaryPredicate1D):
    def __init__(self, iType=IntegrationType.MEAN):
        BinaryPredicate1D.__init__(self)
        self._GetZ = GetZF1D(iType)

    def __call__(self, i1, i2):
        return (self._GetZ(i1) > self._GetZ(i2))


class pyZDiscontinuityBP1D(BinaryPredicate1D):
    def __init__(self, iType=IntegrationType.MEAN):
        BinaryPredicate1D.__init__(self)
        self._GetZDiscontinuity = ZDiscontinuityF1D(iType)

    def __call__(self, i1, i2):
        return (self._GetZDiscontinuity(i1) > self._GetZDiscontinuity(i2))


class pyLengthBP1D(BinaryPredicate1D):
    def __call__(self, i1, i2):
        return (i1.length_2d > i2.length_2d)


class pySilhouetteFirstBP1D(BinaryPredicate1D):
    def __call__(self, inter1, inter2):
        bpred = SameShapeIdBP1D()
        if (bpred(inter1, inter2) != 1):
            return False
        if (inter1.nature & Nature.SILHOUETTE):
            return (inter2.nature & Nature.SILHOUETTE) != 0
        return (inter1.nature == inter2.nature)


class pyNatureBP1D(BinaryPredicate1D):
    def __call__(self, inter1, inter2):
        return (inter1.nature & inter2.nature)


class pyViewMapGradientNormBP1D(BinaryPredicate1D):
    def __init__(self,l, sampling=2.0):
        BinaryPredicate1D.__init__(self)
        self._GetGradient = pyViewMapGradientNormF1D(l, IntegrationType.MEAN)

    def __call__(self, i1,i2):
        #print("compare gradient")
        return (self._GetGradient(i1) > self._GetGradient(i2))


class pyShuffleBP1D(BinaryPredicate1D):
    def __init__(self):
        BinaryPredicate1D.__init__(self)
        random.seed(1)

    def __call__(self, inter1, inter2):
        r1 = random.uniform(0,1)
        r2 = random.uniform(0,1)
        return (r1<r2)
