module NBT
using GZip
using DataStructures
using StaticArrays
# Defines tags name and id

const   TAG_End = 0
const   TAG_Byte = 1
const   TAG_Short = 2
const   TAG_Int = 3
const   TAG_Long = 4
const   TAG_Float = 5
const   TAG_Double = 6
const   TAG_Byte_Array = 7
const   TAG_String = 8
const   TAG_List = 9
const   TAG_Compound = 10
const   TAG_Int_Array = 11
const   TAG_Long_Array = 12


mutable struct TAG_Node 
    id::UInt8
    name::String
    payload::Vector{TAG_Node}
    value::Union{String, UInt8, Int16, Int32, Int64, Float32, Float64, Vector{UInt8} ,Nothing}
    TAG_Node(id::UInt8, name::String, payload::Vector{TAG_Node}, value) = new(id,name,payload, value)
end

## TODO use similar code in the parse function for each payload types
bitcat(x::Vector{UInt8}) = (UInt16(x[1]) << 8) | x[2]

function IsArrayTag(b::UInt8)::Bool
	b == TAG_Byte_Array || b == TAG_Int_Array || b == TAG_Long_Array
end

function ReadNBT(filepath::String, unzip::Bool=true)::TAG_Node
    fd = GZip.open(filepath)
    bytes =  read(fd)
    a, tree = ParseNBT(bytes,1)
    close(fd)
    return tree
    
end

length(x) = size(x)[1]
function interpret(type::Type, bytes, index::Int64)
    return ntoh(type(reinterpret(type, bytes[index:index+sizeof(type)-1]::Vector{UInt8})[1])), index+sizeof(type)
end
 
# Because the native structure is a container like, the parsing function needs to be recursive
function ParseNBT(bytes::Vector{UInt8}, i)
    root = TAG_Node(UInt8(0),"",  Vector{TAG_Node}(), Nothing() )
    while (i<length(bytes))
        id,i = interpret(UInt8, bytes, i)
        if id == TAG_Compound
            println("compound")
            root.id = TAG_Compound
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            root.name = name 
            i, child = ParseNBT(bytes,i)
            
            push!(root.payload, child)
            continue
        elseif id == TAG_Int
            println("int")
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            value, i = interpret(Int32, bytes, i)
            push!(root.payload, TAG_Node(id, name, Vector{TAG_Node}(), value))
            continue
        elseif id == TAG_Short
            println("short")
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            value, i = interpret(Int16, bytes, i)
            push!(root.payload, TAG_Node(id, name, Vector{TAG_Node}(),value))
            continue
        elseif id == TAG_Float
            println("float")
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            value, i = interpret(Float32, bytes, i)
            push!(root.payload, TAG_Node(id, name,Vector{TAG_Node}(), value))
            continue   
        elseif id == TAG_Double
            println("double")
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            value, i = interpret(Float64, bytes, i)
            push!(root.payload, TAG_Node(id, name, Vector{TAG_Node}(), value))
            continue
        elseif id == TAG_Long
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            value,i = interpret(Int64, bytes, i)
            push!(root.payload, TAG_Node(id, name, Vector{TAG_Node}(), value))
            println("Long",name)
            continue
        elseif id == TAG_String
            println("string")
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            length,i = interpret(Int16, bytes, i)
            value = String(bytes[i:i+length-1])
            i+=length
            push!(root.payload, TAG_Node(id, name, Vector{TAG_Node}(), value))
        elseif id==TAG_Byte
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            print(i)
            value,i = interpret(UInt8, bytes, i)
            push!(root.payload, TAG_Node(id, name, Vector{TAG_Node}(), value))
            println("Byte",name)
            continue
        elseif id==TAG_Byte_Array
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            size,i = interpret(UInt32, bytes, i)
            i+=sizeof(UInt32)
            value = Vector{UInt8}()
            for a in 1:size
                push!(value, bytes[i+a])
            end
            println(value)
            i+=size
            push!(root.payload, TAG_Node(id, name, Vector{TAG_Node}(), value))
            println("Byte Array",name)
        elseif id==TAG_List
            length,i = interpret(UInt16, bytes, i)
            name = String(bytes[i:i+length-1])
            i += length
            type, i = interpret(UInt8, bytes, i)
            length, i = interpret(Int32, bytes, i)
            println(name, " ", type, " ", length)

        elseif id == TAG_End
            println("end")
            return i, root
        else
            println(id)
            println("error")
            continue

        end
        
    end
    
end

function ParseNBT_test(fd::GZip.GZipStream)

    bytes =  read(fd)
    i=1
    id,i = interpret(UInt8, bytes, i)
    length,i = interpret(UInt16, bytes, i)

    name = String(bytes[i:i+length-1])
    i += length

    println("id ", id)
    println("name length ",length)
    println("name ", name)

    id,i = interpret(UInt8, bytes, i)
    length,i = interpret(UInt16, bytes, i)

    name = String(bytes[i:i+length-1])
    i+=length

    long,i =interpret(Int64, bytes, i)

    println("id ", id)
    println("name length ",length)
    println("name ", name)
    println("value ", long)

    id,i = interpret(UInt8, bytes, i)
    length,i = interpret(UInt16, bytes, i)
    
    name = String(bytes[i:i+length-1])
    i += length

    short,i =interpret(Int16, bytes, i)

    println("id ", id)
    println("name length ",length)
    println("name ", name)
    println("value ", short)

    id,i = interpret(UInt8, bytes, i)
    length,i = interpret(UInt16, bytes, i)

    name = String(bytes[i:i+length-1])
    i += length

    str_length,i = interpret(UInt16, bytes, i)
    str = String(bytes[i:i+str_length-1])
    i += str_length
    println("id ", id)
    println("name length ",length)
    println("name ", name)
    println("value ", str)

    id,i = interpret(UInt8, bytes, i)
    length,i = interpret(UInt16, bytes, i)

    name = String(bytes[i:i+length-1])
    i += length


    float,i =interpret(Float32, bytes, i)

    println("id ", id)
    println("name length ",length)
    println("name ", name)
    println("value ", float)


end


# function WriteNBT(tree::Node, filepath::String)
#     # TODO 
# end

end
