abstract type AbstractDiagTriangularMatrix{P,T,L} <: AbstractMatrix{T} end

abstract type AbstractTriangularMatrix{P,T,L} <: AbstractDiagTriangularMatrix{P,T,L} end
abstract type AbstractLowerTriangularMatrix{P,T,L} <: AbstractTriangularMatrix{P,T,L} end
abstract type AbstractUpperTriangularMatrix{P,T,L} <: AbstractTriangularMatrix{P,T,L} end
abstract type AbstractMutableLowerTriangularMatrix{P,T,L} <: AbstractLowerTriangularMatrix{P,T,L} end
abstract type AbstractMutableUpperTriangularMatrix{P,T,L} <: AbstractUpperTriangularMatrix{P,T,L} end

struct LowerTriangularMatrix{P,T,L} <: AbstractLowerTriangularMatrix{P,T,L}
    data::NTuple{L,T}
end
mutable struct MutableLowerTriangularMatrix{P,T,L} <: AbstractMutableLowerTriangularMatrix{P,T,L}
    data::NTuple{L,T}
    MutableLowerTriangularMatrix{P,T,L}(::UndefInitializer) where {P,T,L} = new{P,T,L}()
end
@inline LowerTriangularMatrix(M::MutableLowerTriangularMatrix{P,T,L}) where {P,T,L} = LowerTriangularMatrix{P,T,L}(M.data)
struct UpperTriangularMatrix{P,T,L} <: AbstractUpperTriangularMatrix{P,T,L}
    data::NTuple{L,T}
end
mutable struct MutableUpperTriangularMatrix{P,T,L} <: AbstractMutableUpperTriangularMatrix{P,T,L}
    data::NTuple{L,T}
    MutableUpperTriangularMatrix{P,T,L}(::UndefInitializer) where {P,T,L} = new{P,T,L}()
end
struct PtrLowerTriangularMatrix{P,T,L} <: AbstractMutableLowerTriangularMatrix{P,T,L}
    ptr::Ptr{T}
end
struct PtrUpperTriangularMatrix{P,T,L} <: AbstractMutableUpperTriangularMatrix{P,T,L}
    ptr::Ptr{T}
end

struct UninitializedLowerTriangularMatrix{P,T,L} <: AbstractMutableLowerTriangularMatrix{P,T,L}
    ptr::Ptr{T}
end
struct UninitializedUpperTriangularMatrix{P,T,L} <: AbstractMutableUpperTriangularMatrix{P,T,L}
    ptr::Ptr{T}
end
@inline function ReverseDiffExpressionsBase.uninitialized(L::AbstractMutableLowerTriangularMatrix{P,T,N}) where {P,T,N}
    UninitializedLowerTriangularMatrix{P,T,N}(pointer(L))
end
@inline function ReverseDiffExpressionsBase.uninitialized(U::AbstractMutableUpperTriangularMatrix{P,T,N}) where {P,T,N}
    UninitializedUpperTriangularMatrix{P,T,N}(pointer(U))
end
@inline Base.pointer(L::UninitializedLowerTriangularMatrix) = L.ptr
@inline Base.pointer(U::UninitializedUpperTriangularMatrix) = U.ptr
ReverseDiffExpressionsBase.isinitialized(::Type{<:UninitializedLowerTriangularMatrix}) = false
ReverseDiffExpressionsBase.isinitialized(::Type{<:UninitializedUpperTriangularMatrix}) = false
function PtrLowerTriangularMatrix{P,T,L}(sp::StackPointer) where {P,T,L}
    sp + VectorizationBase.align(sizeof(T)*L), PtrLowerTriangularMatrix{P,T,L}(pointer(sp, T))
end

@generated function PtrLowerTriangularMatrix{P,T}(sp::StackPointer) where {P,T}
    L = PaddedMatrices.calc_padding(binomial2(P+1),T)
    quote
        $(Expr(:meta,:inline))
        A = PtrLowerTriangularMatrix{$P,$T,$L}(pointer(sp, $T))
        sp + $(VectorizationBase.align(L*sizeof(T))), A
    end
end
@generated function PtrUpperTriangularMatrix{P,T}(sp::StackPointer) where {P,T}
    L = PaddedMatrices.calc_padding(binomial2(P+1),T)
    quote
        $(Expr(:meta,:inline))
        A = PtrUpperTriangularMatrix{$P,$T,$L}(pointer(sp, $T))
        sp + $(VectorizationBase.align(L*sizeof(T))), A
    end
end
@generated function ReverseDiffExpressionsBase.alloc_adjoint(
    L::AbstractMutableLowerTriangularMatrix{P,T}
) where {P,T}
    N = PaddedMatrices.calc_padding(binomial2(N+1), T)
    quote
        $(Expr(:meta,:inline))
        MutableLowerTriangularMatrix{$P,$T,$N}(undef)
    end
end
@generated function ReverseDiffExpressionsBase.alloc_adjoint(
    L::AbstractMutableUpperTriangularMatrix{P,T}
) where {P,T}
    N = PaddedMatrices.calc_padding(binomial2(N+1), T)
    quote
        $(Expr(:meta,:inline))
        MutableUpperTriangularMatrix{$P,$T,$N}(undef)
    end
end
@generated function ReverseDiffExpressionsBase.alloc_adjoint(
    ptr::Ptr{T}, L::AbstractMutableLowerTriangularMatrix{P,T}
) where {P,T}
    N = binomial2(P+1)
    quote
        $(Expr(:meta,:inline))
        PtrLowerTriangularMatrix{$P,$T,$N}(ptr)
    end
end
@generated function ReverseDiffExpressionsBase.alloc_adjoint(
    ptr::Ptr{T}, L::AbstractMutableUpperTriangularMatrix{P,T}
) where {P,T}
    N = binomial2(P+1)
    quote
        $(Expr(:meta,:inline))
        PtrUpperTriangularMatrix{$P,$T,$N}(ptr)
    end
end
@generated function ReverseDiffExpressionsBase.alloc_adjoint(
    ptr::StackPointer, L::AbstractMutableLowerTriangularMatrix{P,T}
) where {P,T}
    N = PaddedMatrices.calc_padding(binomial2(P+1), T)
    quote
        $(Expr(:meta,:inline))
        PtrLowerTriangularMatrix{$P,$T,$N}(ptr)
    end
end
@generated function ReverseDiffExpressionsBase.alloc_adjoint(
    ptr::StackPointer, L::AbstractMutableUpperTriangularMatrix{P,T}
) where {P,T}
    N = PaddedMatrices.calc_padding(binomial2(P+1), T)
    quote
        $(Expr(:meta,:inline))
        PtrUpperTriangularMatrix{$P,$T,$N}(ptr)
    end
end

@inline lt_sub2ind_fast(P, i, j) = i == j ? i : j * P - binomial2(j) + i - j
@inline ut_sub2ind_fast(P, i, j) = i == j ? i : 1 + P + binomial2(j) + i - j
function lt_sub2ind(P, i, j)
    i == j && return i
    j, i = minmax(i, j)
    j * P - binomial2(j) + i - j
end
function ut_sub2ind(P, i, j)
    i == j && return i
    i, j = minmax(i, j)
    1 + P + binomial2(j) + i - j
end

@inline UpperTriangularMatrix(M::MutableUpperTriangularMatrix{P,T,L}) where {P,T,L} = UpperTriangularMatrix{P,T,L}(M.data)
@generated function MutableLowerTriangularMatrix{P,T}(undef) where {P,T}
    Lbase = binomial2(P+1)
    Wm1 = VectorizationBase.pick_vector_width(Lbase,T) - 1
    L = (Lbase + Wm1) & ~Wm1
    quote
        $(Expr(:meta,:inline))
        MutableLowerTriangularMatrix{$P,$T,$L}(undef)
    end
end
@generated function MutableUpperTriangularMatrix{P,T}(undef) where {P,T}
    Lbase = binomial2(P+1)
    Wm1 = VectorizationBase.pick_vector_width(Lbase,T) - 1
    L = (Lbase + Wm1) & ~Wm1
    quote
        $(Expr(:meta,:inline))
        MutableUpperTriangularMatrix{$P,$T,$L}(undef)
    end
end
function MutableLowerTriangularMatrix(
    B::Union{A,LinearAlgebra.Adjoint{T,A}}
) where {T,M,A <: AbstractFixedSizeMatrix{M,M,T}}
    L = MutableLowerTriangularMatrix{M,T}(undef)
    @inbounds for mc ∈ 1:M, mr ∈ mc:M
        L[mr,mc] = B[mr,mc]
    end
    L
end
function MutableUpperTriangularMatrix(
    B::Union{A,LinearAlgebra.Adjoint{T,A}}
) where {T,M,A <: AbstractFixedSizeMatrix{M,M,T}}
    U = MutableLowerTriangularMatrix{M,T}(undef)
    @inbounds for mc ∈ 1:M, mr ∈ 1:mc
        U[mr,mc] = B[mr,mc]
    end
    U
end

function copyto!(L::MutableLowerTriangularMatrix{M,T}, A::Matrix) where {M,T}
    for m ∈ 1:M
        L[m] = A[m,m]
    end
    for mc ∈ 1:M-1
        for mr ∈ mc+1:M
            L[mr,mc] = A[mr,mc]
        end
    end
end
function MutableLowerTriangularMatrix(A::Matrix{T}) where {T}
    M, N = size(A)
    @assert M == N
    L = MutableLowerTriangularMatrix{M,T}(undef)
    copyto!(L, A)
    L
end

function copyto!(U::MutableUpperTriangularMatrix{M,T}, A::Matrix) where {M,T}
    for m ∈ 1:M
        U[m] = A[m,m]
    end
    for mc ∈ 2:M
        for mr ∈ 1:mc-1
            U[mr,mc] = A[mr,mc]
        end
    end
end
function MutableUpperTriangularMatrix(A::Matrix{T}) where {T}
    M, N = size(A)
    @assert M == N
    U = MutableUpperTriangularMatrix{M,T}(undef)
    copyto!(U, A)
    U
end

@inline Base.similar(A::AT) where {AT <: Union{<:AbstractMutableLowerTriangularMatrix,<:AbstractMutableUpperTriangularMatrix}} = AT(undef)
@inline function Base.similar(A::PtrLowerTriangularMatrix{P,T,L}) where {P,T,L}
    MutableLowerTriangularMatrix{P,T,L}(undef)
end
@inline function Base.similar(sp::StackPointer, A::AbstractLowerTriangularMatrix{P,T,L}) where {P,T,L}
    PtrLowerTriangularMatrix{P,T,L}(sp)
end
@inline function Base.similar(A::PtrUpperTriangularMatrix{P,T,L}) where {P,T,L}
    MutableUpperTriangularMatrix{P,T,L}(undef)
end
@inline function Base.similar(sp::StackPointer, A::AbstractUpperTriangularMatrix{P,T,L}) where {P,T,L}
    PtrUpperTriangularMatrix{P,T,L}(sp)
end

abstract type AbstractSymmetricMatrix{P,T,L} <: AbstractDiagTriangularMatrix{P,T,L} end
abstract type AbstractSymmetricMatrixL{P,T,L} <: AbstractSymmetricMatrix{P,T,L} end
abstract type AbstractSymmetricMatrixU{P,T,L} <: AbstractSymmetricMatrix{P,T,L} end
abstract type AbstractMutableSymmetricMatrixL{P,T,L} <: AbstractSymmetricMatrixL{P,T,L} end
abstract type AbstractMutableSymmetricMatrixU{P,T,L} <: AbstractSymmetricMatrixU{P,T,L} end

struct SymmetricMatrixL{P,T,L} <: AbstractSymmetricMatrixL{P,T,L}
    data::NTuple{L,T}
end
mutable struct MutableSymmetricMatrixL{P,T,L} <: AbstractMutableSymmetricMatrixL{P,T,L}
    data::NTuple{L,T}
    MutableSymmetricMatrixL{P,T,L}(::UndefInitializer) where {P,T,L} = new{P,T,L}()
end
function MutableSymmetricMatrixL(S::SymmetricMatrixL{M,T,L}) where {M,T,L}
    Sm = MutableSymmetricMatrixL{M,T,L}(undef)
    Sm.data = S.data
    Sm
end

struct PtrSymmetricMatrixL{P,T,L} <: AbstractMutableSymmetricMatrixL{P,T,L}
    ptr::Ptr{T}
end
# @generated function SymmetricMatrixL(S::PaddedMatrices.AbstractFixedSizeMatrix{P,P,T,R}) where {P,T,R}
#     q = quote end
#     qa = q.args
#     PaddedMatrices.load_L_quote!(qa, P, R, :Σ, :Σ)
#     L = binomial2(P+1)
#     Wm1 = VectorizationBase.pick_vector_width(L, T)-1
#     L = (L + Wm1) & ~Wm1
#     lq = store_packed_L_quote!(qa, P, :Σ, T, L)
#     quote
#         # $(Expr(:meta,:inline))
#         @inbounds begin
#             # begin
#             $q
#             $lq
#         end
#     end
# end

struct SymmetricMatrixU{P,T,L} <: AbstractSymmetricMatrix{P,T,L}
    data::NTuple{L,T}
end
struct PtrSymmetricMatrixU{P,T,L} <: AbstractMutableSymmetricMatrixU{P,T,L}
    ptr::Ptr{T}
end
mutable struct MutableSymmetricMatrixU{P,T,L} <: AbstractMutableSymmetricMatrixU{P,T,L}
    data::NTuple{L,T}
    MutableSymmetricMatrixU{P,T,L}(::UndefInitializer) where {P,T,L} = new{P,T,L}()
end
@generated function MutableSymmetricMatrixL{P,T}(undef) where {P,T}
    Lbase = binomial2(P+1)
    W = VectorizationBase.pick_vector_width(Lbase,T)
    Wm1 = W - 1
    L = (Lbase + Wm1) & ~Wm1
    quote
        $(Expr(:meta,:inline))
        MutableSymmetricMatrixL{$P,$T,$L}(undef)
    end
end
@generated function MutableSymmetricMatrixU{P,T}(undef) where {P,T}
    Lbase = binomial2(P+1)
    W = VectorizationBase.pick_vector_width(Lbase,T)
    Wm1 = W - 1
    L = (Lbase + Wm1) & ~Wm1
    quote
        $(Expr(:meta,:inline))
        MutableSymmetricMatrixU{$P,$T,$L}(undef)
    end
end
function Base.copyto!(L::AbstractLowerTriangularMatrix{M,T}, A::AbstractMatrix{T}) where {M,T}
    @boundscheck begin
        N, P = size(A)
        @assert M == N == P
    end
    @inbounds for m in 1:M
        L[m] = A[m,m]
    end
    ind = M
    for c in 1:M
        for r in c+1:M
            ind += 1
            L[ind] = A[r, c]
        end
    end
    L
end
function MutableLowerTriangularMatrix(A::AbstractMatrix{T}) where {T}
    @assert size(A,1) == size(A,2) "Matrix of size $(size(A)) is not square."
    M = size(A,1)
    L = MutableLowerTriangularMatrix{M,T}(undef)
    copyto!(L, A)
    L
end
function MutableLowerTriangularMatrix{M}(A::AbstractMatrix{T}) where {M,T}
    @assert M == size(A,1) == size(A,2) "Matrix of size $(size(A)) is not a square $M x $M matrix."
    L = MutableLowerTriangularMatrix{M,T}(undef)
    copyto!(L, A)
    L
end
function MutableLowerTriangularMatrix{M,T}(A::AbstractMatrix) where {M,T}
    @assert M == size(A,1) == size(A,2) "Matrix of size $(size(A)) is not a square $M x $M matrix."
    L = MutableLowerTriangularMatrix{M,T}(undef)
    copyto!(L, A)
    L
end

const AbstractMutableDiagMatrix{P,T,L} = Union{
    AbstractMutableLowerTriangularMatrix{P,T,L},
    AbstractMutableUpperTriangularMatrix{P,T,L},
    AbstractMutableSymmetricMatrixL{P,T,L},
    AbstractMutableSymmetricMatrixU{P,T,L}
}

@inline function Base.getindex(A::AbstractDiagTriangularMatrix{P,T,L}, i::Integer) where {P,T,L}
    @boundscheck i > L && ThrowBoundsError("i = $i > L = $L")
    @inbounds A.data[i]
end
@inline function Base.getindex(A::LinearAlgebra.Adjoint{T,<:AbstractDiagTriangularMatrix{P,T,L}}, i::Integer) where {P,T,L}
    @boundscheck i > L && ThrowBoundsError("i = $i > L = $L")
    @inbounds A.parent.data[i]
end
@inline function Base.getindex(A::LinearAlgebra.Adjoint{Union{},<:AbstractDiagTriangularMatrix{P,Vec{W,T},L}}, i::Integer) where {P,T,L,W}
    @boundscheck i > L && ThrowBoundsError("i = $i > L = $L")
    @inbounds A.parent.data[i]
end

const MutableDiagTriangle{P,T,L} = Union{
    MutableLowerTriangularMatrix{P,T,L},
    MutableUpperTriangularMatrix{P,T,L},
    MutableSymmetricMatrixL{P,T,L},
    MutableSymmetricMatrixU{P,T,L}
}

@inline Base.pointer(A::MutableDiagTriangle{P,T,L}) where {P,T,L} = Base.unsafe_convert(Ptr{T}, pointer_from_objref(A))
@inline function Base.pointer(A::MutableDiagTriangle{P,NTuple{W,Core.VecElement{T}},L}) where {P,T,L,W}
    Base.unsafe_convert(Ptr{T}, pointer_from_objref(A))
end
@inline Base.pointer(A::PtrLowerTriangularMatrix) = A.ptr
@inline Base.pointer(A::PtrUpperTriangularMatrix) = A.ptr
@inline Base.pointer(A::PtrSymmetricMatrixL) = A.ptr
@inline Base.pointer(A::PtrSymmetricMatrixU) = A.ptr

@inline Base.pointer(A::PtrLowerTriangularMatrix{P,Vec{W,T}}) where {P,W,T} = Base.unsafe_convert(Ptr{T},A.ptr)
@inline Base.pointer(A::PtrUpperTriangularMatrix{P,Vec{W,T}}) where {P,W,T} = Base.unsafe_convert(Ptr{T},A.ptr)
@inline Base.pointer(A::PtrSymmetricMatrixL{P,Vec{W,T}}) where {P,W,T} = Base.unsafe_convert(Ptr{T},A.ptr)
@inline Base.pointer(A::PtrSymmetricMatrixU{P,Vec{W,T}}) where {P,W,T} = Base.unsafe_convert(Ptr{T},A.ptr)

@inline function Base.getindex(A::AbstractMutableDiagMatrix{P,T,L}, i::Integer) where {P,T,L}
    @boundscheck i > L && ThrowBoundsError("i = $i > L = $L")
    VectorizationBase.load(pointer(A) + sizeof(T) * (i-1))
end
@inline function Base.getindex(A::LinearAlgebra.Adjoint{T,<:AbstractMutableDiagMatrix{P,T,L}}, i::Integer) where {P,T,L}
    @boundscheck i > L && ThrowBoundsError("i = $i > L = $L")
    VectorizationBase.load(pointer(A.parent) + sizeof(T) * (i-1))
end
@inline function Base.getindex(A::AbstractMutableDiagMatrix{P,Vec{W,T},L}, i::Integer) where {P,W,T,L}
    @boundscheck i > L && ThrowBoundsError("i = $i > L = $L")
    SIMDPirates.vload(Vec{W,T}, pointer(A) + W*sizeof(T)*(i-1))
end
@inline function Base.getindex(A::LinearAlgebra.Adjoint{Union{},<:AbstractMutableDiagMatrix{P,Vec{W,T},L}}, i::Integer) where {P,W,T,L}
    @boundscheck i > L && ThrowBoundsError("i = $i > L = $L")
    SIMDPirates.vload(Vec{W,T}, pointer(A.parent) + W*sizeof(T)*(i-1))
end

@inline function Base.setindex!(A::AbstractMutableDiagMatrix{P,T,L}, v, i::Integer) where {P,T,L}
    @boundscheck i > L && ThrowBoundsError("i > $L.")
    VectorizationBase.store!(pointer(A) + (i-1) * sizeof(T), convert(T,v))
    v
end
@inline function Base.setindex!(A::AbstractMutableDiagMatrix{P,NTuple{W,Core.VecElement{T}},L}, v::NTuple{W,Core.VecElement{T}}, i::Integer) where {P,T,L,W}
    @boundscheck i > L && ThrowBoundsError("i > $L.")
    SIMDPirates.vstore!(pointer(A) + (i-1) * sizeof(NTuple{W,Core.VecElement{T}}), v)
    v
end

@inline function Base.setindex!(A::AbstractMutableLowerTriangularMatrix{P,T,L}, v, i::Integer, j::Integer) where {P,T,L}
    ind = lt_sub2ind_fast(P, i, j)
    @boundscheck ind > L && ThrowBoundsError("i > $L.")
    VectorizationBase.store!(pointer(A) + (ind-1) * sizeof(T), convert(T,v))
    v
end
@inline function Base.setindex!(A::AbstractMutableLowerTriangularMatrix{P,NTuple{W,Core.VecElement{T}},L}, v::NTuple{W,Core.VecElement{T}}, i::Integer, j::Integer) where {P,T,L,W}
    ind = lt_sub2ind_fast(P, i, j)
    @boundscheck ind > L && ThrowBoundsError("i > $L.")
    SIMDPirates.vstore!(pointer(A) + (ind-1) * sizeof(NTuple{W,Core.VecElement{T}}), v)
    v
end
@inline function Base.setindex!(A::AbstractMutableUpperTriangularMatrix{P,T,L}, v, i::Integer, j::Integer) where {P,T,L}
    ind = ut_sub2ind_fast(P, i, j)
    @boundscheck ind > L && ThrowBoundsError("i > $L.")
    VectorizationBase.store!(pointer(A) + (ind-1) * sizeof(T), convert(T,v))
    v
end
@inline function Base.setindex!(A::AbstractMutableUpperTriangularMatrix{P,NTuple{W,Core.VecElement{T}},L}, v::NTuple{W,Core.VecElement{T}}, i::Integer, j::Integer) where {P,T,L,W}
    ind = ut_sub2ind_fast(P, i, j)
    @boundscheck ind > L && ThrowBoundsError("i > $L.")
    SIMDPirates.vstore!(pointer(A) + (ind-1) * sizeof(NTuple{W,Core.VecElement{T}}), v)
    v
end

#
# struct LowerTriangleColumnIterator{MT}
#     num_col::Int
#     W::Int
#     first_mask::MT
#     last_mask::MT
#     max_mask::MT
# end
# function LowerTriangleColumnIterator(::Val{P}, ::Type{T}) where {P,T}
#     W, Wshift = pick_vector_width_shift(P, T)
#     Wm1 = W - 1
#     MT = VectorizationBase.mask_type(Val(P), T)
#
#     max_mask = MT((1 << Wshift) - 1)
#     LowerTriangleColumnIterator(P, W, first_mask, last_mask, max_mask)
# end
# # Base.eltype(::Type{LowerTriangleColumnIterator}) =
# Base.length(i::LowerTriangleColumnIterator) = i.num_col - 1
#
# function Base.iterate(t::LowerTriangleColumnIterator)
#
# end
# function Base.iterate(t::LowerTriangleColumnIterator, state)
#
# end

@generated type_length(::Type{<:AbstractDiagTriangularMatrix{M}}) where {M} = binomial2(M+1)
@generated type_length(::AbstractDiagTriangularMatrix{P}) where {P} = binomial2(P+1)
@generated param_type_length(::Type{<:AbstractDiagTriangularMatrix{P}}) where {P} = binomial2(P+1)
@generated param_type_length(::AbstractDiagTriangularMatrix{P}) where {P} = binomial2(P+1)

Base.size(::AbstractDiagTriangularMatrix{P}) where {P} = (P,P)

@inline VectorizationBase.vectorizable(A::AbstractMutableDiagMatrix) = VectorizationBase.Pointer(pointer(A))
@inline VectorizationBase.vectorizable(A::AbstractDiagTriangularMatrix) = PaddedMatrices.vStaticPaddedArray(A)

# @inline binom2(n::Int) = (nu = reinterpret(UInt, n); reinterpret(Int, (nu*(nu-one(UInt))) >> one(UInt)))

# function padded_diagonal_length(P, T)
#     Wm1 = VectorizationBase.pick_vector_width(P, T) - 1
#     (P + Wm1) & ~Wm1
# end
# function num_complete_blocks(P, T)
#     W, Wshift = VectorizationBase.pick_vector_width_shift(P, T)
#     (P - 1) >> Wshift
# end
# function calculate_L(P, T)
#     W, Wshift = VectorizationBase.pick_vector_width_shift(P, T)
#     W² = W*W
#     Wm1 = W - 1
#     rem = P & Wm1
#     padded_diag_length = (P + Wm1) & ~Wm1
#     L = padded_diag_length
#
#     Pm1 = P - 1
#     num_complete_blocks = Pm1 >> Wshift
#     L += W² * binomial(1+num_complete_blocks, 2)
#     rem_block = Pm1 & Wm1
#     L += (1+num_complete_blocks)*W*rem_block
#     L
# end
#
# function lower_triangle_sub2ind_quote(P, T)
#     # assume inputs are i, j
#     # for row, column
#     W, Wshift = VectorizationBase.pick_vector_width_shift(P, T)
#     W² = W * W
#     Wm1 = W - 1
#     rem = P & Wm1
#     if rem == 0
#         pad = 0
#         diag_length = P
#         num_rows = P
#         rem_row = Wm1
#     else
#         pad = W - rem
#         diag_length = P + pad
#         if rem == 1
#             num_rows = P - rem
#             rem_row = W
#         else
#             num_rows = diag_length
#             rem_row = rem - 1
#         end
#         # num_rows = rem == 1 ? P - rem : diag_length
#         # rem_row = rem - 1
#     end
#     # @show diag_length, num_rows, rem_row
#     quote
#         # We want i >= j, for a lower triangular matrix.
#         j, i = minmax(j, i)
#         # @boundscheck i > $P && ThrowBoundsError(str)
#         i == j && return i
#         ind = $(P - num_rows + Wm1 - rem_row) + j*$num_rows + i
#         if j > $rem_row
#             j2 = j - $rem_row
#             jrem = j2 & $Wm1
#             number_blocks = 1+(j2 >> $Wshift)
#             ind -= jrem * $W * number_blocks + binomial2(number_blocks) << $(2Wshift)
#         end
#         ind
#     end
# end
# @generated function lower_triangle_sub2ind(::Val{P}, ::Type{T}, i, j) where {T,P}
#     quote
#         # $(Expr(:meta, :inline))
#         $(lower_triangle_sub2ind_quote(P, T))
#     end
# end
#
# function number_lower_triangular_vector_loads_for_column(P, T, j)
#     W, Wshift = VectorizationBase.pick_vector_width_shift(P, T)
# end
#
# function upper_triangle_sub2ind_quote(P, T)
#     # assume inputs are i, j
#     # for row, column
#     W, Wshift = VectorizationBase.pick_vector_width_shift(P, T)
#     Wm1 = W - 1
#     rem = P & Wm1
#     diag_length = (P + Wm1) & ~Wm1
#     quote
#         # We want i <= j, for a upper triangular matrix.
#         i, j = minmax(i, j)
#         i == j && return i
#         num_blocks = (j-2) >> $Wshift
#         ind = $diag_length + ((num_blocks*(num_blocks+1)) << $(2Wshift - 1))
#         jrem = (j-2) & $(W-1)
#         ind += ((num_blocks+1) * jrem) << $Wshift
#         ind + i
#     end
# end
# @generated function upper_triangle_sub2ind(::Val{P}, ::Type{T}, i, j) where {T,P}
#     quote
#         # $(Expr(:meta, :inline))
#         $(upper_triangle_sub2ind_quote(P, T))
#     end
# end

@generated function Base.:+(A::AbstractLowerTriangularMatrix{M,T,L}, B::AbstractLowerTriangularMatrix{M,T,L}) where {M,T,L}
    quote
        $(Expr(:meta,:inline))
        C = MutableLowerTriangularMatrix{$M,$T,$L}(undef)
        @vectorize $T for l ∈ 1:$L
            C[l] = A[l] + B[l]
        end
        C
    end
end
@generated function Base.:+(A::AbstractUpperTriangularMatrix{M,T,L}, B::AbstractUpperTriangularMatrix{M,T,L}) where {M,T,L}
    quote
        $(Expr(:meta,:inline))
        C = MutableUpperTriangularMatrix{$M,$T,$L}(undef)
        @vectorize $T for l ∈ 1:$L
            C[l] = A[l] + B[l]
        end
        C
    end
end
@generated function Base.:+(A::UpperTriangularMatrix{M,T,L}, B::UpperTriangularMatrix{M,T,L}) where {M,T,L}
    quote
        $(Expr(:meta,:inline))
        C = MutableUpperTriangularMatrix{$M,$T,$L}(undef)
        @vectorize $T for l ∈ 1:$L
            C[l] = A[l] + B[l]
        end
        UpperTriangularMatrix(C)
    end
end

@inline SIMDPirates.lifetime_start!(A::MutableLowerTriangularMatrix) = nothing
@inline SIMDPirates.lifetime_end!(A::MutableLowerTriangularMatrix) = nothing
@inline function SIMDPirates.lifetime_start!(A::AbstractMutableLowerTriangularMatrix{M,T,L}) where {M,T,L}
    SIMDPirates.lifetime_start!(pointer(A), Val{L}())
end
@inline function SIMDPirates.lifetime_end!(A::AbstractMutableLowerTriangularMatrix{M,T,L}) where {M,T,L}
    SIMDPirates.lifetime_end!(pointer(A), Val{L}())
end


module DnD

Pkg.activate(".")
using CSV
using DataFrames

creatures = CSV.read("data_d20datascience\\src\\creatures.csv")
fighter   = CSV.read("data_d20datascience\\src\\fighter.csv")
wizard    = CSV.read("data_d20datascience\\src\\wizard.csv")

teamA = creatures[Integer.(round.(rand(1)*300 .+ (1:4))),:]
teamB = creatures[Integer.(round.(rand(1)*300 .+ (1:4))),:]

# function combatsim(teamA, teamB)

  teamA.team = 1
  teamB.team = 2
  teams = vcat(teamA, teamB)

  println("Team 1\n  ", join(teamA.name, "\n  "))
  println("Team 2\n  ", join(teamB.name, "\n  "))

  teams.currentHP = copy(teams.hp)
  teams.order = rand(size(teams,1)) .* 20 .+ 1 + teams.init
  sort!(teams, :order, rev = true)

  team1count = size(teamA, 1)
  team2count = size(teamB, 1)

  currentround = 0

  while ((team1count > 0) & (team2count > 0))
    global currentround
    println(currentround)
    currentround += 1
    # Loop through each creature
    for i in 1:size(teams, 1)
        # If current creature HP is 0 skip
      (teams.currentHP[i] <= 0) && continue

      currentteam =  teams.team[i]
      otherteam = teams.team .!== currentteam

      for j in 1:teams.att_num[i]
        global team1count, team2count

        potential = findall(otherteam .& (teams.currentHP .> 0) .== 1)

        target = potential[1]

        attack_roll  = ceil.(rand(1)*20)[1] |> Integer
        attack_value = attack_roll + teams.hit_mod[i]

        hit = (teams.ac[target] <= attack_value)

        (attack_roll == 20) && (hit == true)
        (attack_roll == 1) && (hit == false)

        hit && (teams.currentHP[target] -= teams.dam_avg[i])

        team1count = sum(teams.currentHP[teams.team .== 1, :] .> 0)
        team2count = sum(teams.currentHP[teams.team .== 2, :] .> 0)

        ((team1count==0) | (team2count==0)) && break

      end

      ((team1count==0) | (team2count==0)) && break
    end

    (team1count==0) && (println("Team 2 wins!"); break)
    (team2count==0) && (println("Team 1 wins!"); break)

    (currentround > 10) && break

  end

# end

end # module


"Read response code from a packet header"
respcode(resp) = resp[1]
"Read response type from a packet header"
resptype(resp) = resp[2]
"Read response message length from a packet header"
resplen(resp) = 256*resp[3] + resp[4]

"Read confirmation response. Usually zero. Could be a warning (positive number)"
respconf(resp) = ntoh(reinterpret(Int32, resp[5:8])[1])
"Read error code in error responses (type 128)"
resperr(resp) = ntoh(reinterpret(Int32, resp[5:8])[1])
function respdata(resp)
    nrows = 256*resp[5] + resp[6]
    ncols = 256*resp[7] + resp[8]
    return nrows, ncols
end

"Read 4 byte integer response from type 8 messages"
respsinglevali(resp) = ntoh(reinterpret(Int32, resp[5:8])[1])
"Read 4 byte float response from type 9 messages"
respsinglevalf(resp) = ntoh(reinterpret(Float32, resp[5:8])[1])

"Is the packet a confirmation packet?"
ispackconf(resp) = resptype(resp) == 4
"Is the packet an error packet?"
ispackerr(resp) = resptype(resp) == 128

"Is the packet a single value packet?"
ispacksingleval(resp) = resptype(resp) ∈ (8,9)
"Is the packet a single integer value packet?"
ispacksinglevali(resp) = resptype(resp) == 8
"Is the packet a single float value packet?"
ispacksinglevalf(resp) = resptype(resp) == 9

"Is the packet a stream data packet?"
ispackstreamdata(resp) = resptype(resp) ∈ (15, 17, 19)
"Is the packet a stream data packet with 4-byte IEEE floats?"
ispackstreamfloat(resp) = resptype(resp) == 19
"Is the packet a stream data packet with raw 2-byte binary numbers?"
ispackstreambin2(resp) = resptype(resp) == 16
"Is the packet a stream data packet with raw 3-byte binary numbers?"
ispackstreambin3(resp) = resptype(resp) == 17

"Return the number of rows of array data packet"
strpacknum(resp) = Int(ntoh(reinterpret(UInt16, resp[5:6])[1]))
"Return the number of columns of array data packet"
strpacklen(resp) = Int(ntoh(reinterpret(UInt16, resp[7:8])[1]))

"Is the packet an array data packet with 4-byte IEEE floating point numbers?"
ispackarray(resp) = resptype(resp) == 33

"Is the packet a valid packet type?"
ispacktypevalid(resp) = resptype(resp) ∈ (4, 8, 9, 16, 17, 19, 33, 128)

"Read 8 byte header from socket"
function readdtcheader!(io, buf::AbstractVector{UInt8})

    readbytes!(io, buf, 8)

    return resptype(resp)
end


module Resolve

using DocStringExtensions

import ..ApFilterTrials
using ..Configuration
import ..Delayed
import ..GMM
import ..Graph
import ..IndexObjects
import ..LogGMM
import ..Resolve
import ..Samplers
import ..Trials

include("posterior.jl")
include("nodes.jl")

end # module Resolve


interval_markers(x::AbstractInterval{T,L,R}) where {T,L,R} = interval_markers(L, R)
interval_markers(L::Type{<:Bound}, R::Type{<:Bound}) = interval_marker.([L, R])
interval_marker(x::Type{Closed}) = :vline
interval_marker(x::Type{Open}) = :none

# TODO: Add support for plotting unbounded intervals
@recipe function f(xs::AbstractVector{<:AbstractInterval{T,L,R}}, ys) where {T, L <: Bounded, R <: Bounded}
    new_xs = T[]
    new_ys = []
    markers = Symbol[]
    for (x, y) in zip(xs, ys)
        # To cause line to not be connected, need to add a breaker point with
        # NaN in one of the coordinates. We put that in `new_ys` as that is probably a
        # float. where-as new_xs is potentially a DateTime etc that would not accept NaN
        append!(new_xs, [first(x), last(x), last(x)])
        append!(new_ys, [y, y, NaN])
        append!(markers, interval_markers(x))
        push!(markers, :none)
    end

    # Work around GR bug that shows :none as a marker
    # TODO: remove once https://github.com/jheinen/GR.jl/issues/295  is fixed
    markeralpha := [x == :none ? 0 : 1 for x in markers]

    markershape := markers
    seriestype  := :path  # always a path, even in a scatter plot
    new_xs, new_ys
end


using Printf

struct Atoms
    names::Array{String, 1}
    natom::Int64
    atom_types::Array{Int64, 1}
    acell::Array{Vector{Float64}, 1}
    cart_coors::Array{Vector{Float64}, 1}
    red_coors::Array{Vector{Float64}, 1}
end

function red2cart(red_coors::Array{Vector{Float64}, 1}, acell::Array{Vector{Float64}, 1})
    #= red coordinates -> cart coordinates =#
    natom = length(red_coors)
    acell_matrix = vcat(map(x -> x', acell)...)
    red_coors_matrix = vcat(map(x -> x', red_coors)...)
    cart_coors = red_coors_matrix * acell_matrix
    cart_coors = [cart_coors[index, :] for index in 1:natom]
    return cart_coors
end

function cart2red(cart_coors::Array{Vector{Float64}, 1}, acell::Array{Vector{Float64}, 1})
    #= cart coordinates -> red coordinates =#
    natom = length(cart_coors)
    acell_matrix = vcat(map(x -> x', acell)...)
    cart_coors_matrix = vcat(map(x -> x', cart_coors)...)
    cart_coors = cart_coors_matrix * inv(acell_matrix)
    cart_coors = [cart_coors[index, :] for index in 1:natom]
    return cart_coors
end

function poscar2Atoms(filename)
    lines = open(filename, "r") do f; readlines(f) end
    scale = parse(Float64, lines[2])
    acell = map(x -> parse.(Float64, x), split.(lines[3:5]))
    names = split(lines[6])
    n_atoms = parse.(Int64, split(lines[7]))
    natom = sum(n_atoms)
    atom_types = vcat([repeat([index], n) for (index, n) in enumerate(n_atoms)]...)
    is_selective::Bool = lines[8][1] in "sS"
    coordinate_type::Char = lines[8+is_selective][1]
    coordinates = map(x -> parse.(Float64, x[1:3]), split.(lines[9+is_selective:8+natom+is_selective]))
    if coordinate_type in "dD"
        cart_coors = red2cart(coordinates, scale*acell)
        red_coors = coordinates
    elseif coordinate_type in "cC"
        cart_coors = coordinates
        red_coors = cart2red(coordinates, scale*acell)
    end
    @assert length(coordinates) == natom
    return Atoms(names, natom, atom_types, scale*acell, cart_coors, red_coors)
end

function generate_salmon(atoms::Atoms, filename)
    #=
    generate salmon.input file
    =#
    names = atoms.names
    pseudo_files = map(x -> "./psps/$(x)", filter(x -> occursin(".fhi", x), readdir("./psps/")))
    open(filename, "w") do f
        # angst, eV, fs
        @printf(f, "&units\n")
        @printf(f, "  unit_system = 'A_eV_fs'\n")
        @printf(f, "/\n\n")

        # calculation
        @printf(f, "&calculation\n")
        @printf(f, "  calc_mode = 'GS'\n")
        @printf(f, "/\n\n")

        # controll
        @printf(f, "&control\n")
        @printf(f, "  sysname = 'test'\n")
        @printf(f, "/\n\n")

        # rgrid
        @printf(f, "&rgrid\n")
        @printf(f, "  dl = 0.10, 0.10, 0.10\n")
        # @printf(f, "  num_rgrid = 12, 12, 12\n")
        @printf(f, "/\n\n")

        # scf
        @printf(f, "&scf\n")
        @printf(f, "  ncg = 4\n")
        @printf(f, "  nscf = 1000\n")
        @printf(f, "  convergence = 'norm_rho_dng'\n")
        @printf(f, "  threshold_norm_rho = 1.0e-15\n")
        @printf(f, "/\n\n")

        # functional
        @printf(f, "&functional\n")
        @printf(f, "  xc = 'PZ'\n")
        @printf(f, "/\n\n")

        # analysis
        @printf(f, "&analysis\n")
        @printf(f, "  out_psi = 'y'\n")
        @printf(f, "  out_dos = 'y'\n")
        @printf(f, "  out_pdos = 'y'\n")
        @printf(f, "  out_dns = 'y'\n")
        @printf(f, "  out_elf = 'y'\n")
        @printf(f, "/\n\n")

        # pseudo
        zions = Array{Int64, 1}()
        @printf(f, "&pseudo\n")
        for (index, name) in enumerate(names)
            filename_psp = pseudo_files[index]
            @printf(f, "  pseudo_file(%d) = '%s'\n", index, filename_psp)
            open(filename_psp, "r") do f_psp
                for (index_line, line) in enumerate(eachline(f_psp))
                    if index_line == 2
                        zatom, zion = parse.(Float64, split(line)[1:2])
                        push!(zions, zion)
                        @printf(f, "  izatom(%d) = %d\n", index, zatom)
                    elseif index_line == 3
                        lmax, lloc = parse.(Int64, split(line)[3:4])
                        @printf(f, "  lmax_ps(%d) = %d\n", index, lmax)
                        @printf(f, "  lloc_ps(%d) = %d\n", index, lloc)
                        break
                    end
                end
            end
        end
        @printf(f, "/\n\n")

        # system
        nelec = sum(zions[atoms.atom_types])
        @printf(f, "&system\n")
        @printf(f, "  iperiodic = 0\n")
        @printf(f, "  al = % .4e, % .4e, % .4e\n", atoms.acell[1][1], atoms.acell[2][2], atoms.acell[3][3])
        @printf(f, "  nstate = %d\n", nelec/2)
        @printf(f, "  nelem = %d\n", length(atoms.names))
        @printf(f, "  natom = %d\n", atoms.natom)
        @printf(f, "  nelec = %d\n", nelec)
        @printf(f, "/\n\n")

        @printf(f, "&atomic_coor\n")
        for (coordinate, atom_id) in zip(atoms.cart_coors, atoms.atom_types)
            @printf(f, "  '%s' % .4f % .4f % .4f %d\n", names[atom_id], coordinate..., atom_id)
        end
        @printf(f, "/\n\n")

        # parallel
        # @printf(f, "&parallel\n")
        # @printf(f, "  nproc_k = 1\n")
        # @printf(f, "  nproc_ob = 1\n")
        # @printf(f, "  nproc_domain = 1 1 1\n")
        # @printf(f, "  nproc_domain_s = 1 1 1\n")
        # @printf(f, "/\n")
    end
end

function main()
    filename = "POSCAR"
    atoms = poscar2Atoms(filename)
    generate_salmon(atoms, "salmon.inp")
end

main()


import HTTP
import JSON
import CodecZlib
import TranscodingStreams

const country_to_currency_conversion = Base.ImmutableDict(
    "CA" => "CAD",
    "MX" => "MXN",
    "US" => "USD",
    "DK" => "DKK",
    "EE" => "EUR",
    "FI" => "EUR",
    "IE" => "EUR",
    "LV" => "EUR",
    "LT" => "EUR",
    "NO" => "NOK",
    "SE" => "SEK",
    "GB" => "GBP",
    "HR" => "EUR",
    "CY" => "EUR",
    "GR" => "EUR",
    "IT" => "EUR",
    "MT" => "EUR",
    "PT" => "EUR",
    "SI" => "EUR",
    "ES" => "EUR",
    "BG" => "EUR",
    "CZ" => "CZK",
    "HU" => "EUR",
    "PL" => "PLN",
    "RO" => "EUR",
    "RU" => "RUB",
    "SK" => "EUR",
    "AT" => "EUR",
    "BE" => "EUR",
    "FR" => "EUR",
    "DE" => "EUR",
    "LU" => "EUR",
    "NL" => "EUR",
    "CH" => "CHF",
    "AU" => "AUD",
    "NZ" => "NZD",
    "ZA" => "ZAR",
    "JP" => "JPY",
)

function download_decompressed_gzip_json(url::String)::Dict
    r = HTTP.get(url)
    decompressed = transcode(CodecZlib.GzipDecompressor, r.body)
    JSON.parse(String(decompressed))
end

function download_game_data()::Vector{Dict{String,Any}}
    url = "http://eshop-checker.xyz/games.json"
    result = download_decompressed_gzip_json(url)
    [info["price"] for info in result["list"] if any(x -> x !== 0, values(info["price"]))]
end

function download_currency_rate()::Dict{String,Any}
    url = "http://eshop-checker.xyz/beta/statics/conv_rate.json"
    result = download_decompressed_gzip_json(url)
    result["rates"]
end

function compute(data::Vector{Dict{String,Any}}, rate::Dict{String,Any})
    release_count = Dict([(area, 0) for area in Base.keys(country_to_currency_conversion)])
    price_count = Dict([(area, 0) for area in Base.keys(country_to_currency_conversion)])
    for game in data
        _price, _country = Inf, missing
        for (country, price) in filter(a -> a[1] !== 0, pairs(game))
            release_count[country] += 1
            normalized_price = price / rate[country_to_currency_conversion[country]]
            if normalized_price < _price
                _price, _country = normalized_price, country
            end
        end
        price_count[_country] += 1
    end
    release_count, price_count
end

function download_all()
    t_data = @async download_game_data()
    t_rate = @async download_currency_rate()
    fetch(t_data), fetch(t_rate)
end

function main()
    data, rate = download_all()

    release_count, price_count = compute(data, rate)

    most_count, c_country = findmax(price_count)
    most_rate, r_country = findmax(Dict([(area, price_win / get(release_count, area, Inf)) for (area, price_win) in price_count]))

    println("$c_country has $most_count games with good prices")
    println("$r_country has $(round(most_rate * 100, digits=2))% of games with good prices")
    println(release_count, price_count)
end


using MIDI
using StatsBase

# export scale_identification, SCALES
export note_to_fundamental, pitch_frequency

const SCALES = Dict{String, Vector{String}}()

SCALES["C Major/A minor"] = ["C", "D", "E", "F", "G", "A", "B"]
SCALES["C♯ Major/A♯ minor"] = ["C", "C♯", "D♯", "F", "F♯", "G♯", "A♯"]
SCALES["D Major/B minor"] = ["C♯", "D", "E", "F♯", "G", "A", "B"]
SCALES["D♯ Major/C minor"] = ["C", "D", "D♯", "F", "G", "G♯", "A♯"]
SCALES["E Major/C♯ minor"] = ["C♯", "D♯", "E", "F♯", "G♯", "A", "B"]
SCALES["F Major/D minor"] = ["C", "D", "E", "F", "G", "A", "A♯"]
SCALES["F♯ Major/D♯ minor"] = ["C♯", "D♯", "F", "F♯", "G♯", "A♯", "B"]
SCALES["G Major/E minor"] = ["C", "D", "E", "F♯", "G", "A", "B"]
SCALES["G♯ Major/F minor"] = ["C","C♯", "D♯", "F", "G", "G♯", "A♯"]
SCALES["A Major/F♯ minor"] = ["C♯", "D", "E", "F♯", "G♯", "A", "B"]
SCALES["A♯ Major/G minor"] = ["C", "D", "D♯", "F", "G", "A", "A♯"]
SCALES["B Major/G♯ minor"] = ["C♯", "D♯", "E", "F♯", "G♯", "A♯", "B"]
SCALES["C minor harmonic"] = ["C", "D", "D♯", "F", "G", "G♯", "B"]
SCALES["C♯ minor harmonic"] = ["C", "C♯","D♯", "E", "F♯", "G♯", "A"]
SCALES["D minor harmonic"] = ["C♯", "D", "E", "F", "G", "A", "A♯"]
SCALES["D♯ minor harmonic"] = ["B", "D", "D♯", "F", "F♯", "G♯", "A♯"]
SCALES["E minor harmonic"] = ["C", "D♯", "E", "F♯", "G", "A", "B"]
SCALES["F minor harmonic"] = ["C", "C♯", "E", "F", "G", "G♯", "A♯"]
SCALES["F♯ minor harmonic"] =  ["C♯", "D", "F", "F♯", "G♯", "A", "B"]
SCALES["G minor harmonic"] = ["C", "D", "D♯", "F♯", "G", "A", "A♯"]
SCALES["G♯ minor harmonic"] = ["C♯", "D♯", "E", "G", "G♯", "A♯", "B"]
SCALES["A minor harmonic"] = ["C", "D", "E", "F", "G♯", "A", "B"]
SCALES["A♯ minor harmonic"] = ["C", "C♯", "D♯", "F", "F♯", "A", "A♯"]
SCALES["B minor harmonic"] = ["C♯", "D", "E", "F♯", "G", "A♯", "B"]
SCALES["C minor melodic"] = ["C", "D", "D♯", "F", "G", "A", "B"]
SCALES["C♯ minor melodic"] = ["C", "C♯","D♯", "E", "F♯", "G♯", "A♯"]
SCALES["D minor melodic"] = ["C♯", "D", "E", "F", "G", "A", "B"]
SCALES["D♯ minor harmonic"] = ["C", "D", "D♯", "F", "F♯", "G♯", "A♯"]
SCALES["E minor melodic"] = ["C♯", "D♯", "E", "F♯", "G", "A", "B"]
SCALES["F minor melodic"] = ["C", "D", "E", "F", "G", "G♯", "A♯"]
SCALES["F♯ minor melodic"] =  ["C♯", "D♯", "F", "F♯", "G♯", "A", "B"]
SCALES["G minor melodic"] = ["C", "D", "E", "F♯", "G", "A", "A♯"]
SCALES["G♯ minor melodic"] = ["C♯", "D♯", "F", "G", "G♯", "A♯", "B"]
SCALES["A minor melodic"] = ["C", "D", "E", "F♯", "G♯", "A", "B"]
SCALES["A♯ minor melodic"] = ["C", "C♯", "D♯", "F", "G", "A", "A♯"]
SCALES["B minor melodic"] = ["C♯", "D", "E", "F♯", "G♯", "A♯", "B"]

"""
    note_to_fundamental(note(s))

Return a `String` or `Vector{String}` with the fundamental pitch of the notes
(i.e. without the octave information).
"""
note_to_fundamental(n::AbstractNote) = MIDI.PITCH_TO_NAME[mod(n.pitch, 12)]
note_to_fundamental(notes) = note_to_fundamental.(notes)

"""
    pitch_frequency(notes)

Return the pitches (without octaves) in the given `notes`,
sorted by most to least frequent.
"""
pitch_frequency(notes) = pitch_frequency(note_to_fundamental(notes))
function pitch_frequency(fundamentals::Vector{String})
    occurency = sort(Dict(value => key for (key, value) in countmap(fundamentals)), rev = true)
    most_frequent = collect(values(occurency))
    return most_frequent
end

"""
    scale_identification(notes)

Return the most probable scale that represents the given `notes`.
"""
function scale_identification(notes)
    fund = note_to_fundamental(notes)
    mostfreq = pitch_frequency(fund)[1:7] # only 7 most frequent
    for (k,v) in SCALES
        tester = true
        for n in mostfreq
            if n ∉ v
                tester = false
            end
        end
        if tester == true
            return k
        end
    end
    if tester == false
        error("Atonal/modulating musical key center or Unregistered exotic scale")
    end
end


using YaoPlots, Yao
using Test, Compose

@testset "LabelBlock" begin
    x = put(5, (2,3)=>matblock(rand_unitary(4)))
    cb = LabelBlock(x, "x")
    @test mat(copy(cb)) == mat(cb)
    @test isunitary(cb)
    @test ishermitian(cb) == ishermitian(x)
    @test isreflexive(cb) == isreflexive(x)
    @test mat(cb) == mat(x)
    reg = rand_state(5)
    @test apply!(copy(reg), cb) ≈ apply!(copy(reg), x)
    @test cb' isa LabelBlock && mat(cb') ≈ mat(cb)'
    @test (cb').name == "x†" && (cb'').name == "x"

    y = put(5, (3,4)=>matblock(rand_unitary(4)))
    cc = chsubblocks(cb, y)
    @test YaoPlots.is_continuous_chunk([1,2,3]) == true
    @test YaoPlots.is_continuous_chunk([1,2,4]) == false
    @test YaoPlots.is_continuous_chunk([3,2,4]) == true

    c1 = chain(5, [put(5, (2,3)=>label(SWAP, "SWAP")), put(5, 2=>label(I2, "id")), put(5, 2=>label(X, "X")), control(5, (5,3), (2,4,1)=>put(3, (1,3)=>label(SWAP, "SWAP")))])
    c2 = chain(5, [put(5, (2,3)=>label(SWAP, "SWAP")), put(5, 2=>label(I2, "id")), put(5, 2=>label(X, "X")), control(5, (5,3), (2,4,1)=>put(3, (1,2)=>label(SWAP, "SWAP")))])

    @test vizcircuit(c1) isa Compose.Context
    @test_throws ErrorException vizcircuit(c2)
end


"""

    heattransfer(myARGS)

Extract command-line arguments for computing the radiative heat transfer among a collection
of molecules, then compute the radiative heat transfer appropriately.

!!! note

    Any frequencies will be converted to their absolute values. Frequencies are not allowed
    to be exactly zero.

!!! note

    This computes the integrand
    ``\\operatorname{Tr}(\\operatorname{asym}(\\mathbb{V}_{m}^{-1}) \\mathbb{P}_{m} \\mathbb{T}^{\\dagger} \\operatorname{asym}(\\mathbb{P}_{n} \\mathbb{G}^{\\mathrm{env}}) \\mathbb{T}\\mathbb{P}_{m})``
    at specified real frequencies, without doing any integration. This includes neither
    numerical prefactors outside of the trace, nor the Planck function.

!!! tip

    This function can be embarrassingly parallelized over frequency and wavevector. However,
    the integrand at each frequency and wavevector is calculated for every transformation,
    as this maximizes the efficiency of the program by virtue of reusing quantities
    independent of geometric transformations.

"""
function heattransfer(myARGS)

    mollistfilename = readRequiredFilenameARG(myARGS, "mollistfilename", "AARMBEM_heat.jl");
    outfilename = readRequiredFilenameARG(myARGS, "outfilename", "AARMBEM_heat.jl");

    translistfilename = readOptionalFilenameARG(myARGS, "translistfilename");
    periodicfilename = readOptionalFilenameARG(myARGS, "periodicfilename");

    Genvstr = readGenvstr(myARGS);

    nophonons, outfilename = readOptionalBoolcondARG(myARGS, "nophonons", outfilename);
    nonretarded, outfilename = readOptionalBoolcondARG(myARGS, "nonretarded", outfilename);

    FT = readOptionalFloatType(myARGS);
    println("FT is ", FT);

    myAllMolData = readMolSystem(mollistfilename, nophonons, FT);
    myTransData = readTransData(myAllMolData, translistfilename);
    myPeriodicData = readPeriodicData(FT, periodicfilename);

    freqlist = Array{FT, 1}(undef, 0);
    readfreqlist!(freqlist, myARGS, "AARMBEM_heat.jl");

    klist = readklist(myARGS, "AARMBEM_heat.jl", myPeriodicData.numdims, FT);

    freqklist = hcat(vec(repeat(transpose(freqlist), size(klist, 1), 1)),
                     repeat(klist, length(freqlist), 1));

    heattransfer_general(outfilename, myAllMolData, myTransData, myPeriodicData, Genvstr,
                      nonretarded, freqklist)

end

"""

    heattransfer_general(outfilename, myAllMolData, myTransData, myPeriodicData, Genvstr,
                         nonretarded, freqklist)

Compute the actual radiative heat transfer integrand and print it to `outfilename`.

"""
function heattransfer_general(outfilename::AbstractString,
                              myAllMolData::MolSystem{<:OneMol{FT}},
                              myTransData::TransData{FT},
                              myPeriodicData::PeriodicData{FT},
                              Genvstr::AbstractString, nonretarded::Bool,
                              freqklist::Array{FT, 2}) where FT<:AbstractFloat

    GFSCAGG! = GFVACGG_sca!;
    if (Genvstr == "PEC")
        GFSCAGG! = GFPECGG!;
    end

    totalTinvmat = SharedArray{Complex{FT}}(3*myAllMolData.cumnumatomslist[end],
                                            3*myAllMolData.cumnumatomslist[end]);
    totalGenvmat = SharedArray{Complex{FT}}(3*myAllMolData.cumnumatomslist[end],
                                            3*myAllMolData.cumnumatomslist[end]);
    totalTmat = SharedArray{Complex{FT}}(3*myAllMolData.cumnumatomslist[end],
                                         3*myAllMolData.cumnumatomslist[end]);
    Phimn = zeros(FT, myAllMolData.nummols, myAllMolData.nummols);

    for ll=1:size(freqklist, 1)

        currfreq = abs(freqklist[ll, 1]);

        currfreqG = currfreq;
        if (nonretarded)
            currfreqG = zero(currfreqG);
        end
        currk = vec(freqklist[ll, 2:4]);
        fill!(totalTinvmat, zero(eltype(totalTinvmat)));
        fill!(totalGenvmat, zero(eltype(totalGenvmat)));
        fill!(totalTmat, zero(eltype(totalTmat)));

        for nn=1:myAllMolData.nummols

            currumolind = myAllMolData.uniquemolinds[nn];

            if (currumolind > -1)

                constructMolAlpha!(myAllMolData.uniqueMolDataArray[currumolind],
                                   myPeriodicData, currfreq, currk);

                assemble1MolGvacinf!(myAllMolData.uniqueMolDataArray[currumolind],
                                     myPeriodicData, currfreqG, currk);

                assemble1MolGvacinfdiag!(myAllMolData.uniqueMolDataArray[currumolind],
                                         currfreqG);

            end

        end

        for tt=1:myTransData.numTrans

            transformAtomPos!(myTransData, myAllMolData, tt);

            for nn2=1:myAllMolData.nummols

                startidx2 = 1;
                if (nn2 > 1)
                    startidx2 = 3*myAllMolData.cumnumatomslist[nn2-1] + 1;
                end
                currmolind2 = myAllMolData.uniquemolinds[nn2];
                if (currmolind2 < 0)
                    currmolind2 = myAllMolData.dupemolinds[nn2];
                end

                if (myTransData.changedFromBefore[nn2, tt])
                    assembleTinvGenvDiagBlock!(totalTinvmat, totalGenvmat, myAllMolData,
                                               myTransData, nn2, currmolind2, startidx2,
                                               tt, myPeriodicData, GFSCAGG!, currfreqG,
                                               currk);
                end

            end

            for nn2=1:myAllMolData.nummols

                startidx2 = 1;
                if (nn2 > 1)
                    startidx2 = 3*myAllMolData.cumnumatomslist[nn2-1] + 1;
                end

                for nn1=(nn2+1):myAllMolData.nummols

                    startidx1 = 3*myAllMolData.cumnumatomslist[nn1-1] + 1;

                    if (myTransData.changedFromBefore[nn2, tt] ||
                        myTransData.changedFromBefore[nn1, tt])

                        assembleTinvGenvOffDiagBlock!(totalTinvmat, totalGenvmat,
                                                      myAllMolData, myTransData, nn1, nn2,
                                                      startidx1, startidx2, myPeriodicData,
                                                      GFSCAGG!, currfreqG, currk);
                    end
                end

            end

            if (myPeriodicData.numdims > 0)

                for nn2=1:myAllMolData.nummols

                    startidx2 = 1;
                    if (nn2 > 1)
                        startidx2 = 3*myAllMolData.cumnumatomslist[nn2-1] + 1;
                    end

                    for nn1=(nn2+1):myAllMolData.nummols

                        startidx1 = 3*myAllMolData.cumnumatomslist[nn1-1] + 1;

                        if (myTransData.changedFromBefore[nn2, tt] ||
                            myTransData.changedFromBefore[nn1, tt])

                            assembleTinvGenvOffDiagBlock!(totalTinvmat, totalGenvmat,
                                                          myAllMolData, myTransData, nn2, nn1,
                                                          startidx2, startidx1, myPeriodicData,
                                                          GFSCAGG!, currfreqG, currk);
                        end
                    end

                end
            end

            totalTmat .= inv(totalTinvmat);

            assemblePhimn!(Phimn, myAllMolData, myPeriodicData, totalTmat, totalGenvmat);

            outfile = open(outfilename, "a+");
            if (myPeriodicData.numdims == 0)

                @printf(outfile, "%s %.16e", myTransData.transLabels[tt], abs(currfreq));
                for nn1=1:myAllMolData.nummols
                    for nn2=1:myAllMolData.nummols
                        @printf(outfile, " %.16e", Phimn[nn1, nn2]);;
                    end
                end
                @printf(outfile, "\n");
            else
                @printf(outfile, "%s %.16e %.16e %.16e %.16e", myTransData.transLabels[tt],
                        abs(currfreq), currk[1], currk[2], currk[3]);
                for nn1=1:myAllMolData.nummols
                    for nn2=1:myAllMolData.nummols
                        @printf(outfile, " %.16e", Phimn[nn1, nn2]);
                    end
                end
                @printf(outfile, "\n%s %.16e %.16e %.16e %.16e",
                        myTransData.transLabels[tt], abs(currfreq), -1*currk[1],
                        -1*currk[2], -1*currk[3]);
                for nn1=1:myAllMolData.nummols
                    for nn2=1:myAllMolData.nummols
                        @printf(outfile, " %.16e", Phimn[nn2, nn1]);
                    end
                end
                @printf(outfile, "\n");
            end
            close(outfile);

            untransformAtomPos!(myTransData, myAllMolData, tt);

        end

    end

end

"""

    assemblePhimn!(Phimn::Array{FT, 2},
                   myAllMolData::MolSystem{OneMol_WithPhonons{FT}},
                   myPeriodicData::PeriodicData{FT},
                   totalTmat::SharedArray{Complex{FT}, 2},
                   totalGenvmat::SharedArray{Complex{FT}, 2})

Assemble the matrix `Phimn`, whose elements `Phimn[nn1, nn2]` represent the energy transfer
from molecule `nn1` to molecule `nn2`, computed from trace expressions involving the
matrices `totalTmat` and `totalGenvmat`, in-place. If there is no spatial periodicity,
`Phimn` should be symmetric. If there is spatial periodicity, the transpose of `Phimn` at
wavevector ``\\vec{k}`` is the equivalent of computing `Phimn` at ``-\\vec{k}``. This assumes
the presence of phonons.

"""
function assemblePhimn!(Phimn::Array{FT, 2},
                        myAllMolData::MolSystem{OneMol_WithPhonons{FT}},
                        myPeriodicData::PeriodicData{FT},
                        totalTmat::SharedArray{Complex{FT}, 2},
                        totalGenvmat::SharedArray{Complex{FT}, 2}) where FT<:AbstractFloat

    for nn2=1:myAllMolData.nummols

        startidx2 = 1;
        if (nn2 > 1)
            startidx2 = 3*myAllMolData.cumnumatomslist[nn2-1] + 1;
        end
        Genvview = view(totalGenvmat,
                        (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                        1:3*myAllMolData.cumnumatomslist[end]);

        startmol1 = 1;
        if (myPeriodicData.numdims == 0)
            startmol1 = nn2;
        end

        for nn1=startmol1:myAllMolData.nummols

            startidx1 = 1;
            if (nn1 > 1)
                startidx1 = 3*myAllMolData.cumnumatomslist[nn1-1] + 1;
            end
            Tview = view(totalTmat,
                         1:3*myAllMolData.cumnumatomslist[end],
                         (startidx1-1).+(1:3*myAllMolData.numatomslist[nn1]));

            currmolind1 = 1;
            if (myAllMolData.dupemolinds[nn1] == -1)
                currmolind1 = myAllMolData.uniquemolinds[nn1];
            else
                currmolind1 = myAllMolData.dupemolinds[nn1];
            end
            Phimn[nn1, nn2] =
            imag(tr(ahermpart(myAllMolData.uniqueMolDataArray[currmolind1].alphainv) *
                    adjoint(Tview)[:, (startidx2-1) .+
                                   (1:3*myAllMolData.numatomslist[nn2])] *
                    Genvview * Tview));
            if (myPeriodicData.numdims == 0)
                Phimn[nn2, nn1] = Phimn[nn1, nn2];
            end
        end

    end

end

"""

    assemblePhimn!(Phimn::Array{FT, 2},
                   myAllMolData::MolSystem{OneMol_NoPhonons{FT}},
                   myPeriodicData::PeriodicData{FT},
                   totalTmat::SharedArray{Complex{FT}, 2},
                   totalGenvmat::SharedArray{Complex{FT}, 2})

Assemble the matrix `Phimn`, whose elements `Phimn[nn1, nn2]` represent the energy transfer
from molecule `nn1` to molecule `nn2`, computed from trace expressions involving the
matrices `totalTmat` and `totalGenvmat`, in-place. If there is no spatial periodicity,
`Phimn` should be symmetric. If there is spatial periodicity, the transpose of `Phimn` at
wavevector ``\\vec{k}`` is the equivalent of computing `Phimn` at ``-\\vec{k}``. This assumes
the absence of phonons.

"""
function assemblePhimn!(Phimn::Array{FT, 2},
                        myAllMolData::MolSystem{OneMol_NoPhonons{FT}},
                        myPeriodicData::PeriodicData{FT},
                        totalTinvmat::SharedArray{Complex{FT}, 2},
                        totalGenvmat::SharedArray{Complex{FT}, 2}) where FT<:AbstractFloat

    for nn2=1:myAllMolData.nummols

        startidx2 = 1;
        if (nn2 > 1)
            startidx2 = 3*myAllMolData.cumnumatomslist[nn2-1] + 1;
        end
        Genvview = view(totalGenvmat,
                        (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                        1:3*myAllMolData.cumnumatomslist[end]);

        startmol1 = 1;
        if (myPeriodicData.numdims == 0)
            startmol1 = nn2;
        end

        for nn1=startmol1:myAllMolData.nummols

            startidx1 = 1;
            if (nn1 > 1)
                startidx1 = 3*myAllMolData.cumnumatomslist[nn1-1] + 1;
            end
            Tview = view(totalTmat,
                         1:3*myAllMolData.cumnumatomslist[end],
                         (startidx1-1).+(1:3*myAllMolData.numatomslist[nn1]));

            currmolind1 = 1;
            if (myAllMolData.dupemolinds[nn1] == -1)
                currmolind1 = myAllMolData.uniquemolinds[nn1];
            else
                currmolind1 = myAllMolData.dupemolinds[nn1];
            end
            Phimn[nn1, nn2] =
            imag(tr(mulMatDiagleft(adjoint(Tview)[:, (startidx2-1) .+
                                                  (1:3*myAllMolData.numatomslist[nn2])] *
                                   Genvview * Tview,
                                   vecNto3N(imag.(myAllMolData.uniqueMolDataArray[currmolind1].alphainv)))));
            if (myPeriodicData.numdims == 0)
                Phimn[nn2, nn1] = Phimn[nn1, nn2];
            end
        end

    end

end

"""

    assembleTinvGenvDiagBlock!(totalTinvmat::SharedArray{Complex{FT}, 2},
                               totalGenvmat::SharedArray{Complex{FT}, 2},
                               myAllMolData::MolSystem{OneMol_WithPhonons{FT}},
                               myTransData::TransData{FT}, nn2::Integer,
                               currmolind2::Integer, startidx2::Integer,
                               tt::Integer, myPeriodicData::PeriodicData{FT},
                               GFSCAGG!::Function,
                               currfreqG::Union{FT, Complex{FT}},
                               currk::Array{FT, 1}=zeros(FT, 3)) where FT<:AbstractFloat

Assemble the molecular diagonal block of the scattering Green's function interaction matrix
of molecule `nn2` (whose unique molecular index `currmolind2` is precomputed and passed as
an argument) at frequency `currfreqG` and wavevector `currk` using `GFSCAGG!`, then add the
vacuum contribution of molecule `nn2`, performing any required matrix rotations
corresponding to transformation `tt` for molecule `nn2`. Stamp that added to coincident
contributions into
`totalGenvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]), (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])]`,
or that (without coincident contributions) subtracted from the inverse molecular
susceptibility into
`totalTinvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]), (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])]`.
Finally, undo any rotation corresponding to transformation `tt` in the inverse T-operator of
molecule `nn2` for reuse with future transformations. This assumes the presence of phonons.

"""
function assembleTinvGenvDiagBlock!(totalTinvmat::SharedArray{Complex{FT}, 2},
                                    totalGenvmat::SharedArray{Complex{FT}, 2},
                                    myAllMolData::MolSystem{OneMol_WithPhonons{FT}},
                                    myTransData::TransData{FT}, nn2::Integer,
                                    currmolind2::Integer, startidx2::Integer,
                                    tt::Integer, myPeriodicData::PeriodicData{FT},
                                    GFSCAGG!::Function,
                                    currfreqG::Union{FT, Complex{FT}},
                                    currk::Array{FT, 1}=zeros(FT, 3)) where FT<:AbstractFloat

    tempview = view(totalGenvmat, (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                    (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]));

    fill!(tempview, zero(eltype(tempview)));

    assembleGscaMolBlock!(tempview, myAllMolData, myTransData, nn2, nn2, 1, 1,
                          myPeriodicData, GFSCAGG!, currfreqG, currk);

    rotate3Nmat!(myAllMolData.uniqueMolDataArray[currmolind2].alphainv,
                 myTransData.rotArray[3*nn2-2:3*nn2, 3*tt-2:3*tt] *
                 myTransData.baselineRot[3*nn2-2:3*nn2, :]);

    rotate3Nmat!(myAllMolData.uniqueMolDataArray[currmolind2].GvacGGinf,
                 myTransData.rotArray[3*nn2-2:3*nn2, 3*tt-2:3*tt] *
                 myTransData.baselineRot[3*nn2-2:3*nn2, :]);

    tempview .+= myAllMolData.uniqueMolDataArray[currmolind2].GvacGGinf[:, :];

    totalTinvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                 (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])] .=
    myAllMolData.uniqueMolDataArray[currmolind2].alphainv .- tempview;

    addtodiag!(tempview,
               vecNto3N(myAllMolData.uniqueMolDataArray[currmolind2].GvacGGinfdiag));

    rotate3Nmat!(myAllMolData.uniqueMolDataArray[currmolind2].alphainv,
                 transpose(myTransData.rotArray[3*nn2-2:3*nn2, 3*tt-2:3*tt] *
                           myTransData.baselineRot[3*nn2-2:3*nn2, :]));

    rotate3Nmat!(myAllMolData.uniqueMolDataArray[currmolind2].GvacGGinf,
                 transpose(myTransData.rotArray[3*nn2-2:3*nn2, 3*tt-2:3*tt] *
                           myTransData.baselineRot[3*nn2-2:3*nn2, :]));

end

"""

    assembleTinvGenvDiagBlock!(totalTinvmat::SharedArray{Complex{FT}, 2},
                               totalGenvmat::SharedArray{Complex{FT}, 2},
                               myAllMolData::MolSystem{OneMol_NoPhonons{FT}},
                               myTransData::TransData{FT}, nn2::Integer,
                               currmolind2::Integer,
                               startidx2::Integer,
                               tt::Integer, myPeriodicData::PeriodicData{FT},
                               GFSCAGG!::Function,
                               currfreqG::Union{FT, Complex{FT}},
                               currk::Array{FT, 1}=zeros(FT, 3)) where FT<:AbstractFloat

Assemble the molecular diagonal block of the scattering Green's function interaction matrix
of molecule `nn2` (whose unique molecular index `currmolind2` is precomputed and passed as
an argument) at frequency `currfreqG` and wavevector `currk` using `GFSCAGG!`, then add the
vacuum contribution of molecule `nn2`, performing any required matrix rotations
corresponding to transformation `tt` for molecule `nn2`. Stamp that added to coincident
contributions into
`totalGenvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]), (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])]`,
or that (without coincident contributions) subtracted from the inverse molecular
susceptibility into
`totalTinvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]), (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])]`.
Finally, undo any rotation corresponding to transformation `tt` in the inverse T-operator of
molecule `nn2` for reuse with future transformations. This assumes the absence of phonons.

"""
function assembleTinvGenvDiagBlock!(totalTinvmat::SharedArray{Complex{FT}, 2},
                                    totalGenvmat::SharedArray{Complex{FT}, 2},
                                    myAllMolData::MolSystem{OneMol_NoPhonons{FT}},
                                    myTransData::TransData{FT}, nn2::Integer,
                                    currmolind2::Integer,
                                    startidx2::Integer,
                                    tt::Integer, myPeriodicData::PeriodicData{FT},
                                    GFSCAGG!::Function,
                                    currfreqG::Union{FT, Complex{FT}},
                                    currk::Array{FT, 1}=zeros(FT, 3)) where FT<:AbstractFloat

    tempview = view(totalGenvmat, (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                    (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]));

    fill!(tempview, zero(eltype(tempview)));

    assembleGscaMolBlock!(tempview, myAllMolData, myTransData, nn2, nn2, 1, 1,
                          myPeriodicData, GFSCAGG!, currfreqG, currk);

    rotate3Nmat!(myAllMolData.uniqueMolDataArray[currmolind2].GvacGGinf,
                 myTransData.rotArray[3*nn2-2:3*nn2, 3*tt-2:3*tt] *
                 myTransData.baselineRot[3*nn2-2:3*nn2, :]);

    tempview .+= myAllMolData.uniqueMolDataArray[currmolind2].GvacGGinf[:, :];

    totalTinvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                 (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])] .=
    subtractfromdiag(tempview,
                     vecNto3N(myAllMolData.uniqueMolDataArray[currmolind2].alphainv));

    addtodiag!(tempview,
               vecNto3N(myAllMolData.uniqueMolDataArray[currmolind2].GvacGGinfdiag));

    rotate3Nmat!(myAllMolData.uniqueMolDataArray[currmolind2].GvacGGinf,
                 transpose(myTransData.rotArray[3*nn2-2:3*nn2, 3*tt-2:3*tt] *
                           myTransData.baselineRot[3*nn2-2:3*nn2, :]));

end

"""

    assembleTinvGenvOffDiagBlock!(totalTinvmat::SharedArray{Complex{FT}, 2},
                                  totalGenvTinvmat::SharedArray{Complex{FT}, 2},
                                  myAllMolData::MolSystem{<:OneMol{FT}},
                                  myTransData::TransData{FT},
                                  nn1::Integer, nn2::Integer,
                                  startidx1::Integer,
                                  startidx2::Integer,
                                  myPeriodicData::PeriodicData{FT},
                                  GFSCAGG!::Function,
                                  currfreqG::Union{FT, Complex{FT}},
                                  currk::Array{FT, 1}=zeros(FT, 3)) where FT<:AbstractFloat

Assemble the molecular off-diagonal block of the scattering Green's function interaction
matrix of molecules `nn2` as the source and `nn1` as the field (whose respective unique
molecular indices `currmolind1` and `currmolind2` are precomputed and passed as arguments)
at frequency `currfreqG` and wavevector `currk` using `GFSCAGG!`, multiply on
the left by the electronic polarizabilities, add the vacuum contribution, and stamp that
into
`totalGenvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]), (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])]`
and its negative into
`totalTinvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]), (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])]`.
If there is no spatial periodicity, do the same for the (unconjugated) transpose, swapping
`nn2` and `nn1` without performing further Green's function computations.

"""
function assembleTinvGenvOffDiagBlock!(totalTinvmat::SharedArray{Complex{FT}, 2},
                                       totalGenvmat::SharedArray{Complex{FT}, 2},
                                       myAllMolData::MolSystem{<:OneMol{FT}},
                                       myTransData::TransData{FT},
                                       nn1::Integer, nn2::Integer,
                                       startidx1::Integer,
                                       startidx2::Integer,
                                       myPeriodicData::PeriodicData{FT},
                                       GFSCAGG!::Function,
                                       currfreqG::Union{FT, Complex{FT}},
                                       currk::Array{FT, 1}=zeros(FT, 3)) where FT<:AbstractFloat

    tempview = view(totalGenvmat, (startidx1-1).+(1:3*myAllMolData.numatomslist[nn1]),
                    (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]));
    fill!(tempview, zero(eltype(tempview)));

    assembleGvacMolBlock!(tempview, myAllMolData, myTransData, nn1, nn2, 1, 1,
                          myPeriodicData, currfreqG, currk);
    assembleGscaMolBlock!(tempview, myAllMolData, myTransData, nn1, nn2, 1, 1,
                          myPeriodicData, GFSCAGG!, currfreqG, currk);

    totalTinvmat[(startidx1-1).+(1:3*myAllMolData.numatomslist[nn1]),
                 (startidx2-1).+(1:3*myAllMolData.numatomslist[nn2])] .=
    -1 .* tempview;

    if (myPeriodicData.numdims == 0)
        totalGenvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                     (startidx1-1).+(1:3*myAllMolData.numatomslist[nn1])] .=
        transpose(deepcopy(tempview));
        totalTinvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                     (startidx1-1).+(1:3*myAllMolData.numatomslist[nn1])] .= -1 .*
        totalGenvmat[(startidx2-1).+(1:3*myAllMolData.numatomslist[nn2]),
                     (startidx1-1).+(1:3*myAllMolData.numatomslist[nn1])];
    end

end


## Triangular
immutable Triangular{T,S<:AbstractMatrix{T},UpLo,IsUnit} <: AbstractMatrix{T}
    data::S
end
function Triangular{T}(A::AbstractMatrix{T}, uplo::Symbol, isunit::Bool=false)
    uplo != :L && uplo != :U && throw(ArgumentError("uplo argument must be either :U or :L"))
    return Triangular{T,typeof(A),uplo,isunit}(A)
end

######################
# BlasFloat routines #
######################

### Note! the BlasFloat restriction can be removed if generic triangular multiplication methods are written.
for (func1, func2) in ((:*, :A_mul_B!), (:Ac_mul_B, :Ac_mul_B!), (:/, :A_rdiv_B!))
    @eval begin
        ($func1){T<:BlasFloat,S<:StridedMatrix,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::Triangular{T,S,UpLo,IsUnit}) = ($func2)(A, full(B))
        ($func1){T<:BlasFloat,S<:StridedMatrix,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::StridedVecOrMat) = ($func2)(A, copy(B))
    end
end
for (func1, func2) in ((:A_mul_Bc, :A_mul_Bc!), (:A_rdiv_Bc, :A_rdiv_Bc!))
    @eval begin
        ($func1){T<:BlasFloat,S<:StridedMatrix,UpLo,IsUnit}(A::StridedMatrix, B::Triangular{T,S,UpLo,IsUnit}) = ($func2)(copy(A), B)
    end
end

# Vector multiplication
A_mul_B!{T<:BlasFloat,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, b::StridedVector{T}) = BLAS.trmv!(UpLo == :L ? 'L' : 'U', 'N', IsUnit ? 'U' : 'N', A.data, b)
Ac_mul_B!{T<:BlasReal,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, b::StridedVector{T}) = BLAS.trmv!(UpLo == :L ? 'L' : 'U', 'T', IsUnit ? 'U' : 'N', A.data, b)
Ac_mul_B!{T<:BlasComplex,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, b::StridedVector{T}) = BLAS.trmv!(UpLo == :L ? 'L' : 'U', 'C', IsUnit ? 'U' : 'N', A.data, b)
At_mul_B!{T<:BlasFloat,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, b::StridedVector{T}) = BLAS.trmv!(UpLo == :L ? 'L' : 'U', 'T', IsUnit ? 'U' : 'N', A.data, b)

# Matrix multiplication
A_mul_B!{T<:BlasFloat,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::StridedMatrix{T}) = BLAS.trmm!('L', UpLo == :L ? 'L' : 'U', 'N', IsUnit ? 'U' : 'N', one(T), A.data, B)
A_mul_B!{T<:BlasFloat,S,UpLo,IsUnit}(A::StridedMatrix{T}, B::Triangular{T,S,UpLo,IsUnit}) = BLAS.trmm!('R', UpLo == :L ? 'L' : 'U', 'N', IsUnit ? 'U' : 'N', one(T), B.data, A)
Ac_mul_B!{T<:BlasComplex,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::StridedMatrix{T}) = BLAS.trmm!('L', UpLo == :L ? 'L' : 'U', 'C', IsUnit ? 'U' : 'N', one(T), A.data, B)
Ac_mul_B!{T<:BlasReal,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::StridedMatrix{T}) = BLAS.trmm!('L', UpLo == :L ? 'L' : 'U', 'T', IsUnit ? 'U' : 'N', one(T), A.data, B)
A_mul_Bc!{T<:BlasComplex,S,UpLo,IsUnit}(A::StridedMatrix{T}, B::Triangular{T,S,UpLo,IsUnit}) = BLAS.trmm!('R', UpLo == :L ? 'L' : 'U', 'C', IsUnit ? 'U' : 'N', one(T), B.data, A)
A_mul_Bc!{T<:BlasReal,S,UpLo,IsUnit}(A::StridedMatrix{T}, B::Triangular{T,S,UpLo,IsUnit}) = BLAS.trmm!('R', UpLo == :L ? 'L' : 'U', 'T', IsUnit ? 'U' : 'N', one(T), B.data, A)

A_ldiv_B!{T<:BlasFloat,S<:AbstractMatrix,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::StridedVecOrMat{T}) = LAPACK.trtrs!(UpLo == :L ? 'L' : 'U', 'N', IsUnit ? 'U' : 'N', A.data, B)
function \{T<:BlasFloat,S<:AbstractMatrix,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::StridedVecOrMat{T})
    x = A_ldiv_B!(A, copy(B))
    errors = LAPACK.trrfs!(UpLo == :L ? 'L' : 'U', 'N', IsUnit ? 'U' : 'N', A.data, B, x)
    all(isfinite, [errors...]) || all([errors...] .< one(T)/eps(T)) || warn("""Unreasonably large error in computed solution:
forward errors:
$(errors[1])
backward errors:
$(errors[2])""")
    x
end
Ac_ldiv_B!{T<:BlasReal,S<:AbstractMatrix,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::StridedVecOrMat{T}) = LAPACK.trtrs!(UpLo == :L ? 'L' : 'U', 'T', IsUnit ? 'U' : 'N', A.data, B)
Ac_ldiv_B!{T<:BlasComplex,S<:AbstractMatrix,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::StridedVecOrMat{T}) = LAPACK.trtrs!(UpLo == :L ? 'L' : 'U', 'C', IsUnit ? 'U' : 'N', A.data, B)

A_rdiv_B!{T<:BlasFloat,S<:AbstractMatrix,UpLo,IsUnit}(A::StridedVecOrMat{T}, B::Triangular{T,S,UpLo,IsUnit}) = BLAS.trsm!('R', UpLo == :L ? 'L' : 'U', 'N', IsUnit ? 'U' : 'N', one(T), B.data, A)
A_rdiv_Bc!{T<:BlasReal,S<:AbstractMatrix,UpLo,IsUnit}(A::StridedMatrix{T}, B::Triangular{T,S,UpLo,IsUnit}) = BLAS.trsm!('R', UpLo == :L ? 'L' : 'U', 'T', IsUnit ? 'U' : 'N', one(T), B.data, A)
A_rdiv_Bc!{T<:BlasComplex,S<:AbstractMatrix,UpLo,IsUnit}(A::StridedMatrix{T}, B::Triangular{T,S,UpLo,IsUnit}) = BLAS.trsm!('R', UpLo == :L ? 'L' : 'U', 'C', IsUnit ? 'U' : 'N', one(T), B.data, A)

inv{T<:BlasFloat,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = Triangular{T,S,UpLo,IsUnit}(LAPACK.trtri!(UpLo == :L ? 'L' : 'U', IsUnit ? 'U' : 'N', copy(A.data)))

#Eigensystems
function eigvecs{T<:BlasFloat,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit})
    if UpLo == :U
        V = LAPACK.trevc!('R', 'A', Array(Bool,1), A.data)
    else # Uplo == :L
        V = LAPACK.trevc!('L', 'A', Array(Bool,1), A.data')
    end
    for i=1:size(V,2) #Normalize
        V[:,i] /= norm(V[:,i])
    end
    V
end

function cond{T<:BlasFloat,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, p::Real=2)
    chksquare(A)
    if p==1
        return inv(LAPACK.trcon!('O', UpLo == :L ? 'L' : 'U', IsUnit ? 'U' : 'N', A.data))
    elseif p==Inf
        return inv(LAPACK.trcon!('I', UpLo == :L ? 'L' : 'U', IsUnit ? 'U' : 'N', A.data))
    else #use fallback
        return cond(full(A), p)
    end
end

####################
# Generic routines #
####################

size(A::Triangular, args...) = size(A.data, args...)

convert{T,S,UpLo,IsUnit}(::Type{Triangular{T,S,UpLo,IsUnit}}, A::Triangular{T,S,UpLo,IsUnit}) = A
convert{T,S,UpLo,IsUnit}(::Type{Triangular{T,S,UpLo,IsUnit}}, A::Triangular) = Triangular{T,S,UpLo,IsUnit}(convert(AbstractMatrix{T}, A.data))
function convert{T,TA,S,UpLo,IsUnit}(::Type{AbstractMatrix{T}}, A::Triangular{TA,S,UpLo,IsUnit})
    M = convert(AbstractMatrix{T}, A.data)
    Triangular{T,typeof(M),UpLo,IsUnit}(M)
end
function convert{Tret,T,S,UpLo,IsUnit}(::Type{Matrix{Tret}}, A::Triangular{T,S,UpLo,IsUnit})
    B = Array(Tret, size(A, 1), size(A, 1))
    copy!(B, A.data)
    (UpLo == :L ? tril! : triu!)(B)
    if IsUnit
        for i = 1:size(B,1)
            B[i,i] = 1
        end
    end
    B
end
convert{T,S,UpLo,IsUnit}(::Type{Matrix}, A::Triangular{T,S,UpLo,IsUnit}) = convert(Matrix{T}, A)

function full!{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit})
    B = A.data
    (UpLo == :L ? tril! : triu!)(B)
    if IsUnit
        for i = 1:size(A,1)
            B[i,i] = 1
        end
    end
    B
end
full{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = convert(Matrix, A)

fill!(A::Triangular, x) = (fill!(A.data, x); A)

function similar{T,S,UpLo,IsUnit,Tnew}(A::Triangular{T,S,UpLo,IsUnit}, ::Type{Tnew}, dims::Dims)
    dims[1] == dims[2] || throw(ArgumentError("a Triangular matrix must be square"))
    length(dims) == 2 || throw(ArgumentError("a Traigular matrix must have two dimensions"))
    A = similar(A.data, Tnew, dims)
    return Triangular{Tnew, typeof(A), UpLo, IsUnit}(A)
end

getindex{T,S}(A::Triangular{T,S,:L,true}, i::Integer, j::Integer) = i == j ? one(T) : (i > j ? A.data[i,j] : zero(T))
getindex{T,S}(A::Triangular{T,S,:L,false}, i::Integer, j::Integer) = i >= j ? A.data[i,j] : zero(T)
getindex{T,S}(A::Triangular{T,S,:U,true}, i::Integer, j::Integer) = i == j ? one(T) : (i < j ? A.data[i,j] : zero(T))
getindex{T,S}(A::Triangular{T,S,:U,false}, i::Integer, j::Integer) = i <= j ? A.data[i,j] : zero(T)
getindex{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, i::Integer) = ((m, n) = divrem(i - 1, size(A,1)); A[m + 1, n + 1])

istril{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = UpLo == :L
istriu{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = UpLo == :U

transpose{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = Triangular{T, S, UpLo == :U ? :L : :U, IsUnit}(transpose(A.data))
ctranspose{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = Triangular{T, S, UpLo == :U ? :L : :U, IsUnit}(ctranspose(A.data))
transpose!{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = Triangular{T, S, UpLo == :U ? :L : :U, IsUnit}(copytri!(A.data, UpLo == :L ? 'L' : 'U'))
ctranspose!{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = Triangular{T, S, UpLo == :U ? :L : :U, IsUnit}(copytri!(A.data, UpLo == :L ? 'L' : 'U', true))
diag{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}) = IsUnit ? ones(T, size(A,1)) : diag(A.data)
function big{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit})
    M = big(A.data)
    Triangular{T,typeof(M),UpLo,IsUnit}(M)
end

function (*){T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, x::Number)
    n = size(A,1)
    for j = 1:n
        for i = UpLo == :L ? j:n : 1:j
            A.data[i,j] = i == j & IsUnit ? x : A.data[i,j]*x
        end
    end
    A
end
function (*){T,S,UpLo,IsUnit}(x::Number, A::Triangular{T,S,UpLo,IsUnit})
    n = size(A,1)
    for j = 1:n
        for i = UpLo == :L ? j:n : 1:j
            A.data[i,j] = i == j & IsUnit ? x : x*A.data[i,j]
        end
    end
    A
end

A_mul_B!{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::Triangular{T,S,UpLo,IsUnit}) = Triangular{T,S,UpLo,IsUnit}(A*full!(B))
A_mul_B!(A::Tridiagonal, B::Triangular) = A*full!(B)
A_mul_B!(C::AbstractVecOrMat, A::Triangular, B::AbstractVecOrMat) = A_mul_B!(A, copy!(C, B))

A_mul_Bc!(A::Triangular, B::QRCompactWYQ) = A_mul_Bc!(full!(A),B)
A_mul_Bc!(A::Triangular, B::QRPackedQ) = A_mul_Bc!(full!(A),B)
A_mul_Bc!(C::AbstractVecOrMat, A::Triangular, B::AbstractVecOrMat) = A_mul_Bc!(A, copy!(C, B))

#Generic multiplication
*(A::Tridiagonal, B::Triangular) = A_mul_B!(full(A), B)
A_mul_Bc(A::Triangular, B::Union(QRCompactWYQ,QRPackedQ)) = A_mul_Bc(full(A), B)
for func in (:*, :Ac_mul_B, :A_mul_Bc, :/, :A_rdiv_Bc)
    @eval begin
        ($func){TA,TB,SA<:AbstractMatrix,SB<:AbstractMatrix,UpLoA,UpLoB,IsUnitA,IsUnitB}(A::Triangular{TA,SA,UpLoA,IsUnitA}, B::Triangular{TB,SB,UpLoB,IsUnitB}) = ($func)(A, full(B))
        ($func){T,S<:AbstractMatrix,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, B::AbstractVecOrMat) = ($func)(full(A), B)
        ($func){T,S<:AbstractMatrix,UpLo,IsUnit}(A::AbstractMatrix, B::Triangular{T,S,UpLo,IsUnit}) = ($func)(A, full(B))
    end
end

function sqrtm{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit})
    n = size(A, 1)
    R = zeros(T, n, n)
    if UpLo == :U
        for j = 1:n
            (T<:Complex || A[j,j]>=0) ? (R[j,j] = IsUnit ? one(T) : sqrt(A[j,j])) : throw(SingularException(j))
            for i = j-1:-1:1
                r = A[i,j]
                for k = i+1:j-1
                    r -= R[i,k]*R[k,j]
                end
                r==0 || (R[i,j] = r / (R[i,i] + R[j,j]))
            end
        end
        return Triangular{T,S,UpLo,IsUnit}(R)
    else # UpLo == :L #Not the usual case
        return sqrtm(A.').'
    end
end

#Generic solver using naive substitution
function naivesub!{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit}, b::AbstractVector, x::AbstractVector=b)
    N = size(A, 2)
    N==length(b)==length(x) || throw(DimensionMismatch(""))

    if UpLo == :L #do forward substitution
        for j = 1:N
            x[j] = b[j]
            for k = 1:j-1
                x[j] -= A[j,k] * x[k]
            end
            if !IsUnit
                x[j]/= A[j,j]==0 ? throw(SingularException(j)) : A[j,j]
            end
        end
    elseif UpLo == :U #do backward substitution
        for j = N:-1:1
            x[j] = b[j]
            for k = j+1:1:N
                x[j] -= A[j,k] * x[k]
            end
            if !IsUnit
                x[j]/= A[j,j]==0 ? throw(SingularException(j)) : A[j,j]
            end
        end
    else
        throw(ArgumentError("Unknown UpLo=$(UpLo)"))
    end
    x
end

#Generic eigensystems
eigvals(A::Triangular) = diag(A.data)
det(A::Triangular) = prod(eigvals(A))

function eigvecs{T,S,UpLo,IsUnit}(A::Triangular{T,S,UpLo,IsUnit})
    N = size(A,1)
    evecs = zeros(T, N, N)
    if IsUnit #Trivial
        return eye(A)
    elseif UpLo == :L #do forward substitution
        for i=1:N
            evecs[i,i] = one(T)
            for j = i+1:N
                for k = i:j-1
                    evecs[j,i] -= A[j,k] * evecs[k,i]
                end
                evecs[j,i] /= A[j,j] - A[i,i]
            end
            evecs[i:N, i] /= norm(evecs[i:N, i])
        end
        evecs
    elseif UpLo == :U #do backward substitution
        for i=1:N
            evecs[i,i] = one(T)
            for j = i-1:-1:1
                for k = j+1:i
                    evecs[j,i] -= A[j,k] * evecs[k,i]
                end
                evecs[j,i] /= A[j,j]-A[i,i]
            end
            evecs[1:i, i] /= norm(evecs[1:i, i])
        end
    else
        throw(ArgumentError("Unknown uplo=$(UpLo)"))
    end
    evecs
end

eigfact(A::Triangular) = Eigen(eigvals(A), eigvecs(A))

#Generic singular systems
for func in (:svd, :svdfact, :svdfact!, :svdvals, :svdvecs)
    @eval begin
        ($func)(A::Triangular) = ($func)(full(A))
    end
end


struct CPolygon
  vertexlist::Ptr{Cint}
  numberofvertices::Cint
end

struct CFacet{T}
  polygonlist::Ptr{CPolygon}
  numberofpolygons::Cint
  holelist::Ptr{T}
  numberofholes::Cint
end

struct CPPTetGenIO{T}
    firstnumber::Cint # 0 or 1, default 0.
    mesh_dim::Cint # must be 3.

    pointlist::Ptr{T}
    pointattributelist::Ptr{T}
    pointmtrlist::Ptr{T}

    pointmarkerlist::Ptr{Cint}
    numberofpoints::Cint
    numberofpointattributes::Cint
    numberofpointmtrs::Cint

    tetrahedronlist::Ptr{Cint}
    tetrahedronattributelist::Ptr{T}
    tetrahedronvolumelist::Ptr{T}
    neighborlist::Ptr{Cint}
    numberoftetrahedra::Cint
    numberofcorners::Cint
    numberoftetrahedronattributes::Cint

    facetlist::Ptr{CFacet{T}}
    facetmarkerlist::Ptr{Cint}
    numberoffacets::Cint

    holelist::Ptr{T}
    numberofholes::Cint

    regionlist::Ptr{T}
    numberofregions::Cint

    facetconstraintlist::Ptr{T}
    numberoffacetconstraints::Cint

    segmentconstraintlist::Ptr{T}
    numberofsegmentconstraints::Cint

    trifacelist::Ptr{Cint}
    trifacemarkerlist::Ptr{Cint}
    numberoftrifaces::Cint

    edgelist::Ptr{Cint}
    edgemarkerlist::Ptr{Cint}
    numberofedges::Cint
end

"""
   Error struct for TetGen
"""
struct TetGenError <: Exception
    rc::Cint
end

"""
   Show TetGen error, messages have been lifted
   from TetGen
"""
function Base.show(io::IO, e::TetGenError)
    if e.rc==1
        println(io,"TetGen error $(e.rc): out of memory.");
    elseif e.rc==2
        println(io,"TetGen error $(e.rc): internal error.")
    elseif e.rc==3
        println(io,"TetGen error $(e.rc): a self-intersection was detected. Hint: use -d option to detect all self-intersections.");
    elseif e.rc==4
        println(io,"TetGen error $(e.rc): a very small input feature size was detected. Hint: use -T option to set a smaller tolerance.")
    elseif e.rc==5
        println(io,"TetGen error $(e.rc): two very close input facets were detected. Hint: use -Y option to avoid adding Steiner points in boundary.\n");
    elseif e.rc==10
        println(io,"TetGen error $(e.rc): an input error was detected.\n");
    else
        println(io,"TetGen error $(e.rc): unknown error.\n");
    end
end

"""
$(SIGNATURES)

Tetrahedralization with error handling
"""
function tetrahedralize(input::CPPTetGenIO{Float64}, command::String)
    rc=Cint[0]
    output = ccall((:tetrahedralize2_f64, libtet), CPPTetGenIO{Float64}, (CPPTetGenIO{Float64}, Cstring,  Ptr{Cint}), input, command, rc)
    if rc[1]!=0
        throw(TetGenError(rc[1]))
    end
    output
end

"""
   Trivial Julia tetunsuitable function
"""
my_jl_tetunsuitable=(pa,pb,pc,pd)->0

"""
   Tetunsuitable function called from C wrapper
"""
function jl_wrap_tetunsuitable(pa::Ptr{Float64}, pb::Ptr{Float64}, pc::Ptr{Float64}, pd::Ptr{Float64})
    pax=Base.unsafe_wrap(Array,pa,(3,),own=false)
    pbx=Base.unsafe_wrap(Array,pb,(3,),own=false)
    pcx=Base.unsafe_wrap(Array,pc,(3,),own=false)
    pdx=Base.unsafe_wrap(Array,pd,(3,),own=false)
    Cint(my_jl_tetunsuitable(pax,pbx,pcx,pdx))
end

"""
   Set tetunsuitable function called from C wrapper.
   Setting this function is valid only for one subsequent
   call to tetrahedralize
"""
function tetunsuitable(unsuitable::Function;check_signature=true)
    if check_signature
        unsuitable(rand(3),rand(3),rand(3),rand(3))
    end
    global my_jl_tetunsuitable
    my_jl_tetunsuitable=unsuitable
    c_wrap_tetunsuitable=@cfunction(jl_wrap_tetunsuitable, Cint, (Ptr{Cdouble},Ptr{Cdouble},Ptr{Cdouble},Ptr{Cdouble}))
    ccall((:tetunsuitable_callback,libtet),Cvoid,(Ptr{Cvoid},),c_wrap_tetunsuitable)
end


# Reference: https://github.com/mdavezac/spglib.jl/blob/master/src/spglib.jl#L70
# This is an internal function, do not export!
function cchars2string(itr)
    vec = collect(Char, Iterators.filter(!iszero, itr))
    return String(vec)
end

# See https://github.com/spglib/spglib/blob/444e061/python/spglib/spglib.py#L115-L165
"""
    get_symmetry(cell::Cell, symprec=1e-5)

Return the symmetry operations of a `cell`.
"""
function get_symmetry(cell::Cell, symprec = 1e-5)
    max_size = length(cell.types) * 48
    rotation = Array{Cint,3}(undef, 3, 3, max_size)
    translation = Array{Cdouble,2}(undef, 3, max_size)
    if iszero(cell.magmoms)
        return get_symmetry!(rotation, translation, cell, symprec)
    else
        equivalent_atoms = zeros(length(cell.magmoms))
        primitive_lattice = zeros(Cdouble, 3, 3)
        if ndims(cell.magmoms) == 1
            spin_flips = zeros(length(rotation))
        else
            spin_flips = nothing
        end
        # TODO: unfinished!
    end
end

function get_symmetry!(
    rotation::AbstractArray,
    translation::AbstractMatrix,
    cell::Cell,
    symprec = 1e-5,
)
    if size(rotation, 3) != size(translation, 2)
        throw(DimensionMismatch("`rotation` & `translation` have different max size!"))
    end
    if !(size(rotation, 1) == size(rotation, 2) == size(translation, 1) == 3)
        throw(DimensionMismatch("`rotation` & `translation` don't have the right size!"))
    end
    @unpack lattice, positions, types = _expand_cell(cell)
    rotation = Base.cconvert(Array{Cint,3}, rotation)
    translation = Base.cconvert(Matrix{Cdouble}, translation)
    max_size = Base.cconvert(Cint, size(rotation, 3))
    num_atom = Base.cconvert(Cint, length(types))
    num_sym = ccall(
        (:spg_get_symmetry, libsymspg),
        Cint,
        (
            Ptr{Cint},
            Ptr{Float64},
            Cint,
            Ptr{Float64},
            Ptr{Float64},
            Ptr{Cint},
            Cint,
            Float64,
        ),
        rotation,
        translation,
        max_size,
        lattice,
        positions,
        types,
        num_atom,
        symprec,
    )
    if num_sym == 0
        throw(SpglibError("Symmetry operation search failed!"))
    end
    return rotation[:, :, 1:num_sym], translation[:, 1:num_sym]
end

function get_symmetry_with_collinear_spin!(
    rotation::AbstractArray{T,3},
    translation::AbstractMatrix,
    equivalent_atoms::AbstractVector,
    max_size::Integer,
    cell::Cell,
    symprec = 1e-5,
) where {T}
    @unpack lattice, positions, types, magmoms = _expand_cell(cell)
    rotation = Base.cconvert(Array{Cint,3}, rotation)
    translation = Base.cconvert(Matrix{Cdouble}, translation)
    equivalent_atoms = Base.cconvert(Vector{Cint}, equivalent_atoms)
    max_size = Base.cconvert(Cint, max_size)
    num_atom = Base.cconvert(Cint, length(types))
    num_sym = ccall(
        (:spg_get_symmetry_with_collinear_spin, libsymspg),
        Cint,
        (
            Ptr{Cint},
            Ptr{Cdouble},
            Ptr{Cint},
            Cint,
            Ptr{Cdouble},
            Ptr{Cdouble},
            Ptr{Cint},
            Ptr{Cdouble},
            Cint,
            Cdouble,
        ),
        rotation,
        translation,
        equivalent_atoms,
        max_size,
        lattice,
        positions,
        types,
        magmoms,
        num_atom,
        symprec,
    )
    if num_sym == 0
        throw(SpglibError("Symmetry operation search failed!"))
    end
    return num_sym
end
function get_symmetry_with_collinear_spin(cell::Cell, symprec = 1e-5)
    num_atom = length(cell.types)
    max_size = num_atom * 48
    rotation = Array{Cint,3}(undef, 3, 3, max_size)
    translation = Matrix{Cdouble}(undef, 3, max_size)
    equivalent_atoms = Vector{Cint}(undef, num_atom)
    num_sym = get_symmetry_with_collinear_spin!(
        rotation,
        translation,
        equivalent_atoms,
        max_size,
        cell,
        symprec,
    )
    return rotation[:, :, 1:num_sym], translation[:, 1:num_sym], equivalent_atoms
end

"""
    get_symmetry_from_database(hall_number)

Return the symmetry operations given a `hall_number`.

This function allows to directly access to the space group operations in the
`spglib` database. To specify the space group type with a specific choice,
`hall_number` is used.
"""
function get_symmetry_from_database(hall_number)
    rotation = Array{Cint,3}(undef, 3, 3, 192)
    translation = Array{Cdouble,2}(undef, 3, 192)
    return get_symmetry_from_database!(rotation, translation, hall_number)
end

function get_symmetry_from_database!(
    rotation::AbstractArray,
    translation::AbstractMatrix,
    hall_number,
)
    if !(size(rotation, 3) == size(translation, 2) == 192)
        throw(
            DimensionMismatch(
                "`rotation` & `translation` should have space for 192 symmetry operations!",
            ),
        )
    end
    if !(size(rotation, 1) == size(rotation, 2) == size(translation, 1) == 3)
        throw(DimensionMismatch("`rotation` & `translation` don't have the right size!"))
    end
    rotation = Base.cconvert(Array{Cint,3}, rotation)
    translation = Base.cconvert(Matrix{Cdouble}, translation)
    hall_number = Base.cconvert(Cint, hall_number)
    num_sym = ccall(
        (:spg_get_symmetry_from_database, libsymspg),
        Cint,
        (Ptr{Cint}, Ptr{Float64}, Cint),
        rotation,
        translation,
        hall_number,
    )
    if num_sym == 0
        throw(SpglibError("Symmetry operation search failed!"))
    end
    return rotation[:, :, 1:num_sym], translation[:, 1:num_sym]
end

"""
    get_hall_number_from_symmetry(rotation::AbstractArray{T,3}, translation::AbstractMatrix, num_operations::Integer, symprec=1e-5) where {T}

Obtain `hall_number` from the set of symmetry operations.

This is expected to work well for the set of symmetry operations whose
distortion is small. The aim of making this feature is to find space-group-type
for the set of symmetry operations given by the other source than spglib. Note
that the definition of `symprec` is different from usual one, but is given in the
fractional coordinates and so it should be small like `1e-5`.
"""
function get_hall_number_from_symmetry(
    rotation::AbstractArray{T,3},
    translation::AbstractMatrix,
    num_operations::Integer,
    symprec = 1e-5,
) where {T}
    rotation = Base.cconvert(Array{Cint,3}, rotation)
    translation = Base.cconvert(Matrix{Cdouble}, translation)
    num_operations = Base.cconvert(Cint, num_operations)
    return ccall(
        (:spg_get_hall_number_from_symmetry, libsymspg),
        Cint,
        (Ptr{Cint}, Ptr{Float64}, Cint, Float64),
        rotation,
        translation,
        num_operations,
        symprec,
    )
end

"""
    get_multiplicity(cell::Cell, symprec=1e-5)

Return the exact number of symmetry operations. An error is thrown when it fails.
"""
function get_multiplicity(cell::Cell, symprec = 1e-5)
    @unpack lattice, positions, types = _expand_cell(cell)
    num_atom = Base.cconvert(Cint, length(types))
    num_sym = ccall(
        (:spg_get_multiplicity, libsymspg),
        Cint,
        (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}, Cint, Cdouble),
        lattice,
        positions,
        types,
        num_atom,
        symprec,
    )
    if num_sym == 0
        throw(SpglibError("Symmetry operation search failed!"))
    end
    return num_sym
end

"""
    get_dataset(cell::Cell, symprec=1e-5)

Search symmetry operations of an input unit cell structure.
"""
function get_dataset(cell::Cell, symprec = 1e-5)
    @unpack lattice, positions, types = _expand_cell(cell)
    num_atom = Base.cconvert(Cint, length(types))
    ptr = ccall(
        (:spg_get_dataset, libsymspg),
        Ptr{SpglibDataset},
        (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}, Cint, Cdouble),
        lattice,
        positions,
        types,
        num_atom,
        symprec,
    )
    if ptr == C_NULL
        return nothing
    else
        raw = unsafe_load(ptr)
        return convert(Dataset, raw)
    end
end

"""
    get_dataset_with_hall_number(cell::Cell, hall_number::Integer, symprec=1e-5)

Search symmetry operations of an input unit cell structure, using a given Hall number.
"""
function get_dataset_with_hall_number(cell::Cell, hall_number::Integer, symprec = 1e-5)
    @unpack lattice, positions, types = _expand_cell(cell)
    num_atom = Base.cconvert(Cint, length(types))
    hall_number = Base.cconvert(Cint, hall_number)
    ptr = ccall(
        (:spg_get_dataset_with_hall_number, libsymspg),
        Ptr{SpglibDataset},
        (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}, Cint, Cint, Cdouble),
        lattice,
        positions,
        types,
        num_atom,
        hall_number,
        symprec,
    )
    if ptr == C_NULL
        return nothing
    else
        raw = unsafe_load(ptr)
        return convert(Dataset, raw)
    end
end

"""
    get_spacegroup_type(hall_number::Integer)

Translate Hall number to space group type information.
"""
function get_spacegroup_type(hall_number::Integer)
    spgtype = ccall(
        (:spg_get_spacegroup_type, libsymspg),
        SpglibSpacegroupType,
        (Cint,),
        hall_number,
    )
    return convert(SpacegroupType, spgtype)
end

"""
    get_spacegroup_number(cell::Cell, symprec=1e-5)

Get the spacegroup number of a `cell`.
"""
function get_spacegroup_number(cell::Cell, symprec = 1e-5)
    dataset = get_dataset(cell, symprec)
    return dataset.spacegroup_number
end
"""
    get_spacegroup_type(cell::Cell, symprec=1e-5)

Get `SpacegroupType` from `cell`.
"""
function get_spacegroup_type(cell::Cell, symprec = 1e-5)  # See https://github.com/spglib/spglib/blob/444e061/python/spglib/spglib.py#L307-L324
    dataset = get_dataset(cell, symprec)
    return get_spacegroup_type(dataset.hall_number)
end

"""
    get_international(cell::Cell, symprec=1e-5)

Return the space group type in Hermann–Mauguin (international) notation.
"""
function get_international(cell::Cell, symprec = 1e-5)
    @unpack lattice, positions, types = _expand_cell(cell)
    symbol = Vector{Cchar}(undef, 11)
    exitcode = ccall(
        (:spg_get_international, libsymspg),
        Cint,
        (Ptr{Cchar}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}, Cint, Cdouble),
        symbol,
        lattice,
        positions,
        types,
        length(types),
        symprec,
    )
    if exitcode == 0
        throw(SpglibError("Spacegroup search failed!"))
    end
    return cchars2string(symbol)
end

"""
    get_schoenflies(cell::Cell, symprec=1e-5)

Return the space group type in Schoenflies notation.
"""
function get_schoenflies(cell::Cell, symprec = 1e-5)
    @unpack lattice, positions, types = _expand_cell(cell)
    symbol = Vector{Cchar}(undef, 7)
    exitcode = ccall(
        (:spg_get_schoenflies, libsymspg),
        Cint,
        (Ptr{Cchar}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cint}, Cint, Cdouble),
        symbol,
        lattice,
        positions,
        types,
        length(types),
        symprec,
    )
    if exitcode == 0
        throw(SpglibError("Spacegroup search failed!"))
    end
    return cchars2string(symbol)
end

function Base.convert(::Type{SpacegroupType}, spgtype::SpglibSpacegroupType)
    values = map(fieldnames(SpacegroupType)) do name
        value = getfield(spgtype, name)
        if value isa Cint
            value
        elseif value isa NTuple{N,Cchar} where {N}
            cchars2string(value)
        else  # This should never happen!
            @assert false "unexpected field type $(typeof(value))!"  # See https://discourse.julialang.org/t/please-stop-using-error-and-errorexception-in-packages-and-base/12096
        end
    end
    return SpacegroupType(values...)
end


(+)(td::TimeDate) = td
(+)(tdz::TimeDateZone) = tdz

function (-)(td1::TimeDate, td2::TimeDate)
    dtm1 = DateTime(td1)
    dtm2 = DateTime(td2)
    dtm0 = canonical(Microsecond(td1) - Microsecond(td2) + Nanosecond(td1) - Nanosecond(td2))
    dtm = dtm1 - dtm2
    dtm += dtm0
    return sum(fldmod(dtm))
end

(-)(td::TimeDate, dt::DateTime) = td - TimeDate(dt)
(-)(dt::DateTime, td::TimeDate) = TimeDate(dt) - td
(-)(td::TimeDate, dt::Date) = td - TimeDate(dt)
(-)(dt::Date, td::TimeDate) = TimeDate(dt) - td

function (-)(td::TimeDate, tm::Time)
    tm_td, dt_td = at_time(td), on_date(td)
    if tm_td < tm
        dt_td -= Day(1)
        tm_td = tm_td - tm + Hour(24)
    else
        tm_td = tm_td - tm
    end
    tm_td = canonical(tm_td)
    tm = Time(tm_td)
    return TimeDate(tm, dt_td)
end

for P in (:Year, :Month, :Day)
    @eval begin
        function (+)(td::TimeDate, p::$P)
            tm, dt = at_time(td), on_date(td)
            dt = dt + p
            return TimeDate(tm, dt)
        end
        function (-)(td::TimeDate, p::$P)
            p = -p
            return td + p
        end
    end
end

function (+)(td::TimeDate, p::Union{Hour, Minute, Second, Millisecond, Microsecond, Nanosecond})
    tm, dt = at_time(td), on_date(td)
    extradays,extratime=divrem(p,oftype(p,oneunit(Day)))
    newtm = sum(promote(extratime,tm.instant))
    if newtm >= Nanosecond(oneunit(Day))
        newtm -= Nanosecond(oneunit(Day))
        extradays += 1
    elseif newtm < zero(Nanosecond)
        newtm += Nanosecond(oneunit(Day))
        extradays -= 1
    end
    return TimeDate(Time(newtm),dt+Day(extradays))
end

function (-)(td::TimeDate, p::Union{Hour, Minute, Second, Millisecond, Microsecond, Nanosecond})
    td + (-p)
end

@inline nonempty(x) = !isempty(x) ? x : Nanosecond(0)

function (-)(x::TimeDateZone, y::TimeDateZone)
    xx = Microsecond(x) + Nanosecond(x)
    xx = nonempty(xx)
    yy = Microsecond(y) + Nanosecond(y)
    yy = nonempty(yy)
    xy = xx - yy
    xy = nonempty(xy)
    zx = ZonedDateTime(x)
    zy = ZonedDateTime(y)
    zxy = zx - zy
    zxy = nonempty(zxy)
    result = zxy + xy
    result = nonempty(result)

    return canonical(result)
end

(-)(x::TimeDateZone, y::ZonedDateTime) = x - TimeDateZone(y)
(-)(x::ZonedDateTime, y::TimeDateZone) = TimeDateZone(y) - y

function(+)(tdz::TimeDateZone, p1::Period, p2::Period)
    p = p1 + p2
    return tdz + p
end

function(-)(tdz::TimeDateZone, p1::Period, p2::Period)
    p = p1 + p2
    return tdz - p
end

function(+)(tdz::TimeDateZone, p1::Period, p2::Period, p3::Period)
    p = p1 + p2 + p3
    return tdz + p
end

function(-)(tdz::TimeDateZone, p1::Period, p2::Period, p3::Period)
    p = p1 + p2 + p3
    return tdz - p
end

function(+)(tdz::TimeDateZone, p1::Period, p2::Period, p3::Period, p4::Period)
    p = p1 + p2 + p3 + p4
    return tdz + p
end

function(-)(tdz::TimeDateZone, p1::Period, p2::Period, p3::Period, p4::Period)
    p = p1 + p2 + p3 + p4
    return tdz - p
end

function(+)(tdz::TimeDateZone, p1::Period, p2::Period, p3::Period, p4::Period, p5::Period)
    p = p1 + p2 + p3 + p4 + p5
    return tdz + p
end

function(-)(tdz::TimeDateZone, p1::Period, p2::Period, p3::Period, p4::Period, p5::Period)
    p = p1 + p2 + p3 + p4 + p5
    return tdz - p
end

function(+)(tdz::TimeDateZone, p1::Period, p2::Period, p3::Period, p4::Period, p5::Period, p6::Period)
    p = p1 + p2 + p3 + p4 + p5 + p6
    return tdz + p
end

function(-)(tdz::TimeDateZone, p1::Period, p2::Period, p3::Period, p4::Period, p5::Period, p6::Period)
    p = p1 + p2 + p3 + p4 + p5 + p6
    return tdz - p
end