# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2012  CEA/DEN, EDF R&D, OPEN CASCADE
#
# This library 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 2.1 of the License.
#
# This library 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 this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
#
"""
This module is used to compute the orientation of the different parts in a
structural element and to build the corresponding markers (trihedrons).
"""

import math

from salome.kernel.logger import Logger
from salome.kernel import termcolor
logger = Logger("salome.geom.structelem.orientation", color = termcolor.RED)


class Orientation1D:
    """
    This class is used to compute the orientation of 1D elements and to build
    the corresponding markers.
    """
    
    def __init__(self):
        self.geom = None
        self._vectorYCoords = None
        self._angle = 0.0

    def __repr__(self):
        reprdict = self.__dict__.copy()
        del reprdict["geom"]
        return '%s(%s)' % (self.__class__.__name__, reprdict)

    def addParams(self, params):
        """
        Add orientation parameters. `params` is a dictionary containing one or
        several orientation parameters. The valid parameters are:

        * "VECT_Y": Triplet defining the local Y axis (the X axis is the
          main direction of the 1D element).
        * "ANGL_VRIL": Angle of rotation along the X axis to define the local
          coordinate system.
        
        The parameters can be specified several times. In this case, only the
        last "VECT_Y" or "ANGL_VRIL" is taken into account.
        """
        if self._vectorYCoords is not None or self._angle != 0.0:
            logger.warning('Orientation parameters are specified several '
                           'times for the same mesh group, only the last '
                           'parameter will be used')
        mydict = params.copy()
        if mydict.has_key("VECT_Y"):
            newVecCoords = mydict.pop("VECT_Y")
            logger.debug("Setting orientation vector Y to %s" %
                             str(newVecCoords))
            self._vectorYCoords = newVecCoords
            self._angle = 0.0
        if mydict.has_key("ANGL_VRIL"):
            newAngle = mydict.pop("ANGL_VRIL")
            logger.debug("Setting orientation angle to %f" % newAngle)
            self._angle = newAngle
            self._vectorYCoords = None
        if len(mydict) > 0:
            logger.warning("Invalid orientation parameter(s) (ignored): %s" %
                           str(mydict))

    def _getDefaultVecYZ(self, center, vecX):
        """
        Get the vectors Y and Z for the default LCS, that use the main
        direction of the 1D object as the local X axis and the global Z axis
        to determine the local Z axis.
        """
        xPoint = self.geom.MakeTranslationVector(center, vecX)
        givenVecZ = self.geom.MakeVectorDXDYDZ(0.0, 0.0, 1.0)
        angle = self.geom.GetAngleRadians(vecX, givenVecZ)
        if abs(angle) < 1e-7 or abs(angle - math.pi) < 1e-7:
            logger.warning("Beam X axis is colinear to absolute Z axis. "
                           "Absolute X axis will be used to determine "
                           "local Z axis.")
            givenVecZ = self.geom.MakeVectorDXDYDZ(1.0, 0.0, 0.0)
        zPoint = self.geom.MakeTranslationVector(center, givenVecZ)
        locPlaneZX = self.geom.MakePlaneThreePnt(center, zPoint, xPoint, 1.0)
        locY = self.geom.GetNormal(locPlaneZX)
        yPoint = self.geom.MakeTranslationVector(center, locY)
        locPlaneXY = self.geom.MakePlaneThreePnt(center, xPoint, yPoint, 1.0)
        locZ = self.geom.GetNormal(locPlaneXY)
        return (locY, locZ)

    def buildMarker(self, geom, center, vecX):
        """
        Create a marker with origin `center` and X axis `vecX`. `geom` is the
        pseudo-geompy object used to build the geometric shapes.
        """
        (locY, locZ) = self.getVecYZ(geom, center, vecX)
        marker = geom.MakeMarkerPntTwoVec(center, vecX, locY)
        return marker

    def getVecYZ(self, geom, center, vecX):
        """
        Get the vectors Y and Z for the LCS with origin `center` and X axis
        `vecX`. `geom` is the pseudo-geompy object used to build the geometric
        shapes.
        """
        self.geom = geom
        locY = None
        locZ = None
        if self._vectorYCoords is None:
            (locY, locZ) = self._getDefaultVecYZ(center, vecX)
        else:
            xPoint = self.geom.MakeTranslationVector(center, vecX)
            givenLocY = self.geom.MakeVectorDXDYDZ(self._vectorYCoords[0],
                                                   self._vectorYCoords[1],
                                                   self._vectorYCoords[2])
            angle = self.geom.GetAngleRadians(vecX, givenLocY)
            if abs(angle) < 1e-7 or abs(angle - math.pi) < 1e-7:
                logger.warning("Vector Y is colinear to the beam X axis, "
                               "using default LCS.")
                (locY, locZ) = self._getDefaultVecYZ(center, vecX)
            else:
                yPoint = self.geom.MakeTranslationVector(center, givenLocY)
                locPlaneXY = self.geom.MakePlaneThreePnt(center, xPoint,
                                                         yPoint, 1.0)
                locZ = self.geom.GetNormal(locPlaneXY)
                zPoint = self.geom.MakeTranslationVector(center, locZ)
                locPlaneZX = self.geom.MakePlaneThreePnt(center, zPoint,
                                                         xPoint, 1.0)
                locY = self.geom.GetNormal(locPlaneZX)

        if self._angle != 0.0:
            angleRad = math.radians(self._angle)
            locY = self.geom.Rotate(locY, vecX, angleRad)
            locZ = self.geom.Rotate(locZ, vecX, angleRad)

        return (locY, locZ)


class Orientation2D:
    """
    This class is used to compute the orientation of 2D elements and to build
    the corresponding markers. Angles `alpha` and `beta` are used to determine
    the local coordinate system for the 2D element. If `vect` is not
    :const:`None`, it is used instead of `alpha` and `beta`.
    """

    def __init__(self, alpha, beta, vect):
        self.geom = None
        self._alpha = alpha
        self._beta = beta
        self._vect = vect

    def __repr__(self):
        reprdict = self.__dict__.copy()
        del reprdict["geom"]
        return '%s(%s)' % (self.__class__.__name__, reprdict)

    def _buildDefaultMarker(self, center, normal, warnings = True):
        """
        Create the default marker, that use the normal vector of the 2D object
        as the local Z axis and the global X axis to determine the local X
        axis. `warnings` can be used to enable or disable the logging of
        warning messages.
        """
        marker = None
        globalVecX = self.geom.MakeVectorDXDYDZ(1.0, 0.0, 0.0)
        angle = self.geom.GetAngleRadians(normal, globalVecX)
        if abs(angle) < 1e-7 or abs(angle - math.pi) < 1e-7:
            if warnings:
                logger.warning("Face normal is colinear to absolute X axis. "
                               "Absolute Y axis will be used to determine "
                               "local X axis.")
            globalVecY = self.geom.MakeVectorDXDYDZ(0.0, 1.0, 0.0)
            marker = self._buildMarkerRefVecX(center, normal, globalVecY)
        else:
            marker = self._buildMarkerRefVecX(center, normal, globalVecX)
        return marker

    def _buildMarkerRefVecX(self, center, normal, refVecX):
        """
        Create a marker using `normal` as Z axis and `refVecX` to determine
        the X axis.
        """
        xPoint = self.geom.MakeTranslationVector(center, refVecX)
        zPoint = self.geom.MakeTranslationVector(center, normal)
        locPlaneZX = self.geom.MakePlaneThreePnt(center, zPoint, xPoint, 1.0)
        locY = self.geom.GetNormal(locPlaneZX)
        yPoint = self.geom.MakeTranslationVector(center, locY)
        locPlaneYZ = self.geom.MakePlaneThreePnt(center, yPoint, zPoint, 1.0)
        locX = self.geom.GetNormal(locPlaneYZ)
        marker = self.geom.MakeMarkerPntTwoVec(center, locX, locY)
        return marker

    def buildMarker(self, geom, center, normal, warnings = True):
        """
        Create a marker with origin `center` and `normal` as Z axis. The other
        axes are computed using the parameters alpha and beta of the
        Orientation2D instance. `geom` is the pseudo-geompy object used to
        build the geometric shapes. `warnings` can be used to enable or
        disable the logging of warning messages.
        """
        self.geom = geom
        marker = None
        refVecX = None
        if self._vect is not None:
            # Using vector parameter
            if abs(self._vect[0]) <= 1e-7 and abs(self._vect[1]) <= 1e-7 and \
                                              abs(self._vect[2]) <= 1e-7:
                if warnings:
                    logger.warning("Vector too small: %s, using default LCS" %
                                   self._vect)
            else:
                refVecX = self.geom.MakeVectorDXDYDZ(self._vect[0],
                                                     self._vect[1],
                                                     self._vect[2])
        elif self._alpha is not None and self._beta is not None:
            # Using alpha and beta angles
            alphaRad = math.radians(self._alpha)
            betaRad = math.radians(self._beta)
            if abs(alphaRad) <= 1e-7 and abs(betaRad) <= 1e-7:
                if warnings:
                    logger.warning("Angles too small: (%g, %g), using "
                                   "default LCS" % (self._alpha, self._beta))
            else:
                # rotate global CS with angles alpha and beta
                refVecX = self.geom.MakeVectorDXDYDZ(1.0, 0.0, 0.0)
                refVecY = self.geom.MakeVectorDXDYDZ(0.0, 1.0, 0.0)
                globalVecZ = self.geom.MakeVectorDXDYDZ(0.0, 0.0, 1.0)
                if abs(alphaRad) > 1e-7:
                    refVecX = self.geom.Rotate(refVecX, globalVecZ, alphaRad)
                    refVecY = self.geom.Rotate(refVecY, globalVecZ, alphaRad)
                if abs(betaRad) > 1e-7:
                    refVecX = self.geom.Rotate(refVecX, refVecY, betaRad)
    
        if refVecX is not None:
            # build local coordinate system
            angle = self.geom.GetAngleRadians(normal, refVecX)
            if abs(angle) < 1e-7 or abs(angle - math.pi) < 1e-7:
                if warnings:
                    logger.warning("Face normal is colinear to the reference "
                                   "X axis, using default LCS.")
            else:
                marker = self._buildMarkerRefVecX(center, normal, refVecX)

        if marker is None:
            marker = self._buildDefaultMarker(center, normal, warnings)

        return marker