
#   This file is part of DirectSum.jl
#   It is licensed under the AGPL license
#   DirectSum Copyright (C) 2019 Michael Reed
#       _           _                         _
#      | |         | |                       | |
#   ___| |__   __ _| | ___ __ __ ___   ____ _| | __ _
#  / __| '_ \ / _` | |/ / '__/ _` \ \ / / _` | |/ _` |
# | (__| | | | (_| |   <| | | (_| |\ V / (_| | | (_| |
#  \___|_| |_|\__,_|_|\_\_|  \__,_| \_/ \__,_|_|\__,_|
#
#   https://github.com/chakravala
#   https://crucialflow.com

import AbstractTensors: scalar, involute, unit, even, odd

## generators

function labels(V::T,vec::String=pre[1],cov::String=pre[2],duo::String=pre[3],dif::String=pre[4]) where T
    N,io,icr = mdims(V),IOBuffer(),1
    els = Array{Symbol,1}(undef,1<<N)
    els[1] = Symbol(vec)
    for i ‚àà 1:N
        set = combo(N,i)
        for k ‚àà 1:length(set)
            @inbounds printlabel(io,V,bit2int(indexbits(N,set[k])),true,vec,cov,duo,dif)
            icr += 1
            @inbounds els[icr] = Symbol(String(take!(io)))
        end
    end
    return els
end

#@pure labels(V::T) where T<:Manifold = labels(V,pre[1],pre[2],pre[3],pre[4])

generate(V::Int) = generate(Submanifold(V),V)
generate(V::Manifold) = generate(V,rank(V))
function generate(V,N)
    exp = Submanifold{V}[Submanifold{V,0}(Zero(UInt))]
    for i ‚àà 1:N
        set = combo(N,i)
        for k ‚àà 1:length(set)
            @inbounds push!(exp,Submanifold{V,i}(bit2int(indexbits(N,set[k]))))
        end
    end
    return exp
end

export @basis, @basis_str, @dualbasis, @dualbasis_str, @mixedbasis, @mixedbasis_str

"""
    alloc(V::Manifold,:V,"v","w","‚àÇ","œµ")

Generates `Basis` declaration having `Manifold` specified by `V`.
The first argument provides pseudoscalar specifications, the second argument is the variable name for the `Manifold`, and the third and fourth argument are variable prefixes of the `Submanifold` vector names (and covector basis names).
"""
function alloc(V,sig=vsn[1],vec=pre[1],cov=pre[2],duo=pre[3],dif=pre[4])
    N = mdims(V)
    if N > algebra_limit
        G = Œõ(V) # fill cache
        basis = generate(V)
        sym = labels(V,string.([vec,cov,duo,dif])...)
    else
        basis = Œõ(V).b
        sym = labels(V,string.([vec,cov,duo,dif])...)
    end
    @inbounds exp = Expr[Expr(:(=),esc(sig),convert(Submanifold,V)),
        Expr(:(=),esc(Symbol(vec)),basis[1])]
    for i ‚àà 2:1<<N
        @inbounds push!(exp,Expr(:(=),esc(Symbol("$(basis[i])")),basis[i]))
        @inbounds push!(exp,Expr(:(=),esc(sym[i]),basis[i]))
    end
    push!(exp,Expr(:(=),esc(Symbol(vec,'‚Éñ')),esc(vec)))
    push!(exp,Expr(:(=),esc(Symbol('ùüé')),Zero(V)))
    push!(exp,Expr(:(=),esc(Symbol('‚àû')),Infinity(V)))
    return Expr(:block,exp...,Expr(:tuple,esc(sig),esc.(sym)...))
end
alloc(V::TensorBundle,args...) = alloc(Submanifold(V),args...)

"""
    @basis

Generates `Submanifold` elements having `Manifold` specified by `V`.
As a result of this macro, all of the `Submanifold{V,G}` elements generated by that `TensorBundle` become available in the local workspace with the specified naming.
The first argument provides pseudoscalar specifications, the second argument is the variable name for the `Manifold`, and the third and fourth argument are variable prefixes of the `Submanifold` vector names (and covector basis names).
Default for `@basis M` is `@basis M V v w ‚àÇ œµ`.
"""
macro basis(q,sig=vsn[1],vec=pre[1],cov=pre[2],duo=pre[3],dif=pre[4])
    T = typeof(q)
    V = T‚àà(Symbol,Expr) ? (@eval(__module__,$q)) : T<:Int ? q : Manifold(q)
    alloc(V,sig,string.([vec,cov,duo,dif])...)
end

macro basis_str(str)
    alloc(Manifold(str))
end

"""
    @dualbasis

Generates `Submanifold` elements having `Manifold` specified by `V'`.
As a result of this macro, all of the `Submanifold{V',G}` elements generated by that `TensorBundle` become available in the local workspace with the specified naming.
The first argument provides pseudoscalar specifications, the second argument is the variable name for the dual `Manifold`, and the third and fourth argument are variable prefixes of the `Submanifold` covector names (and tensor field basis names).
Default for `@dualbasis M` is `@dualbasis M VV w œµ`.
"""
macro dualbasis(q,sig=vsn[2],cov=pre[2],dif=pre[4])
    alloc((typeof(q)‚àà(Symbol,Expr) ? (@eval(__module__,$q)) : Manifold(q))',sig,string.([pre[1],cov,pre[3],dif])...)
end

macro dualbasis_str(str)
    alloc(Manifold(str)',vsn[2])
end

"""
    @mixedbasis

Generates `Submanifold` elements having `Manifold` specified by `V‚äïV'`.
As a result of this macro, all of the `Submanifold{V‚äïV',G}` elements generated by that `TensorBundle` become available in the local workspace with the specified naming.
The first argument provides pseudoscalar specifications, the second argument is the variable name for the `Manifold`, and the third and fourth argument are variable prefixes of the `Submanifold` vector names (and covector basis names).
Default for `@mixedbasis M` is `@mixedbasis M V v w ‚àÇ œµ`.
"""
macro mixedbasis(q,sig=vsn[3],vec=pre[1],cov=pre[2],duo=pre[3],dif=pre[4])
    V = typeof(q)‚àà(Symbol,Expr) ? (@eval(__module__,$q)) : Manifold(q)
    bases = alloc(V‚äïV',sig,string.([vec,cov,duo,dif])...)
    Expr(:block,bases,alloc(V',vsn[2]),alloc(V),bases.args[end])
end

macro mixedbasis_str(str)
    V = Manifold(str)
    bases = alloc(V‚äïV',vsn[3])
    Expr(:block,bases,alloc(V',vsn[2]),alloc(V),bases.args[end])
end

@inline function lookup_basis(V,v::Symbol)::Union{Single,Submanifold}
    p,b,w,z = indexparity(V,v)
    z && return Zero(V)
    d = Submanifold{w}(indexbits(mdims(w),b))
    return p ? Single(-1,d) : d
end

## fundamentals

abstract type SubAlgebra{V} <: TensorAlgebra{V,Int} end

Base.adjoint(G::A) where A<:SubAlgebra{V} where V = Œõ(dual(V))
@pure dual(G::A) where A<: SubAlgebra = G'
Base.firstindex(a::T) where T<:SubAlgebra = 1
Base.lastindex(a::T) where T<:SubAlgebra{V} where V = 1<<mdims(V)
Base.length(a::T) where T<:SubAlgebra{V} where V = 1<<mdims(V)

==(::SubAlgebra{V},::SubAlgebra{W}) where {V,W} = V == W

‚äï(::SubAlgebra{V},::SubAlgebra{W}) where {V,W} = getalgebra(V‚äïW)
+(::SubAlgebra{V},::SubAlgebra{W}) where {V,W} = getalgebra(V‚äïW)

## Algebra{N}

@computed struct Basis{V} <: SubAlgebra{V}
    b::Values{1<<mdims(V),Submanifold{V}}
    g::Dict{Symbol,Int}
end

@doc """
    DirectSum.Basis{V} <: SubAlgebra{V} <: TensorAlgebra{V}

Grassmann basis container with cache of `Submanifold` elements and their `Symbol` names.
""" Basis

getindex(a::Basis,i::Int) = getfield(a,:b)[i]
getindex(a::Basis,i::Colon) = getfield(a,:b)
getindex(a::Basis,i::UnitRange{Int}) = [getindex(a,j) for j ‚àà i]

@pure function Base.getproperty(a::Basis{V},v::Symbol) where V
    return if v ‚àà (:b,:g)
        getfield(a,v)
    elseif haskey(a.g,v)
        a[getfield(a,:g)[v]]
    else
        lookup_basis(V,v)
    end
end

Base.collect(s::M) where M<:Manifold = Basis{s}()
function Basis{s}() where s
    sym = labels(s)
    @inbounds Basis{s}(generate(s),Dict{Symbol,Int}([sym[i]=>i for i ‚àà 1:1<<mdims(s)]))
end

@pure Basis(s::Manifold) = getalgebra(s)
@pure Basis(s::Int) = getalgebra(s)
@pure Basis(n::Int,d::Int,o::Int=0,s=zero(UInt)) = getalgebra(n,d,o,s)
Basis(s::String) = getalgebra(Manifold(s))
Basis(s::String,v::Symbol) = getbasis(Manifold(s),v)

function Base.show(io::IO,a::Basis{V}) where V
    N = typeof(V)<:Int ? V : mdims(V)
    print(io,"DirectSum.Basis{$V,$(1<<N)}(")
    for i ‚àà 1:1<<N-1
        print(io,a[i],", ")
    end
    print(io,a[end],")")
end

export Œõ, @Œõ_str, getalgebra, getbasis, TensorAlgebra, SubAlgebra

const Œõ = Basis

macro Œõ_str(str)
    Basis(str)
end

#=@pure function Base.getproperty(Œª::typeof(Basis),v::Symbol)
    v ‚àà (:body,:var) && (return getfield(Œª,v))
    V = string(v)
    N = parse(Int,V[2])
    C = V[1]‚àâ('D','C') ? 0 : 1
    length(V) < 5 && (V *= join(zeros(Int,5-length(V))))
    S = UInt(parse(Int,V[5:end]))
    getalgebra(N,tensorhash(parse(Int,V[3]),parse(Int,V[4]),C),C>0 ? flipsign(N,S) : S)
end=#

# Allocating thread-safe $(2^n)√óSubmanifold{G,V}
const Œõ0 = Œõ{Submanifold(0)}()
const Œõ0S = Œõ{‚Ñù0}()

for V ‚àà (:Int,:Signature,:DiagonalForm,:MetricTensor)
    @eval const $(Symbol(:algebra_cache_,V)) = Vector{Vector{Dict{UInt,Vector{Dict{UInt,Œõ}}}}}[]
    V‚â†:MetricTensor && @eval @pure getalgebra(V::$V) = getalgebra(Submanifold(V))
end
@eval begin
    @pure function getalgebra(n::Int,m::Int,s,S::UInt,vs::Type,f::Int=0,d::Int=0)
        n==0 && (return vs<:Int ? Œõ0 : Œõ0S)
        n > sparse_limit && (return $(Symbol(:getextended))(n,m,s,S,vs,f,d))
        n > algebra_limit && (return $(Symbol(:getsparse))(n,m,s,S,vs,f,d))
        f1,d1,m1 = f+1,d+1,m+1
        alc = if vs <: Int
            algebra_cache_Int
        elseif vs <: Signature
            algebra_cache_Signature
        elseif vs <: DiagonalForm
            algebra_cache_DiagonalForm
        else
            algebra_cache_MetricTensor
        end
        for F ‚àà length(alc)+1:f1
            push!(alc,Vector{Dict{UInt,Vector{Dict{UInt,Œõ}}}}[])
        end
        for D ‚àà length(alc[f1])+1:d1
            push!(alc[f1],Dict{UInt,Vector{Dict{UInt,Œõ}}}[])
        end
        for D ‚àà length(alc[f1][d1])+1:n
            push!(alc[f1][d1],Dict{UInt,Vector{Dict{UInt,Œõ}}}())
        end
        @inbounds if !haskey(alc[f1][d1][n],S)
            @inbounds push!(alc[f1][d1][n],S=>[Dict{UInt,Œõ}() for k‚àà1:12])
        end
        @inbounds if !haskey(alc[f1][d1][n][S][m1],s)
            @inbounds push!(alc[f1][d1][n][S][m1],s=>Basis{Submanifold{vs<:Int ? n : vs(),count_ones(S),S}()}())
        end
        @inbounds alc[f1][d1][n][S][m1][s]
    end
    @pure function getalgebra(V::Submanifold{M,N,S}) where {M,N,S}
        isdyadic(V) && N>2algebra_limit && (return getextended(V))
        getalgebra(mdims(M),options(M),metric(M),S,typeof(M),diffvars(M),diffmode(M))
    end
end
@pure getalgebra(n::Int,d::Int,o::Int,s,c::Int=0) = getalgebra(n,tensorhash(d,o,c),s)
@pure getalgebra(n::Int,m::Int,s) = getalgebra(n,m,UInt(s),UInt(1)<<n-1,Signature{n,m,UInt(s),0,0})


"""
    getbasis(V::Manifold,v)

Fetch a specific `Submanifold{G,V}` element from an optimal `SubAlgebra{V}` selection.
"""
@inline getbasis(V,b) = getbasis(V,UInt(b))
@pure function getbasis(V,B::UInt)
    if mdims(V) ‚â§ algebra_limit
        @inbounds getalgebra(V).b[basisindex(mdims(V),B)]
    else
        Submanifold{V,count_ones(B)}(B)
    end
end
@pure getbasis(V,v::Symbol) = getproperty(getalgebra(V),v)

@pure Submanifold{V}() where V = getbasis(V,UInt(0))
@pure Submanifold{V}(i::UInt) where V = getbasis(V,i)
Submanifold{V}(b::BitArray{1}) where V = getbasis(V,bit2int(b))
Base.one(b::Type{<:Submanifold{V}}) where V = One(b)
Base.zero(b::Type{<:Submanifold{V}}) where V = Zero(b)
@inline Base.one(t::Type{<:TensorBundle}) = One(t())
@inline Base.zero(t::Type{<:TensorBundle}) = Zero(t)
Base.one(V::T) where T<:TensorBundle = One(V)
Base.zero(V::T) where T<:TensorBundle = Zero(V)
Base.zero(V::Submanifold) = Zero(V)
Base.one(V::Submanifold{M}) where M = Submanifold{isbasis(V) ? M : V}()
Base.zero(::Single{V}) where V = Single{V}(0)
Base.one(::Single{V}) where V = Single{V}(1)
Base.zero(::Type{<:Single{V}}) where V = Single{V}(0)
Base.one(::Type{<:Single{V}}) where V = Single{V}(1)

## SparseAlgebra{V}

"""
    DirectSum.SparseBasis{V} <: SubAlgebra{V} <: TensorAlgebra{V}

Grassmann basis with sparse cache of `Submanifold{G,V}` elements and their `Symbol` names.
"""
struct SparseBasis{V} <: SubAlgebra{V}
    b::Vector{Symbol}
    g::Dict{Symbol,Int}
end

@pure function SparseBasis(s)
    sym = labels(s)
    SparseBasis{s}(sym,Dict{Symbol,Int}([sym[i]=>i for i ‚àà 1:1<<mdims(s)]))
end

@pure function getindex(a::SparseBasis{V},i::Int) where V
    N = mdims(V)
    if N ‚â§ algebra_limit
        getalgebra(V).b[i]
    else
        F = findfirst(x->1+binomsum(N,x)-i>0,0:N)
        G = F ‚â† nothing ? F-2 : N
        @inbounds B = indexbasis(N,G)[i-binomsum(N,G)]
        Submanifold{V,count_ones(B)}(B)
    end
end

@pure function Base.getproperty(a::SparseBasis{V},v::Symbol) where V
    return if v ‚àà (:b,:g)
        getfield(a,v)
    elseif haskey(a.g,v)
        @inbounds a[getfield(a,:g)[v]]
    else
        lookup_basis(V,v)
    end
end

@pure SparseBasis(n::Int,d::Int,o::Int=0,s=zero(UInt)) = getsparse(n,d,o,s)
SparseBasis(s::String) = getsparse(TensorBundle(s))
SparseBasis(s::String,v::Symbol) = getbasis(TensorBundle(s),v)

function Base.show(io::IO,a::SparseBasis{V}) where V
    print(io,"DirectSum.SparseBasis{$V,$(1<<mdims(V))}($(a[1]), ..., $(a[end]))")
end

## ExtendedAlgebra{V}

"""
    DirectSum.ExtendedBasis{V} <: SubAlgebra{V} <: TensorAlgebra{V}

Grassmann basis container without a dedicated `Submanifold` cache (only lazy caching).
"""
struct ExtendedBasis{V} <: SubAlgebra{V} end

@pure ExtendedBasis(s::Manifold) = ExtendedBasis{s}()
@pure ExtendedBasis(s::Int) = ExtendedBasis{s}()

@pure function Base.getproperty(a::ExtendedBasis{V},v::Symbol) where V
    if v ‚àà (:b,:g)
        throw(error("ExtendedBasis does not have field $v"))
    else
        return lookup_basis(V,v)
    end
end

@pure ExtendedBasis(n::Int,d::Int,o::Int=0,s=zero(UInt)) = getextended(n,d,o,s)
ExtendedBasis(s::String) = getextended(TensorBundle(s))
ExtendedBasis(s::String,v::Symbol) = getbasis(TensorBundle(s),v)

function Base.show(io::IO,a::ExtendedBasis{V}) where V
    N = 1<<mdims(V)
    print(io,"DirectSum.ExtendedBasis{$V,$N}($(getbasis(V,0)), ..., $(getbasis(V,N-1)))")
end

# Extending (2^n)√óSubmanifold{V}

for (ExtraBasis,extra) ‚àà ((SparseBasis,:sparse),(ExtendedBasis,:extended))
    getextra = Symbol(:get,extra)
    getalg = Symbol(getextra,:_Signature)
    extra_cache = Symbol(extra,:_cache)
    for V ‚àà (:Int,:Signature,:DiagonalForm,:MetricTensor)
        @eval const $(Symbol(extra_cache,:_,V)) = Vector{Vector{Dict{UInt,Vector{Dict{UInt,$ExtraBasis}}}}}[]
        V‚àâ(:Int,:MetricTensor) && (@eval @pure $getextra(V::$V) = $getextra(Submanifold(V)))
    end
    @eval begin
        @pure function $getextra(n::Int,m::Int,s,S::UInt,vs,f::Int=0,d::Int=0)
            n==0 && (return $ExtraBasis(V0))
            d1,f1,m1 = d+1,f+1,m+1
            exc = if vs <: Int
                $(Symbol(extra_cache,:_Int))
            elseif vs <: Signature
                $(Symbol(extra_cache,:_Signature))
            elseif vs <: DiagonalForm
                $(Symbol(extra_cache,:_DiagonalForm))
            else
                $(Symbol(extra_cache,:_MetricTensor))
            end
            for F ‚àà length(exc)+1:f1
                push!(exc,Vector{Dict{UInt,Vector{Dict{UInt,$ExtraBasis}}}}[])
            end
            for D ‚àà length(exc[f1])+1:d1
                push!(exc[f1],Dict{UInt,Vector{Dict{UInt,$ExtraBasis}}}[])
            end
            for D ‚àà length(exc[f1][d1])+1:n
                push!(exc[f1][d1],Dict{UInt,Vector{Dict{UInt,$ExtraBasis}}}())
            end
            @inbounds if !haskey(exc[f1][d1][n],S)
                @inbounds push!(exc[f1][d1][n],S=>[Dict{UInt,$ExtraBasis}() for k‚àà1:12])
            end
            @inbounds if !haskey(exc[f1][d1][n][S][m1],s)
                @inbounds push!(exc[f1][d1][n][S][m1],s=>$ExtraBasis(Submanifold{vs<:Int ? n : vs(),count_ones(S),S}()))
            end
            @inbounds exc[f1][d1][n][S][m1][s]
        end
        @pure $getextra(V::Submanifold{M,N,S} where N) where {M,S} = $getextra(mdims(M),options(M),metric(M),S,typeof(M),diffvars(M),diffmode(M))
        @pure $getextra(V::Int) = $getextra(mdims(V),options(V),metric(V),metric(V),typeof(V),diffvars(V),diffmode(V))
    end
    @eval begin
        @pure $getextra(n::Int,d::Int,o::Int,s,c::Int=0) = $getalg(n,tensorhash(d,o,c),s)
        @pure $getextra(n::Int,m::Int,s) = $getalg(n,m,UInt(s),UInt(1)<<n-1,Signature{n,m,UInt(s),0,0})
    end
end

# lookup

@noinline function indexparity(V::T,v::Symbol)::Tuple{Bool,Vector,T,Bool} where T
    vs = string(v)
    vt = vs[1:1]‚â†pre[1]
    Z=match(Regex("([$(pre[1])]([0-9a-vx-zA-VX-Z]+))?([$(pre[2])]([0-9a-zA-Z]+))?"),vs)
    ef = String[]
    for k ‚àà (2,4)
        Z[k] ‚â† nothing && push!(ef,Z[k])
    end
    length(ef) == 0 && (return false,Int[],V,true)
    let W = V,fs=false
        C = dyadmode(V)
        X = C‚â•0 && mdims(V)<4sizeof(UInt)+1
        X && (W = T<:Int ? 2V : (C>0 ? V'‚äïV : V‚äïV'))
        V2 = (vt ‚äª (vt ? C‚â†0 : C>0)) ? V' : V
        L = length(ef) > 1
        M = X ? Int(mdims(W)/2) : mdims(W)
        m = ((!L) && vt && (C<0)) ? M : 0
        chars = (L || (Z[2] ‚â† nothing)) ? alphanumv : alphanumw
        (es,e,et) = indexparity!([findfirst(isequal(ef[1][k]),chars) for k‚àà1:length(ef[1])].+m,C<0 ? V : V2)
        et && (return false,Int[],V,true)
        w,d = if L
            (fs,f,ft) = indexparity!([findfirst(isequal(ef[2][k]),alphanumw) for k‚àà1:length(ef[2])].+M,W)
            ft && (return false,Int[],V,true)
            W,[e;f]
        else
            V2,e
        end
        return es‚äªfs, d, w, false
    end
end
