import numpy as np
import matplotlib.pyplot as plt
import sympy as sym
import math
import os
import subprocess
import fileinput
import re
import matlab.engine
import sys
# print(sys.executable)
# from subprocess import Popen, PIPE


# --------------------------------------------------
# 'classifyMin' classifies Minimizers by utilizing the result of
# Lemma1.6
#
#
#
#
# 'classifyMin_ana': (Special-Case : Lemma1.4)
# ..additionally assumes Poisson-ratio=0 => q12==0
#
#
#
# Output : MinimizingMatrix, Angle, Type, Curvature



def get_gstar(q1,q2,q12,q3,b1,b2):

    # a = np.array([a1,a2])
    b = np.array([b1,b2])
    H = np.array([[2*q1, q12+2*q3], [q12+2*q3,2*q2] ])
    A = np.array([[q1,q12/2], [q12/2,q2] ])
    # print('det(H)=', np.linalg.det(H))
    # check if g* is in G^*_R^2
    tmp = A.dot(b)
    ## compute inverse of H :
    inv_H = np.linalg.inv(H)
    g_star = 2*inv_H.dot(tmp)
    # print('g_star=', g_star)

    return g_star


def determine_b(q1,q2,q12,q3,g_star):
    ##
    # Input: g_star
    # Output : b  such that g_star is minimizer, i.e A*b = (1/2)*H*g_star
    # q1=1;
    # q2=2;
    # q12=1/2;
    # q3=((4*q1*q2)**0.5-q12)/2;
    # H=[2*q1,q12+2*q3;q12+2*q3,2*q2];
    H = np.array([[2*q1, q12+2*q3], [q12+2*q3,2*q2] ])
    A = np.array([[q1,1/2*q12], [1/2*q12,q2] ])
    abar = np.array([q12+2*q3, 2*q2])
    rhs = (1/2)*H.dot(g_star)
    print('rhs:', rhs)
    b = np.linalg.lstsq(A, rhs)[0]
    print('b',b)
    b1=b[0]
    b2=b[1]

    return b
    ## ---------------


def get_minimizer(q1,q2,q3,b1,b2):

    # In the case if q12 == 0:
    quotient = (q1*q2-q3**2)
    g_star = np.array([(q1*q2*b1-q3*q2*b2)/quotient, (q1*q2*b2-q3*q1*b1)/quotient])
    print('g_star=', g_star)
    return g_star


def energy(a1,a2,q1,q2,q12,q3,b1,b2):


    a = np.array([a1,a2])
    b = np.array([b1,b2])
    H = np.array([[2*q1, q12+2*q3], [q12+2*q3,2*q2] ])
    A = np.array([[q1,1/2*q12], [1/2*q12,q2] ])


    tmp = H.dot(a)

    # print('H',H)
    # print('A',A)
    # print('b',b)
    # print('a',a)
    # print('tmp',tmp)

    tmp = (1/2)*a.dot(tmp)
    # print('tmp',tmp)

    tmp2 = A.dot(b)
    # print('tmp2',tmp2)
    tmp2 = 2*a.dot(tmp2)

    # print('tmp2',tmp2)
    energy = tmp - tmp2
    # print('energy',energy)


    # energy_axial1.append(energy_1)

    return energy








def determinant(q1,q2,q3,q12):                                  # TODO General:Matrix
    return q1*q2 - (q3**2 + 2*q3*q12 + q12**2)




def harmonicMean(mu_1, beta, theta):
    return mu_1*(beta/(theta+((1-theta)*beta)))


def arithmeticMean(mu_1, beta, theta):
    return mu_1*((1-theta)+theta*beta)


def prestrain_b1(rho_1, beta, alpha, theta):
    return (3.0*rho_1/2.0)*(1-(theta*(1+alpha)))
    # return (3.0*rho_1/2.0)*beta*(1-(theta*(1+alpha)))


def prestrain_b2(rho_1, beta, alpha, theta):
    return (3.0*rho_1/(2.0*((1.0-theta) + theta*beta)))*(1-theta*(1+beta*alpha))
    # return (3.0*rho_1/(4.0*((1.0-theta) + theta*beta)))*(1-theta*(1+beta*alpha))


# Define function to be minimized
def f(a1, a2, q1, q2, q3, q12, b1, b2):
    A = np.array([[q1, q3 + q12/2.0], [q3 + q12/2.0, q2]])
    B = np.array([-2.0*q1*b1-q12*b2, -2.0*q2*b2-q12*b1])
    a = np.array([a1, a2])
    tmp = np.dot(A, a)
    tmp2 = np.dot(a, tmp)
    tmpB = np.dot(B, a)
    return tmp2 + tmpB + q1*(b1**2) + q2*(b2**2) + q12*b1*b2

# ---- Alternative Version using alpha,beta,theta ,mu_1,rho_1
def classifyMin_ana(alpha,beta,theta,q3,mu_1,rho_1,print_Cases=False, print_Output=False):

    # Assumption of Classification-Lemma1.6:
    #  1. [b3 == 0]
    #  2. Q is orthotropic i.e. q13 = q31 = q23 = q32 == 0
    # 3. This additionally assumes that Poisson-Ratio = 0 => q12 == 0
    q12 = 0.0

    q1 = (1.0/6.0)*harmonicMean(mu_1, beta, theta)
    q2 = (1.0/6.0)*arithmeticMean(mu_1, beta, theta)
    # print('q1: ', q1)
    # print('q2: ', q2)
    b1 = prestrain_b1(rho_1, beta, alpha,theta)
    b2 = prestrain_b2(rho_1, beta, alpha,theta)

    # print('alpha:',alpha)
    # print('beta:',beta)
    # print('theta:',theta)

    return classifyMin(q1, q2, q3, q12,  b1, b2,  print_Cases, print_Output)



# Matrix Version that just gets matrices Q & B
def classifyMin_mat(Q,B,print_Cases=False, print_Output=False):
    q1 = Q[0][0]
    q2 = Q[1][1]
    q3 = Q[2][2]
    q12 = Q[0][1]
    b1 = B[0]
    b2 = B[1]
    b3 = B[2]
    return classifyMin(q1, q2, q3, q12,  b1, b2,  print_Cases, print_Output)


# --------------------------------------------------------------------
# Classify Type of minimizer  1 = R1 , 2 = R2 , 3 = R3                          # before : destinction between which axis.. (4Types )
# where
# R1 : unique local (global) minimizer which is not axial
# R2 : continuum of local (global) minimizers which are not axial
# R3 : one or two local (global) minimizers which are axial
# Partition given by
# R1 = E1
# R2 = P1.2
# R3 = E2 U E3 U P1.1 U P2 U H
# -------------------------------------------------------------------
def classifyMin(q1, q2, q3, q12, b1, b2,  print_Cases=False, print_Output=False):   #ClassifyMin_hom?
    # Assumption of Classification-Lemma1.6:
    #  1. [b3 == 0]
    #  2. Q is orthotropic i.e. q13 = q31 = q23 = q32 == 0

    # TODO: check if Q is orthotropic here - assert()


    if print_Output: print("Run ClassifyMin_NEW...")
    CaseCount = 0
    epsilon = sys.float_info.epsilon #Machine epsilon
    # print('epsilon:',epsilon)

    b = np.array([b1,b2])
    H = np.array([[2*q1, q12+2*q3], [q12+2*q3,2*q2] ])
    A = np.array([[q1,q12/2], [q12/2,q2] ])

    # B = np.array([-2.0*q1*b1-q12*b2, -2.0*q2*b2-q12*b1])
    # A = np.array([[q1, q3 + q12/2.0], [q3 + q12/2.0, q2]])

    determinant = np.linalg.det(H)
    # print('determinant:',determinant)
    # determinant = q1*q2 - (q3**2 + 2*q3*q12 + q12**2)
    if print_Cases: print("determinant:", determinant)

    # Define values for axial-Solutions (b1*,0) & (0,b2*)
    # b1_star = (2.0*q1*b1 + b2*q12)/(2*q1)
    # b2_star = (2.0*q2*b2 + b1*q12)/(2*q2)

    # ------------------------------------ Parabolic Case -----------------------------------
    # if abs(determinant) < epsilon:
    if abs(determinant) < epsilon:
        if print_Cases: print('P : parabolic case (determinant equal zero)')
        print('P : PARABOLIC CASE (determinant equal zero)')

    # ------------------------------------ Elliptic Case -----------------------------------
    if determinant >= epsilon:
        if print_Cases: print('E : elliptic case (determinant greater zero)')

        g_star = get_gstar(q1,q2,q12,q3,b1,b2)

        # a1_star = (b1*(q12**2) + 2*b1*q3*q12 - 4*b1*q1*q2 + 4*b2*q2*q3) / \
        #     (4*(q3**2) + 4*q3*q12 + (q12**2) - 4*q1*q2)
        # a2_star = (b2*(q12**2) + 2*b2*q3*q12 + 4*b1*q1*q3 - 4*b2*q1*q2) / \
        #     (4*(q3**2) + 4*q3*q12 + (q12**2) - 4*q1*q2)
        # prod = a1_star*a2_star

        prod = g_star[0]*g_star[1]

        if prod >= epsilon:
            if print_Cases: print('(E1) - inside Lambda ')
            # a1 = a1_star
            # a2 = a2_star
            # type = 1  # non-axial Minimizer
            # CaseCount += 1
            a1 = g_star[0]
            a2 = g_star[1]
            CaseCount += 1
        if prod < epsilon:   # same as prod = 0 ? or better use <=epsilon ?
            if abs(((b2**2)/q1 - (b1**2)/q2)) < epsilon :
                print('((b2**2)/q1 - (b1**2)/q2)', ((b2**2)/q1 - (b1**2)/q2))
                print('two minimizers')
                a1 = b1
                a2 = 0.0
                # type = 3  # 1
                CaseCount += 1
            else:
                #compare energy values
                if energy(b1, 0, q1, q2, q3, q12, b1, b2) > energy(0, b2, q1, q2, q3, q12, b1, b2):
                    a1 = 0
                    a2 = b2
                    CaseCount += 1
                else :
                    a1 = b1
                    a2 = 0
                    CaseCount += 1
    else :
        # g_star = get_gstar(q1,q2,q12,q3,b1,b2)
        # prod = g_star[0]*g_star[1]

        if abs((b2**2)/q1 - (b1**2)/q2) < epsilon :
            print('two minimizers')
            a1 = b1
            a2 = 0.0
            # type = 3  # 1
            CaseCount += 1
        else:
            #compare energy values
            if energy(b1, 0, q1, q2, q3, q12, b1, b2) > energy(0, b2, q1, q2, q3, q12, b1, b2):
                a1 = 0
                a2 = b2
                CaseCount += 1
            else :
                a1 = b1
                a2 = 0
                CaseCount += 1

        #
        #     if print_Cases: print('(E2) - on the boundary of Lambda ')
        #     a1 = a1_star
        #     a2 = a2_star
        #     type = 3  # could check which axis: if a1_star or a2_star close to zero.. ?
        #     CaseCount += 1
        #
        # if prod <= -1.0*epsilon:
        #     if print_Cases: print('(E3) - Outside Lambda ')
        #     if f(b1_star, 0, q1, q2, q3, q12, b1, b2) < f(0, b2_star, q1, q2, q3, q12, b1, b2):
        #         a1 = b1_star
        #         a2 = 0.0
        #         type = 3  # 1
        #         CaseCount += 1
        #     if f(b1_star, 0, q1, q2, q3, q12, b1, b2) > f(0, b2_star, q1, q2, q3, q12, b1, b2):
        #         a1 = 0
        #         a2 = b2_star
        #         type = 3  # 2
        #         CaseCount += 1
        #
        #     # TODO Problem: angle depends on how you choose... THE angle is not defined for this case
        #     if f(b1_star, 0, q1, q2, q3, q12, b1, b2) == f(0, b2_star, q1, q2, q3, q12, b1, b2):
        #         # Two Minimizers pick one
        #         a1 = b1_star
        #         a2 = 0.0
        #         type = 3  # 4
        #         CaseCount += 1

    # ------------------------------------ Hyperbolic Case -----------------------------------
    # if determinant <= -1.0*epsilon:
    #     if print_Cases: print('H : hyperbolic case (determinant smaller zero)')
    #     # One or two minimizers wich are axial
    #     type = 3 # (always type 3)
    #     if f(b1_star, 0, q1, q2, q3, q12, b1, b2) < f(0, b2_star, q1, q2, q3, q12, b1, b2):
    #         a1 = b1_star
    #         a2 = 0.0
    #         # type = 3  # 1
    #         CaseCount += 1
    #     if f(b1_star, 0, q1, q2, q3, q12, b1, b2) > f(0, b2_star, q1, q2, q3, q12, b1, b2):
    #         a1 = 0
    #         a2 = b2_star
    #         # type = 3  # 2
    #         CaseCount += 1
    #     # TODO can add this case to first or second ..
    #     if f(b1_star, 0, q1, q2, q3, q12, b1, b2) == f(0, b2_star, q1, q2, q3, q12, b1, b2):
    #         # Two Minimizers pick one
    #         a1 = b1_star
    #         a2 = 0.0
    #         # type = 3  # 4
    #         CaseCount += 1
    # ---------------------------------------------------------------------------------------

    if (CaseCount > 1):
        print('Error: More than one Case happened!')

    # compute a3
    # a3 = math.sqrt(2.0*a1*a2)   # never needed?

    # print('a1:', a1)
    # print('a2:', a2)

    # compute the angle <(e,e_1) where Minimizer = kappa* (e (x) e)
    # e = [math.sqrt((a1/(a1+a2))), math.sqrt((a2/(a1+a2)))]
    e = [((a1/(a1+a2)))**0.5, ((a2/(a1+a2)))**0.5]
    angle = math.atan2(e[1], e[0])

    type = 1 # ToDO..

    # compute kappa
    kappa = (a1 + a2)

    # Minimizer G
    # Minimizer = np.array([[a1, math.sqrt(a1*a2)], [math.sqrt(a1*a2), a2]],dtype=object)
    Minimizer = np.array([[a1, (a1*a2)**0.5], [(a1*a2)**0.5, a2]],dtype=object)
    # Minimizer = np.array([[a1, math.sqrt(a1*a2)], [math.sqrt(a1*a2), a2]])

    # MinimizerVec = np.array([a1, a2],dtype=object)
    MinimizerVec = np.array([a1, a2])

    if print_Output:
        print('--- Output ClassifyMin ---')
        print("Minimizing Matrix G:")
        print(Minimizer)
        print("angle = ", angle)
        print("type: ", type)
        print("kappa = ", kappa)

    return Minimizer, angle, type, kappa
    # return MinimizerVec, angle, type, kappa    #return Minimizer Vector instead
# ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


# ---------------------------------------------- Main ---------------------

# --- Input Parameters ----
# mu_1 = 1.0
# rho_1 = 1.0
# alpha = 9.0
# beta = 2.0
# theta = 1.0/8.0

# # define q1, q2 , mu_gamma, q12
# # 1. read from Cell-Output
# # 2. define values from analytic formulas (expect for mu_gamma)
# q1 = (1.0/6.0)*harmonicMean(mu_1, beta, theta)
# q2 = (1.0/6.0)*arithmeticMean(mu_1, beta, theta)
# # TEST
# q12 = 0.0  # (analytical example)
# # q12 = 12.0  # (analytical example)

# set mu_gamma  to value or read from Cell-Output
# mu_gamma = q1   # TODO read from Cell-Output
# b1 = prestrain_b1(rho_1, beta, alpha, theta)
# b2 = prestrain_b2(rho_1, beta, alpha, theta)


# print('---- Input parameters: -----')
# print('mu_1: ', mu_1)
# print('rho_1: ', rho_1)
# print('alpha: ', alpha)
# print('beta: ', beta)
# print('theta: ', theta)
# print("q1: ", q1)
# print("q2: ", q2)
# print("mu_gamma: ", mu_gamma)
# print("q12: ", q12)
# print("b1: ", b1)
# print("b2: ", b2)
# print('----------------------------')
# # print("machine epsilon", sys.float_info.epsilon)
#
#
# # ------- Options --------
# print_Cases = True
# print_Output = True

# G, angle, type, kappa = classifyMin(q1, q2, mu_gamma, q12,  b1, b2, print_Cases, print_Output)
#
# G, angle, type, kappa = classifyMin_ana(alpha, beta, theta, mu_gamma, q12, print_Cases, print_Output)
#
# Out = classifyMin_ana(alpha, beta, theta, mu_gamma, q12, print_Cases, print_Output)

# print('TEST:')
# Out = classifyMin_ana(alpha, beta, theta)

# print('Out[0]', Out[0])
# print('Out[1]', Out[1])
# print('Out[2]', Out[2])
# print('Out[3]', Out[3])


# #supress certain Outout..
# _,_,T,_ = classifyMin_ana(alpha, beta, theta, mu_gamma, q12, print_Cases, print_Output)
# print('Output only type..:', T)

# Test = f(1,2 ,q1,q2,mu_gamma,q12,b1,b2)
# print("Test", Test)

# -----------------------------------------------------------------------------------------------------------------------------------------------------------------