abstract type Shape end

struct Ellipse{T} <: Shape
    x::T
    y::T
    A::T
    B::T
    α::T
    ρ::T
end

function isinside(e::Ellipse, x, y)
    # https://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
    (cosd(e.α) * (x - e.x) + sind(e.α) * (y - e.y))^2 / e.A^2 +
    (sind(e.α) * (x - e.x) - cosd(e.α) * (y - e.y))^2 / e.B^2 ≤ one(x)
end

function projection(e::Ellipse, θᵢ, tᵢ)
    # p. 56
    s = √(e.x^2 + e.y^2)
    γ = atan(e.y, e.x)
    θ = θᵢ - deg2rad(e.α)
    t = tᵢ - s * cos(γ - θᵢ)
    a²θ = e.A^2 * cos(θ)^2 + e.B^2 * sin(θ)^2
    abs(t) ≤ √(a²θ) ? 2 * e.ρ * e.A * e.B * √(a²θ - t^2) / a²θ : zero(a²θ)
end

function projection(
    ellipses::AbstractVector{<:Ellipse},
    θ::AbstractVector,
    t::AbstractVector,
)
    [sum(projection(e, θᵢ, tᵢ) for e in ellipses) for tᵢ in t, θᵢ in θ]
end

# Table 3.1 summary of parameters for tomography simulations
SheppLoganPhantom = [
    Ellipse(0.0, 0.0, 0.92, 0.69, 90.0, 2.0),
    Ellipse(0.0, -0.0184, 0.874, 0.6624, 90.0, -0.98),
    Ellipse(0.22, 0.0, 0.31, 0.11, 72.0, -0.02),
    Ellipse(-0.22, 0.0, 0.41, 0.16, 108.0, -0.02),
    Ellipse(0.0, 0.35, 0.25, 0.21, 90.0, 0.01),
    Ellipse(0.0, 0.1, 0.046, 0.046, 0.0, 0.01),
    Ellipse(0.0, -0.1, 0.046, 0.046, 0.0, 0.01),
    Ellipse(-0.08, -0.605, 0.046, 0.023, 0.0, 0.01),
    Ellipse(0.0, -0.605, 0.023, 0.023, 0.0, 0.01),
    Ellipse(0.06, -0.605, 0.046, 0.023, 90.0, 0.01),
]

function construct(ellipses::AbstractVector{<:Ellipse}, N::Integer)
    f = zeros(N, N)
    for (i, xᵢ) in enumerate(range(-1, 1, length = N)),
        (j, yⱼ) in enumerate(range(-1, 1, length = N))

        @inbounds f[j, i] = sum([isinside(e, xᵢ, yⱼ) ? e.ρ : zero(e.ρ) for e in ellipses])
    end
    f
end


function Integrate(f, x0, tspan, N, M, tol, imax)

    # Get length of state vector
    L = length(x0)

    # Transformation of independant variable
    ω₁ = 0.5*(tspan[2] + tspan[1])
    ω₂ = 0.5*(tspan[2] - tspan[1])

    g(τ,x) =   ω₂*f(ω₁ + ω₂*τ,x)

    # Compute Chebyshev-Gauss-Lobatto Nodes
    τs = SizedVector{M + 1}(zeros(M + 1))
    @inbounds for j in 0:M
        τs[j + 1] = -cos(j*π/M)
    end

    # Compute constant matricies
    Cx  = SizedMatrix{M + 1, N + 1}(zeros(M + 1, N + 1))
    ChebyshevInterpMat!(Cx, τs)
    CI1 = SizedMatrix{N + 1, N}(zeros(N + 1, N))
    initCI1!(CI1)
    Cf  = SizedMatrix{N, M + 1}(zeros(N, M + 1))
    initCf!(Cf, τs)
    C   = SizedMatrix{N + 1, M + 1}(zeros(N + 1, M + 1))
    mul!(C, CI1, Cf)

    # Initialize required matricies
    Xold  = SizedMatrix{M + 1, L}(zeros(M + 1, L))
    XoldT = Xold'
    Xnew  = SizedMatrix{M + 1, L}(zeros(M + 1, L))
    X0    = SizedMatrix{N + 1, L}(zeros(N + 1, L))
    X0[1,:] .= x0
    G     = SizedMatrix{M + 1, L}(zeros(M + 1, L))
    β     = SizedMatrix{N + 1, L}(zeros(N + 1, L))

    # Set Initial Guess for Xold (Could provide as input in future)
    Xold[1,:] .= x0

    # Begin integration loop
    stop = false
    eold = Inf
    enew = Inf
    iter = 0
    while !stop
        iter += 1
        println(iter)

        # Compute forcing function
        @inbounds for i in 1:M+1
            L == 1 ? G[i,1]  = g(τs[i], Xold[i]) :
                     G[i,:] .= g(τs[i], @view(XoldT[:, i]))
        end

        # Update Coefficients
        #β = X0 + C*G
        mul!(β,C,G)
        β .= β + X0

        # Update State
        #Xnew = Cx*β
        mul!(Xnew, Cx, β)

        # Compute error
        enew = 0
        for col in 1:L
            for row in 1:M+1
                temp = Xnew[row,col] - Xold[row,col]
                if temp > enew
                    enew = temp
                end
            end
        end

        if (enew <= tol && eold <= tol) || iter >= imax
            stop = true
        else
            Xold .= Xnew
            eold  = enew
        end
    end

    return (ω₁ .+ ω₂*τs, Xnew)
end

function initCI1!(CI1)
    # Get N
    dims = size(CI1)
    N = dims[2]

    # Initialize R and S
    R = Diagonal(SizedVector{N + 1}(zeros(N + 1)))
    S = SizedMatrix{N + 1, N}(zeros(N + 1, N))

    # Fill Matricies
    R[1,1] = 1
    R[2,2] = 0.5
    S[1,1] = 1
    S[1,2] = -0.25
    S[2,1] = 2
    S[2,3] = -1
    @inbounds for i in 3:N + 1
        R[i,i] = 1/(2*(i - 1))
        S[i,i - 1] = 1
        if i < N
            S[i,i + 1] = -1
        end
        if i < N + 1
            S[1,i] = (1/(2*(i - 2)) - 1/(2*i))*(-1)^i
        end
    end

    # Fill CI1
    #CI1 .= R*S
    lmul!(R,S)
    CI1 .= S
end

function initCf!(Cf, τs)
    # Get N and M
    dims = size(Cf)
    N = dims[1]
    M = dims[2] - 1

    # Set p variable depending on number of nodes and check Cf size for validity
    p = 1
    if M > N
        p = 2
    elseif N > M
        throw(ArgumentError("Improper size of Cf matrix!"))
    end

    # Initialize temporary matricies
    V  = Diagonal(SizedVector{N}(zeros(N)))
    W  = Diagonal(SizedVector{M + 1}(zeros(M + 1)))
    Tm = SizedMatrix{N, M + 1}(zeros(N, M + 1))

    # Fill T
    T!(Tm, τs)

    # Fill V
    @inbounds for i in 1:N
        # Get numerator
        i == 1 ? num = 1 : i == N ? num = p : num = 2

        # Set value
        V[i, i] = num/M
    end

    # Fill W
    @inbounds for i in 1:M + 1
        # Get val
        i == 1 ? val = 0.5 : i == M + 1 ? val = 0.5 : val = 1

        # Set value
        W[i, i] = val
    end

    # Compute Cf
    #Cf .= V*Tm*W
    rmul!(Tm,W)
    lmul!(V,Tm)
    Cf .= Tm
end


# This file is part of the IntervalArithmetic.jl package; MIT licensed

# Intersection
"""
    intersect(a, b)
    ∩(a,b)

Returns the intersection of the intervals `a` and `b`, considered as
(extended) sets of real numbers. That is, the set that contains
the points common in `a` and `b`.
"""
intersect(a::Interval{T}, b::Interval{T}) where T =
    isdisjoint(a, b) ? emptyinterval(T) : unsafe_interval(max(a.lo, b.lo), min(a.hi, b.hi))
# Specific promotion rule for intersect:
intersect(a::Interval{T}, b::Interval{S}) where {T,S} =
    intersect(promote(a, b)...)
intersect(::ValidInterval{T}, ::ValidInterval{T}) where T = emptyinterval(T)

## Hull
"""
    hull(a, b)

Returns the "interval hull" of the intervals `a` and `b`, considered as
(extended) sets of real numbers, i.e. the smallest interval that contains
all of `a` and `b`.
"""
hull(a::Interval{T}, b::Interval{T}) where T =
    unsafe_interval(min(a.lo, b.lo), max(a.hi, b.hi))
hull(::EmptyInterval{T}, b::Interval{T}) where T = b
hull(a::Interval{T}, ::EmptyInterval{T}) where T = a
hull(::EmptyInterval{T}, ::EmptyInterval{S}) where {T, S} =
    emptyinterval(promote_type(T, S))


"""
`ExponentiatedKernel([ρ=1])`
The exponentiated kernel is a Mercer kernel given by:
```
    κ(x,y) = exp(ρ²xᵀy)
```
"""
struct ExponentiatedKernel <: BaseKernel end

kappa(κ::ExponentiatedKernel, xᵀy::Real) = exp(xᵀy)

metric(::ExponentiatedKernel) = DotProduct()

iskroncompatible(::ExponentiatedKernel) = true


using AlphaStructures
using ViewerGL
GL =  ViewerGL

"""
Return points from file.
"""
function load_points(filename::String)::Matrix
    io = open(filename, "r")
    point = readlines(io)
    close(io)

    b = [tryparse.(Float64,split(point[i], " ")) for i in 1:length(point)]
    V = hcat(b...)
    return V
end

filename = "examples/examples2D/model/lar_text.txt";
V = load_points(filename);

# view points
GL.VIEW([
	GL.GLPoints(permutedims(V))
])

# points by columns
filtration = AlphaStructures.alphaFilter(V);
VV,EV,FV = AlphaStructures.alphaSimplex(V, filtration, 0.01)

# View triangle
GL.VIEW([
	GL.GLGrid(V, FV, GL.COLORS[1], 0.8)
])


@testset "The Graph Manifold" begin
    M1 = Euclidean(3)
    v1 = [1., 0., 0.]
    v2 = [0., 2., 0.]
    d1 = [0., 1., 1.]
    d2 = [1., 0., 1.]

    x1 = RnPoint(v1)
    x2 = RnPoint(v2)
    ξ1 = RnTVector(d1)
    ξ2 = RnTVector(d2)

    A = [0 1;1 0]
    M = Graph(M1,A)
    x = GraphVertexPoint([x1,x2])
    y = GraphVertexPoint([x2,x1])
    ξ = GraphVertexTVector([ξ1,ξ2])
    η = GraphVertexTVector([ξ2,ξ1])

    @test getValue(x) == [x1,x2]
    @test getValue(y) == [x2,x1]
    @test getValue(ξ) == [ξ1,ξ2]
    @test getValue(η) == [ξ2,ξ1]

    @test distance(M,x,y) == sqrt(2)*distance(M1,x1,x2)
    @test dot(M,x,ξ,η) == dot(M1,x1,ξ1,ξ2) + dot(M1,x2,ξ2,ξ1)
    @test exp(M,x,ξ) == GraphVertexPoint([exp(M1,x1,ξ1),exp(M1,x2,ξ2)])
    @test log(M,x,y) == GraphVertexTVector([log(M1,x1,x2),log(M1,x2,x1)])

    @test manifoldDimension(M) == 6
    @test manifoldDimension(x) == 6

    @test norm(M,x,ξ) == sqrt(dot(M,x,ξ,ξ))

    # parallelTransport
    # random
    # validate

    @test typicalDistance(M) ≈ sqrt(6) atol=5*10^(-16)

    @test zeroTVector(M,x) == GraphVertexTVector([
        zeroTVector(M1,x1), zeroTVector(M1,x2)
    ])

    @test "$M" == "The manifold on vertices and edges of a graph of The 3-dimensional Euclidean space of (vertex manifold) dimension 6."
    @test "$x" == "GraphVertexV[Rn([1.0, 0.0, 0.0]), Rn([0.0, 2.0, 0.0])]"
    @test "$ξ" == "GraphVertexT[RnT([0.0, 1.0, 1.0]), RnT([1.0, 0.0, 1.0])]"
end


# Mutual Information

function _mutualinfo(A::AbstractMatrix{<:Integer}, normed::Bool)
    N = sum(A)
    (N == 0.0) && return 0.0

    rows = sum(A, dims=2)
    cols = sum(A, dims=1)
    entA = entropy(A)
    entArows = entropy(rows)
    entAcols = entropy(cols)

    hck = (entA - entAcols)/N
    hc = entArows/N + log(N)
    hk = entAcols/N + log(N)

    mi = hc - hck
    return if normed
        2*mi/(hc+hk)
    else
        mi
    end
end

"""
    mutualinfo(a, b; normed=true) -> Float64

Compute the *mutual information* between the two clusterings of the same
data points.

`a` and `b` can be either [`ClusteringResult`](@ref) instances or
assignments vectors (`AbstractVector{<:Integer}`).

If `normed` parameter is `true` the return value is the normalized mutual information (symmetric uncertainty),
see "Data Mining Practical Machine Tools and Techniques", Witten & Frank 2005.

# References
> Vinh, Epps, and Bailey, (2009). “Information theoretic measures for clusterings comparison”.
Proceedings of the 26th Annual International Conference on Machine Learning - ICML ‘09.
"""
mutualinfo(a, b; normed::Bool=true) = _mutualinfo(counts(a, b), normed)