using RapidFEM
function get_âˆ‚u_âˆ‚X!(âˆ‚u_âˆ‚X::Array{Float64, 2}, solAtNodes::Array{Float64, 1}, âˆ‚Ï•_âˆ‚X::Array{Float64,2}, problemDim::Int64)
    fill!(âˆ‚u_âˆ‚X, 0.0)
    for a âˆˆ 1:size(âˆ‚Ï•_âˆ‚X, 1)
        for J âˆˆ 1:size(âˆ‚Ï•_âˆ‚X, 2)
            for i âˆˆ 1:problemDim
                âˆ‚u_âˆ‚X[i,J] += âˆ‚Ï•_âˆ‚X[a,J]*solAtNodes[problemDim*(a-1)+i]
            end
        end
    end
end

function local_Î´E_S_Vector!(f::Vector, hyperElasticData::T, problemDim::Int64,
    element::AbstractElement, elementNo::Int64, shapeFunction::Array{ShapeFunction},
    coordArray::Array{Float64,2}; kwargs4function...)where T


    model::HyperElasticModel = hyperElasticData[1]
    modelParams::Tuple  = hyperElasticData[2]
    lastSoln::Array{Float64, 1} = hyperElasticData[3]

    solAtNodes::Array{Float64, 1} = getSolAtElement(lastSoln, element, problemDim)

    noOfIpPoints::Int64 = RapidFEM.getNoOfElementIpPoints(shapeFunction)
    noOfNodes::Int64 = RapidFEM.getNoOfElementNodes(shapeFunction)
    âˆ‚u_âˆ‚X_array::Array{Float64, 2} = zeros(problemDim, problemDim)
    for ipNo::Int64 âˆˆ 1:noOfIpPoints
        âˆ‚X_âˆ‚Î¾::Array{Float64,2} = get_âˆ‚x_âˆ‚Î¾(coordArray, shapeFunction, ipNo)
        dÎ© = get_dÎ©(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        âˆ‚Ï•_âˆ‚X_array::Array{Float64} = get_âˆ‚Ï•_âˆ‚x(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        âˆ‚Ï•_âˆ‚X = LargeDefs.get1DTensor(âˆ‚Ï•_âˆ‚X_array')
        get_âˆ‚u_âˆ‚X!(âˆ‚u_âˆ‚X_array, solAtNodes, âˆ‚Ï•_âˆ‚X_array, problemDim)
        âˆ‚u_âˆ‚X = LargeDefs.get_âˆ‚u_âˆ‚X_Tensor(âˆ‚u_âˆ‚X_array)
        F = LargeDefs.getDeformationGradient(âˆ‚u_âˆ‚X)
        E = LargeDefs.getGreenLagrangeStrain(F)
        S = model.secondPiolaStress(E, modelParams)
        S_dot_Fáµ€ = Sâ‹…F'
        F_dot_S = Fâ‹…S
        for a âˆˆ 1:noOfNodes
            âˆ‚Ï•_âˆ‚X_a = âˆ‚Ï•_âˆ‚X[a]
            for i âˆˆ 1:problemDim
                f[problemDim*(a-1)+i] += 0.5*(âˆ‚Ï•_âˆ‚X_a âŠ— F[i,:] + F[i,:] âŠ— âˆ‚Ï•_âˆ‚X_a) âŠ¡ S*dÎ©
            end
        end
    end
    return nothing
end

function local_Î´E_Cáµ€_Î”E!(ğ�•‚::Array{Float64,2}, hyperElasticData::T,
    problemDim::Int64, element::AbstractElement, elementNo::Int64, shapeFunction::Array{ShapeFunction},
    coordArray::Array{Float64,2}; kwargs4function...)where T


    model::HyperElasticModel = hyperElasticData[1]
    modelParams::Tuple  = hyperElasticData[2]
    lastSoln::Array{Float64, 1} = hyperElasticData[3]

    solAtNodes::Array{Float64, 1} = getSolAtElement(lastSoln, element, problemDim)
    noOfIpPoints::Int64 = RapidFEM.getNoOfElementIpPoints(shapeFunction)
    noOfNodes::Int64 = RapidFEM.getNoOfElementNodes(shapeFunction)
    âˆ‚u_âˆ‚X_array::Array{Float64, 2} = zeros(problemDim, problemDim)
    for ipNo::Int64 âˆˆ 1:noOfIpPoints
        âˆ‚X_âˆ‚Î¾ = get_âˆ‚x_âˆ‚Î¾(coordArray, shapeFunction, ipNo)
        dÎ© = RapidFEM.get_dÎ©(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        #Ï• = shapeFunction[ipNo].Ï•
        #X::Array{Float64,1} = getInterpolated_x(coordArray, Ï•)
        âˆ‚Ï•_âˆ‚X_array = RapidFEM.get_âˆ‚Ï•_âˆ‚x(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        âˆ‚Ï•_âˆ‚X = LargeDefs.get1DTensor(âˆ‚Ï•_âˆ‚X_array')
        get_âˆ‚u_âˆ‚X!(âˆ‚u_âˆ‚X_array, solAtNodes, âˆ‚Ï•_âˆ‚X_array, problemDim)
        âˆ‚u_âˆ‚X = LargeDefs.get_âˆ‚u_âˆ‚X_Tensor(âˆ‚u_âˆ‚X_array)
        F = LargeDefs.getDeformationGradient(âˆ‚u_âˆ‚X)
        E = LargeDefs.getGreenLagrangeStrain(F)
        S = model.secondPiolaStress(E, modelParams)
        â„‚ = model.materialTangentTensor(E, modelParams)
        for b::Int64 âˆˆ 1:noOfNodes
            âˆ‚Ï•_âˆ‚X_b = âˆ‚Ï•_âˆ‚X[b]
            for a::Int64 âˆˆ 1:noOfNodes
                âˆ‚Ï•_âˆ‚X_a = âˆ‚Ï•_âˆ‚X[a]
                for j âˆˆ 1:problemDim
                    ğ�•‚[problemDim*(a-1)+j, problemDim*(b-1)+j] += (âˆ‚Ï•_âˆ‚X_aâ‹…Sâ‹…âˆ‚Ï•_âˆ‚X_b)*dÎ©
                    F_j = F[j,:]
                    for i âˆˆ 1:problemDim
                        #ğ�•‚[problemDim*(a-1)+i, problemDim*(b-1)+j] +=
                        #0.25*(âˆ‚Ï•_âˆ‚X[a] âŠ— F[i,:] + F[i,:] âŠ— âˆ‚Ï•_âˆ‚X[a]) âŠ¡ â„‚ âŠ¡
                        #(âˆ‚Ï•_âˆ‚X[b] âŠ— F_j + F_j âŠ— âˆ‚Ï•_âˆ‚X[b])*dÎ©
                        F_i = F[i,:]
                        ğ�•‚[problemDim*(a-1)+i, problemDim*(b-1)+j] +=
                        0.25*(âˆ‚Ï•_âˆ‚X_a âŠ— F_i + F_i âŠ— âˆ‚Ï•_âˆ‚X_a) âŠ¡ â„‚ âŠ¡
                        (âˆ‚Ï•_âˆ‚X_b âŠ— F_j + F_j âŠ— âˆ‚Ï•_âˆ‚X_b)*dÎ©
                    end
                end
            end
        end
    end
    #SmallStrainPlastic.updateStateDict!(stateDictBuffer, stateDictBufferCopy)
    return nothing
end


function localReferenceSource!(S::Vector, hyperElasticData::T, problemDim::Int64,
    element::AbstractElement, elementNo::Int64, shapeFunction::Array{ShapeFunction},
    coordArray::Array{Float64,2}; kwargs4function...) where T

    sourceFunc = hyperElasticData[1]
    lastSoln::Array{Float64, 1} = hyperElasticData[2]

    noOfIpPoints::Int64 = RapidFEM.getNoOfElementIpPoints(shapeFunction)
    noOfNodes::Int64 = RapidFEM.getNoOfElementNodes(shapeFunction)
    #S::Vector = zeros(noOfNodes*problemDim)
    for ipNo âˆˆ 1:noOfIpPoints
        âˆ‚X_âˆ‚Î¾::Array{Float64,2} = get_âˆ‚x_âˆ‚Î¾(coordArray, shapeFunction, ipNo)
        dÎ©::Float64 = RapidFEM.get_dÎ©(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        Ï•::Array{Float64,1} = RapidFEM.get_Ï•(shapeFunction, ipNo)
        X::Array{Float64,1} = getInterpolated_x(coordArray, Ï•)
        s::Array{Float64,1} = sourceFunc(X; kwargs4function...)
        for a âˆˆ 1:noOfNodes
            for i âˆˆ 1:problemDim
                S[problemDim*(a-1)+i] += Ï•[a]*s[i]*dÎ©
            end
        end
    end
    return nothing
end


function localReferenceNeumann!(Nm::Vector, hyperElasticData::T,
    problemDim::Int64, element::AbstractElement, elementNo::Int64, shapeFunction::Array{ShapeFunction},
    coordArray::Array{Float64,2}; kwargs4function...) where T

    neumannFunc = hyperElasticData[1]
    lastSoln::Array{Float64, 1} = hyperElasticData[2]

    noOfIpPoints::Int64 = RapidFEM.getNoOfElementIpPoints(shapeFunction)
    noOfNodes::Int64 = RapidFEM.getNoOfElementNodes(shapeFunction)
    #Nm::Vector = zeros(noOfNodes*problemDim)
    for ipNo âˆˆ 1:noOfIpPoints
        âˆ‚X_âˆ‚Î¾::Array{Float64,2} =  get_âˆ‚x_âˆ‚Î¾(coordArray, shapeFunction, ipNo)
        dS::Float64 = RapidFEM.get_dS(element, âˆ‚X_âˆ‚Î¾,  shapeFunction, ipNo)
        Ï•::Array{Float64,1} = shapeFunction[ipNo].Ï•
        X::Array{Float64,1} = getInterpolated_x(coordArray, Ï•)
        nm::Array{Float64,1} = neumannFunc(X; kwargs4function...)
        for a âˆˆ 1:noOfNodes
            for i âˆˆ 1:problemDim
                Nm[problemDim*(a-1)+i] += Ï•[a]*nm[i]*dS
            end
        end
    end
    return nothing
end

function gaussianSecondPiolaStress(hyperElasticData::T, solAtNodes::Array{Float64,1}, problemDim::Int64,
    element::AbstractElement, elementNo::Int64, shapeFunction::Array{ShapeFunction},
    coordArray::Array{Float64,2}; kwargs4function...) where T

    model::HyperElasticModel = hyperElasticData[1]
    modelParams::Tuple  = hyperElasticData[2]

    
    noOfIpPoints::Int64 = RapidFEM.getNoOfElementIpPoints(shapeFunction)
    noOfNodes::Int64 = RapidFEM.getNoOfElementNodes(shapeFunction)
    âˆ‚u_âˆ‚X_array::Array{Float64, 2} = zeros(problemDim, problemDim)
    S_g::Array{Array{Float64,1},1} = Array{Array{Float64,1},1}(undef, noOfIpPoints)
    for ipNo::Int64 âˆˆ 1:noOfIpPoints
        âˆ‚X_âˆ‚Î¾::Array{Float64,2} =  get_âˆ‚x_âˆ‚Î¾(coordArray, shapeFunction, ipNo)
        
        #X::Array{Float64,1} = getInterpolated_x(coordArray, Ï•)
        âˆ‚Ï•_âˆ‚X_array::Array{Float64} = get_âˆ‚Ï•_âˆ‚x(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        âˆ‚Ï•_âˆ‚X = LargeDefs.get1DTensor(âˆ‚Ï•_âˆ‚X_array')
        get_âˆ‚u_âˆ‚X!(âˆ‚u_âˆ‚X_array, solAtNodes, âˆ‚Ï•_âˆ‚X_array, problemDim)
        âˆ‚u_âˆ‚X = LargeDefs.get_âˆ‚u_âˆ‚X_Tensor(âˆ‚u_âˆ‚X_array)
        F = LargeDefs.getDeformationGradient(âˆ‚u_âˆ‚X)
        E = LargeDefs.getGreenLagrangeStrain(F)
        S_tensor = model.secondPiolaStress(E, modelParams)
        S_g[ipNo] = zeros(problemDim^2)
        k = 1
        for i âˆˆ 1:problemDim
            for j âˆˆ 1:problemDim
                S_g[ipNo][k] = S_tensor[i,j]
                k +=1
            end
        end
    end
    return S_g
end

function gaussianGreenStrain(hyperElasticData::T, solAtNodes::Array{Float64,1}, problemDim::Int64,
    element::AbstractElement, elementNo::Int64, shapeFunction::Array{ShapeFunction},
    coordArray::Array{Float64,2}; kwargs4function...) where T

    model::HyperElasticModel = hyperElasticData[1]
    modelParams::Tuple  = hyperElasticData[2]

    
    noOfIpPoints::Int64 = RapidFEM.getNoOfElementIpPoints(shapeFunction)
    noOfNodes::Int64 = RapidFEM.getNoOfElementNodes(shapeFunction)
    âˆ‚u_âˆ‚X_array::Array{Float64, 2} = zeros(problemDim, problemDim)
    E_g::Array{Array{Float64,1},1} = Array{Array{Float64,1},1}(undef, noOfIpPoints)
    for ipNo::Int64 âˆˆ 1:noOfIpPoints
        âˆ‚X_âˆ‚Î¾::Array{Float64,2} =  get_âˆ‚x_âˆ‚Î¾(coordArray, shapeFunction, ipNo)
        #Ï•::Array{Float64,1} = shapeFunction[ipNo].Ï•
        #X::Array{Float64,1} = getInterpolated_x(coordArray, Ï•)
        âˆ‚Ï•_âˆ‚X_array::Array{Float64} = get_âˆ‚Ï•_âˆ‚x(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        get_âˆ‚u_âˆ‚X!(âˆ‚u_âˆ‚X_array, solAtNodes, âˆ‚Ï•_âˆ‚X_array, problemDim)
        âˆ‚u_âˆ‚X = LargeDefs.get_âˆ‚u_âˆ‚X_Tensor(âˆ‚u_âˆ‚X_array)
        F = LargeDefs.getDeformationGradient(âˆ‚u_âˆ‚X)
        E_tensor = LargeDefs.getGreenLagrangeStrain(F)
        E_g[ipNo] = zeros(problemDim^2)
        k = 1
        for i âˆˆ 1:problemDim
            for j âˆˆ 1:problemDim
                E_g[ipNo][k] = E_tensor[i,j]
                k +=1
            end
        end
    end
    return E_g
end

function gaussianDeformationGrad(hyperElasticData::T, solAtNodes::Array{Float64,1}, problemDim::Int64,
    element::AbstractElement, elementNo::Int64, shapeFunction::Array{ShapeFunction},
    coordArray::Array{Float64,2}; kwargs4function...) where T

    model::HyperElasticModel = hyperElasticData[1]
    modelParams::Tuple  = hyperElasticData[2]

    noOfIpPoints::Int64 = RapidFEM.getNoOfElementIpPoints(shapeFunction)
    noOfNodes::Int64 = RapidFEM.getNoOfElementNodes(shapeFunction)
    âˆ‚u_âˆ‚X_array::Array{Float64, 2} = zeros(problemDim, problemDim)
    F_g::Array{Array{Float64,1},1} = Array{Array{Float64,1},1}(undef, noOfIpPoints)
    for ipNo::Int64 âˆˆ 1:noOfIpPoints
        âˆ‚X_âˆ‚Î¾::Array{Float64,2} =  get_âˆ‚x_âˆ‚Î¾(coordArray, shapeFunction, ipNo)
        #X::Array{Float64,1} = getInterpolated_x(coordArray, Ï•)
        âˆ‚Ï•_âˆ‚X_array::Array{Float64} = get_âˆ‚Ï•_âˆ‚x(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        get_âˆ‚u_âˆ‚X!(âˆ‚u_âˆ‚X_array, solAtNodes, âˆ‚Ï•_âˆ‚X_array, problemDim)
        âˆ‚u_âˆ‚X = LargeDefs.get_âˆ‚u_âˆ‚X_Tensor(âˆ‚u_âˆ‚X_array)
        F_tensor = LargeDefs.getDeformationGradient(âˆ‚u_âˆ‚X)
        F_g[ipNo] = zeros(problemDim^2)
        k = 1
        for i âˆˆ 1:problemDim
            for j âˆˆ 1:problemDim
                F_g[ipNo][k] = F_tensor[i,j]
                k +=1
            end
        end
    end
    return F_g
end

function gaussianCauchyStress(hyperElasticData::T, solAtNodes::Array{Float64,1}, problemDim::Int64,
    element::AbstractElement, elementNo::Int64, shapeFunction::Array{ShapeFunction},
    coordArray::Array{Float64,2}; kwargs4function...) where T

    model::HyperElasticModel = hyperElasticData[1]
    modelParams::Tuple  = hyperElasticData[2]

    
    noOfIpPoints::Int64 = RapidFEM.getNoOfElementIpPoints(shapeFunction)
    noOfNodes::Int64 = RapidFEM.getNoOfElementNodes(shapeFunction)
    âˆ‚u_âˆ‚X_array::Array{Float64, 2} = zeros(problemDim, problemDim)
    Ïƒ_g::Array{Array{Float64,1},1} = Array{Array{Float64,1},1}(undef, noOfIpPoints)
    for ipNo::Int64 âˆˆ 1:noOfIpPoints
        âˆ‚X_âˆ‚Î¾::Array{Float64,2} =  get_âˆ‚x_âˆ‚Î¾(coordArray, shapeFunction, ipNo)
        
        #X::Array{Float64,1} = getInterpolated_x(coordArray, Ï•)
        âˆ‚Ï•_âˆ‚X_array::Array{Float64} = get_âˆ‚Ï•_âˆ‚x(element, âˆ‚X_âˆ‚Î¾, shapeFunction, ipNo)
        get_âˆ‚u_âˆ‚X!(âˆ‚u_âˆ‚X_array, solAtNodes, âˆ‚Ï•_âˆ‚X_array, problemDim)
        âˆ‚u_âˆ‚X = LargeDefs.get_âˆ‚u_âˆ‚X_Tensor(âˆ‚u_âˆ‚X_array)
        F = LargeDefs.getDeformationGradient(âˆ‚u_âˆ‚X)
        E = LargeDefs.getGreenLagrangeStrain(F)
        S_tensor = model.secondPiolaStress(E, modelParams)
        Ïƒ_tensor = 1.0/det(F)*Fâ‹…S_tensorâ‹…F'
        Ïƒ_g[ipNo] = zeros(problemDim^2)
        k = 1
        for i âˆˆ 1:problemDim
            for j âˆˆ 1:problemDim
                Ïƒ_g[ipNo][k] = Ïƒ_tensor[i,j]
                k +=1
            end
        end
    end
    return Ïƒ_g
end
