# This file is a part of Julia. License is MIT: https://julialang.org/license

module VersionWeights

importall ....Base.Operators

export VersionWeight

struct HierarchicalValue{T}
    v::Vector{T}
    rest::T
end

HierarchicalValue{T}(v::Vector{T}) = HierarchicalValue{T}(v, zero(T))
HierarchicalValue(T::Type) = HierarchicalValue(T[])

Base.zero(::Type{HierarchicalValue{T}}) where {T} = HierarchicalValue(T)

Base.typemin(::Type{HierarchicalValue{T}}) where {T} = HierarchicalValue(T[], typemin(T))

for f in (:-, :+)
    @eval function Base.$f(a::HierarchicalValue{T}, b::HierarchicalValue{T}) where T
        av = a.v
        bv = b.v
        la = length(a.v)
        lb = length(b.v)
        l0 = min(la, lb)
        l1 = max(la, lb)
        ld = la - lb
        rv = Vector{T}(l1)
        rf = ($f)(a.rest, b.rest)
        @inbounds for i = 1:l0
            rv[i] = ($f)(av[i], bv[i])
        end
        @inbounds for i = l0+1:l0+ld
            rv[i] = ($f)(av[i], b.rest)
        end
        @inbounds for i = l0+1:l0-ld
            rv[i] = ($f)(a.rest, bv[i])
        end
        return HierarchicalValue(rv, rf)
    end
end

Base.:-(a::HierarchicalValue) = HierarchicalValue(-a.v, -a.rest)

function Base.cmp(a::HierarchicalValue{T}, b::HierarchicalValue{T}) where T
    av = a.v
    bv = b.v
    la = length(a.v)
    lb = length(b.v)
    l0 = min(la, lb)
    l1 = max(la, lb)
    ld = la - lb
    @inbounds for i = 1:l0
        c = cmp(av[i], bv[i]); c != 0 && return c
    end
    @inbounds for i = l0+1:l0+ld
        c = cmp(av[i], b.rest); c != 0 && return c
    end
    @inbounds for i = l0+1:l0-ld
        c = cmp(a.rest, bv[i]); c != 0 && return c
    end
    return cmp(a.rest, b.rest)
end
Base.isless(a::HierarchicalValue{T}, b::HierarchicalValue{T}) where {T} = cmp(a,b) < 0
Base.:(==)(a::HierarchicalValue{T}, b::HierarchicalValue{T}) where {T} = cmp(a,b) == 0

Base.abs(a::HierarchicalValue{T}) where {T} = HierarchicalValue(T[abs(x) for x in a.v], abs(a.rest))

Base.copy(a::HierarchicalValue{T}) where {T} = HierarchicalValue(T[copy(x) for x in a.v], copy(a.rest))

struct VWPreBuildItem
    nonempty::Int
    s::HierarchicalValue{Int}
    i::Int
end
VWPreBuildItem() = VWPreBuildItem(0, HierarchicalValue(Int), 0)
VWPreBuildItem(i::Int) = VWPreBuildItem(1, HierarchicalValue(Int), i)
VWPreBuildItem(s::String) = VWPreBuildItem(1, HierarchicalValue(Int[s...]), 0)

Base.zero(::Type{VWPreBuildItem}) = VWPreBuildItem()

Base.typemin(::Type{VWPreBuildItem}) = (x=typemin(Int); VWPreBuildItem(x, typemin(HierarchicalValue{Int}), x))

Base.:-(a::VWPreBuildItem, b::VWPreBuildItem) = VWPreBuildItem(a.nonempty-b.nonempty, a.s-b.s, a.i-b.i)
Base.:+(a::VWPreBuildItem, b::VWPreBuildItem) = VWPreBuildItem(a.nonempty+b.nonempty, a.s+b.s, a.i+b.i)

Base.:-(a::VWPreBuildItem) = VWPreBuildItem(-a.nonempty, -a.s, -a.i)

function Base.cmp(a::VWPreBuildItem, b::VWPreBuildItem)
    c = cmp(a.nonempty, b.nonempty); c != 0 && return c
    c = cmp(a.s, b.s); c != 0 && return c
    return cmp(a.i, b.i)
end
Base.isless(a::VWPreBuildItem, b::VWPreBuildItem) = cmp(a,b) < 0
Base.:(==)(a::VWPreBuildItem, b::VWPreBuildItem) = cmp(a,b) == 0

Base.abs(a::VWPreBuildItem) = VWPreBuildItem(abs(a.nonempty), abs(a.s), abs(a.i))

Base.copy(a::VWPreBuildItem) = VWPreBuildItem(a.nonempty, copy(a.s), a.i)

struct VWPreBuild
    nonempty::Int
    w::HierarchicalValue{VWPreBuildItem}
end

const _vwprebuild_zero = VWPreBuild(0, HierarchicalValue(VWPreBuildItem))

function VWPreBuild(ispre::Bool, desc::Tuple{Vararg{Union{Int,String}}})
    isempty(desc) && return _vwprebuild_zero
    desc == ("",) && return VWPreBuild(ispre ? -1 : 1, HierarchicalValue(VWPreBuildItem[]))
    hv = HierarchicalValue([VWPreBuildItem(item) for item in desc])
    return VWPreBuild(ispre ? -1 : 0, hv)
end
VWPreBuild() = _vwprebuild_zero

Base.zero(::Type{VWPreBuild}) = VWPreBuild()

const _vwprebuild_min = VWPreBuild(typemin(Int), typemin(HierarchicalValue{VWPreBuildItem}))
Base.typemin(::Type{VWPreBuild}) = _vwprebuild_min

function Base.:(-)(a::VWPreBuild, b::VWPreBuild)
    b === _vwprebuild_zero && return a
    a === _vwprebuild_zero && return -b
    VWPreBuild(a.nonempty-b.nonempty, a.w-b.w)
end
function Base.:(+)(a::VWPreBuild, b::VWPreBuild)
    b === _vwprebuild_zero && return a
    a === _vwprebuild_zero && return b
    VWPreBuild(a.nonempty+b.nonempty, a.w+b.w)
end

function Base.:(-)(a::VWPreBuild)
    a === _vwprebuild_zero && return a
    VWPreBuild(-a.nonempty, -a.w)
end

@inline function Base.cmp(a::VWPreBuild, b::VWPreBuild)
    a === _vwprebuild_zero && b === _vwprebuild_zero && return 0
    c = cmp(a.nonempty, b.nonempty); c != 0 && return c
    return cmp(a.w, b.w)
end
Base.isless(a::VWPreBuild, b::VWPreBuild) = cmp(a,b) < 0
Base.:(==)(a::VWPreBuild, b::VWPreBuild) = cmp(a,b) == 0

function Base.abs(a::VWPreBuild)
    a === _vwprebuild_zero && return a
    VWPreBuild(abs(a.nonempty), abs(a.w))
end

function Base.copy(a::VWPreBuild)
    a === _vwprebuild_zero && return a
    VWPreBuild(a.nonempty, copy(a.w))
end

function Base.deepcopy_internal(a::VWPreBuild, dict::ObjectIdDict)
    haskey(dict, a) && return dict[a]
    b = (a === _vwprebuild_zero) ? _vwprebuild_zero : VWPreBuild(a.nonempty, Base.deepcopy_internal(a.w, dict))
    dict[a] = b
    return b
end

# The numeric type used to determine how the different
# versions of a package should be weighed
struct VersionWeight
    major::Int
    minor::Int
    patch::Int
    prerelease::VWPreBuild
    build::VWPreBuild
end
VersionWeight(major::Int, minor::Int, patch::Int, prerelease::VWPreBuild) = VersionWeight(major, minor, patch, prerelease, zero(VWPreBuild))
VersionWeight(major::Int, minor::Int, patch::Int) = VersionWeight(major, minor, patch, zero(VWPreBuild))
VersionWeight(major::Int, minor::Int) = VersionWeight(major, minor, 0)
VersionWeight(major::Int) = VersionWeight(major, 0)
VersionWeight() = VersionWeight(0)

VersionWeight(vn::VersionNumber) =
    VersionWeight(vn.major, vn.minor, vn.patch,
                  VWPreBuild(true, vn.prerelease), VWPreBuild(false, vn.build))

Base.zero(::Type{VersionWeight}) = VersionWeight()

Base.typemin(::Type{VersionWeight}) = (x=typemin(Int); y=typemin(VWPreBuild); VersionWeight(x, x, x, y, y))

Base.:(-)(a::VersionWeight, b::VersionWeight) =
    VersionWeight(a.major-b.major, a.minor-b.minor, a.patch-b.patch,
                  a.prerelease-b.prerelease, a.build-b.build)

Base.:(+)(a::VersionWeight, b::VersionWeight) =
    VersionWeight(a.major+b.major, a.minor+b.minor, a.patch+b.patch,
                  a.prerelease+b.prerelease, a.build+b.build)

Base.:(-)(a::VersionWeight) =
    VersionWeight(-a.major, -a.minor, -a.patch,
                  -a.prerelease, -a.build)

function Base.cmp(a::VersionWeight, b::VersionWeight)
    c = cmp(a.major, b.major); c != 0 && return c
    c = cmp(a.minor, b.minor); c != 0 && return c
    c = cmp(a.patch, b.patch); c != 0 && return c
    c = cmp(a.prerelease, b.prerelease); c != 0 && return c
    return cmp(a.build, b.build)
end
Base.isless(a::VersionWeight, b::VersionWeight) = cmp(a,b) < 0
Base.:(==)(a::VersionWeight, b::VersionWeight) = cmp(a,b) == 0

Base.abs(a::VersionWeight) =
    VersionWeight(abs(a.major), abs(a.minor), abs(a.patch),
                  abs(a.prerelease), abs(a.build))

Base.copy(a::VersionWeight) =
    VersionWeight(a.major, a.minor, a.patch,
                  copy(a.prerelease), copy(a.build))

# This isn't nice, but it's for debugging only anyway
function Base.show(io::IO, a::VersionWeight)
    print(io, "(", a.major)
    a == VersionWeight(a.major) && @goto done
    print(io, ".", a.minor)
    a == VersionWeight(a.major, a.minor) && @goto done
    print(io, ".", a.patch)
    a.prerelease ≠ _vwprebuild_zero && print(io, "-", a.prerelease)
    a.build ≠ _vwprebuild_zero && print(io, "+", a.build)
    @label done
    print(io, ")")
end

end
