import numpy as np
import math

class Ply(object):

    def __init__(self, plyID = None, angle = None, material = None, 
    thickness = None):
        """Initialize the Ply instance.
        
        Parameters
        ----------

        plyID : str
            Ply id (required)

        angle : float
            Ply angle in degrees (required).

        material : material.Orthotropic
            Ply material (required).

        thickness : float
            Ply thickness (required).
        
        """

        self.plyID = str(plyID)
        self.angle = float(angle)
        self.material = material
        self.thickness = float(thickness)
        self._invalidate()

    @classmethod
    def fromDict(cls, **kwargs):
        """Initialize the Ply from kwargs dict.
        
        Parameters
        ----------

        kwargs : dict
            {"plyID": str, "angle" : float, "material" : material.Orthotropic,
            "thickness": float}

        Returns
        -------

        cls : Ply
        
        """
        plyID = kwargs.get("plyID")
        angle = kwargs.get("angle")
        material = kwargs.get("material")
        thickness = kwargs.get("thickness")
        return cls(plyID, angle, material, thickness)

    def _invalidate(self):
        """Invalidates all the computed attributes of the Ply object."""

        self._Q = None
        self._Qbar = None
        self._T = None
        self._R = None
        self._Q44bar = None
        self._Q45bar = None
        self._Q55bar = None
        self._alpha = None
        self._beta = None
        self._alphabar = None
        self._betabar = None

    @property
    def Q(self):
        """Reduced stiffness maxtrix of the ply in material othotropy.

        sigma = Q * epsilon

        Returns
        -------
        
        Q : numpy.array
        
        """

        if self._Q is None:
            E1 = self.material.E1
            E2 = self.material.E2
            G12 = self.material.G12
            v12 = self.material.v12
            v21 = self.material.v21
            d = 1.0-(v12*v21)
            self._Q = np.array([[E1/d, v12*E2/d, 0.0],
                                [v12*E2/d, E2/d, 0.0],
                                [0.0, 0.0, G12]])
        return self._Q

    @property
    def T(self):
        """Transformation matrix to convert tensorial stress and strain
        matrix in reference coordinate axes (x-y) into material 
        orthotropy (1-2).

        sigma(1-2) = T sigma(x-y)

        epislon(1-2) = T epsilon(x-y)

        Returns
        -------

        T : numpy.array
        
        """

        if self._T is None:
            m = math.cos(math.radians(self.angle))
            n = math.sin(math.radians(self.angle))
            self._T = np.array([[m**2, n**2, 2.0*m*n],
                                [n**2, m**2, -2.0*m*n],
                                [-m*n, m*n, (m**2)-(n**2)]])
        return self._T

    @property
    def R(self):
        """Conversion matrix to convert tensorial strain matrix into
        engineering strain matrix.

        Returns
        -------

        R : numpy.array
        
        """

        if self._R is None:
            self._R = np.array([[1.0, 0.0, 0.0],
                                [0.0, 1.0, 0.0],
                                [0.0, 0.0, 2.0]])
        return self._R

    @property
    def Qbar(self):
        """Transformed Q matrix in reference coordinate axes (x-y).

        sigma(x-y) = Qbar * epsilon(x-y)

        Returns
        -------

        Qbar : numpy.array
        
        """
        
        if self._Qbar is None:
            T = self.T
            TI = np.linalg.inv(T)
            Q = self.Q
            R = self.R
            RI = np.linalg.inv(R)
            self._Qbar = TI @ Q @ R @ T @ RI
        return self._Qbar

    @property
    def Q44(self):
        """Returns transverse stiffness in material direction."""

        return self.material.G23

    @property
    def Q55(self):
        """Returns transverse stiffness in material direction"""
        return self.material.G13
    
    @property
    def Q44bar(self):
        """Returns transformed transverse stiffness in reference
        coordinate axes (x-y).
        
        """
        
        if self._Q44bar is not None:
            return self._Q44bar

        m = math.cos(math.radians(self.angle))
        n = math.sin(math.radians(self.angle))
        self._Q44bar = self.Q44*(m**2) + self.Q55*(n**2)
        return self._Q44bar

    @property
    def Q45bar(self):
        """Returns transformed transverse stiffness in reference
        coordinate axes (x-y).
        
        """

        if self._Q45bar is not None:
            return self._Q45bar

        m = math.cos(math.radians(self.angle))
        n = math.sin(math.radians(self.angle))
        self._Q45bar = (self.Q44-self.Q55)*m*n
        return self._Q45bar

    @property
    def Q55bar(self):
        """Returns transformed transverse stiffness in reference
        coordinate axes (x-y).
        
        """
        
        if self._Q55bar is not None:
            return self._Q55bar

        m = math.cos(math.radians(self.angle))
        n = math.sin(math.radians(self.angle))
        self._Q55bar = self.Q44*(n**2) + self.Q55*(m**2)
        return self._Q55bar

    @property
    def alpha(self):
        """Returns alpha vector in material coordinate axes (1-2).
        
        """

        if self._alpha is not None:
            return self._alpha

        self._alpha = np.array([self.material.alpha1,
                                self.material.alpha2,
                                0.0])
        return self._alpha

    @property
    def beta(self):
        """Returns beta vector in material coordinate axes (1-2).
        
        """

        if self._beta is not None:
            return self._beta

        self._beta = np.array([self.material.beta1,
                               self.material.beta2,
                               0.0])
        return self._beta

    @property
    def alphabar(self):
        """Returns transformed alpha vector in reference
        coordinate axes (x-y)."""

        if self._alphabar is not None:
            return self._alphabar

        m = math.cos(math.radians(self.angle))
        n = math.sin(math.radians(self.angle))
        a1 = self.material.alpha1
        a2 = self.material.alpha2
        self._alphabar = np.array([(a1*(m**2)) + (a2*(n**2)),
                                   (a1*(n**2)) + (a2*(m**2)),
                                   2.0*(a1-a2)*m*n])
        return self._alphabar

    @property
    def betabar(self):
        """Returns transformed beta vector in reference
        coordinate axes (x-y)."""

        if self._betabar is not None:
            return self._betabar

        m = math.cos(math.radians(self.angle))
        n = math.sin(math.radians(self.angle))
        b1 = self.material.beta1
        b2 = self.material.beta2
        self._betabar = np.array([(b1*(m**2)) + (b2*(n**2)),
                                  (b1*(n**2)) + (b2*(m**2)),
                                  2.0*(b1-b2)*m*n])
        return self._betabar

