import copy
import numpy as np
from math import pi, sin, cos
from utils.utils import get_pairs, kronecker, delta1

class Lambend(object):

    def __init__(self, panel = None, loads = None, number_of_terms = None,
    number_of_points_x = None, number_of_points_y = None):
        """Initialize the Solver003 instance.
        
        Parameters
        ----------

        panel : panel.Panel (required)

        loads : loads.Loads (required)

        number_of_terms : int (required)

        """

        self.panel = panel
        self.loads = loads
        self.number_of_terms = int(number_of_terms)
        self.number_of_points_x = int(number_of_points_x)
        self.number_of_points_y = int(number_of_points_y)
        self._invalidate()

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

            {"panel": Panel, "loads": Loads, "number_of_terms": int}

        Returns
        -------

        cls : Solver003
        
        """
        panel = kwargs.get("panel")
        loads = kwargs.get("loads")
        number_of_terms = kwargs.get("number_of_terms")
        return cls(id, panel, loads, number_of_terms)

    def _invalidate(self):
        """Invalidate all calculation to force re calculation."""

        self._NT = None
        self._NC = None

        self._pairs = None
        self._K33 = None

        self._Pmn = None

        self._u = None
        self._v = None
        self._w = None
        self._phix = None
        self._phiy = None
        self._loc = None

        self._uloc = None
        self._vloc = None
        self._wloc = None
        self._phixloc = None
        self._phiyloc = None

        self._ex0Bloc = None
        self._ey0Bloc = None
        self._gxy0Bloc = None
        self._kx0Bloc = None
        self._ky0Bloc = None
        self._kxy0Bloc = None
        self._gxz0Bloc = None
        self._gyz0Bloc = None

        self._ek0Bloc = None
        self._gT0Bloc = None
        self._NMBloc = None
        self._QBloc = None

        self._coeff = None
        self._KFull = None
        self._PFull = None

    @property
    def NT(self):
        """Returns thermal inplane loads on the laminate."""

        if self._NT is not None:
            return self._NT
        
        self._NT = np.zeros(3)
        deltaT = self.loads.deltaT
        z = self.panel.laminate.z
        for i, ply in enumerate(self.panel.laminate.stacking_sequence, start=1):
            self._NT += (ply.Qbar @ ply.alphabar) * deltaT * (z[i]-z[i-1])
        return self._NT

    @property
    def NC(self):
        """Returns hygral inplane loads on the laminate."""

        if self._NC is not None:
            return self._NC
        
        self._NC = np.zeros(3)
        deltaC = self.loads.deltaC
        z = self.panel.laminate.z
        for i, ply in enumerate(self.panel.laminate.stacking_sequence, start=1):
            self._NC += (ply.Qbar @ ply.betabar) * deltaC * (z[i]-z[i-1])
        return self._NC

    @property
    def pairs(self):
        """List of pairs (i, j) for all i, j = 1, 2, ..., M, where
        M is number of terms in analysis.
        
        Returns
        -------
        
        pairs : [(1,1), (1,2), ...]
        
        """

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

        self._pairs = get_pairs(self.number_of_terms)
        return self._pairs
    
    @property
    def K33(self):
        """Part of K33 that depends on load
        
        Returns
        -------
        
        K33 : numpy.array

            Loads dependent part of K33 for all pairs

        """

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

        self._K33 = [ [ self._calculate_K33(m, n, p, q)
        for p, q in self.pairs ] for m, n in self.pairs ]
        self._K33 = np.array(self._K33)
        return self._K33

    def _calculate_K33(self, m, n, p, q):
        L = self.panel.length
        W = self.panel.width
        a = m*pi/L
        b = n*pi/W
        Nxx = self.loads.Nxx+self.NT[0]+self.NC[0]
        Nyy = self.loads.Nyy+self.NT[1]+self.NC[1]
        Nxy = self.loads.Nxy+self.NT[2]+self.NC[2]
        val = 0.0
        val += 0.25*(Nxx*(a**2))*L*W*kronecker(m,p)*kronecker(n,q)
        val += 0.25*(Nyy*(b**2))*L*W*kronecker(m,p)*kronecker(n,q)
        val -= 8*Nxy*delta1(m,n,p,q)
        return val

    @property
    def Pmn(self):
        """Returns the loads vector
        
        Returns
        -------
        
        Pmn : numpy.array
        
        """

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

        self._Pmn = [self._calculate_Pmn(m ,n) for m, n in self.pairs]
        self._Pmn = np.array(self._Pmn)
        return self._Pmn

    @property
    def PFull(self):
        """Returns the full loads vector
        
        Returns
        -------
        
        PFull : numpy.array
        
        """

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

        dim = len(self.pairs)
        self._PFull = np.zeros(5*dim)
        self._PFull[2*dim:3*dim] = self.Pmn
        return self._PFull
    
    def _calculate_Pmn(self, m, n):
        """Calculated Pmn entries for a given m, n"""

        val = 0.0
        L = self.panel.length
        W = self.panel.width
        if self.loads.type == "sinusoidal pressure":
            p0 = self.loads.p0
            if m == 1 and n == 1:
                val = p0*L*W/4.0
        elif self.loads.type == "uniform pressure":
            p0 = self.loads.p0
            if m % 2 != 0 and n % 2 != 0:
                val = 4.0*p0*L*W/((pi**2)*m*n)
        elif self.loads.type == "point load":
            P = self.loads.P
            xCG = self.loads.xCG
            yCG = self.loads.yCG
            val = P*sin(m*pi*xCG/L)*sin(n*pi*yCG/W)
        elif self.loads.type == "partial pressure":
            p0 = self.loads.p0
            xCG = self.loads.xCG
            yCG = self.loads.yCG
            xLength = self.loads.xLength
            yLength = self.loads.yLength
            val = (4.0*p0*L*W/((pi**2)*m*n))*sin(m*pi*xCG/L)*sin(n*pi*yCG/W)*sin(m*pi*xLength/(2.0*L))*sin(n*pi*yLength/(2.0*W))
        elif self.loads.type == "hydrostatic pressure in x direction":
            p0 = self.loads.p0
            if n % 2 != 0:
                val = 2.0*p0*L*W*((-1)**(m+1))/((pi**2)*m*n)
        elif self.loads.type == "hydrostatic pressure in y direction":
            p0 = self.loads.p0
            if m % 2 != 0:
                val = 2.0*p0*L*W*((-1)**(n+1))/((pi**2)*m*n)
        elif self.loads.type == "triangular pressure in x direction":
            p0 = self.loads.p0
            if m % 2 != 0 and n % 2 != 0:
                val = 8.0*p0*L*W*((-1)**((m-1)/2))/((pi**3)*(m**2)*n)
        elif self.loads.type == "triangular pressure in y direction":
            p0 = self.loads.p0
            if m % 2 != 0 and n % 2 != 0:
                val = 8.0*p0*L*W*((-1)**((n-1)/2))/((pi**3)*(n**2)*m)
        return val

    @property
    def u(self):
        """Unknown coeffecient u_mn
        
        Returns
        -------

        u : numpy.array
        
        """

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

        dim = len(self.pairs)
        self._u = self.coeff[0:dim]
        return self._u

    @property
    def v(self):
        """Unknown coeffecient v_mn
        
        Returns
        -------

        v : numpy.array
        
        """

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

        dim = len(self.pairs)
        self._v = self.coeff[dim:2*dim]
        return self._u

    @property
    def w(self):
        """Unknown coeffecient w_mn
        
        [w] = K^-1 * Pmn

        Returns
        -------

        w : numpy.array
        
        """

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

        #self._w = np.linalg.inv(self.panel.K+self.K33) @ self.Pmn
        dim = len(self.pairs)
        self._w = self.coeff[2*dim:3*dim]
        return self._w

    @property
    def phix(self):
        """Unknown coeffecient phix_mn
        
        Returns
        -------

        phix : numpy.array
        
        """

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

        #self._w = np.linalg.inv(self.panel.K+self.K33) @ self.Pmn
        dim = len(self.pairs)
        self._phix = self.coeff[3*dim:4*dim]
        return self._phix

    @property
    def phiy(self):
        """Unknown coeffecient phiy_mn
        
        Returns
        -------

        phiy : numpy.array
        
        """

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

        #self._w = np.linalg.inv(self.panel.K+self.K33) @ self.Pmn
        dim = len(self.pairs)
        self._phiy = self.coeff[4*dim:5*dim]
        return self._phiy

    @property
    def loc(self):
        """Returns a list of location where w is computed
        
        Returns
        -------
        
        loc : [(0,0), (0,0.25), ...]
        
        """

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

        x = np.linspace(0, self.panel.length, num=self.number_of_points_x)
        y = np.linspace(0, self.panel.width, num=self.number_of_points_y)
        xv, yv = np.meshgrid(x, y)
        self._loc = [(xv[i,j], yv[i,j]) for i in range(self.number_of_points_y) for j in range(self.number_of_points_x)]
        return self._loc

    @property
    def uloc(self):
        """Returns a list of u computed at loc
        
        Returns
        -------
        
        uloc :[float, float, ...]
        
        """

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

        self._uloc = [self._computeUVal(x, y) for x, y in self.loc]
        return self._uloc

    def _computeUVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        uval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            uval += cos(m*pi*x/L)*sin(n*pi*y/W)*self.u[index]
        return uval

    @property
    def vloc(self):
        """Returns a list of v computed at loc
        
        Returns
        -------
        
        vloc :[float, float, ...]
        
        """

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

        self._vloc = [self._computeVVal(x, y) for x, y in self.loc]
        return self._vloc

    def _computeVVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        vval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            vval += sin(m*pi*x/L)*cos(n*pi*y/W)*self.v[index]
        return vval

    @property
    def wloc(self):
        """Returns a list of w computed at loc
        
        Returns
        -------
        
        wloc :[float, float, ...]
        
        """

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

        self._wloc = [self._computeWVal(x, y) for x, y in self.loc]
        return self._wloc

    def _computeWVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        wval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            wval += sin(m*pi*x/L)*sin(n*pi*y/W)*self.w[index]
        return wval

    @property
    def phixloc(self):
        """Returns a list of phix computed at loc
        
        Returns
        -------
        
        phixloc :[float, float, ...]
        
        """

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

        self._phixloc = [self._computePhixVal(x, y) for x, y in self.loc]
        return self._phixloc

    def _computePhixVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        phixval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            phixval += cos(m*pi*x/L)*sin(n*pi*y/W)*self.phix[index]
        return phixval

    @property
    def phiyloc(self):
        """Returns a list of phiy computed at loc
        
        Returns
        -------
        
        phiyloc :[float, float, ...]
        
        """

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

        self._phiyloc = [self._computePhiyVal(x, y) for x, y in self.loc]
        return self._phiyloc

    def _computePhiyVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        phiyval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            phiyval += sin(m*pi*x/L)*cos(n*pi*y/W)*self.phiy[index]
        return phiyval

    @property
    def ex0Bloc(self):
        """Returns a list of ex0 = du/dx computed at loc due to bending
        
        Returns
        -------
        
        ex0Bloc :[float, float, ...]
        
        """

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

        self._ex0Bloc = [self._computeEx0BVal(x, y) for x, y in self.loc]
        return self._ex0Bloc

    def _computeEx0BVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        ex0Bval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            ex0Bval += -(m*pi/L)*sin(m*pi*x/L)*sin(n*pi*y/W)*self.u[index]
        return ex0Bval
    
    @property
    def ey0Bloc(self):
        """Returns a list of ey0 = dv/dy computed at loc due to bending
        
        Returns
        -------
        
        ey0Bloc :[float, float, ...]
        
        """

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

        self._ey0Bloc = [self._computeEy0BVal(x, y) for x, y in self.loc]
        return self._ey0Bloc

    def _computeEy0BVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        ey0Bval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            ey0Bval += -(n*pi/W)*sin(m*pi*x/L)*sin(n*pi*y/W)*self.v[index]
        return ey0Bval

    @property
    def gxy0Bloc(self):
        """Returns a list of gxy0 = du/dy+dv/dx computed at loc due to bending
        
        Returns
        -------
        
        gxy0Bloc :[float, float, ...]
        
        """

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

        self._gxy0Bloc = [self._computeGxy0BVal(x, y) for x, y in self.loc]
        return self._gxy0Bloc

    def _computeGxy0BVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        gxy0Bval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            gxy0Bval += (((n*pi/W)*cos(m*pi*x/L)*cos(n*pi*y/W)*self.u[index])+
                         ((m*pi/L)*cos(m*pi*x/L)*cos(n*pi*y/W)*self.v[index]))
        return gxy0Bval

    @property
    def kx0Bloc(self):
        """Returns a list of kx0 = dphix/dx computed at loc due to bending
        
        Returns
        -------
        
        kx0Bloc :[float, float, ...]
        
        """

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

        self._kx0Bloc = [self._computeKx0BVal(x, y) for x, y in self.loc]
        return self._kx0Bloc

    def _computeKx0BVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        kx0Bval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            kx0Bval += -(m*pi/L)*sin(m*pi*x/L)*sin(n*pi*y/W)*self.phix[index]
        return kx0Bval
    
    @property
    def ky0Bloc(self):
        """Returns a list of ky0 = dphiy/dy computed at loc due to bending
        
        Returns
        -------
        
        ky0Bloc :[float, float, ...]
        
        """

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

        self._ky0Bloc = [self._computeKy0BVal(x, y) for x, y in self.loc]
        return self._ky0Bloc

    def _computeKy0BVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        ky0Bval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            ky0Bval += -(n*pi/W)*sin(m*pi*x/L)*sin(n*pi*y/W)*self.phiy[index]
        return ky0Bval

    @property
    def kxy0Bloc(self):
        """Returns a list of kxy0 = dphix/dy+dphiy/dx computed at loc due to bending
        
        Returns
        -------
        
        kxy0Bloc :[float, float, ...]
        
        """

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

        self._kxy0Bloc = [self._computeKxy0BVal(x, y) for x, y in self.loc]
        return self._kxy0Bloc

    def _computeKxy0BVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        kxy0Bval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            kxy0Bval += (((n*pi/W)*cos(m*pi*x/L)*cos(n*pi*y/W)*self.phix[index])+
                         ((m*pi/L)*cos(m*pi*x/L)*cos(n*pi*y/W)*self.phiy[index]))
        return kxy0Bval

    @property
    def gxz0Bloc(self):
        """Returns a list of gxz0 = phix+dw/dx computed at loc due to bending
        
        Returns
        -------
        
        gxz0Bloc :[float, float, ...]
        
        """

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

        self._gxz0Bloc = [self._computeGxz0BVal(x, y) for x, y in self.loc]
        return self._gxz0Bloc

    def _computeGxz0BVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        gxz0Bval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            gxz0Bval += ((cos(m*pi*x/L)*sin(n*pi*y/W)*self.phix[index])+
                         ((m*pi/L)*cos(m*pi*x/L)*sin(n*pi*y/W)*self.w[index]))
        return gxz0Bval

    @property
    def gyz0Bloc(self):
        """Returns a list of gyz0 = phiy+dw/dy computed at loc due to bending
        
        Returns
        -------
        
        gyz0Bloc :[float, float, ...]
        
        """

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

        self._gyz0Bloc = [self._computeGyz0BVal(x, y) for x, y in self.loc]
        return self._gyz0Bloc

    def _computeGyz0BVal(self, x, y):

        L = self.panel.length
        W = self.panel.width
        gyz0Bval = 0.0
        for index, pair in enumerate(self.pairs):
            m, n = pair
            gyz0Bval += ((sin(m*pi*x/L)*cos(n*pi*y/W)*self.phiy[index])+
                         ((n*pi/W)*sin(m*pi*x/L)*cos(n*pi*y/W)*self.w[index]))
        return gyz0Bval
    
    @property
    def KFull(self):
        """Returns KFull for Solver003
        
        Returns
        -------
        
        KFull: numpy.array
        
        """

        if self._KFull is not None:
            return self._KFull
        
        dim = len(self.pairs)
        _KFull = copy.deepcopy(self.panel.KFull)
        _KFull[2*dim:3*dim, 2*dim:3*dim] += self.K33
        self._KFull = _KFull
        return self._KFull

    @property
    def coeff(self):
        """Returns all the coeff using KFull
        
        Returns
        -------
        
        coeff: numpy.array
               [u, v, w, phi_x, phi_y]
        
        """

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

        self._coeff = np.linalg.solve(self.KFull, self.PFull)
        return self._coeff

    @property
    def ek0Bloc(self):
        """Returns (ex0, ey0, gxy0, kx0, ky0, kxy0) vec at every loc due to bending
        
        Returns
        -------
        
        ek0Bloc: [numpy.array, ...]
        
        """

        if self._ek0Bloc is not None:
            return self._ek0Bloc
        
        self._ek0Bloc = [np.array([self.ex0Bloc[index], self.ey0Bloc[index], 
        self.gxy0Bloc[index], self.kx0Bloc[index], self.ky0Bloc[index],
        self.kxy0Bloc[index]]) for index, pair in enumerate(self.loc)]
        return self._ek0Bloc

    @property
    def gT0Bloc(self):
        """Returns (gyz0, gxz0) vec at every loc due to bending
        
        Returns
        -------
        
        gT0loc: [numpy.array, ...]
        
        """

        if self._gT0Bloc is not None:
            return self._gT0Bloc
        
        self._gT0Bloc = [np.array([self.gyz0Bloc[index], self.gxz0Bloc[index]]) for index, pair in enumerate(self.loc)]
        return self._gT0Bloc

    @property
    def NMBloc(self):
        """Returns (Nxx, Nyy, Nxy, Mxx, Myy, Mxy) vec at every loc due to bending.
        
        Returns
        -------
        
        NMBloc: [numpy.array, ...]
        
        """

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

        self._NMBloc = [self.panel.laminate.ABD @ self.ek0Bloc[index] for index, pair in enumerate(self.loc)]

        return self._NMBloc

    @property
    def QBloc(self):
        """Returns (Qyz, Qxz) vec at every loc due to bending.
        
        Returns
        -------
        
        QBloc: [numpy.array, ...]
        
        """

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

        AT = np.array([[self.panel.laminate.A44, self.panel.laminate.A45],
        [self.panel.laminate.A45, self.panel.laminate.A55]])
        self._QBloc = [AT @ self.gT0Bloc[index] for index, pair in enumerate(self.loc)]

        return self._QBloc