export find_treewidth_from_order
export find_similar_groups
export graph_from_gr, graph_to_gr, load_example_graph

###
### IO functions for reading and writing graphs in gr format.
###

"""
    graph_from_gr(filename::String)

Read a graph from the provided gr file.
"""
function graph_from_gr(filename::String)
    lines = readlines(filename)

    # Create a Graph with the correct number of vertices.
    num_vertices = parse.(Int, split(lines[1], ' ')[3])
    G = lg.SimpleGraph(num_vertices)

    # Add an edge to the graph for every other line in the file.
    for line in lines[2:end]
        src, dst = parse.(Int, split(line, ' '))
        lg.add_edge!(G, src, dst)
    end

    G
end

"""
    graph_to_gr(G::AbstractGraph, filename::String)

Write the provided graph to a file in gr format.
"""
function graph_to_gr(G::lg.AbstractGraph, filename::String)
    open(filename, "w") do io
        write(io, "p tw $(nv(G)) $(ne(G))\n")
        for e in lg.edges(G)
            write(io, "$(e.src) $(e.dst)\n")
        end
    end
end

"""
    load_example_graph(graph_name::String="test")

Return one of the example graphs from the `VertexEliminationOrders` package.

# Possible arguments:
- bristlecone\\_48\\_1-16-1\\_0
- rectangular\\_2x2\\_1-2-1\\_0
- rectangular\\_4x4\\_1-16-1\\_0
- sycamore\\_53\\_8\\_0
- sycamore\\_53\\_20\\_0
- test
"""
function load_example_graph(graph_name::String="test")
    filename = joinpath(dirname(dirname(@__FILE__)), "examples", graph_name * ".gr")
    graph_from_gr(filename)
end

###
### functions to analyse elimination orders.
###

"""
    find_treewidth_from_order(G::AbstractGraph, order::Array{Symbol, 1})

Return the treewidth of `G` with respect to the elimination order in `order`.
"""
function find_treewidth_from_order(G::lg.AbstractGraph, order::Array{<:Integer, 1})
    G = deepcopy(G)
    labels = collect(1:lg.nv(G))
    τ = 0
    for v_label in order
        v = findfirst(vl -> vl == v_label, labels)
        τ = max(τ, lg.degree(G, v))
        lg_eliminate!(G, labels, v)
    end
    τ
end

###
### Functions to analyse a graph.
###

"""Return a vector containing the similar groups of G as defined by Yaun_2011"""
function find_similar_groups(G::lg.SimpleGraph{Int})
    groups = Vector{Int}[]
    for v in lg.vertices(G)
        v_in_a_group = false
        for group in groups
            if are_similar(G, group[1], v)
                push!(group, v)
                v_in_a_group = true
                break
            end
        end
        v_in_a_group || push!(groups, [v])
    end

    groups
end

function are_similar(G::lg.SimpleGraph, u::Int, v::Int)
    return Set(setdiff(lg.all_neighbors(G, u), [v])) == Set(setdiff(lg.all_neighbors(G, v), [u]))
end

"""
    cliqueness(G::AbstractGraph, v::Integer)
Return the number of edges that need to be added to `G` in order to make the neighborhood of
vertex `v` a clique.
"""
function cliqueness(g::lg.AbstractGraph, v::Integer)::Int
    neighborhood = lg.all_neighbors(g, v)::Array{Int64, 1}
    count = 0
    for i in 1:length(neighborhood)-1
        for j in i+1:length(neighborhood)
            vi = neighborhood[i]
            ui = neighborhood[j]
            if !lg.has_edge(g, ui, vi)::Bool
                count += 1
            end
        end
    end
    count
end

###
### Functions to modify a graph.
###

"""
    eliminate!(graph, labels, vertex)

Connects the neighbours of the given vertex into a clique before removing
it from the graph.

The array of vertex labels are also updated to reflect the
reording of vertex indices when the graph is updated.
"""
function lg_eliminate!(g::lg.AbstractGraph, labels, v)
    Nᵥ = lg.all_neighbors(g, v)::Array{Int64, 1}
    for i = 1:length(Nᵥ)-1
        vi = Nᵥ[i]
        for j = i+1:length(Nᵥ)
            vj = Nᵥ[j]
            lg.add_edge!(g, vi, vj)
        end
    end

    lg.rem_vertex!(g, v)
    labels[v] = labels[end]
    pop!(labels)
    g
end

"""
    eliminate!(graph, labels, vertex)
Connects the neighbours of the given vertex into a clique before removing
it from the graph.
The arrays of vertex labels and cliqueness are also updated to reflect the
reording of vertex indices when the graph is updated.
"""
function lg_eliminate!(g::lg.AbstractGraph, labels, c_map, v)
    Nᵥ = lg.all_neighbors(g, v)::Array{Int64, 1}
    for i = 1:length(Nᵥ)-1
        vi = Nᵥ[i]
        for j = i+1:length(Nᵥ)
            vj = Nᵥ[j]

            # Try add an edge connecting vi and ui. If successful, update `c_map`.
            edge_added = lg.add_edge!(g, vi, vj)
            if edge_added
                # Common neighbours of vi and vj have one less edge to add
                # when being eliminated after vi and ui are connected.
                Nvi = lg.all_neighbors(g, vi)::Array{Int64, 1}
                Nvj = lg.all_neighbors(g, vj)::Array{Int64, 1}
                for n in Nvi
                    if n in Nvj
                        c_map[n] -= 1
                    end
                end

                # ui and vi are now neighbours so their cliqueness may increase.
                for n in Nvi
                    if !(n == vj) && !(lg.has_edge(g, n, vj)::Bool)
                        c_map[vi] += 1
                    end
                end
                for n in Nvj
                    if !(n == vi) && !(lg.has_edge(g, n, vi)::Bool)
                        c_map[vj] += 1
                    end
                end
            end
        end
    end

    # Removing v from G means it's also removed from its neighbour's neighbourhood, so their
    # cliqueness may be reduced.
    for n in Nᵥ
        Nₙ = lg.all_neighbors(g, n)::Array{Int64, 1}
        for u in Nₙ
            if !(u == v)
                if !lg.has_edge(g, v, u)
                    c_map[n] -= 1
                end
            end
        end
    end

    lg.rem_vertex!(g, v)
    c_map[v] = c_map[end]
    labels[v] = labels[end]
    g
end


using StanOptimize, Test

@testset "Bernoulli optimize" begin
  include(joinpath(@__DIR__, "../examples/Bernoulli/bernoulli.jl"))
  @test optim1["theta"][end] ≈ 0.3 atol=0.1
  @test optim2["theta"][end] ≈ 0.3 atol=0.1
end


julia> all(iseven, 1:5)              # not all integers between 1 and 5 are even.
false

julia> findfirst(iseven, 1:5)        # the first even integer is at index 2 in the range.
2

julia> count(iseven, 1:5)            # there are two even integers between 1 and 5.
2

julia> filter(iseven, 1:5)           # here are the even integers in the given range.
2-element Array{Int64,1}:
 2
 4

julia> map(iseven, 1:5)              # we apply our function to all integers in range.
5-element Array{Bool,1}:
 false
  true
 false
  true
 false


@testset "1414.find-the-minimum-number-of-fibonacci-numbers-whose-sum-is-k.jl" begin
    @test find_min_fibonacci_numbers(7) == 2
    @test find_min_fibonacci_numbers(10) == 2
    @test find_min_fibonacci_numbers(19) == 3
    for (x, y) in zip([557, 832, 846, 931, 896], [4, 5, 3, 7, 6])
        @test find_min_fibonacci_numbers(x) == y
    end
end


immutable AffineTransform{T,N}
    scalefwd::Matrix{T}
    scaleinv::Matrix{T}
    offset::Vector{T}
    temp::Vector{T}

    function AffineTransform(S::AbstractMatrix, t::AbstractVector)
        if size(S) != (N,N) || size(t) != (N,)
            throw(DimensionMismatch("Inputs must be $N-dimensional"))
        end
        temp = Array(eltype(t), length(t))
        new(S, inv(S), t, temp)
    end
end
function AffineTransform(S, offset)
    T = promote_type(eltype(S),eltype(offset))
    AffineTransform{T,length(offset)}(convert(Matrix{T},S), convert(Vector{T},offset))
end

ndims{T,N}(tform::AffineTransform{T,N}) = N
eltype{T}(tf::AffineTransform{T}) = T

function show(io::IO, tf::AffineTransform)
    println(io, typeof(tf), ":")
    println(io, "matrix: ", tf.scalefwd)
    println(io, "translation: ", tf.offset)
end

scale(tf::AffineTransform, s::Vector) = AffineTransform(scale(tf.scalefwd, s), tf.offset)
scale(s::Vector, tf::AffineTransform) = AffineTransform(scale(s, tf.scalefwd), tf.offset.*s)

*(a::AffineTransform, v::AbstractVecOrMat) = tformfwd(a, v)
\(a::AffineTransform, x::AbstractVecOrMat) = tforminv(a, x)

tformfwd{T}(a::AffineTransform{T}, x::AbstractVector) = tformfwd!(Array(typeof(one(T)*one(eltype(x))), length(x)), a, x)
tforminv{T}(a::AffineTransform{T}, x::AbstractVector) = tforminv!(Array(typeof(one(T)*one(eltype(x))), length(x)), a, x)
tformfwd{T}(a::AffineTransform{T}, X::AbstractMatrix) = tformfwd!(Array(typeof(one(T)*one(eltype(X))), size(X,1), size(X,2)), a, X)
tforminv{T}(a::AffineTransform{T}, X::AbstractMatrix) = tforminv!(Array(typeof(one(T)*one(eltype(X))), size(X,1), size(X,2)), a, X)

tformfwd(a::AffineTransform, v::AbstractMatrix) = a.scalefwd*v .+ a.offset
tforminv(a::AffineTransform, x::AbstractMatrix) = a.scaleinv*(x .- a.offset)

# Implementation of a.scalefwd*X .+ a.offset
function tformfwd!(dest, a::AffineTransform, X::AbstractVecOrMat)
    A_mul_B!(dest, a.scalefwd, X)
    @inbounds for j = 1:size(X,2), i = 1:size(X,1)
        dest[i,j] += a.offset[i]
    end
    dest
end

# Implementation of a.scaleinv*(X .- a.offset)
function tforminv!(dest, a::AffineTransform, X::AbstractVecOrMat)
    size(X,1) == ndims(a) || throw(DimensionMismatch("Dimensionality $(size(X,1)) of inputs does not match $(ndims(a)) for the transformation"))
    A_mul_B!(dest, a.scaleinv, X)
    A_mul_B!(a.temp, a.scaleinv, a.offset)
    @inbounds for j = 1:size(X,2), i = 1:size(X,1)
        dest[i,j] -= a.temp[i]
    end
    dest
end

# Faster versions for particular dimensionalities (with vectors)
for VecType in (AbstractVector, CartesianIndex)
    @eval begin
        function tforminv!{T}(v, a::AffineTransform{T,3}, x::$VecType)
            length(x) == 3 || throw(DimensionMismatch("vector must be 3-dimensional"))
            t = a.temp
            o = a.offset
            @inbounds t[1] = x[1]-o[1]
            @inbounds t[2] = x[2]-o[2]
            @inbounds t[3] = x[3]-o[3]
            gemv3!(v, a.scaleinv, t)
        end
        function tformfwd!{T}(x, a::AffineTransform{T,3}, v::$VecType)
            length(x) == 3 || throw(DimensionMismatch("vector must be 3-dimensional"))
            gemv3!(x, a.scalefwd, v)
            o = a.offset
            @inbounds x[1] += o[1]
            @inbounds x[2] += o[2]
            @inbounds x[3] += o[3]
            x
        end
    end
end

function tforminv{T}(a::AffineTransform{T,3}, x1::Number, x2::Number, x3::Number)
    o = a.offset
    @inbounds t1 = x1 - o[1]
    @inbounds t2 = x2 - o[2]
    @inbounds t3 = x3 - o[3]
    gemv3(a.scaleinv, t1, t2, t3)
end

function tformfwd{T}(a::AffineTransform{T,3}, x1::Number, x2::Number, x3::Number)
    t1, t2, t3 = gemv3(a.scalefwd, x1, x2, x3)
    o = a.offset
    @inbounds r1 = t1+o[1]
    @inbounds r2 = t2+o[2]
    @inbounds r3 = t3+o[3]
    r1, r2, r3
end

for VecType in (AbstractVector, CartesianIndex)
    @eval begin
        function tforminv!{T}(v, a::AffineTransform{T,2}, x::$VecType)
            length(x) == 2 || throw(DimensionMismatch("vector must be 2-dimensional"))
            t = a.temp
            o = a.offset
            @inbounds t[1] = x[1]-o[1]
            @inbounds t[2] = x[2]-o[2]
            gemv2!(v, a.scaleinv, t)
        end
        function tformfwd!{T}(x, a::AffineTransform{T,2}, v::$VecType)
            length(x) == 2 || throw(DimensionMismatch("vector must be 2-dimensional"))
            gemv2!(x, a.scalefwd, v)
            t = a.offset
            @inbounds x[1] += t[1]
            @inbounds x[2] += t[2]
            x
        end
    end
end

function tforminv{T}(a::AffineTransform{T,2}, x1::Number, x2::Number)
    o = a.offset
    @inbounds t1 = x1 - o[1]
    @inbounds t2 = x2 - o[2]
    gemv2(a.scaleinv, t1, t2)
end

function tformfwd{T}(a::AffineTransform{T,2}, x1::Number, x2::Number)
    t1, t2 = gemv2(a.scalefwd, x1, x2)
    o = a.offset
    @inbounds r1 = t1+o[1]
    @inbounds r2 = t2+o[2]
    r1, r2
end

# For just the 3rd coordinate (we sometimes only need that one)
function tforminv3rd{T}(a::AffineTransform{T,3}, x::AbstractVector)
    o = a.offset
    A = a.scaleinv
    A[3]*(x[1]-o[1]) + A[6]*(x[2]-o[2]) + A[9]*(x[3]-o[3])
end

function scaled_add3!(x, s::Number, r)
    @inbounds x[1]+=s*r[1]
    @inbounds x[2]+=s*r[2]
    @inbounds x[3]+=s*r[3]
end

function gemv3!(res, A, x)
    @inbounds res[1] = A[1]*x[1] + A[4]*x[2] + A[7]*x[3]
    @inbounds res[2] = A[2]*x[1] + A[5]*x[2] + A[8]*x[3]
    @inbounds res[3] = A[3]*x[1] + A[6]*x[2] + A[9]*x[3]
    res
end

function gemv3(A, x1::Number, x2::Number, x3::Number)
    @inbounds r1 = A[1]*x1 + A[4]*x2 + A[7]*x3
    @inbounds r2 = A[2]*x1 + A[5]*x2 + A[8]*x3
    @inbounds r3 = A[3]*x1 + A[6]*x2 + A[9]*x3
    r1, r2, r3
end

function scaled_add2!(x, s::Number, r)
    @inbounds x[1]+=s*r[1]
    @inbounds x[2]+=s*r[2]
end

function gemv2!(res, A, x)
    @inbounds res[1] = A[1]*x[1] + A[3]*x[2]
    @inbounds res[2] = A[2]*x[1] + A[4]*x[2]
    res
end

function gemv2(A, x1::Number, x2::Number)
    @inbounds r1 = A[1]*x1 + A[3]*x2
    @inbounds r2 = A[2]*x1 + A[4]*x2
    r1, r2
end

### Identity transformation
tformeye(T, nd) = AffineTransform(eye(T, nd), zeros(T, nd))
tformeye(nd) = AffineTransform(eye(nd), zeros(nd))

### Rotations
# Right-hand rotation around an axis (axis points towards viewer)

rotation2(angle) = [cos(angle) -sin(angle); sin(angle) cos(angle)]

function tformrotate(angle)
    A = rotation2(angle)
    AffineTransform{eltype(A), 2}(A, zeros(eltype(A),2))
end

# The following assumes uaxis is normalized
function _rotation3(uaxis::Vector, angle)
    if length(uaxis) != 3
        error("3d rotations only")
    end
    ux, uy, uz = uaxis[1], uaxis[2], uaxis[3]
    c = cos(angle)
    s = sin(angle)
    cm = one(typeof(c)) - c
    R = [c+ux*ux*cm       ux*uy*cm-uz*s      ux*uz*cm+uy*s;
         uy*ux*cm+uz*s      c+uy*uy*cm       uy*uz*cm-ux*s;
         uz*ux*cm-uy*s    uz*uy*cm+ux*s      c+uz*uz*cm]
end

function rotation3{T}(axis::Vector{T}, angle)
    n = norm(axis)
    axisn = n>0 ? axis/n : (tmp = zeros(T,length(axis)); tmp[1] = 1)
    _rotation3(axisn, angle)
end

# Angle/axis representation where the angle is the norm of the vector (so axis is not normalized)
function rotation3{T}(axis::Vector{T})
    n = norm(axis)
    axisn = n>0 ? axis/n : (tmp = zeros(typeof(one(T)/1),length(axis)); tmp[1] = 1; tmp)
    _rotation3(axisn, n)
end

# Convert rotation matrix to axis representation (angle/axis with the norm of the vector coding the angle)
function rotationparameters{T}(R::Matrix{T})
    size(R, 1) == size(R, 2) || error("Matrix must be square")
    if size(R, 1) == 2
        return [atan2(-R[1,2],R[1,1])]
    elseif size(R, 1) == 3
        # See Itzhack Y. Bar-Itzhack.  "New Method for Extracting the Quaternion from a Rotation Matrix",
        # Journal of Guidance, Control, and Dynamics, Vol. 23, No. 6 (2000), pp. 1085-1087.
        # On https://en.wikipedia.org/wiki/Rotation_matrix#Conversions
        # This is said to be robust even if R is not orthogonal
        Q12p, Q13p, Q23p = R[1,2]+R[2,1], R[1,3]+R[3,1], R[2,3]+R[3,2]
        Q12m, Q13m, Q23m = R[1,2]-R[2,1], R[1,3]-R[3,1], R[2,3]-R[3,2]
        K = [R[1,1]-R[2,2]-R[3,3]  Q12p  Q13p  Q23m;
             Q12p  R[2,2]-R[1,1]-R[3,3]  Q23p -Q13m;
             Q13p  Q23p  R[3,3]-R[1,1]-R[2,2]  Q12m;
             Q23m -Q13m  Q12m  R[1,1]+R[2,2]+R[3,3]]
        D, V = eig(K)
        imax = indmax(D)
        q = V[:,imax]
        ax = q[1:3]
        n = norm(ax)
        if n > 0
            ax = ax/n
        end
        th = -2*atan(n/q[4])
        return th*ax
    else
        error("Rotations in $(size(R, 1)) dimensions not supported")
    end
end

tformtranslate(trans::Vector) = AffineTransform(eye(length(trans)), trans)

tformscale(scale::Real, ndims::Int) = AffineTransform(scale*eye(typeof(scale),ndims), zeros(typeof(scale),ndims))

function tformrotate(axis::Vector, angle)
    if length(axis) == 3
        return AffineTransform(rotation3(axis, angle), zeros(eltype(axis),3))
    else
        error("Dimensionality ", length(axis), " not supported")
    end
end

function tformrotate(x::Vector)
    if length(x) == 3
        return AffineTransform(rotation3(x), zeros(eltype(x),3))
    else
        error("Dimensionality ", length(x), " not supported")
    end
end

# The following is useful in optimization routines, which tend to work on parameter vectors
function tformrigid(p::Vector)
    if length(p) == 3
        # 2d: [angle, shiftx, shifty]
        return AffineTransform(rotation2(p[1]), p[2:3])
    elseif length(p) == 6
        # 3d: [axis, shift]
        return AffineTransform(rotation3(p[1:3]), p[4:6])
    else
        error("Rigid transformations with $(length(p)) parameters not supported")
    end
end

function(*)(T2::AffineTransform, T1::AffineTransform)
    A1 = T1.scalefwd
    A2 = T2.scalefwd
    o = T2.offset + A2*T1.offset
    AffineTransform(A2*A1, o)
end

function(\)(T2::AffineTransform, T1::AffineTransform)
    A1 = T1.scalefwd
    A2 = T2.scaleinv
    o = A2*(T1.offset-T2.offset)
    AffineTransform(A2*A1, o)
end


function create_historical_scenarios(container:: FXForwardMarketDataContainer; valuation_date:: Union{Date, Nothing}=nothing, time_horizon:: Integer = 1, lookback:: Integer = 250)

    scenario_dates_raw = collect(keys(first(container.fx_forward_curves).second.curves))
    if valuation_date === nothing
        idx = length(scenario_dates_raw)
    else
        idx = findlast(scenario_dates_raw .<= valuation_date)
    end
    scenario_dates = @view scenario_dates_raw[1:idx]

    fx_scenarios = Dict(key => _calculate_returns(val, time_horizon, lookback, scenario_dates, idx)
        for (key, val) in pairs(container.fx_forward_curves))

    disc_scenarios = Dict(key => _calculate_returns(val, time_horizon, lookback, scenario_dates, idx)
        for (key, val) in pairs(container.discount_curves))

    return FXForwardMarketDataContainer(fx_scenarios, disc_scenarios)
end

function _calculate_returns(ts:: T, time_horizon:: Integer, lookback:: Integer, ts_dates, idx:: Int) where {T <: AbstractTimeSeries}

    ts_values_raw = collect(values(ts.curves))
    ts_values = @view ts_values_raw[1:idx]

    base_scenario = last(ts_values)
    values_in_lookback = @view ts_values[end-lookback-time_horizon:end-1] # excluding base scenario

    shifts_raw = values_in_lookback ./ lag(values_in_lookback, time_horizon)
    shifts = [x for x in shifts_raw if !ismissing(x)] # the lag function adds `missing` values at the beginning of the time series, they are removed here.

    scenarios = shifts .* base_scenario
    push!(scenarios, base_scenario) # last scenario is base scenario

    # the scenarios are from the end of the time period
    scenario_dates =  ts_dates[end-length(scenarios)+1:end]

    return T(SortedDict(scenario_dates .=> scenarios))

end


using PyPlot
using PyCall
mpl = pyimport("tikzplotlib")
using JLD2
using NNFEM
include("../nnutil.jl")
nntype= "ae_scaled"
stress_scale = 100.0

tid = 3
@load "../Data/domain$tid.jld2" domain
@load "../Data/learn_domain$(tid)_te.jld2" domain_te

u1 = hcat(domain.history["state"]...)
u2 = hcat(domain_te.history["state"]...)
err = zeros(8, 101)
for i = 1:8
    u1_ = [u1[i,:] u1[i+8,:]]
    u2_ = [u2[i,:] u2[i+8,:]]
    err[i,:] = sqrt.(sum((u1_-u2_).^2,dims=2)[:])
end

T = 0.5
NT = 100
t = LinRange(0.0,T, NT+1)
for i = 1:8
    plot(t, err[i,:])
end
xlabel("t")
ylabel("\$u_{ref}-u_{est}\$")
mpl.save("truss2d_learn_loc_diff$tid.tex")

@load "Data/domain3.jld2" domain
X, Y = prepare_strain_stress_data1D(domain)
y = squeeze(nn(constant(X[:,1]), constant(X[:,2]), constant(X[:,3])))
sess = Session(); init(sess)
close("all")
ADCME.load(sess, "Data/learned_nn.mat")
out = run(sess, y)
plot(X[:,1], out,"+", label="NN")
plot(X[:,1], Y, ".", label="Exact")
legend()


struct Simulation{DT <: Number}
    Δt::DT
    x::OffsetArray{DT,3,Array{DT,3}}

    function Simulation(x₀::AbstractArray{DT,2}, Δt::DT, nt::Int) where {DT}
        x = OffsetArray(zeros(DT, size(x₀)..., nt+1), axes(x₀)..., 0:nt)
        x[:,:,0] .= x₀
        new{DT}(Δt, x)
    end
end

ntimesteps(sim::Simulation) = lastindex(sim.x,3)
nsamples(sim::Simulation) = lastindex(sim.x,2)

eachtimestep(sim::Simulation) = axes(sim.x,3)[1:end]
eachsample(sim::Simulation) = axes(sim.x,2)

function run!(sim::Simulation)
    for n in eachtimestep(sim)
        for i in eachsample(sim)
            sim.x[1,i,n] = sim.x[1,i,n-1] + sim.Δt * sim.x[2,i,n-1]
            sim.x[2,i,n] = sim.x[2,i,n-1] - sim.Δt * sin(sim.x[1,i,n-1])
        end
    end
    return sim.x
end


using Test
using VlasovSolvers

include("vlasov-ampere-fft-1d1v.jl")
include("vlasov-poisson-bsl-spline-1d1v.jl")
include("test_deposition_pic.jl")
include("test_poisson_solve_pic.jl")


struct PredatorPreyHexWorldMG
    hexes::Vector{Tuple{Int, Int}}
    hexWorldDiscreteMDP::DiscreteMDP
end

n_agents(mg::PredatorPreyHexWorldMG) = 2

ordered_states(mg::PredatorPreyHexWorldMG, i::Int) = vec(collect(1:length(mg.hexes)))
ordered_states(mg::PredatorPreyHexWorldMG) = vec(collect(Iterators.product([ordered_states(mg, i) for i in 1:n_agents(mg)]...)))

ordered_actions(mg::PredatorPreyHexWorldMG, i::Int) = vec(collect(1:n_actions(mg.hexWorldDiscreteMDP)))
ordered_joint_actions(mg::PredatorPreyHexWorldMG) = vec(collect(Iterators.product([ordered_actions(mg, i) for i in 1:n_agents(mg)]...)))

n_actions(mg::PredatorPreyHexWorldMG, i::Int) = length(ordered_actions(mg, i))
n_joint_actions(mg::PredatorPreyHexWorldMG) = length(ordered_joint_actions(mg))

function transition(mg::PredatorPreyHexWorldMG, s, a, s′)
    # When a prey is caught (new prey born), it teleports to a random location and the predator remains (eating).
    # Otherwise, both transition following HexWorldMDP.
    if s[1] == s[2]
        prob = Float64(s′[1] == s[1]) / length(mg.hexes)
    else
        prob = mg.hexWorldDiscreteMDP.T[s[1], a[1], s′[1]] * mg.hexWorldDiscreteMDP.T[s[2], a[2], s′[2]]
    end
    return prob
end

function reward(mg::PredatorPreyHexWorldMG, i::Int, s, a)
    r = 0.0

    if i == 1
        # Predators get -1 for moving and 10 for catching the prey.
        if s[1] == s[2]
            return 10.0
        else
            return -1.0
        end
    elseif i == 2
        # Prey get -1 for moving and -100 for being caught.
        if s[1] == s[2]
            r = -100.0
        else
            r = -1.0
        end
    end

    return r
end

function joint_reward(mg::PredatorPreyHexWorldMG, s, a)
    return [reward(mg, i, s, a) for i in 1:n_agents(mg)]
end

function MG(mg::PredatorPreyHexWorldMG)
    return MG(
        mg.hexWorldDiscreteMDP.γ,
        vec(collect(1:n_agents(mg))),
        ordered_states(mg),
        [ordered_actions(mg, i) for i in 1:n_agents(mg)],
        (s, a, s′) -> transition(mg, s, a, s′),
        (s, a) -> joint_reward(mg, s, a)
    )
end

function PredatorPreyHexWorldMG(hexes::Vector{Tuple{Int,Int}},
                                r_bump_border::Float64,
                                p_intended::Float64,
                                γ::Float64)
    hexWorld = HexWorldMDP(hexes,
                           r_bump_border,
                           p_intended,
                           Dict{Tuple{Int64,Int64},Float64}(),
                           γ)
    mdp = hexWorld.mdp
    return PredatorPreyHexWorldMG(hexes, mdp)
end

# const CirclePredatorPreyHexWorld = PredatorPreyHexWorldMG(
#     [
#      (-1, 2), (0, 2),
#      (-1, 1), (1, 1),
#      (0, 0), (1, 0),
#      ],
#     HexWorldRBumpBorder,
#     HexWorldPIntended,
#     0.95
# )

function CirclePredatorPreyHexWorld()
    CirclePredatorPreyHexWorld = PredatorPreyHexWorldMG(
        [
         (-1, 2), (0, 2), (1, 2),
         (-1, 1), (1, 1), (3, 1), (4, 1),
         (0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
         ],
        HexWorldRBumpBorder,
        HexWorldPIntended,
        0.95
    )
    return CirclePredatorPreyHexWorld
end

# const PredatorPreyHexWorld = PredatorPreyHexWorldMG(
#     [
#      (-1, 2), (0, 2), (1, 2),
#      (-1, 1), (1, 1), (3, 1), (4, 1),
#      (0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
#      ],
#     HexWorldRBumpBorder,
#     HexWorldPIntended,
#     0.95
# )

function PredatorPreyHexWorld()
    PredatorPreyHexWorld = PredatorPreyHexWorldMG(
        [
         (-1, 2), (0, 2), (1, 2),
         (-1, 1), (1, 1), (3, 1), (4, 1),
         (0, 0), (1, 0), (2, 0), (3, 0), (4, 0),
         ],
        HexWorldRBumpBorder,
        HexWorldPIntended,
        0.95
    )
    return PredatorPreyHexWorld
end


"""
Perform a union between two HyperRectangles.
"""
function union{T,N}(h1::HyperRectangle{N, T}, h2::HyperRectangle{N, T})
    m = min(minimum(h1), minimum(h2))
    mm = max(maximum(h1), maximum(h2))
    HyperRectangle{N, T}(m, mm-m)
end

"""
Perform a difference between two HyperRectangles.
"""
diff(h1::HyperRectangle, h2::HyperRectangle) = h1

"""
Perform a intersection between two HyperRectangles.
"""
function intersect{T,N}(h1::HyperRectangle{N, T}, h2::HyperRectangle{N, T})
    m = max(minimum(h1), minimum(h2))
    mm = min(maximum(h1), maximum(h2))
    HyperRectangle{N, T}(m, mm-m)
end

function intersect(a::SimpleRectangle, b::SimpleRectangle)
    min_n = max(minimum(a), minimum(b))
    max_n = min(maximum(a), maximum(b))
    w = max_n - min_n
    SimpleRectangle(min_n[1], min_n[2], w[1], w[2])
end


# Test each op and gradients, along with some combinations
# (TODO) Needs a much cleaner design long term
using cg
using Base.Test
using Base

show_test(test::AbstractString) = println("==============\n Running tests $test\n==============")
i = () -> cg.placeholder([1]) # Shape doesn't matter yet
c = cg.constant

function genarg(shape::AbstractArray, filltype::Symbol ; offset = 0, rand_offset = -0.5)
    if filltype == :range
        # val[j] = i + j
        return reshape(collect(Float64, 1:(*(shape...))), shape...) + offset
    elseif filltype == :rand
        return rand(shape...) + rand_offset
    elseif filltype == :ones
        return ones(shape...)
    else
        @assert false && "invalid filltype"
    end
end

function get_inputs(n::cg.Node)
    G = cg.get_graph([n])
    inputs = filter(x -> isa(x.op, cg.Placeholder), G.nodes)
    collect(inputs)
end

function test_output(out::cg.Node, truth::Function, args::Tuple{cg.Node, Array{Float64, 2}}...)
    # Float64/2 should be TensorValue once type system updated
    session = cg.Session(out)
    for (node, value) in args
        session.values[node] = value
    end
    graphresult = cg.interpret(session, out)
    truthresult = truth([x[2] for x in args]...)
    @test_approx_eq_eps(graphresult, truthresult, 0.001)
end

# Test each operator
function test_gradients(out::cg.Node, shape=[5] ; filltype::Symbol=:range, iters=1, debug=false)
    inputs = get_inputs(out)
    gradients = cg.grad(out, inputs)

    session = cg.Session(out)
    for iter = 1:iters
        for (i,input) = enumerate(inputs)
            session.values[input] = genarg(shape, filltype, offset = i)
        end

        for (input,grad) = zip(inputs, gradients)
            numeric = cg.numeric_grad(session, out, input, 0.0000001)
            symbolic = cg.interpret(session, grad, debug=debug)
            if debug
                @show session.values[input]
                @show session.values[out]
                @show numeric
                @show symbolic
            end
            @test_approx_eq_eps(symbolic, numeric, 0.01)
            for i = 1:length(symbolic)
                @test_approx_eq_eps(symbolic[i], numeric[i], 0.0001)
            end
        end
    end
end

i = () -> cg.placeholder([1]) # Shape doesn't matter yet
function test_scalar_gradients()
    show_test("scalar")
    @show test_gradients(sum(i()))

    # Scalar ops
    @show test_gradients(sum(i() + i()))
    @show test_gradients(sum(i() - i()))
    @show test_gradients(sum(i() * i()))
    @show test_gradients(sum(i() / i()))
    @show test_gradients(sum(i() ^ cg.constant(3.0)))
    @show test_gradients(sum(cg.constant(3.0) ^ i()))

    @show test_gradients(sum(-i()))
    @show test_gradients(sum(sign(i())))
    @show test_gradients(sum(sign(i())), filltype=:rand, iters=100)
    @show test_gradients(sum(exp(i())))
    @show test_gradients(sum(log(i())))
    @show test_gradients(sum(sin(i())))
    @show test_gradients(sum(cos(i())))
    @show test_gradients(sum(abs(i())))

    @show test_gradients(sum(max(i(), i())))
    @show test_gradients(sum(max(i(), i())), filltype=:rand, iters=100)
    @show test_gradients(sum(min(i(), i())))
    @show test_gradients(sum(min(i(), i())), filltype=:rand, iters=100)

    @show test_gradients(sum(cg.sigmoid(i())))
end

function test_shape_gradients()
    show_test("shape")
    # Shape related ops
    shape = [10,15]
    ops = [cg.maximum, cg.sum]
    for op in ops
        @show op
        for graph in [sum(op(i())),
                      sum(op(i(), cg.constant(1))), sum(op(i(), cg.constant(2)))]
            for filltype = [:ones, :rand, :range]
                @show (op, graph, filltype)
                test_gradients(graph, shape, filltype=filltype, iters=100)
            end
        end
    end
end

function test_get_and_set_gradients()
    c = cg.constant
    show_test("get_and_set")
    @show test_gradients(cg.getindex(i(), c(4)))
    @show test_gradients(cg.getindex(cg.setindex(i(), c(3), c(1000)), c(3)))
    @show test_gradients(cg.getindex(cg.setindex(i(), c(3), c(1000)), c(2)))
end

function test_other_gradients()
    show_test("other gradients")
    a = cg.constant(rand(4,5))
    b = i()
    # Mat mul
    @show test_gradients(cg.sum(cg.matmul(cg.t(b), b)), [4, 3])
    @show test_gradients(cg.sum(cg.matmul(b, cg.t(b))), [4, 3])
    @show test_gradients(cg.sum(cg.matmul(cg.t(a), b)), [4, 3])
    @show test_gradients(cg.sum(cg.matmul(cg.t(b), a)), [4, 3])
end

function test_broadcast()
    show_test("Broadcast")
    i1 = i()
    subScalar = c(2.0) - i()
    divScalar =  i() / c(2.0)
    subVec = cg.broadcastop(cg.Sub(), c([1.0, 2.0, 3.0]), i())
    divVec = cg.broadcastop(cg.Div(), i(), c([1.0, 2.0, 3.0]))
    #@show test_output(add, (x) -> [10.0, 10.0] .+ x, (i1, [1.0]'))
    for test in [subScalar, divScalar, subVec, divVec]
        for filltype in [:ones, :range, :rand]
            @show test
            @show filltype
            @show test_gradients(cg.sum(test), [3,5], filltype=filltype)
        end
    end
end

function test_nn()
    show_test("nn")

    for filltype in [:ones, :rand]
        si1 = i()
        softmax = cg.softmax(si1)
        ssi1 = i()
        softmax_stable = cg.softmax_stable(ssi1)
        ci1 = i()
        ci2 = i()
        crossentropy = cg.crossentropy(ci1, ci2)
        sci1 = i()
        sci2 = i()
        softmax_crossentropy = cg.crossentropy(sci1, cg.softmax(sci2))

        softmax_t(x) = exp(x) ./ sum(exp(x), 1)
        crossentropy_t(p, q) = -sum(p .* log(q))
        softmax_crossentropy_t(p, q) = crossentropy_t(p, softmax_t(q))
        shape = [5,10]
        a1 = genarg(shape, :rand, rand_offset=0)
        a2 = genarg(shape, :rand, rand_offset=0)

        @show test_output(softmax, softmax_t, (si1, a1))
        @show test_output(softmax_stable, softmax_t, (ssi1, a1))
        @show test_output(crossentropy, crossentropy_t, (ci1, a1), (ci2, a2))
        @show test_output(softmax_crossentropy, softmax_crossentropy_t, (sci1, a1), (sci2, a2))

        @show test_gradients(cg.sum(broadcast("/", si1, sum(si1, cg.constant(1)))))
        @show test_output(print(cg.sum(broadcast("/", si1, sum(si1)))), x -> sum(x / sum(x)), (si1, a1))

        @show test_gradients(cg.sum(broadcast("/", si1, sum(si1))))
        @show test_gradients(cg.getindex(si1, cg.constant(1)))
        @show test_gradients(sum(si1))

        scal1 = i()
        scal2 = i()
        @show test_gradients(scal1 / (scal1 + scal2), [1])
        @show test_gradients(cg.getindex(broadcast("/", si1, sum(si1)), cg.constant(1)), [2])

        @show test_gradients(cg.sum(softmax))
        @show test_gradients(cg.sum(softmax_stable))
        @show test_gradients(cg.sum(crossentropy))
        @show test_gradients(cg.sum(softmax_crossentropy))
    end
end

function test_sgd_basics()
    show_test("sgd")
    # Test basic optimization - minimize sum((b - a)^2)
    target = rand(10)
    a = cg.constant(target, "a")
    b = cg.variable(cg.constant(zeros(10)), "b") # Parameter to optimize
    c = b - a
    d = c * c
    e = sum(d)
    values = Dict{cg.Node, cg.TensorValue}()
    optimizer = cg.sgd_optimizer(e, [b], cg.constant(0.001, "step_size"))
    #cg.render(cg.get_graph([b]), "graph.png")
    session = cg.Session(optimizer)
    for i = 1:10000
        cg.interpret(session, optimizer)
        if session.values[e][1] <= 0.001
            break
        end
    end
    @test_approx_eq_eps target session.values[b] 0.1
end

# Test that sum gradients work properly
function test_sum()
    show_test("sum")
    @show test_gradients(cg.sum(i()), [3, 5])
    a = cg.placeholder([3, 5])
    b = cg.sum(a, cg.constant(1))
    c = cg.sum(b)
    @show test_gradients(c, [3, 5])
end

test_other_gradients()
test_sum()
test_broadcast()
test_nn()
test_scalar_gradients()
test_shape_gradients()
test_get_and_set_gradients()
test_sgd_basics()


## Test π₀ estimator methods ##
module Test_pi0

using MultipleTesting
using Test
using StatsBase

@testset "π₀ estimators" begin

    ## test case: deterministic
    p0 = collect(0.01:0.01:1)
    p1 = p0 .^ 10
    p = [p0; p1] ## unsorted
    pi0 = length(p0) / length(p)

    lambdas = collect(0.05:0.05:0.95)

    unsort = MultipleTesting.unsort

    @testset "Storey π₀" begin

        @test typeof(Storey()) <: Pi0Estimator
        @test typeof(Storey(0.5)) <: Pi0Estimator
        @test estimate_pi0(p, Storey(0.2)) ≈ 0.6
        @test estimate_pi0(p, Storey(0.0)) ≈ 1.0
        @test estimate_pi0(p, Storey(1.0)) ≈ 1.0

        p_unsort = unsort(p)
        @test !issorted(p_unsort)
        @test estimate_pi0(p_unsort, Storey(0.2)) ≈ 0.6
        @test !issorted(p_unsort)

        @test_throws DomainError Storey(-0.1)
        @test_throws DomainError Storey(1.1)
        #@test_throws DomainError estimate_pi0(p, Storey(1.0)) ## CHCK

    end

    @testset "StoreyBootstrap π₀" begin

        q = 0.1

        @test typeof(StoreyBootstrap()) <: Pi0Estimator

        @test estimate_pi0(p, StoreyBootstrap()) ≈ 0.6
        @test estimate_pi0(p0, StoreyBootstrap()) ≈ 1.0
        @test estimate_pi0(p1, StoreyBootstrap()) ≈ 0.15

        @test estimate_pi0(p, StoreyBootstrap(lambdas, q)) ≈ 0.6
        @test estimate_pi0(p0, StoreyBootstrap(lambdas, q)) ≈ 1.0
        @test estimate_pi0(p1, StoreyBootstrap(lambdas, q)) ≈ 0.15

        ## unsorted lambdas
        lambdas_unsort = unsort(lambdas)
        @test !issorted(lambdas_unsort)
        @test estimate_pi0(p, StoreyBootstrap(lambdas_unsort, q)) ≈ 0.6
        @test !issorted(lambdas_unsort)

        @test_throws DomainError StoreyBootstrap(lambdas, -0.1)
        @test_throws DomainError StoreyBootstrap(lambdas, 1.1)
        @test_throws MethodError StoreyBootstrap(0.5)
        @test_throws MethodError StoreyBootstrap(lambdas)
        @test_throws MethodError StoreyBootstrap(0.5, lambdas)

    end

    @testset "LeastSlope π₀" begin

        @test typeof(LeastSlope()) <: Pi0Estimator

        ## checked against structSSI::pi0.lsl
        @test isapprox( estimate_pi0(p, LeastSlope()), 0.62, atol = 1e-2 )
        @test isapprox( estimate_pi0(p0, LeastSlope()), 1.0, atol = 1e-2 )
        @test isapprox( estimate_pi0(p1, LeastSlope()), 0.16, atol = 1e-2 )

        ## check against internal reference implementation
        @test estimate_pi0(p, LeastSlope()) ≈ MultipleTesting.lsl_pi0_vec(p)
        @test estimate_pi0(p0, LeastSlope()) ≈ MultipleTesting.lsl_pi0_vec(p0)
        @test estimate_pi0(p1, LeastSlope()) ≈ MultipleTesting.lsl_pi0_vec(p1)

        @test_throws MethodError LeastSlope(0.1)

        ## unsorted p-values
        p_unsort = unsort(p)
        @test !issorted(p_unsort)
        @test isapprox( estimate_pi0(p_unsort, LeastSlope()), 0.62, atol = 1e-2 )
        @test !issorted(p_unsort)

        p_unsort = unsort(p)
        @test !issorted(p_unsort)
        @test isapprox( MultipleTesting.lsl_pi0_vec(p_unsort), 0.62, atol = 1e-2 )
        @test !issorted(p_unsort)

    end

    @testset "Oracle π₀" begin

        @test estimate_pi0(p, Oracle(0.5)) == 0.5
        @test estimate_pi0(p0, Oracle(0.6)) == 0.6
        @test estimate_pi0(p1, Oracle()) == 1.0

    end

    @testset "TwoStep π₀" begin

        alpha = 0.05

        @test typeof(TwoStep(alpha)) <: Pi0Estimator

        ## checked against mutoss::TSBKY_pi0_est
        @test estimate_pi0(p, TwoStep()) ≈ 0.665
        @test estimate_pi0(p, TwoStep(alpha)) ≈ 0.665
        @test estimate_pi0(p0, TwoStep(alpha)) ≈ 1.0
        @test estimate_pi0(p1, TwoStep(alpha)) ≈ 0.29
        @test estimate_pi0(p, TwoStep(alpha, BenjaminiHochberg())) ≈ 0.665

        @test estimate_pi0(p, TwoStep(0.1)) ≈ 0.63
        @test estimate_pi0(p, TwoStep(0.0)) ≈ 1.0
        @test estimate_pi0(p, TwoStep(1.0)) ≈ 0.415
        @test estimate_pi0(p, TwoStep(0.1, BenjaminiHochberg())) ≈ 0.63

        ## unsorted p-values
        p_unsort = unsort(p)
        @test !issorted(p_unsort)
        @test estimate_pi0(p_unsort, TwoStep(alpha)) ≈ 0.665
        @test !issorted(p_unsort)

    end

    @testset "RightBoundary π₀" begin

        # R code tested against (note this uses equidistant grid so we have to use
        # λseq as in Storey for comparison
        # ```R
        # library(pi0)
        # p0 <- seq(0.01,1, by=0.01)
        # histf1(p0, rightBoundary=TRUE)
        # [1] 1
        # p1 <- p0^10
        # histf1(p1, rightBoundary=TRUE)
        # [1] 0.1428571
        # p <- c(p0,p1)# histf1(p, rightBoundary=TRUE)
        # [1] 0.5714286
        # ```

        @test typeof(RightBoundary()) <: Pi0Estimator
        @test typeof(RightBoundary(lambdas)) <: Pi0Estimator

        # only eps because pi0 package uses right closed histograms
        @test isapprox( estimate_pi0(p, RightBoundary(lambdas)), 0.5714286, atol = 0.02 )
        @test estimate_pi0(p0, RightBoundary(lambdas)) ≈ 1.0
        @test isapprox( estimate_pi0(p1, RightBoundary(lambdas)), 0.1428571, atol = 1e-7 )

        @test estimate_pi0(p, RightBoundary(lambdas)) ≈ estimate_pi0(p, RightBoundary(unsort(lambdas)))

        # not checked against R implementation but should hold (check for default lambda grid)
        @test estimate_pi0(p0, RightBoundary()) ≈ 1.0

        p_unsort = unsort(p1)
        @test !issorted(p_unsort)
        @test isapprox( estimate_pi0(p_unsort, RightBoundary(lambdas)), 0.1428571, atol = 1e-7 )
        @test !issorted(p_unsort)

    end

    @testset "CensoredBUM π₀" begin

        @test typeof(CensoredBUM()) <: Pi0Estimator
        @test typeof(CensoredBUM(0.2, 0.1)) <: Pi0Estimator

        @test isapprox( estimate_pi0(p, CensoredBUM()), 0.55797, atol = 1e-5 )
        @test isapprox( estimate_pi0(p0, CensoredBUM()), 1.0, atol = 1e-5 )
        @test isapprox( estimate_pi0(p1, CensoredBUM()), 0.11608, atol = 2e-5 )

        # test against internal reference implementation
        # p0 case is not handled well by naive implementation
        @test estimate_pi0(p, CensoredBUM()) ≈ MultipleTesting.cbum_pi0_naive(p)[1]
        @test estimate_pi0(p1, CensoredBUM()) ≈ MultipleTesting.cbum_pi0_naive(p1)[1]

        ## test case that does not converge
        @test isnan(estimate_pi0(p, CensoredBUM(0.5, 0.05, 1e-6, 2)))

        @test_throws DomainError CensoredBUM(-0.5, 0.05)
        @test_throws DomainError CensoredBUM(1.5, 0.05)
        @test_throws DomainError CensoredBUM(0.5, -0.05)
        @test_throws DomainError CensoredBUM(0.5, 1.05)

        @test_throws DomainError CensoredBUM(0.5, 0.05, -1e-6, 100)
        @test_throws DomainError CensoredBUM(0.5, 0.05, 1.1, 100)
        @test_throws DomainError CensoredBUM(0.5, 0.05, 1e-6, -10)

        f = fit(CensoredBUM(), p)
        @test typeof(f) <: CensoredBUMFit
        @test isapprox( f.π0, 0.55797, atol = 1e-5 )

        # denominator becomes 0 if all p-values are 1
        @test isnan( estimate_pi0(ones(20), CensoredBUM()) )

        pi0_est, pars, is_converged = MultipleTesting.cbum_pi0(ones(20))
        @test isnan( pi0_est )
        @test !is_converged

        pi0_est, pars, is_converged = MultipleTesting.cbum_pi0_naive(ones(20))
        @test isnan( pi0_est )
        @test !is_converged

    end

    @testset "BUM π₀" begin

        @test isapprox( estimate_pi0(p, BUM(0.5)), 0.55528, atol = 1e-5 )
        @test isapprox( estimate_pi0(p, BUM()), 0.55528, atol = 1e-5 )
        @test isapprox( estimate_pi0(p0, BUM()), 1.0, atol = 1e-5 )
        @test isapprox( estimate_pi0(p1, BUM()), 0.10874, atol = 1e-5 )

        ## test case that does not converge
        @test isnan(estimate_pi0(p, BUM(0.5, 1e-6, 2)))

        @test typeof(BUM()) <: Pi0Estimator

        @test_throws DomainError BUM(-0.5)
        @test_throws DomainError BUM(1.5)

    end

    @testset "FlatGrenander π₀" begin

        @test typeof(FlatGrenander()) <: Pi0Estimator

        pu = collect(0.1:0.05:0.9)
        @test estimate_pi0(pu, FlatGrenander()) ≈ 1.0
        @test estimate_pi0(pu.^0.5, FlatGrenander()) ≈ 1.0

        # no reference values from literature/software available
        @test estimate_pi0(p0, FlatGrenander()) ≈ 1.0
        @test isapprox( estimate_pi0(p1, FlatGrenander()), 0.10458, atol = 1e-4 )
        @test isapprox( estimate_pi0(p, FlatGrenander()), pi0, atol = 0.1)

        ## longest constant interval: low level
        lci = MultipleTesting.longest_constant_interval

        px = [0:0.1:1;];
        f = [1.5, 1.5, 1.5, 1.5, 1.5, 0.5, 0.5, 0.5, 0.2, 0.2, 0.1];
        @test lci(px, f) ≈ 0.5

        f = [1.5, 1.5, 1.5, 1.5, 1.5, 1.2, 1.2, 1.2, 0.2, 0.2, 0.1];
        @test lci(px, f) ≈ 0.2

        f = [0.5, 0.5, 0.5, 0.5, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.1];
        @test lci(px, f) ≈ 0.2

        f = [0.5, 0.5, 0.5, 0.5, 0.5, 0.2, 0.2, 0.2, 0.2, 0.2, 0.1];
        @test lci(px, f) ≈ 0.5

        px = [0.1, 0.3, 0.5, 0.9];
        f = [0.5, 0.5, 0.2, 0.2];
        @test lci(px, f) ≈ 0.2

        px = [0.1, 0.5, 0.7, 0.9];
        f = [0.5, 0.5, 0.2, 0.2];
        @test lci(px, f) ≈ 0.5

    end

    @testset "ConvexDecreasing π₀" begin

        @test typeof(ConvexDecreasing()) <: Pi0Estimator
        @test typeof(ConvexDecreasing(100, 1e-6, 1000)) <: Pi0Estimator

        @test isapprox( estimate_pi0(p, ConvexDecreasing()), 0.5739054, atol = 1e-4)
        @test isapprox( estimate_pi0(p0, ConvexDecreasing()), 1.0, atol = 1e-6 )
        @test isapprox( estimate_pi0(p1, ConvexDecreasing()), 0.1323284, atol = 1e-3)

        @test isapprox( estimate_pi0(ones(20), ConvexDecreasing()), 1.0, atol = 1e-6 )

        ## test case that does not converge
        @test isnan(estimate_pi0(p, ConvexDecreasing(100, 1e-6, 2)))

        @test_throws DomainError ConvexDecreasing(100, 2.0, 1000)
        @test_throws DomainError ConvexDecreasing(100, -1.0, 1000)
        @test_throws DomainError ConvexDecreasing(-100, 0.01, 1000)
        @test_throws DomainError ConvexDecreasing(100, 0.01, -1000)

        f = fit(ConvexDecreasing(), p)
        @test typeof(f) <: ConvexDecreasingFit
        @test isapprox( f.π0, 0.5739054, atol = 1e-4 )

    end

end

end


## PARTE 1
cd(@__DIR__)
using DelimitedFiles
input = readdlm("input.txt");
get_move(::Val{:forward}, x) = CartesianIndex(x, 0)
get_move(::Val{:up}, x) = CartesianIndex(0, -x)
get_move(::Val{:down}, x) = CartesianIndex(0, x)
get_move(::Val, x) = missing

##
origin = CartesianIndex(0, 0)
res = foldl(eachrow(input); init=origin) do pos, (s, Δ)
    move = get_move(Val(Symbol(s)), Δ)
    ismissing(move) && return
    pos += move
end
prod(res.I)


relu(x::AbstractArray) = max.(x, 0)
relu_grad(x::AbstractArray) = float(x .> 0)

@primitive relu(x::AbstractArray)
@primitive relu_grad(x::AbstractArray)

@grad relu(x::AbstractArray) 1 relu_grad(x)

relu_test(x) = sum(relu(x))

@testset "macro" begin

    tape = Tape()
    x = tracked(tape, rand(10))
    y = relu(x)

    @test length(tape) == 1
    @test tape[1].val == x.val

    grad(relu_test, rand(5))

end


# ---
# title: 1171. Remove Zero Sum Consecutive Nodes from Linked List
# id: problem1171
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Linked List
# link: <https://leetcode.com/problems/remove-zero-sum-consecutive-nodes-from-linked-list/description/>
# hidden: true
# ---
#
# Given the `head` of a linked list, we repeatedly delete consecutive sequences
# of nodes that sum to `0` until there are no such sequences.
#
# After doing so, return the head of the final linked list.  You may return any
# such answer.
#
#
#
# (Note that in the examples below, all sequences are serializations of
# `ListNode` objects.)
#
# **Example 1:**
#
#
#
#     Input: head = [1,2,-3,3,1]
#     Output: [3,1]
#     **Note:** The answer [1,2,1] would also be accepted.
#
#
# **Example 2:**
#
#
#
#     Input: head = [1,2,3,-3,4]
#     Output: [1,2,4]
#
#
# **Example 3:**
#
#
#
#     Input: head = [1,2,3,-3,-2]
#     Output: [1]
#
#
#
#
# **Constraints:**
#
#   * The given linked list will contain between `1` and `1000` nodes.
#   * Each node in the linked list has `-1000 <= node.val <= 1000`.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


using Aqua
using Ripserer

Aqua.test_all(Ripserer; ambiguities=false)


#####
##### Generic API
#####

export is_function_basis, dimension, domain, basis_at, linear_combination,
    InteriorGrid, EndpointGrid, grid, collocation_matrix

"""
$(TYPEDEF)

Abstract type for function families.

Not part of the API, just used internally for dispatch. See [`is_function_basis`](@ref).
"""
abstract type FunctionBasis end

Broadcast.broadcastable(basis::FunctionBasis) = Ref(basis)

"""
`$(FUNCTIONNAME)(F)`

`$(FUNCTIONNAME)(f::F)`

Test if the argument is a *function basis*, supporting the following interface:

- [`domain`](@ref) for querying the domain,

- [`dimension`](@ref) for the dimension,

- [`basis_at`](@ref) for function evaluation,

- [`grid`](@ref) to obtain collocation points.

[`linear_combination`](@ref) and [`collocation_matrix`](@ref) are also supported, building
on the above.

Can be used on both types (preferred) and values (for convenience).
"""
is_function_basis(T::Type) = false

is_function_basis(::Type{<:FunctionBasis}) = true

is_function_basis(f) = is_function_basis(typeof(f))

"""
`$(FUNCTIONNAME)(basis)`

The domain of a function basis. A tuple of numbers (of arbitrary type, but usually
`Float64`), or a tuple the latter.
"""
function domain end

"""
`$(FUNCTIONNAME)(basis)`

Return the dimension of `basis`, a positive `Int`.
"""
function dimension end

"""
`$(FUNCTIONNAME)(basis, x)`

Return an iterable with known element type and length (`Base.HasEltype()`,
`Base.HasLength()`) of basis functions in `basis` evaluated at `x`.

Methods are type stable.

!!! note
    Consequences are undefined when evaluating outside the domain.
"""
function basis_at end

"""
$(SIGNATURES)

Evaluate the linear combination of ``∑ θₖ⋅fₖ(x)`` of function basis ``f₁, …`` at `x`, for
the given order.

The length of `θ` should equal `dimension(θ)`.
"""
function linear_combination(basis, θ, x)
    mapreduce(*, +, θ, basis_at(basis, x))
end

# FIXME define a nice Base.show method
struct LinearCombination{B,T}
    basis::B
    θ::T
end

(l::LinearCombination)(x) = linear_combination(l.basis, l.θ, x)

"""
$(SIGNATURES)

Return a callable that calculates `linear_combination(basis, θ, x)` when called with `x`.
"""
linear_combination(basis, θ) = LinearCombination(basis, θ)

"""
$(TYPEDEF)

Abstract type for all grid specifications.
"""
abstract type AbstractGrid end

"""
$(TYPEDEF)

Grid with interior points (eg Gauss-Chebyshev).
"""
struct InteriorGrid <: AbstractGrid end

Base.show(io::IO, ::InteriorGrid) = print(io, "interior grid")

"""
$(TYPEDEF)

Grid that includes endpoints (eg Gauss-Lobatto).
"""
struct EndpointGrid <: AbstractGrid end

Base.show(io::IO, ::EndpointGrid) = print(io, "grid w/ endpoints")

"""
$(SIGNATURES)

Return a gridpoint for collocation, with `1 ≤ i ≤ dimension(basis)`.

`T` is used *as a hint* for the element type of grid coordinates, and defaults to `Float64`.
The actual type can be broadened as required. Methods are type stable.

!!! note
    Not all grids have this method defined, especially if it is impractical. See
    [`grid`](@ref), which is part of the API, this function isn't.
"""
gridpoint(basis, i) = gridpoint(Float64, basis, i)

"""
`$(FUNCTIONNAME)([T], basis)`

Return a grid recommended for collocation, with `dimension(basis)` elements.

`T` is used *as a hint* for the element type of grid coordinates, and defaults to `Float64`.
The actual type can be broadened as required. Methods are type stable.
"""
grid(basis) = grid(Float64, basis)

function grid(::Type{T}, basis) where {T<:Real}
    map(i -> gridpoint(T, basis, i), 1:dimension(basis))
end

"""
$(SIGNATURES)

Convenience function to obtain a collocation matrix at gridpoints `x`, which is assumed to
have a concrete `eltype`.

Methods are type stable.
"""
function collocation_matrix(basis, x)
    @argcheck isconcretetype(eltype(x))
    N = dimension(basis)
    C = Matrix{eltype(basis_at(basis, first(x)))}(undef, N, N)
    for i in 1:N
        foreach(((j, f),) -> C[i, j] = f, enumerate(basis_at(basis, x[i])))
    end
    C
end


function extract_weights(model)
	return Flux.destructure(model)
end
sqnorm(x) = sum(abs2, x)
function cyclic_LR(epoch, total_epochs; lr_init=0.01, lr_ratio=0.05)
	t = (epoch + 1) / total_epochs

	if t <= 0.5
	    factor = 1.0
	elseif t <= 0.9
	    factor = 1.0 - (1.0 - lr_ratio) * (t - 0.5) / 0.4
	else
	    factor = lr_ratio
	end

	return (factor * lr_init)
end

function extract_params(ps)

	return mapreduce(i -> vec(ps.order.data[i]), vcat, 1:ps.params.dict.count)
end
function getbackend(backend)
	if backend == :forwarddiff
		adbackend = ForwardDiff
	elseif backend == :reversediff
		adbackend = ReverseDiff
	elseif backend == :zygote
		adbackend = Zygote
	else
		throw("Error: No backend found")
	end
	return adbackend
end
function model_re(model,W)
	if model isa NeuralODE
		if (model.model isa Chain)
			θ, re = Flux.destructure(model.model)
			dudt = re(W)
			kwargs = Dict()
			for k in keys(model.kwargs)
				kwargs[k] = model.kwargs[k]
			end
			new_model = NeuralODE(dudt,model.tspan,model.args[1];
				kwargs...
			)
		elseif model.model isa FastChain
			ps3 = Flux.params(model)
			ps3.order.data[1] = typeof(ps3.order.data[1])(W)
			Flux.loadparams!(model, ps3)
			new_model = model
		else
			throw("Error: This NN model type is not found")
		end
	elseif model isa Chain
		θ, re = Flux.destructure(model)
		new_model = re(W)
	else
		throw("Error: model_re function is not available for this model")
	end
	return new_model
end

#to convert matrix to array of vectors (row wise)
function slicematrix(A::AbstractMatrix{T}) where T
   m, n = size(A)
   B = Vector{T}[Vector{T}(undef, m) for _ in 1:n]
   for i in 1:n
       B[i] .= A[:,i]
   end
   return B
end

function split_data(data)
	return data.data[1], data.data[2]
end

generate_model(w_til, re) = re(w_til)

function predict_out(w_til, re, in_data)
	new_model = generate_model(w_til, re)
	return new_model(in_data)
end

function form_matrix(A)
	m,n, p = size(A)
	datan = [vec(A[:,i,:]') for i in 1:n]
	fdata = Array{Float64}(undef, 2p, n)
	#convert ODE solution as matrix
	for j in 1:n
		fdata[:,j] = datan[j]
	end
	return fdata
end


# Script to calculate the availabily of wind power over the year
using DataFrames, FeatherFiles, Statistics

AREA = 0.25445
V_MIN = 6.0    # minimal wind speed at average height [m/s]

function calc_average_power(df)
    minpower = df[df.Wind_Speed_at_Hub_Height .> V_MIN, :Power_Generation]
    return AREA * mean(minpower) * calc_rel_availability(df, 0.0) / 100.0
end

function calc_peak_power(df)
    return AREA * maximum(df.Power_Generation)
end

function calc_capacity_factor(df)
    return 100 * calc_average_power(df) / calc_peak_power(df)
end

function calc_rel_availability(df, min_power)
    minpower = df[(df.Power_Generation * AREA .> min_power) .& (df.Wind_Speed_at_Hub_Height .> V_MIN), :Power_Generation]
    return 100 * size(minpower, 1) / size(df, 1)
end

if !isdefined(@__MODULE__, :df) dfw=DataFrame(load("data/windyear.feather")) end

av = calc_average_power(dfw)
println("Average wind power [W]:                      ", round(av, digits=1))
println("Capacity factor [%]:                          ", round(calc_capacity_factor(dfw), digits=1))
println("Percentage of wind power availability    [%]: ", round(calc_rel_availability(dfw, 0.0), digits=1))
println("Percentage of time for more than 50% av. [%]: ", round(calc_rel_availability(dfw, av/2), digits=1))
println("Percentage of time for more than average [%]: ", round(calc_rel_availability(dfw, av), digits=1))


### A Pluto.jl notebook ###
# v0.12.20

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 63f5861e-6244-11eb-268b-a16bc3f8265c
using LinearAlgebra

# ╔═╡ dfc779ee-6246-11eb-240b-4dc7a7d95641
using Plots, RecipesBase

# ╔═╡ 23bcbb02-62ef-11eb-27f9-13ed327ac098
# edit the code below to set your name and UGent username

student = (name = "Joel Janssen", email = "Joel.Janssen@UGent.be");

# press the ▶ button in the bottom right of this cell to run your edits
# or use Shift+Enter

# you might need to wait until all other cells in this notebook have completed running.
# scroll down the page to see what's up

# ╔═╡ 1657b9b2-62ef-11eb-062e-4758f9ea1075
begin
	using DSJulia;
	using PlutoUI;
	tracker = ProgressTracker(student.name, student.email);
	md"""

	Submission by: **_$(student.name)_**
	"""
end

# ╔═╡ b1d21552-6242-11eb-2665-c9232be7026e
md"""
# Flatland

![](https://i.imgur.com/ex7JS3Z.png)

## Introduction and goal
In this notebook, we will implement a variety of two-dimensional geometric shapes.
The different shapes might have drastically different representations. For example, we can describe a rectangle
by the coordinates of its center, its length and its width. A triangle, on the other hand,
is more naturally represented by its three points. Similarly, computing the area of a rectangle or a triangle
involves two different formulas. The nice thing about Julia is that you can hide this complexity from the users.
You have to create your structures, subtypes of the abstract `Shape` type and have custom methods that will work
for each type!

Below, we suggest a variety of shapes, each with its unique representation. For this assignment, you have to complete **one**
type and make sure all the provided functions `corners`, `area`, `move!`, `rotate!`,... work. Using `PlottingRecipes`, you can easily
plot all your shapes (provided you implemented all the helper functions).

Implementing such shapes can have various exciting applications, such as making a drawing tool or a ray tracer. Our
end goal is to implement a simulator of a toy statistical physics system. Here, we simulate a system with inert particles, leading to self-organization.
Our simple rejection sampling algorithm that we will use is computationally very demanding, an ideal case study for Julia!
"""

# ╔═╡ 7189b1ee-62ef-11eb-121a-8d7bb3df52c3
md"""
## Assignments
"""

# ╔═╡ f8b080fe-6309-11eb-17aa-fb098fc00b11
md"""

| shape  | difficulty   |
|---|---|
| `Rectangle`  |  ⭐   |
| `Square`  |  ⭐   |
|  `Circle` | ⭐ ⭐   |
|`RegularPolygon` | ⭐ ⭐ ⭐ |
|`Triangle` | ⭐ ⭐ ⭐ ⭐ |
|`Quadrilateral` | ⭐ ⭐ ⭐ ⭐ ⭐ |

"""

# ╔═╡ d65b61ba-6242-11eb-030d-b18a7518731b
md"## Types
We define all kinds of shapes. For the constructors, we follow the convention: `Shape((x,y); kwargs)` where `kwargs` are the keyword arguments determining
the shape.
"

# ╔═╡ e3f846c8-6242-11eb-0d12-ed9f7e534db8
abstract type Shape end

# ╔═╡ e7e43620-6242-11eb-1e2e-65874fe8e293
md"""
 `AbstractRectangle` is for simple rectangles and squares, for which the sides are always aligned with the axes.
They have a `l`ength and `w`idth attribute, in addition to an `x` and `y` for their center.

"""

# ╔═╡ f4b05730-6242-11eb-0e24-51d4c60dc451
abstract type AbstractRectangle <: Shape end

# ╔═╡ fe413efe-6242-11eb-3c38-13b9d996bc90
begin
	mutable struct Rectangle <: AbstractRectangle
		x::Float64
		y::Float64
		l::Float64
		w::Float64
		function Rectangle((x, y); l=1.0, w=1.0)
			return missing # replace this with the correct statement
		end
	end

	function Rectangle((xmin, xmax), (ymin, ymax))
		@assert xmin < xmax && ymin < ymax "Corners have to be ordered: `xmin < xmax && ymin < ymax `"
		x = (xmin + xmax) / 2
		y = (ymin + ymax) / 2
		l = xmax - xmin
		w = ymax - ymin
		return Rectangle((x, y), l=l, w=w)
	end
end

# ╔═╡ 4d4285e8-6334-11eb-0d76-136cc5f645cd

# ╔═╡ 12ddaece-6243-11eb-1e9d-2be312d2e22d
md"Squares are a special case of rectangle."

# ╔═╡ 16666cac-6243-11eb-0e0f-dd0d0ec53926
mutable struct Square <: AbstractRectangle
    x::Float64
    y::Float64
    l::Float64
    function Square((x, y); l=1.0)
        return missing # replace this with the correct statement
    end
end

# ╔═╡ 501f9828-6334-11eb-0f2a-ebaa1d5b0f46

# ╔═╡ 23ea0a46-6243-11eb-145a-b38e34969cfd
md"This small function to get `l` and `w` will allow you to treat `Square` and `Rectangle` the same!"

# ╔═╡ 1b129bf4-6243-11eb-1fa2-d7bd5563a1b4
begin
	lw(shape::Rectangle) = shape.l, shape.w
	lw(shape::Square) = shape.l, shape.l
end

# ╔═╡ 94ec5382-6335-11eb-100c-15d70f27e703

# ╔═╡ 3d67d61a-6243-11eb-1f83-49032ad146da
mutable struct Circle <: Shape
    x::Float64
    y::Float64
    R::Float64
    function Circle((x, y); R=1.0)
        return missing # replace this with the correct statement
    end
end

# ╔═╡ 4ce7abea-6335-11eb-1657-a3ee8986d55e

# ╔═╡ 33757f2c-6243-11eb-11c2-ab5bbd90aa6b
mutable struct RegularPolygon{N} <: Shape
    x::Float64
    y::Float64
    R::Float64
    θ::Float64  # angle
    function RegularPolygon((x, y), n::Int; R=1.0, θ=0.0)
        @assert n ≥ 3 "polygons need a minimum of three corners"
        return missing # replace this with the correct statement
    end
end

# ╔═╡ bbbe4c9a-6335-11eb-1dc7-55ddf17887f4

# ╔═╡ 4234b198-6243-11eb-2cfa-6102bfd9b896
md"Triangles will be described by their three points. Its center will be computed when needed."

# ╔═╡ 473d9b5c-6243-11eb-363d-23108e81eb93
abstract type AbstractTriangle <: Shape end

# ╔═╡ ce3393a8-6335-11eb-06e9-af93a3794902
md"This one is for free:"

# ╔═╡ 50e45ac6-6243-11eb-27f9-d5e7d0e1dc01
mutable struct Triangle <: AbstractTriangle
    x1::Float64
    x2::Float64
    x3::Float64
    y1::Float64
    y2::Float64
    y3::Float64
    Triangle((x1, y1), (x2, y2), (x3, y3)) = new(x1, x2, x3, y1, y2, y3)
end

# ╔═╡ dad14258-6309-11eb-0a9a-37c0386c8cb4
md"Define some examples."

# ╔═╡ 55de4f76-6243-11eb-1445-a54d01242f64
rect = Rectangle((1, 2), l=1, w=2)

# ╔═╡ 5b6b9854-6243-11eb-2d5b-f3e41ecf2914
square = Square((0, 1))

# ╔═╡ 5f120f1a-6243-11eb-1448-cb12a75680b0
triangle = Triangle((-0.1, 0.5), (1, 2), (1, -0.5))

# ╔═╡ 64fcb6a0-6243-11eb-1b35-437e8e0bfac8
pent = RegularPolygon((0, 0), 5)

# ╔═╡ 668f568a-6243-11eb-3f01-adf1b603e0e4
hex = RegularPolygon((1.2, 3), 6)

# ╔═╡ 7b785b7a-6243-11eb-31c2-9d9deea78842
circle = Circle((10, 10))

# ╔═╡ 5a61e0da-6338-11eb-2a58-ad06aae62940
md"""**Select one of the shapes you have developed.**

My shape type: $(@bind myshapeType Select(["Square", "Rectangle", "Circle", "RegularPolygon{N}", "Triangle"]))

"""

# ╔═╡ b6a4c98a-6300-11eb-0542-ab324d8e4d7e
begin
	if myshapeType == "Square"
		myshape = square
	elseif myshapeType == "Rectangle"
		myshape = rect
	elseif myshapeType == "Circle"
		myshape = circle
	elseif myshapeType == "RegularPolygon{N}"
		myshape = pent  # needs to be more general WIP
	elseif myshapeType == "Triangle"
		myshape = triangle  # needs to be more general WIP
	else
		myshape = missing
	end
		tester = ismissing(myshape) ? md"❌ **$myshapeType is not properly defined!** First complete the inner constructors for $myshapeType or change to a type that do have defined." : md""
end;

# ╔═╡ ca5302b2-6337-11eb-2e98-efb764a792a4
tester

# ╔═╡ 7c80d608-6243-11eb-38ba-f97f7476b245
md"""
## Corners and center

"""

# ╔═╡ 58eb84be-63c3-11eb-09f5-6d16973c7aa7
md"![](https://i.imgur.com/6cKB1xr.png)"

# ╔═╡ 57dee25a-63c3-11eb-0c7a-bfb1ac79bc7b
md"""
Some very basic functions to get or generate the corners and centers of your shapes. The corners are returned as a list of tuples, e.g. `[(x1, y1), (x2, y2), ...]`.
"""

# ╔═╡ 62e7e05e-62fe-11eb-1611-61274c5498cc
begin
	q_cc1 = Question(
			description = md"""
			**The number of corners**

			```julia
			ncorners(shape::myShape)
			```
			that returns the number of corners.

			"""
		)

	q_cc2 = Question(
		description = md"""
		**The corners**

		```julia
		corners(shape::myShape)
		```
		that returns the coordinates of the corners.

		"""
	)

	q_cc3 = Question(
		description = md"""
		**The center**

		```julia
		center(shape::myShape)
		```
		that returns the center.

		"""
	)

	q_cc4 = Question(
		description = md"""
		**The outline**

		```julia
		xycoords(shape::Shape)
		```
		xycoords returns two vectors of the outline: xcoords and ycoords, for `Circle`, you can specify the number of points to take (50 by default). For a lot of shapes this is just another representation of the corners.

		"""
	)

	qb_cc = QuestionBlock(;
		title=md"**Assignment: corners and center**",
		description = md"""

	Complete the following functions for your shape ($myshapeType)

		""",
		questions = [q_cc1, q_cc2, q_cc3, q_cc4]
	)

	#validate(qb_cc, tracker)
end

# ╔═╡ a005992e-6243-11eb-3e29-61c19c6e5c7c
begin
	ncorners(::Circle) = 0  # this one is for free!
	ncorners(shape::Shape) = missing # leave this default
	#...add your own ncorners

end

# ╔═╡ ac423fa8-6243-11eb-1385-a395d208c42d
begin
	function corners(shape::Shape)
		return missing
	end
end

# ╔═╡ ddf0ac38-6243-11eb-3a1d-cd39d70b2ee0
#=begin
	center(shape::Shape) = (missing, missing)
end=#

# ╔═╡ ecc9a53e-6243-11eb-2784-ed46ccbcadd2
begin
	#xycoords(shape::Shape) = missing, missing
	xycoords(s::Shape) = [first(p) for p in corners(s)], [last(p) for p in corners(s)]

	function xycoords(shape::Circle; n=50)
		# compute `n` points of the circle
		return missing, missing
	end
end

# ╔═╡ c16c36f6-6339-11eb-20d4-27ef9f74b747

# ╔═╡ 5de0c912-6244-11eb-13fd-bfd8328191a6
md"""
## x,y-bounding
"""

# ╔═╡ fe48d2f0-63c2-11eb-12f7-d5a8d6d0ce27
md"![](https://i.imgur.com/G7worze.png)"

# ╔═╡ aa186788-63c3-11eb-1bd1-d138d586e8b6
begin
   q_bound = Question(;
			description=md"""
The fuctions below is supposed to yield the outer limits of the x and y axes of your shape. Can you complete the methods with a oneliner?
""")

   qb_bound = QuestionBlock(;
	title=md"**Assignment: bounding box ⭐⭐**",
	questions = [q_bound],
	hints=[hint(md"The function `extrema` could be useful here...")]
	)
end

# ╔═╡ a89bdba6-6244-11eb-0b83-c1c64e4de17d
begin
	xlim(shape::Shape) = missing
end

# ╔═╡ b1372784-6244-11eb-0279-27fd755cda6a
begin
	ylim(shape::Shape) = missing
end

# ╔═╡ bd706964-6244-11eb-1d9d-2b60e53cdce1
md"This should return the bounding box, as the smallest rectangle that can completely contain your shape."

# ╔═╡ b91e1e62-6244-11eb-1045-0770fa92e040
boundingbox(shape::Shape) = missing

# ╔═╡ d60f8ca4-6244-11eb-2055-4551e4c10906
md"""
## Area
"""

# ╔═╡ ebf4a45a-6244-11eb-0965-197f536f8e87
begin
	area(shape::Shape) = missing
end

# ╔═╡ 230dd290-6303-11eb-0f55-311ef2b9541e

# ╔═╡ 36cc0492-6246-11eb-38dd-4f42fb7066dc
md"""
## Moving, rotating and scaling
"""

# ╔═╡ ed4bfad2-63c6-11eb-0292-73c34e4d34a6
md"![](https://i.imgur.com/nUlpTDA.png)"

# ╔═╡ 285930b8-63c7-11eb-372b-edc4a4ed0d0a
begin
   q_mrs = Question(;
			description=md"""

Next, let us define some translation, rotation and scaling operations on the shapes.

Important: the functions work *in-place*, meaning that the modify your object (that is why use use `mutable` structures).

For `Circle` and `AbstractRectangle` types, `rotate!` leaves them unchanged.

**Note**: rotations are in radials, so between $0$ and $2\pi$.
			""")

   qb_mrs = QuestionBlock(;
	title=md"**Assignment: I like to move it ⭐⭐⭐**",
	questions = [q_mrs])
end

# ╔═╡ 83c6d25c-6246-11eb-1a24-57e20f5e7262
begin
	function move!(shape::Shape, (dx, dx))
		# move the shape
		return shape
	end
end

# ╔═╡ a1b2a4f8-6246-11eb-00ea-8f6042c72f4e
begin
	function rotate!(shape::Shape, dθ)
		# rotate the shape, counterclockwise
		return shape
	end
end

# ╔═╡ b907e8fc-6246-11eb-0beb-bb44930d033c
begin
	function scale!(shape::Shape, a)
		@assert a > 0 "scaling has to be a positive number"
		# scale with a factor a
		return shape
	end
end

# ╔═╡ 2d7a63cc-64db-11eb-0a4c-bb7771af8b14

# ╔═╡ d08ab6d0-6246-11eb-08a8-152f9802cdfc
md"""
## Plotting

OK, let's take a look at our shapes! We use `RecipesBase` to allow plotting.
This falls back on `xycoords` (can you see how it works?), so make sure this method is operational.
"""

# ╔═╡ e30d10d2-6246-11eb-1d59-332b5916712e
@recipe function f(s::Shape)
    xguide --> "x"
    yguide --> "y"
    label --> ""
    aspect_ratio := :equal
    seriestype := :shape
    x, y = xycoords(s)
    return x, y
end

# ╔═╡ e7e90744-6246-11eb-157c-cf67e8619d6e
"""Plots a list of shapes."""
function plotshapes(shapes; kwargs...)
    p = plot(;kwargs...)
    plot!.(shapes)
    return p
end

# ╔═╡ 1aec9fc2-6247-11eb-2942-edc370918f9e
md"Let's look!"

# ╔═╡ 8bdc61b0-6330-11eb-3e9a-15412fecf8af
myshape

# ╔═╡ 16d0ea9c-6247-11eb-12c6-1709f6d0ac99
plot(myshape)

# ╔═╡ 6bae2128-6303-11eb-34f2-1dfa96e46ae6
md"This is how it should look like,"

# ╔═╡ 287a7506-6247-11eb-2bad-0778802c00d5
plot(myshape, color="pink")

# ╔═╡ 01d899e6-6305-11eb-017b-27bb2c104ef5

# ╔═╡ 221e09a2-6247-11eb-12a8-a13c0a2f96e7
md"""
## In and intersection

Here, we want to perform some geometric checks.

```julia
(x, y) in shape
```
should return a Boolean whether the point is in the shape.
"""

# ╔═╡ 6851ebb2-6339-11eb-2ab7-39e07c4e3154
begin
   q_in = Question(;
			description=md"""
			Complete the function `in(q, shape::Shape)` that checks whether a points falls inside a shape. The function `same_side((a, b), p, q)` is provided to check whether two points are on the same side of a line. This should prove very useful to complete this task.""")

   qb_in = QuestionBlock(;
	title=md"**Is point in shape? ⭐⭐⭐**",
	questions = [q_in],
	hints=[
		hint(md"It has something to do with the center..."),
		hint(md"... but also with the edges"),
		hint(md"Given that a point is outside a shape, it is always **outside** all edges.")
	]
	)
end

# ╔═╡ e565d548-6247-11eb-2824-7521d4fa6b2b
begin
	function Base.in((x, y), s::Shape)
		# compute something
		return missing
	end
end

# ╔═╡ 150f1dae-6248-11eb-276f-9bbf7eba58fd
"""
    same_side((a, b), p, q)

Given a line described by two points, `a` and `b`, check whether two points
`p` and `q` are on the same side.
"""
function same_side((a, b), p, q)
    # normal vector on the line
    n = (a[2] - b[2], b[1] - a[1])
    # check if they are on both sides by projection
	return sign(n ⋅ (p .- a)) == sign(n ⋅ (q .-a ))
end

# ╔═╡ b8ed26f2-633b-11eb-380e-9379b0f4697f
md"Too prove that this function works:"

# ╔═╡ f4873fce-6249-11eb-0140-871354ca5430
let
	# verfication
	points_all = [1randn(2) for i in 1:1_000]
	points =filter(p->p ∈ myshape, points_all)
	scatter(first.(points_all), last.(points_all), opacity=0.1, color=:lightgrey, label="all")
	scatter!(first.(points), last.(points), label="out")
	plot!(myshape, alpha=0.2)
end

# ╔═╡ 22f63a5e-633a-11eb-27c7-27fcabc7bc6f

# ╔═╡ f3ea648e-633b-11eb-3444-317a4eb5b8ea
begin
   q_circle = Question(;
			description=md"""

**Two circles intersecting ⭐**
```julia
	Base.intersect(shape1::Circle, shape2::Circle)
```

""")

   q_recrec = Question(;
		description=md"""

**Rectangle intersecting another rectangle ⭐**
```julia
	Base.intersect(shape1::AbstractRectangle, shape2::AbstractRectangle)
```

""")

   q_triatria = Question(;
		description=md"""

**Two triangles intersecting (efficient) ⭐⭐⭐⭐⭐**
```julia
	Base.intersect(shape1::Triangle, shape2::Triangle)
```

""")

   q_general = Question(;
		description=md"""

**Any two shapes intersecting (naive) ⭐⭐⭐**
```julia
	Base.intersect(shape1::T, shape2::T) where {T<:Shape}
```

""")

   qb_inter = QuestionBlock(;
	title=md"**Intersection between shapes?**",
	description=md"""
Similarly, we want to check whether two shapes overlap (partially):

```julia
intersect(shape1, shape2)
```

Complete the function `intersect(shape1, shape2)` that checks whether there is a partial overlap (intersection) between two shapes

The efficiency and the process of checking intersection is very different for each shape and each combination of two shapes. Complete **at least one** of the following combinations.""",
	questions = [q_circle, q_recrec, q_triatria, q_general],
	hints=[
		hint(md"Checking overlapping bounding boxes is very efficient"),
		hint(md"For most shapes: overlapping bounding boxes is a required, but not a sufficient condition for overlap"),
		hint(md"[Very good hint](https://i.imgur.com/TpIStMK.png)")

		]
	)
end

# ╔═╡ 5368c46e-633e-11eb-0d98-b1ccb37cc7f8
begin
	function Base.intersect(shape1::AbstractRectangle, shape2::AbstractRectangle)
		return missing
	end

	function Base.intersect(shape1::Circle, shape2::Circle)
		return missing
	end

	function Base.intersect(shape1::Triangle, shape2::Triangle)
		return missing
	end

	function Base.intersect(shape1::T, shape2::T) where {T<:Shape}
		return missing
	end

end

# ╔═╡ e6efb632-6338-11eb-2e22-eb0b1ff577c4
md"We have defined some functions you might find useful:"

# ╔═╡ 0381fbba-6248-11eb-3e80-b37137438531
crossprod((x1, y1), (x2, y2)) = x1 * y2 - x2 * y1

# ╔═╡ 91273cd2-6248-11eb-245c-abb6269f916b
md"Note, Julia will parse composite arguments:"

# ╔═╡ 653af7c6-6248-11eb-2a7b-fbf7550ef92b
"""
    linecross((p1, p2), (q1, q2))

Check whether line segments `(p1, p2)` and `(q1, q2)` intersect.
"""
function linecross((p1, p2), (q1, q2))
    v = p2 .- p1
    w = q2 .- q1
    vw = crossprod(v, w)
    t = crossprod(q1 .- p1, w) / vw
    s = crossprod(q1 .- p1, v) / vw
    return 0.0 ≤ t ≤ 1.0 &&  0.0 ≤ s ≤ 1.0
end

# ╔═╡ 6aa3519a-6248-11eb-193d-a3537f7d3bd0
linecross(((0, 0), (1, 1)), ((0, 1), (1, 0)))

# ╔═╡ 51bca412-6340-11eb-3f38-8bfc8377715b

# ╔═╡ e21b0f1c-633b-11eb-3609-9b9dae71c915
md"""
By completing these functions, the following mathematical syntax should also work:

```julia
(x, y) ∈ shape  # \in<TAB>
shape1 ∩ shape2  # \cap<TAB>
```
"""

# ╔═╡ f97bf1c0-6247-11eb-1acc-e30068a277d0
md"""
## Random placement

Finally, `randplace!` takes a shape, rotates it randomly and moves it randomly within the bounds of the limits `(xmin, xmax)` and `(ymin, ymax)`. Note that the **whole** shape should be within these bounds, not only the center!
"""

# ╔═╡ 97c8cd32-6340-11eb-1d6d-b7d364c0c987
begin
   q_rand = Question(;
			description=md"""
			Complete the function `randplace!(shape::Shape, (xmin, xmax), (ymin, ymax); rotate=true)` that places a shape at a random location withing a given bounding box. Can you define it in such a way that it also gives a random rotation to the shape?""")

   qb_rand = QuestionBlock(;
	title=md"**Assignment: random placement⭐⭐⭐**",
	questions = [q_rand],
	hints=[

	]
	)
end

# ╔═╡ 8d73b66c-624e-11eb-0a52-2309ef897b1c
function randplace!(shape::Shape, (xmin, xmax), (ymin, ymax); rotate=true)
    # random rotation

    # random translation within bound

    return shape
end

# ╔═╡ 3e0a2e20-6341-11eb-3c23-a38b04c89b37
md"Testing if the function works:"

# ╔═╡ 0ee778d2-6341-11eb-10b0-7146fbbc71ff
begin
	# verification
	my_shapes = [randplace!(deepcopy(myshape), (-10,10),(-10,10)) for i in 1:100]
	plotshapes(my_shapes, alpha=0.2)
end

# ╔═╡ 2338ef6a-630b-11eb-1837-431b567ad619
md"""
## Simulating a system of shapes

Suppose we want to use our shape(s) to study a system of non-interacting particles.
Here, we assume that the shapes are rigid and cannot overlap.
There are no forces that attract or repel particles.
Such studies might be of interest in nanoscience, molecular dynamics or self-organization of complex systems.

One approach to study systems of particles is to model every particle's dynamics, keep track of all collisions, etc.
We will do something more ingenious: we will use ideas from statistical physics.
Namely, every valid state (i.e., no shapes overlap and all shapes are within the box) is equally likely.
So instead of simulating the system, we will take samples from it!
These samples are equivalent to random 'snapshots' of a more complex simulation.
Pretty cool, right?

To generate the samples, we will use [rejection sampling](https://en.wikipedia.org/wiki/Rejection_sampling).
Here, we will randomly place shapes within the box until we are lucky and found one that does not overlap.
More concretely, we follow the following steps:
1. generate all the shapes you want to place;
2. randomly place the shapes into the box (using `randplace!`);
3. from the moment a single shape overlaps with another shape, you have to start entirely anew to step 2.

The last point is crucial! If you place a shape that overlaps an earlier shape,
it is insufficient to redistribute that shape. **You have to start over completely.** Only then will you generate correct samples.

The inputs of our function implementing the above algorithm are:
- `shapes`: a list of your shapes (same type, but not necessarily with the same dimensions);
- `xlims`, `ylims`: tuples outlining the box;
The function works inplace, and returns the number of trials needed to generate a valid sample.
This quantity is relevant by itself (it is related to the partition function of the Boltzmann distribution), but we will only use it for diagnostic purposes.

As you might imagine, this algorithm is still very computationally expensive.
Try with about 20 shapes, and work yourself up to more extensive examples.
Try a mixture of small and large shapes. You should see some self-organization going on!

"""

# ╔═╡ 965b578e-63a5-11eb-2cf4-690ec58e939d
xlims = (0, 100)

# ╔═╡ 961ea26c-63a5-11eb-1227-4bcaf4778d82
ylims = (0, 80)

# ╔═╡ a30ded16-63a5-11eb-35f2-2b1ff724eb54
function rejection_sampling!(shapes::Vector{<:Shape}, xlims, ylims)
    # place all the shapes one-by-one, such that they don't overlap
	# the moment you find a single conflict, you have to start over again

	return shapes, trials

end

# ╔═╡ 3a961b6e-62f1-11eb-250b-13a3f6f17eaa
begin
	checkbox(test::Bool)= test ? "✅" : "◯"
	checkbox2(test::Bool)= test ? "✅" : ""
	test1 = @safe !ismissing(myshape)
	test2 = @safe !ismissing(myshape |> corners) && !ismissing(myshape|> ncorners)
	test3 = @safe !ismissing(myshape |> center)
	test4 = @safe !ismissing(myshape |> xycoords)
	test5 = @safe !ismissing(myshape |> xlim) && !ismissing(myshape |> ylim) &&
		!ismissing(myshape |> boundingbox)
	test6 = @safe !ismissing(myshape |> area)
	test7 = @safe !ismissing(move!(myshape |> deepcopy,(1.0,1.0))) &&
				!ismissing(rotate!(myshape |> deepcopy,(1.0))) &&
				!ismissing(scale!(myshape |> deepcopy,(1.0)))
	test8 = @safe !ismissing(in((0.5, 0), myshape))
	test9 = @safe !ismissing(intersect(myshape, myshape))
	test10 = @safe !ismissing(randplace!(myshape, (0.0, 1.0), (0.0, 1.0)))
	test11 = @safe rejection_sampling!([deepcopy(myshape) for i in 1:2], (0, 100), (0, 100)) |> !ismissing

end;

# ╔═╡ 7545c788-62f0-11eb-3f6e-01deeaf990e0
md"""
 $(checkbox(test1)) add the correct *inner* constructor to your type (see below);

 $(checkbox(test2)) complete `corners` and `ncorners`, which return the corners and the number of corners, respecitively;

 $(checkbox(test3)) complete `center` to return the center of mass of the shape;

 $(checkbox(test4)) complete `xycoords`, which give two vectors with the x- and y-coordinates of the shape, used for plotting;

 $(checkbox(test5)) complete `xlim` and `ylim` to give the range on the x- and y-axes of your shape, in addition to `boundingbox` to generate a bounding box of your shape;

 $(checkbox(test6)) complete `area`, this computes the area of your shape;

 $(checkbox(test7)) complete `move!`, `rotate!` and `scale!` to transform your shape **in place** (note: `AbstractRectangle`s cannot be rotated, they are always aligned to the axes);

 $(checkbox(test8)) complete the function `in`, to check whether a point is in your shape;

 $(checkbox(test9)) complete `intersect`, to check whether two shapes overlap;

 $(checkbox(test10)) complete `randplace!`, which randomly moves and rotates a shape within a box;

 $(checkbox(test11)) complete the rejection sampling algorithm and experiment with your shape(s).

**Note:** You will need to create specific methods for different types. It's your job to split the template for the functions in several methods and use dispatch.
"""

# ╔═╡ 30c89806-6331-11eb-0610-d3545e7aeba4
begin
   test_rect = @safe Rectangle((1.0, 1.0)) !== missing

   q_rect_con = Question(;
			description=md"""
			So we have defined a composite Rectangle type with a few fields but the inner constructor is missing. This inner constructor should to instantiate a Rectangle with center (`x`,`y`) and a default length and width of 1.0.

			Multiple dispatch allows us to define multiple constructors for different scenario's. So we have defined an additional constructor where the extremum coordinates are provided (`xmin`, `xmin`), (`ymin`, `ymax`), assuming that the rectangle is always aligned with the axes.
			""")

   q_rect_con = QuestionBlock(;
	title=md"**Rectangle ⭐** $(checkbox2(test_rect)) ",
	questions = [q_rect_con],
	hints=[
		hint(md"Remember, `new()`?")
	]
	)
end

# ╔═╡ abc99468-6333-11eb-1a9d-e50f8e56e468
begin
   test_sq = @safe Square((1.0, 1.0)) !== missing

   q_sq_con = Question(;
			description=md"""
			Can you complete the inner constructor for the square type?
			""")

   qb_sq_con = QuestionBlock(;
	title=md"**Square ⭐** $(checkbox2(test_sq)) ",
	questions = [q_sq_con]
	)
end

# ╔═╡ 5dcdba4e-6335-11eb-19d2-2d10ae81fa39
begin
   test_circle = @safe Circle((1.0, 1.0)) !== missing

   q_circle_con = Question(;
			description=md"""
`Circle`'s are pretty straightforward, having a center and a radius.
			""")

   qb_circle_con = QuestionBlock(;
	title=md"**Circles ⭐⭐** $(checkbox2(test_circle)) ",
	questions = [q_circle_con]
	)
end

# ╔═╡ 6d06ddfc-6334-11eb-2995-81333ac5e1cd
begin
   test_poly = @safe RegularPolygon((1.0, 1.0), 5) !== missing

   q_poly_con = Question(;
			description=md"""
Regular polygons have a center (`x`, `y`), a radius `R` (distance center to one of the corners) and an angle `θ` how it is tilted.
The order of the polygon is part of its parametric type, so we give the compiler some hints on how it will behave.

Can you complete the inner constructor for the polygon type? This is a little more challenging since it is a **parametric composite type** where `N` is the number of corners.
			""")

   qb_poly_con = QuestionBlock(;
	title=md"**Regular polygons ⭐⭐⭐** $(checkbox2(test_poly)) ",
	questions = [q_poly_con],
	hints=[hint(md"`new{n}(...)`")]
	)
end

# ╔═╡ 94cbef4e-6348-11eb-030f-d7a9debdd305
begin
   q_area = Question(;
			description=md"""

Next, let us compute the area of our shapes.
			""")

   qb_area = QuestionBlock(;
	title=md"**Assignment: Area ⭐** $(checkbox2(test_circle)) ",
	questions = [q_area],
	hints= [
		hint(md"The area of a triangle can be computed as $${\frac {1}{2}}{\big |}(x_{A}-x_{C})(y_{B}-y_{A})-(x_{A}-x_{B})(y_{C}-y_{A}){\big |}$$."),
		hint(md"A regular polygon consists of a couple of isosceles triangles.")
		]
	)
end

# ╔═╡ e5a7eee2-63a5-11eb-0267-499409488b19
md"Function to place `n` copies of a given `shape`:"

# ╔═╡ de6124d2-63a5-11eb-1145-5b11f5f1c0f4
function rejection_sampling(shape, n, xlims, ylims)
    shapes = [deepcopy(shape) for i in 1:n]
    trials = rejection_sampling!(shapes, xlims, ylims)
    return shapes, trials
end

# ╔═╡ Cell order:
# ╠═23bcbb02-62ef-11eb-27f9-13ed327ac098
# ╟─1657b9b2-62ef-11eb-062e-4758f9ea1075
# ╠═63f5861e-6244-11eb-268b-a16bc3f8265c
# ╟─b1d21552-6242-11eb-2665-c9232be7026e
# ╟─7189b1ee-62ef-11eb-121a-8d7bb3df52c3
# ╟─7545c788-62f0-11eb-3f6e-01deeaf990e0
# ╟─f8b080fe-6309-11eb-17aa-fb098fc00b11
# ╟─3a961b6e-62f1-11eb-250b-13a3f6f17eaa
# ╟─d65b61ba-6242-11eb-030d-b18a7518731b
# ╠═e3f846c8-6242-11eb-0d12-ed9f7e534db8
# ╟─e7e43620-6242-11eb-1e2e-65874fe8e293
# ╠═f4b05730-6242-11eb-0e24-51d4c60dc451
# ╠═fe413efe-6242-11eb-3c38-13b9d996bc90
# ╟─30c89806-6331-11eb-0610-d3545e7aeba4
# ╟─4d4285e8-6334-11eb-0d76-136cc5f645cd
# ╟─12ddaece-6243-11eb-1e9d-2be312d2e22d
# ╠═16666cac-6243-11eb-0e0f-dd0d0ec53926
# ╟─abc99468-6333-11eb-1a9d-e50f8e56e468
# ╟─501f9828-6334-11eb-0f2a-ebaa1d5b0f46
# ╟─23ea0a46-6243-11eb-145a-b38e34969cfd
# ╠═1b129bf4-6243-11eb-1fa2-d7bd5563a1b4
# ╟─94ec5382-6335-11eb-100c-15d70f27e703
# ╟─5dcdba4e-6335-11eb-19d2-2d10ae81fa39
# ╠═3d67d61a-6243-11eb-1f83-49032ad146da
# ╟─4ce7abea-6335-11eb-1657-a3ee8986d55e
# ╟─6d06ddfc-6334-11eb-2995-81333ac5e1cd
# ╠═33757f2c-6243-11eb-11c2-ab5bbd90aa6b
# ╟─bbbe4c9a-6335-11eb-1dc7-55ddf17887f4
# ╟─4234b198-6243-11eb-2cfa-6102bfd9b896
# ╠═473d9b5c-6243-11eb-363d-23108e81eb93
# ╟─ce3393a8-6335-11eb-06e9-af93a3794902
# ╠═50e45ac6-6243-11eb-27f9-d5e7d0e1dc01
# ╟─dad14258-6309-11eb-0a9a-37c0386c8cb4
# ╠═55de4f76-6243-11eb-1445-a54d01242f64
# ╠═5b6b9854-6243-11eb-2d5b-f3e41ecf2914
# ╠═5f120f1a-6243-11eb-1448-cb12a75680b0
# ╠═64fcb6a0-6243-11eb-1b35-437e8e0bfac8
# ╠═668f568a-6243-11eb-3f01-adf1b603e0e4
# ╠═7b785b7a-6243-11eb-31c2-9d9deea78842
# ╟─b6a4c98a-6300-11eb-0542-ab324d8e4d7e
# ╟─5a61e0da-6338-11eb-2a58-ad06aae62940
# ╟─ca5302b2-6337-11eb-2e98-efb764a792a4
# ╟─7c80d608-6243-11eb-38ba-f97f7476b245
# ╟─58eb84be-63c3-11eb-09f5-6d16973c7aa7
# ╟─57dee25a-63c3-11eb-0c7a-bfb1ac79bc7b
# ╟─62e7e05e-62fe-11eb-1611-61274c5498cc
# ╠═a005992e-6243-11eb-3e29-61c19c6e5c7c
# ╠═ac423fa8-6243-11eb-1385-a395d208c42d
# ╠═ddf0ac38-6243-11eb-3a1d-cd39d70b2ee0
# ╠═ecc9a53e-6243-11eb-2784-ed46ccbcadd2
# ╟─c16c36f6-6339-11eb-20d4-27ef9f74b747
# ╟─5de0c912-6244-11eb-13fd-bfd8328191a6
# ╟─fe48d2f0-63c2-11eb-12f7-d5a8d6d0ce27
# ╟─aa186788-63c3-11eb-1bd1-d138d586e8b6
# ╠═a89bdba6-6244-11eb-0b83-c1c64e4de17d
# ╠═b1372784-6244-11eb-0279-27fd755cda6a
# ╟─bd706964-6244-11eb-1d9d-2b60e53cdce1
# ╠═b91e1e62-6244-11eb-1045-0770fa92e040
# ╟─d60f8ca4-6244-11eb-2055-4551e4c10906
# ╟─94cbef4e-6348-11eb-030f-d7a9debdd305
# ╠═ebf4a45a-6244-11eb-0965-197f536f8e87
# ╟─230dd290-6303-11eb-0f55-311ef2b9541e
# ╟─36cc0492-6246-11eb-38dd-4f42fb7066dc
# ╟─ed4bfad2-63c6-11eb-0292-73c34e4d34a6
# ╟─285930b8-63c7-11eb-372b-edc4a4ed0d0a
# ╠═83c6d25c-6246-11eb-1a24-57e20f5e7262
# ╠═a1b2a4f8-6246-11eb-00ea-8f6042c72f4e
# ╠═b907e8fc-6246-11eb-0beb-bb44930d033c
# ╟─2d7a63cc-64db-11eb-0a4c-bb7771af8b14
# ╟─d08ab6d0-6246-11eb-08a8-152f9802cdfc
# ╠═dfc779ee-6246-11eb-240b-4dc7a7d95641
# ╠═e30d10d2-6246-11eb-1d59-332b5916712e
# ╠═e7e90744-6246-11eb-157c-cf67e8619d6e
# ╟─1aec9fc2-6247-11eb-2942-edc370918f9e
# ╠═8bdc61b0-6330-11eb-3e9a-15412fecf8af
# ╠═16d0ea9c-6247-11eb-12c6-1709f6d0ac99
# ╟─6bae2128-6303-11eb-34f2-1dfa96e46ae6
# ╠═287a7506-6247-11eb-2bad-0778802c00d5
# ╟─01d899e6-6305-11eb-017b-27bb2c104ef5
# ╟─221e09a2-6247-11eb-12a8-a13c0a2f96e7
# ╟─6851ebb2-6339-11eb-2ab7-39e07c4e3154
# ╠═e565d548-6247-11eb-2824-7521d4fa6b2b
# ╠═150f1dae-6248-11eb-276f-9bbf7eba58fd
# ╟─b8ed26f2-633b-11eb-380e-9379b0f4697f
# ╠═f4873fce-6249-11eb-0140-871354ca5430
# ╟─22f63a5e-633a-11eb-27c7-27fcabc7bc6f
# ╟─f3ea648e-633b-11eb-3444-317a4eb5b8ea
# ╠═5368c46e-633e-11eb-0d98-b1ccb37cc7f8
# ╟─e6efb632-6338-11eb-2e22-eb0b1ff577c4
# ╠═0381fbba-6248-11eb-3e80-b37137438531
# ╟─91273cd2-6248-11eb-245c-abb6269f916b
# ╠═653af7c6-6248-11eb-2a7b-fbf7550ef92b
# ╠═6aa3519a-6248-11eb-193d-a3537f7d3bd0
# ╟─51bca412-6340-11eb-3f38-8bfc8377715b
# ╟─e21b0f1c-633b-11eb-3609-9b9dae71c915
# ╟─f97bf1c0-6247-11eb-1acc-e30068a277d0
# ╟─97c8cd32-6340-11eb-1d6d-b7d364c0c987
# ╠═8d73b66c-624e-11eb-0a52-2309ef897b1c
# ╟─3e0a2e20-6341-11eb-3c23-a38b04c89b37
# ╠═0ee778d2-6341-11eb-10b0-7146fbbc71ff
# ╟─2338ef6a-630b-11eb-1837-431b567ad619
# ╠═965b578e-63a5-11eb-2cf4-690ec58e939d
# ╠═961ea26c-63a5-11eb-1227-4bcaf4778d82
# ╠═a30ded16-63a5-11eb-35f2-2b1ff724eb54
# ╟─e5a7eee2-63a5-11eb-0267-499409488b19
# ╠═de6124d2-63a5-11eb-1145-5b11f5f1c0f4


################################################################################
# Discovery value
"""
	calcZd(G::Normal,cs,cd,nd)

Returns discovery value. Calculation is shown in appendix of paper. For `nd=1`, a faster implementation based on bivariate normal distribution is used.

# Arguments:
- `G`: Distribution of valuations revealed on overview page. Must be Normal distribution. Assumes F is standard Normal.
- `cs`: search costs
- `cd`: discovery costs
- `nd`: # products revealed per discovery
"""
function calcZd(G::Normal,cs,cd,nd)
	cs = max(cs,0)
	# Get ξ(cs) here instead of in EV(), as requires calculation only once
	ξ = min(calcZs(0,Normal(),cs)[1],1e6)  # limit, as with too large does not converge

	zd = if nd == 1 # Special case where more efficient calculation available
			if integrateCdfSingle(cd,ξ,cs,mean(G),std(G))-cd ≈ - cd  # case where no convergence
				-cd
			elseif cd <= 0 || (std(G) > 1e9 && cd <= 1e8)
				Inf
			else
				fzero(t -> integrateCdfSingle(t,ξ,cs,mean(G),std(G))-cd,cd)
			end
		else
			if integrateCdfMax(cs,ξ,mean(G),std(G),nd)-cs-cd ≈ -cd
				-cd
			elseif cd <= 0 || (std(G) > 1e9 && cd < 1e8)
				Inf
			else
				fzero(t -> integrateCdfMax(t,ξ,mean(G),std(G),nd)-t-cd,cd)
			end
		end
	return zd::Float64
end

function cdfW(z,ξ,μ,σ)
	a = 1/sqrt(1+σ^2)
	return cdf(Normal(),(z-ξ-μ)/σ) + cdf(Normal(),(z-μ)*a) - cdf(BiNormal(σ*a),[(z-μ)*a,(z-ξ-μ)/σ])
end

function integrateCdfMax(z,ξ,μ,σ,nd)
	return quadgk(t -> 1 - cdfW(t,ξ,μ,σ)^nd,z,Inf)[1] + z
end

function integrateCdfSingle(z,ξ,cs,μ,σ)
	a = z-μ ; b = z-ξ-μ ; c = 1/sqrt(1+σ^2)

	f(x) = pdf(Normal(),x)
	F(x) = cdf(Normal(),x)

	return (1-F(b/σ))*(μ-z -cs)+σ*f(b/σ) +
			(z-μ) * (F(a*c) -cdf(BiNormal(σ*c),[a*c,b/σ]) )	-
			σ*(-σ*c*f(a*c)*(1-F(b/σ/c-a*σ*c)) + F(a-b)*f(b/σ)) +
				c*f(a*c)*(1-F(b/σ/c-a*σ*c))
end

################################################################################
# Search value
"""
	calcZs(x,F::Distribution,cs::T) where T
	calcZs(x::Array{T,1},F::Distribution,cs::Vector ) where T

Returns search value(s)  zs = x .+ ξ(cs), where ξ(cs) is defined
as the lower bound of the integral in cs = ∫(1-F(ϵ))dϵ.

If both x and cs are 1d-arrays, then returns 1d-array zs, where each element z[i] is calculated based on x[i],cs[i]

# Arguments:
- `x`: partial valuation (in u = x + ϵ)
- `F`: distribution of ϵ
- `cs`: inspection costs
"""
function calcZs(x,F::Distribution,cs::T) where T
	zs = Array{T,1}(undef,length(x))
	if cs <= 0 || (std(F) > 1e9 && cs < 10)
		zs .= Inf
		return zs
	else
		if F == Normal() # Faster way when having std normal, and also structured to be suitable for Autodiff
			fz_N(cs) = fzero(ξ-> -ξ + ξ*cdf(F,ξ)+pdf(F,ξ)-cs ,-abs(cs)*10,100*std(F))
			ξ = fz_N(cs)
			zs .= x .+ ξ
			return zs
		else
			fz(cs) = fzero(ξ->zs_inner_integral(ξ,F)-cs,-cs,30*std(F))
			ξ = fz(cs)
			zs .= x .+ ξ
			return zs
		end
	end
end

function calcZs(x::Array{T,1},F::Distribution,cs::Vector ) where T
	@argcheck size(x)==size(cs) "x and search costs need to have same size"
	zs = Array{T,1}(undef,length(x))
	@inbounds for cii in unique(cs)
		ξ = calcZs(0.0,F,cii)
		ind = (cs.==cii)
		zs[ind] .= x[ind] .+ ξ
	end
	return zs
end

"""
	zs_inner_integral(ξ,F)
	zs_inner_integral(ξ,F::Normal)
Returns ∫_ξ (1-F(ϵ))dϵ. Analytical expression for normal distribution available.
"""
function zs_inner_integral(ξ,F)
	quadgk(e->(1-cdf(F,e)),ξ,maximum(F))[1]
end

function zs_inner_integral(ξ,F::Normal)
	 σ = std(F)
	 μ = mean(F)
	 return (1.0-cdf(Normal(),(ξ-μ)/σ))*(μ - ξ) + σ * pdf(Normal(),(ξ-μ)/σ)
 end


# sod shock tube problem
# build mesh
include("../src/DiscontinuousGalerkin.jl")
using DiscontinuousGalerkin
const CFL = 0.5
const Tend = 0.2
const γ=1.4 #gas constant
const xdiafragm = 0.0

function f(::Type{Val{:jac}},u::Vector)
  ρ = u[1]; v = u[2]/u[1]; ϵ=u[3]
  p = (ϵ-0.5*ρ*v^2)*(γ-1)
  F =[0.0 1.0 0.0;-v^2*(1+γ)/2 v*(3-γ) (γ-1);v^3*(γ-1)+γ*ϵ*v/ρ 3/2*v^2*(1-γ)+γ*ϵ/ρ γ*v]
  F
end

function f(u::Vector)
  ρ = u[1]; v = u[2]/u[1]; ϵ=u[3]
  p = (ϵ-0.5*ρ*v^2)*(γ-1)
  [u[2];u[2]^2/u[1]+p;(ϵ+p)*v]
end

#initial condition
function f0(x::Real)
  ufis_l = [1.0, 0.0,	1.0]
  ufis_r = [0.125 0.0 0.1]
  if x <= xdiafragm
    ρ,u,p = ufis_l
  else
    ρ,u,p = ufis_r
  end
  return [ρ, ρ*u, 1.0/(γ - 1.0)*p + 0.5*ρ*u*u]
end

positive(x) = max(0,x)
#define max wave speed (generic euler system)
function max_w_speed(u)
  ρ = u[:,1];v = u[:,2]./ρ;E = u[:,3]
  p = zeros(ρ); wave_speed = zeros(ρ)
  @. p = (γ-1)*(E-0.5*ρ*v*v)
  @. wave_speed = abs(v) + sqrt(γ*positive(p/ρ))
  return maximum(wave_speed)
end

function Nflux(ul::Vector, ur::Vector)
  ρl = ul[1]; vl = ul[2]/ul[1]; ϵl=ul[3]
  pl = (ϵl-0.5*ρl*vl^2)*(γ-1)
  ρr = ur[1]; vr = ur[2]/ur[1]; ϵr=ur[3]
  pr = (ϵr-0.5*ρr*vr^2)*(γ-1)
  zl = sqrt(ρl/pl)*[1;vl;pl]
  zr = sqrt(ρr/pr)*[1;vr;pr]
  zm = 0.5*(zl+zr)
  zln = (zr-zl)./(log(zr)-log(zl))
  F = zeros(ul)
  F[1] = zm[2]*zln[3]
  F[2] = zm[3]/zm[1]+zm[2]/zm[1]*F[1]
  F[2] = 0.5*zm[2]/zm[1]*((γ+1)/(γ-1)*zln[3]/zln[1]+F[2])
  F
end

function ve(u::Vector)
  ρ = u[1]; v = u[2]/u[1]; ϵ=u[3]
  p = (ϵ-0.5*ρ*v^2)*(γ-1)
  s = log(p)-γ*log(ρ)
  return [(γ-s)/(γ-1)-ρ*v^2/(2*p);ρ*v/p;-ρ/p]
end

function get_problem(N)
    mesh = Uniform1DFVMesh(N,-1.0,1.0,:ZERO_FLUX, :ZERO_FLUX)
    ConservationLawsProblem(f0,f,CFL,Tend,mesh)
end
prob = get_problem(20)

basis=legendre_basis(3)
limiter! = DGLimiter(prob, basis, Linear_MUSCL_Limiter())
@time sol = solve(prob, DiscontinuousGalerkinScheme(basis, rusanov_euler_num_flux); TimeIntegrator = SSPRK22(limiter!))

#Plot
using Plots
plot(sol)


# This is a crude replication of Carvalho and Vilela (2015)
## By Daniel Coutinho and Rafaela

####### WARNING############
## NOT DEBUGGED AS MUCH AS IT SHOULD!
#################

### TODO

## 1. Pi and Psi matrices
## 2. More comments
## 3. Coefficients as strings for latexify

#######

### ORDER
## c_t
##c_{t-1}
##i_{t-1}
##i_{t-2}
## pi_t
## gamma_t
## y_t
## tot_{t-1}
## y^{star}_t
## q_{t-1}
## s_t
## s_{t-1}
## pi^{star}_t
## pi_{D,t}
## pi_{D,t-1}
## pi_{I,t}
## pi_{I,t-1}
## a_t
## z_t
## i^{star}_t
## varepsilon_{cp,t}
## phi_t

## Order of the expectational errors

#c_t
#pi_t
#gamma_t
#pi^d_t
#pi^I_t
#s_t

## Order of fundamental shocks
#
#epsilon_a
#epsilon_gamma
#epsilon_cp
#epsion_phi
#epsilon_i
#epsilon_y*
#epsilon_pi*
#epsilon_i*

#Creating Matrices

G0 = zeros(23,23)
G1 = zeros(23,23)
Pi = zeros(23,6)
Psi = zeros(23,8)

kappa_d = (1-theta_D)*(1-theta_D*bet)/theta_D
kappa_i = (1-theta_I)*(1-theta_I*bet)/theta_I

#First equation

G0[1,1] = 1
G0[1,5] = (1-h)/sig
G0[1,6] = (1-h)/sig
G0[1,3] = -(1-h)/sig

G1[1,1] = (1-h)
G1[1,2] = h
G1[1,6] = (1-h)/sig

Pi[1,1] = -1
Pi[1,2] = -(1-h)/sig
Pi[1,3] = (1-h)/sig

#Second Equation

G1[2,7] = -1
G1[2,1] = 1-alfa
G1[2,8] = alfa*eta*(2-alfa)
G1[2,9] = alfa*eta
G1[2,10] = alfa

#Third Equation

G0[3,8] = 1

G1[3,8] = 1
G1[3,15] = -1
G1[3,17] = 1

#Fourth Equation

G1[4,11] = -1
G1[4,9] = 1
G1[4,8] = 1 -alfa

#Fifth equation

G0[5,11] = -1
G0[5,14] = -1

G1[5,11] = 1
G1[5,12] = 1
G1[5,13] = -1
G1[5,5] = -1

#Equation number 6

G0[6,15] = bet

G1[6,15] = 1+delta_D
G1[6,16] = bet*delta_D
G1[6,8] = -kappa_d*alfa
G1[6,7] = -kappa_d*phi
G1[6,1] = -kappa_d*sig/(1-h)
G1[6,2] = kappa_d*sig/(1-h)
G1[6,19] = kappa_d*(1+phi)

Pi[6,4] = -bet

#Equation number 7

G0[7,17] = bet

G1[7,17] = (1+bet*delta_I)
G1[7,18] = delta_I
G1[7,9] = kappa_i

Pi[7,5] = -bet

#Equation number 8

G0[8,8] = -alfa

G1[8,5] = -1
G1[8,15] = 1
G1[8,8] = alfa

#Equation number 9

G0[9,20] = 1
G0[9,8] = alfa

G1[9,20] = 1/bet
G1[9,9] = -alfa
G1[9,7] = 1
G1[9,1] = -1

# Equation number 10

G0[10,12] = 1
G0[10,21] = 1
G0[10,3] = -1

G1[10,20] = chi

Pi[10,6] = -1

# Equation number 11: Taylor rule

G0[11,3] = 1
G1[11,3] = rho_1
G1[11,4] = rho_2
G1[11,5] = (1-rho_1-rho_2)*lambda_pi
G1[11,7] = (1-rho_1-rho_2)*lambda_y
G1[11,12] = (1-rho_1-rho_2)*lambda_s
G1[11,13] = -(1-rho_1-rho_2)*lambda_s ## its for the delta tot

Psi[11,5] = sigma_i

###################
## Foreign Block ##
###################

# Equation 12

G0[12,10] = 1

G1[12,10] = a_1yy
G1[12,14] = a_1ypi
G1[12,21] = a_1yi

Psi[12,6] = sigma_y_star

#Equation 13

G0[13,14] = 1
G0[13,10] = a_0piy

G1[13,10] = a_1piy
G1[13,14] = a_1pipi
G1[13,21] = a_1pii

Psi[13,7] = sigma_pi_star

#Equation 14

G0[14,21] = 1
G0[14,14] = a_0ipi
G0[14,10] = a_0iy

G1[14,10] = a_1iy
G1[14,14] = a_1ipi
G1[14,21] = a_1ii

Psi[14,8] = sigma_i_star

#################
## Shocks block #
#################

# Equation 15

G0[15,19] = 1

G1[15,19] = rho_a

Psi[15,1] = sigma_a

# Equation 16

G0[16,6] = 1

G1[16,6] = rho_gamma

Psi[16,2] = sigma_gamma

# Equation 17

G0[17,22] = 1

G1[17,22] = rho_cp

Psi[17,3] = sigma_cp

# Equation 18

G0[18,23] = 1

G1[18,23] = rho_phi

Psi[18,4] = sigma_phi

####################
## Identity block ##
####################

# Equation 19

G0[19,1] = 1
G1[19,2] = 1

# Eqquation 20

G0[20,3] = 1
G1[20,4] = 1

# Equation 21

G0[21,12] = 1
G1[21,13] = 1

# Equation 22

G0[22,15] = 1
G1[22,16] = 1

# Equation 23

G0[23,17] = 1
G1[23,18] = 1