using Markdown
# using HorizonSideRobots
#########################################################
md"""
    #Базовые функции, связанные с передвижением робота
"""
#########################################################
"""
Делает попытку шагнуть в сторону {side} и возвращает {false} при неудачной попытке, иначе {true}
"""
function carefull_move!(r::Robot, side::HorizonSide)
    if !isborder(r, side)
        move!(r, side)
        return true
    else
        return false
    end
end

"""
Двигает робота до перегородки в сторону {side} и возвращает кол-во шагов {steps}
"""
function move_till_border!(r, side::HorizonSide)
    steps = 0
    while isborder(r, side) == 0
        move!(r, side)
        steps += 1
    end
    return steps
end

"""
Двигает робота на {x} клеток в сторону {side}
"""
function move_for!(r, side::HorizonSide, x::Int)
    for _ in 1:x
        if !isborder(r, side)
            move!(r, side)
        else
            return false
        end
    end
    return true
end

"""
Перемещает робота в угол ({side_1}, {side_2}) и возвращает проделанный при этом путь {path}
"""
function move_to_corner!(r, side_1::HorizonSide, side_2::HorizonSide,)
    path = []
    while !isborder(r, side_1) || !isborder(r, side_2)
        push!(path, move_till_border!(r, side_1))
        push!(path, move_till_border!(r, side_2))
    end
    return path
end

"""
Перемещает робота из угла в стартовую позицию по {back_path}
"""
function move_back_from_corner!(r, side_1, side_2, back_path)
    side_1 = opposite_side(side_1)
    side_2 = opposite_side(side_2)
    for i in 1:2:size(back_path)[1]
        move_for!(r, side_2, back_path[i])
        move_for!(r, side_1, back_path[i+1])
    end
end

"""
Двигает робота на клетку (x, y)
"""
function move_to!(r::Robot, x, y)
    move_till_border!(r, West)
    move_till_border!(r, Sud)
    for _ in 1:y-1
        move!(r, Nord)
    end
    for _ in 1:x-1
        move!(r, Ost)
    end
end

"""Возвращает противоположную {side} сторону."""
opposite_side(side::HorizonSide) = HorizonSide((Int(side) + 2) % 4)

"""Возвращает следующую сторону от {side} по часовой стрелке"""
next_side(side::HorizonSide) = opposite_side(HorizonSide((Int(side) + 1) % 4))

"""Возвращает следующую сторону от {side} против часовой стрелке"""
previous_side(side::HorizonSide) = HorizonSide((Int(side) + 1) % 4)

"""Возвращает сторону, в которой нет перегородки"""
function get_no_border_side(r)
    for side in [Nord, Ost, Sud, West]
        if !isborder(r, side)
            return side
        end
    end
    return false
end

"""Возвращает координаты клетки сверху/снизу/слева/справа от переданной"""
function side_to_i_j(side::HorizonSide, cell)
    i, j = cell
    if side == Nord
        return (i - 1, j)
    elseif side == Sud
        return (i + 1, j)
    elseif side == West
        return (i, j - 1)
    elseif side == Ost
        return (i, j + 1)
    end
end

"""Поиск в глубину, робот проходится по всем клеткам и маркирует переданные в {cells}"""
function mark_some_cells_with_dfs!(r, visited, cell, side, cells)
    is_not_at_start = side != false

    if is_not_at_start
        cheker = isborder(r, side) == false
    else
        cheker = true
    end

    if (cell in visited) == false && cheker && length(cells) > 0
        if is_not_at_start
            move!(r, side)
        end
        push!(visited, cell)

        if cell in cells
            putmarker!(r)
            println("!Marker at ", cell)
            filter!(el -> el ≠ cell, cells)
        end

        for tempSide in [Nord, Ost, Sud, West]
            if mark_some_cells_with_dfs!(r, visited, side_to_i_j(tempSide, cell), tempSide, cells)
                move!(r, opposite_side(tempSide))
            end
        end

        return true

    else
        return false
    end
end

"""Проверяет {x}-овую клетку в сторону {side} от места старта робота на наличие выхода в стороне {border_side}"""
function find_or_move!(r, side, x, border_side)
    move_for!(r, side, x)
    if isborder(r, border_side) == false
        return true
    end
    move_for!(r, opposite_side(side), x)
    return false
end

"""Перемещает робота к выходу со стороны {border_side}"""
function escape_find(r, border_side)
    count = 1
    sides = (next_side(border_side), opposite_side(next_side(border_side)))
    while true                                  # Робот двигается поочередно влево-вправо (с каждой итерацией проверяя на одну клетку с каждой стороны больше), пока не наткнется на выход
        for side in sides
            if find_or_move!(r, side, count, border_side)
                return (count, side)
            end
        end
        count += 1
    end
    return false
end

"""Обходит любую одинарную прямоугольную перегородку в стороне {side}"""
function try_move!(r::Robot, side::HorizonSide)
    if isborder(r, side)
        n = 0;
        ort_side = next_side(side)
        while isborder(r, side)
            if !isborder(r, ort_side)
                move!(r, ort_side)
                n+=1
            else
                move_for!(r, opposite_side(ort_side), n)
                return false
            end
        end

        move!(r, side)
        while isborder(r, opposite_side(ort_side))
            move!(r, side)
        end
        move_for!(r, opposite_side(ort_side), n)
        return true
    else
        move!(r, side)
        return true
    end
end

"""
Обходит любую одинарную прямоугольную перегородку в стороне {side}
Возвращает:
        {-1} - преград нет
         {0} - неудача
         {1} - перегородка линейна
         {2} - перегородка прямоугольна
"""
function try_move_extra!(r::Robot, side::HorizonSide)
    result = 0
    if isborder(r, side)
        n = 0;
        ort_side = next_side(side)
        while isborder(r, side)
            if !isborder(r, ort_side)
                move!(r, ort_side)
                n+=1
            else
                move_for!(r, opposite_side(ort_side), n)
                return result
            end
        end

        move!(r, side)
        while isborder(r, opposite_side(ort_side))
            result = 1
            move!(r, side)
        end
        move_for!(r, opposite_side(ort_side), n)
        result += 1
    else
        move!(r, side)
        result = -1
    end
    return result
end

"""Посещает все доступные клетки и возвращет {ans}, имеющий значение соответствующее {[i_min, i_max, j_min, j_max]}"""
function visit_all_cells!(r, side=Nord, cell=(0, 0), visited=Set(), ans=[0, 0, 0, 0,])

    if (cell in visited) == false && !isborder(r, side)
        move!(r, side)
        push!(visited, cell)

        ans[1] = min(cell[1], ans[1])
        ans[2] = max(cell[1], ans[2])
        ans[3] = min(cell[2], ans[3])
        ans[4] = max(cell[2], ans[4])

        for tempSide in [Nord, Ost, Sud, West]
            tempAns = visit_all_cells!(r, tempSide, side_to_i_j(tempSide, cell), visited, ans)
            if tempAns != false
                move!(r, opposite_side(tempSide))
                ans = tempAns
            end
        end
        return ans
    else
        return false
    end
end

"""Возвращает размер поля {(M, N)} и текущую позицию робота {r} {(i, j)}"""
function get_field_size_and_position!(r)
    side = get_no_border_side(r)
    if side == false
        return (1, 1), (1, 1)
    end
    ans = visit_all_cells!(r, side, side_to_i_j(side, (0,0)))
    move!(r, opposite_side(side))
    M = ans[2]-ans[1]+1
    N = ans[4]-ans[3]+1
    return (M, N), (M-ans[2], N-ans[4])
end

#########################################################
md"""
    #Базовые функции, связанные с базовыми типами данных, и сами типы и связанные структуры
"""
#########################################################

import HorizonSideRobots
"""Переопределение стандартных функций робота для стандартного робота {::Robot}"""
move!(robot::Robot, side::HorizonSide) = HorizonSideRobots.move!(robot, side)
isborder(robot::Robot,  side::HorizonSide) = HorizonSideRobots.isborder(robot, side)
putmarker!(robot::Robot) = HorizonSideRobots.putmarker!(robot)
ismarker(robot::Robot) = HorizonSideRobots.ismarker(robot)
temperature(robot::Robot) = HorizonSideRobots.temperature(robot)
show(robot::Robot) = HorizonSideRobots.show(robot)
show!(robot::Robot) = HorizonSideRobots.show!(robot)

#---------------------------------#

"""Структура координат {i, j}, где i - номер строки поля, а {j} - номер столбца поля (для робота)"""
mutable struct Coordinates
    i::Integer
    j::Integer

    Coordinates() = new(0,0)
    Coordinates(i::Integer,j::Integer) = new(i,j)
    Coordinates((i, j)::Tuple{Int64, Int64}) = new(i, j)
end

"""Изменяет координаты соответственно перемещению робота"""
function move!(coordinates::Coordinates, side::HorizonSide)
    if side==Nord
        coordinates.i -= 1
    elseif side==Sud
        coordinates.i += 1
    elseif side==Ost
        coordinates.j += 1
    else
        coordinates.j -= 1
    end
end

"""Обновляет значения координат соответственно на {i} и {j}"""
function update!(coordinates::Coordinates, (i, j)::Tuple{Int64, Int64})
    coordinates.i = i
    coordinates.j = j
end

#---------------------------------#

"""Базовый абстрактный робот"""
abstract type AbstractRobot end
"""Определение стандартных функций робота для робота {::AbstractRobot}"""
move!(robot::AbstractRobot, side::HorizonSide) = HorizonSideRobots.move!(get(robot), side)
isborder(robot::AbstractRobot,  side::HorizonSide) = HorizonSideRobots.isborder(get(robot), side)
putmarker!(robot::AbstractRobot) = HorizonSideRobots.putmarker!(get(robot))
ismarker(robot::AbstractRobot) = HorizonSideRobots.ismarker(get(robot))
temperature(robot::AbstractRobot) = HorizonSideRobots.temperature(get(robot))
show(robot::AbstractRobot) = HorizonSideRobots.show(get(robot))
show!(robot::AbstractRobot) = HorizonSideRobots.show!(get(robot))
"""Возвращает {nothing} для робота типа {AbstractRobot}"""
get(robot::AbstractRobot) = nothing

"""Двигает робота змейкой по всему полю"""
function move_as_snake!(r::AbstractRobot, side=Ost)
    do_movements!(r, side) #этаче, должно быть {do_movements} (которая еще и переопределяется для разных типов в зависимости от задач получается(?))
    while isborder(r, Nord)
        move!(r, Nord)
        side = opposite_side(side)
        do_movements!(r, side)
    end
end

#---------------------------------#

"""Абстрактный тип робота, обходящего перегородки"""
abstract type AbstractBorderRobot <: AbstractRobot end

"""Переопределенная {move_till_border!} для {AbstractBorderRobot}:
                        + Использует {try_move!}, что позволяет обходить одиночные перегородки по пути
"""
function move_till_border!(r::AbstractBorderRobot, side::HorizonSide)
    counter = 0

    while try_move!(get(r), side)
        counter += 1
    end

    return n
end

#---------------------------------#

"""Маркирующий робот от {AbstractBorderRobot}"""
struct PutMarkersBorderRobot <: AbstractBorderRobot
    robot::Robot
end

"""Возвращает ссылку на робота {::Robot} для робота типа {PutMarkersBorderRobot}"""
get(r::PutMarkersBorderRobot) = r.robot

"""
Переопределенная {try_move!}:
                        + Ставит маркеры
"""
function try_move!(r::PutMarkersBorderRobot, side::HorizonSide)
    try_move!(get(r), side)
    putmarker!(r)
end

"""Конструктор {PutMarkersBorderRobot}"""
function PutMarkersBorderRobot(r::Robot)
    putmarker!(r)
    new(r)
end

"""Маркирует все поле используя {move_as_snake!}"""
function mark_field(r::Robot)
    # утв: робот в ЮЗ углу
    r = PutMarkersBorderRobot(r)
    move_as_snake!(r)
end

#---------------------------------#

"""Тип робота, использующего глобальные координаты"""
struct SuperCheatRobot <: AbstractRobot
    robot::Robot
    position::Coordinates
    field_size::Tuple{Int64, Int64}

    SuperCheatRobot(r, (i, j), (M, N)) = new(r, Coordinates(i, j), (M, N))
    function SuperCheatRobot(r)
        (M, N), (i, j) = get_field_size_and_position!(r)
        return SuperCheatRobot(r, (i, j), (M, N))
    end
end

"""Возвращает ссылку на робота {::Robot} для робота типа {SuperCheatRobot}"""
get(robot::SuperCheatRobot) = robot.robot

"""Обновляет данные о текущей позиции {r.position} робота {r} (происходит вызов {get_field_size_and_position}!)"""
function update!(r::SuperCheatRobot)
    update!(r.position, get_field_size_and_position!(r.robot)[2])
end

"""Устанавливает данные о текущей позиции {r.position} робота {r} равными {(i, j)}"""
function setPositionTo!(r::SuperCheatRobot, (i, j))
    update!(r.position, (i,j))
end

"""
Переопределенная {move!(::Robot)}:
                        + Изменяет текущие координаты робота {r} согласно перемещению
"""
function move!(robot::SuperCheatRobot, side::HorizonSide)
    move!(robot.robot, side)
    move!(robot.position, side)
end

"""Возвращает текущую позицию робота {r.position} в виде {(i, j)}"""
position(r) = (r.position.i, r.position.j)


using DataDeps

register(DataDep(
  "vote-for-clinton",
  "http://www.stat.ufl.edu/~winner/data/clinton1.txt",
  "http://www.stat.ufl.edu/~winner/data/clinton1.dat",
  "51ae0be6fbf96ce4f9b36684a036c6e6c144549bb673ccd4ff045696ed6d9382",
  post_fetch_method=(path -> begin
    # IDs have spaces in them which is the delimiter
    lines = readlines(path)
    map!(x -> lstrip(x[30:end]), lines, lines)
    write(path, join(lines, '\n'))

    UCIData.process_dataset(path,
        target_index=1,
        feature_indices=2:10,
        delim=' ',
        ignorerepeated=true,
    )
  end),
))


@testset "PolyTopeVertexMesh" begin
    # Simply check that constructors work as intended
    @testset "TriangleMesh" begin
        for T in Floats
            for U in [UInt16, UInt32, UInt64]
                mesh = setup_TriangleMesh(T,U)
            end
        end
    end

    @testset "QuadrialteralMesh" begin
        for T in Floats
            for U in [UInt16, UInt32, UInt64]
                mesh = setup_QuadrilateralMesh(T,U)
            end
        end
    end

    @testset "MixedPolygonMesh" begin
        for T in Floats
            for U in [UInt16, UInt32, UInt64]
                mesh = setup_MixedPolygonMesh(T,U)
            end
        end
    end

    @testset "QuadraticTriangleMesh" begin
        for T in Floats
            for U in [UInt16, UInt32, UInt64]
                mesh = setup_QuadraticTriangleMesh(T,U)
            end
        end
    end

    @testset "QuadraticQuadrialteralMesh" begin
        for T in Floats
            for U in [UInt16, UInt32, UInt64]
                mesh = setup_QuadraticQuadrilateralMesh(T,U)
            end
        end
    end

    @testset "MixedQuadraticPolygonMesh" begin
        for T in Floats
            for U in [UInt16, UInt32, UInt64]
                mesh = setup_MixedQuadraticPolygonMesh(T,U)
            end
        end
    end
end


function findBoundaryIndex(boundaries, name)
    for boundary in boundaries
        boundary.name == name && return boundary.index
    end
end

function assignBC(boundaryName, field, type, value=0)
    iBoundary = findBoundaryIndex(mesh.boundaries, boundaryName)
    boundary = mesh.boundaries[iBoundary]
    boundary.bcType = type

    iCellStart = mesh.nCells + boundary.startFace - mesh.nInternalFaces
    iCellEnd = iCellStart + boundary.nFaces - 1

    iFaceStart = boundary.startFace
    iFaceEnd = iFaceStart + boundary.nFaces - 1

    if type == "fixedValue"
        field.cellValues[iCellStart:iCellEnd] = [value for _ in 1:boundary.nFaces]
    elseif type == "zeroGradient"
        owners = [mesh.faces[iFace].iOwner for iFace in iFaceStart:iFaceEnd]
        field.cellValues[iCellStart:iCellEnd] = [field.cellValues[iOwner] for iOwner in owners]
    end
    field.faceValues[iFaceStart:iFaceEnd] = field.cellValues[iCellStart:iCellEnd]
end


import Pkg

const POLYHEDRA_TEST = joinpath(dirname(dirname(pathof(Polyhedra))), "test")

include(joinpath(POLYHEDRA_TEST, "utils.jl"))
include(joinpath(POLYHEDRA_TEST, "config.jl"))
include(joinpath(POLYHEDRA_TEST, "solvers.jl")) # defines lp_solver
include(joinpath(POLYHEDRA_TEST, "jump.jl"))

include(joinpath(POLYHEDRA_TEST, "basic.jl"))
include(joinpath(POLYHEDRA_TEST, "simplex.jl"))
include(joinpath(POLYHEDRA_TEST, "permutahedron.jl"))
include(joinpath(POLYHEDRA_TEST, "board.jl"))
include(joinpath(POLYHEDRA_TEST, "docexample.jl"))
include(joinpath(POLYHEDRA_TEST, "issue48.jl"))
include(joinpath(POLYHEDRA_TEST, "empty.jl"))
include(joinpath(POLYHEDRA_TEST, "sparse.jl"))
include(joinpath(POLYHEDRA_TEST, "sparserect.jl"))
include(joinpath(POLYHEDRA_TEST, "support_function.jl"))
const misctests = Dict("basic" => basictest,
                       "doc" => doctest,
                       "simplex" => simplextest,
                       "permutahedron" => permutahedrontest,
                       "board" => boardtest,
                       "issue48" => issue48test,
                       "empty" => emptytest,
                       "sparse" => sparsetest,
                       "sparserect" => sparserecttest,
                       "support_function" => support_function_test)

@polytestset misc

include(joinpath(POLYHEDRA_TEST, "decompose.jl"))

polyhedratests = Dict("jump" => jumptest,
                      "misc" => misctest,
                      "decompose" => decomposetest)

@polytestset polyhedra true

@testset "Polyhedra tests in $arith arithmetics" for arith in [:float, :exact]
    polyhedratest(ConvexHull.Library(arith, lp_solver), [
        "jumpsimplex", "nonfulldimensional", "simplex", "permutahedron",
        "board", "issue48", "empty"
    ])
end


struct TDHH3DSingleLayerNear{T}
    speed_of_light::T
end


using Test
import MathOptInterface
const MOI = MathOptInterface
const MOIT = MOI.Test
const MOIU = MOI.Utilities

const DoubleDicts = MathOptInterface.Utilities.DoubleDicts

include("../dummy.jl")

remove_moi(x::String) = replace(x, "MathOptInterface." => "")
function compare_without_moi(x::String, y::String)
    @test remove_moi(x) == remove_moi(y)
end

@testset "IndexMap" begin
    map = MOIU.IndexMap()
    @test length(map) == 0
    compare_without_moi(sprint(show, map), "Utilities.IndexMap()")
    x = MOI.VariableIndex(1)
    y = MOI.VariableIndex(2)
    cx = MOI.ConstraintIndex{MOI.SingleVariable, MOI.Integer}(1)
    cy = MOI.ConstraintIndex{MOI.SingleVariable, MOI.Integer}(2)
    map = MOIU.IndexMap(Dict(x => y), DoubleDicts.IndexDoubleDict())
    map.conmap[cx] = cy
    @test length(map) == 2
    # `x=>y` in Julia <= 1.1 and `x => y` in Julia >= 1.2
    if VERSION < v"1.2"
        x_y = string(x) *  "=>" * string(y)
        c_x_y = string(cx) *  "=>" * string(cy)
    else
        x_y = string(x => y)
        c_x_y = string(cx => cy)
    end
    compare_without_moi(sprint(show, map), "Utilities.IndexMap($x_y,$c_x_y)")
end

@testset "AUTOMATIC" begin
    src = DummyModel()
    dest = DummyModel()
    @test_throws ErrorException MOIU.automatic_copy_to(dest, src)
    try
        @test_throws ErrorException MOIU.automatic_copy_to(dest, src)
    catch err
        @test sprint(showerror, err) == "Model DummyModel does not" *
        " support copy with names."
    end
end
@testset "Default" begin
    @test !MOIU.supports_default_copy_to(DummyModel(), false)
    @test !MOIU.supports_default_copy_to(DummyModel(), true)
    model = MOIU.Model{Float64}()
    MOIT.failcopytestc(model)
    MOIT.failcopytestia(model)
    MOIT.failcopytestva(model)
    MOIT.failcopytestca(model)
    MOIT.copytest(model, MOIU.Model{Float64}())
end
@testset "Allocate-Load" begin
    @test !MOIU.supports_allocate_load(DummyModel(), false)
    @test !MOIU.supports_allocate_load(DummyModel(), true)
    mock = MOIU.MockOptimizer(MOIU.Model{Float64}(), needs_allocate_load=true)
    MOIT.failcopytestc(mock)
    MOIT.failcopytestia(mock)
    MOIT.failcopytestva(mock)
    MOIT.failcopytestca(mock)
    MOIT.copytest(mock, MOIU.Model{Float64}())
end

struct DummyEvaluator <: MOI.AbstractNLPEvaluator end

@testset "Create variables in same ordering when NLPBlock is used (#849)" begin
    model = MOIU.UniversalFallback(MOIU.Model{Float64}())
    a = MOI.add_variable(model)
    b, c = MOI.add_variables(model, 2)
    x, cx = MOI.add_constrained_variable(model, MOI.GreaterThan(0.0))
    y, cy = MOI.add_constrained_variables(model, MOI.Nonnegatives(1))
    nlp_data = MOI.NLPBlockData(
        [MOI.NLPBoundsPair(0.0, 1.0) for i in 1:5],
        DummyEvaluator(), false)
    MOI.set(model, MOI.NLPBlock(), nlp_data)
    copy = MOIU.UniversalFallback(MOIU.Model{Float64}())
    index_map = MOIU.default_copy_to(copy, model, true)
    for vi in [a, b, c, x, y[1]]
        @test index_map[vi] == vi
    end
    for ci in [cx, cy]
        @test index_map[ci] == ci
    end
end

# Model that distinguish variables created constrained
struct ConstrainedVariablesModel <: MOI.ModelLike
    allocate_load::Bool
    added_constrained::Vector{Bool}
end
function ConstrainedVariablesModel(allocate_load::Bool)
    return ConstrainedVariablesModel(allocate_load, Bool[])
end
function MOI.empty!(model::ConstrainedVariablesModel)
    empty!(model.added_constrained)
end

function MOIU.supports_default_copy_to(model::ConstrainedVariablesModel, ::Bool)
    return !model.allocate_load
end
function MOIU.supports_allocate_load(model::ConstrainedVariablesModel, ::Bool)
    return model.allocate_load
end
function MOI.copy_to(dest::ConstrainedVariablesModel, src::MOI.ModelLike; kws...)
    MOIU.automatic_copy_to(dest, src; kws...)
end

function MOI.add_variables(model::ConstrainedVariablesModel, n)
    m = length(model.added_constrained)
    for i in 1:n
        push!(model.added_constrained, false)
    end
    return MOI.VariableIndex.(m .+ (1:n))
end
function MOIU.allocate_variables(model::ConstrainedVariablesModel, n)
    return MOI.add_variables(model, n)
end
function MOIU.load_variables(model::ConstrainedVariablesModel, n)
end
function MOI.add_constrained_variables(model::ConstrainedVariablesModel,
                                       set::MOI.AbstractVectorSet)
    m = length(model.added_constrained)
    for i in 1:MOI.dimension(set)
        push!(model.added_constrained, true)
    end
    ci = MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(set)}(m + 1)
    return MOI.VariableIndex.(m .+ (1:MOI.dimension(set))), ci
end
function MOIU.allocate_constrained_variables(model::ConstrainedVariablesModel, set::MOI.AbstractVectorSet)
    return MOI.add_constrained_variables(model, set)
end
function MOIU.load_constrained_variables(model::ConstrainedVariablesModel, vis::Vector{MOI.VariableIndex}, ci::MOI.ConstraintIndex{MOI.VectorOfVariables}, set::MOI.AbstractVectorSet)
end
function MOI.add_constraint(model::ConstrainedVariablesModel,
                            func::MOI.VectorOfVariables,
                            set::MOI.AbstractVectorSet)
    return MOI.ConstraintIndex{typeof(func), typeof(set)}(func.variables[1].value)
end
function MOIU.allocate_constraint(model::ConstrainedVariablesModel, func::MOI.VectorOfVariables, set::MOI.AbstractVectorSet)
    return MOI.add_constraint(model, func, set)
end
function MOIU.load_constraint(model::ConstrainedVariablesModel, ::MOI.ConstraintIndex{MOI.VectorOfVariables}, ::MOI.VectorOfVariables, ::MOI.AbstractVectorSet)
end

@testset "Duplicates in VectorOfVariables: $allocate_load" for allocate_load in [false, true]
    src = MOIU.Model{Int}()
    x = MOI.add_variables(src, 3)
    cx = MOI.add_constraint(src, [x[1], x[3], x[1], x[2]], MOI.Nonnegatives(4))
    y, cy = MOI.add_constrained_variables(src, MOI.Nonpositives(3))
    dest = ConstrainedVariablesModel(allocate_load)
    idxmap = MOI.copy_to(dest, src)
    for vi in x
        @test !dest.added_constrained[idxmap[vi].value]
    end
    for vi in y
        @test dest.added_constrained[idxmap[vi].value]
    end
end

abstract type AbstractConstrainedVariablesModel <: MOI.ModelLike end
mutable struct OrderConstrainedVariablesModel <: AbstractConstrainedVariablesModel
    constraintIndices       ::Array{MOI.ConstraintIndex}
    inner                   ::MOIU.Model{Float64}
    OrderConstrainedVariablesModel() = new(MOI.ConstraintIndex[], MOIU.Model{Float64}())
end
mutable struct ReverseOrderConstrainedVariablesModel <: AbstractConstrainedVariablesModel
    constraintIndices       ::Array{MOI.ConstraintIndex}
    inner                   ::MOIU.Model{Float64}
    ReverseOrderConstrainedVariablesModel() = new(MOI.ConstraintIndex[], MOIU.Model{Float64}())
end

MOI.add_variables(model::AbstractConstrainedVariablesModel, n) = MOI.add_variables(model.inner, n)
MOI.add_variable(model::AbstractConstrainedVariablesModel) = MOI.add_variable(model.inner)

function MOI.add_constraint(model::AbstractConstrainedVariablesModel, f::F, s::S) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet}
    ci = MOI.add_constraint(model.inner, f, s)
    push!(model.constraintIndices, ci)
    return ci
end

function MOI.copy_to(dest::AbstractConstrainedVariablesModel, src::MOI.ModelLike; kws...)
    MOIU.automatic_copy_to(dest, src; kws...)
end

MOIU.supports_default_copy_to(model::AbstractConstrainedVariablesModel, ::Bool) = true

function MOI.empty!(model::AbstractConstrainedVariablesModel)
    model.constraintIndices = MOI.ConstraintIndex[]
    MOI.empty!(model.inner)
end

MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = false
MOI.supports_add_constrained_variables(::OrderConstrainedVariablesModel, ::Type{MOI.Nonnegatives}) = true
MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = true
MOI.supports_add_constrained_variables(::OrderConstrainedVariablesModel, ::Type{MOI.Nonpositives}) = false

MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = true
MOI.supports_add_constrained_variables(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.Nonnegatives}) = false
MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = false
MOI.supports_add_constrained_variables(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.Nonpositives}) = true

MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.GreaterThan}) = true
MOI.supports_add_constrained_variable(::OrderConstrainedVariablesModel, ::Type{<:MOI.GreaterThan}) = false
MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.LessThan}) = false
MOI.supports_add_constrained_variable(::OrderConstrainedVariablesModel, ::Type{<:MOI.LessThan}) = true

MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.GreaterThan}) = false
MOI.supports_add_constrained_variable(::ReverseOrderConstrainedVariablesModel, ::Type{<:MOI.GreaterThan}) = true
MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.LessThan}) = true
MOI.supports_add_constrained_variable(::ReverseOrderConstrainedVariablesModel, ::Type{<:MOI.LessThan}) = false

@testset "Create variables using supports_add_constrained_variable(s) (#987)" begin
    # With vectors
    src = MOIU.Model{Float64}()
    a, c1 = MOI.add_constrained_variables(src, MOI.Nonpositives(3))
    c2 = MOI.add_constraint(src, a, MOI.Nonnegatives(3))

    dest = OrderConstrainedVariablesModel()
    index_map = MOI.copy_to(dest, src)
    @test typeof(c1) == typeof(dest.constraintIndices[2])
    @test typeof(c2) == typeof(dest.constraintIndices[1])

    dest = ReverseOrderConstrainedVariablesModel()
    index_map = MOI.copy_to(dest, src)
    @test typeof(c1) == typeof(dest.constraintIndices[1])
    @test typeof(c2) == typeof(dest.constraintIndices[2])

    # With single variables
    src = MOIU.Model{Float64}()
    a, c1 = MOI.add_constrained_variable(src, MOI.GreaterThan{Float64}(5.0))
    c2 = MOI.add_constraint(src, a, MOI.LessThan{Float64}(1.0))

    dest = OrderConstrainedVariablesModel()
    index_map = MOI.copy_to(dest, src)
    @test typeof(c1) == typeof(dest.constraintIndices[2])
    @test typeof(c2) == typeof(dest.constraintIndices[1])

    dest = ReverseOrderConstrainedVariablesModel()
    index_map = MOI.copy_to(dest, src)
    @test typeof(c1) == typeof(dest.constraintIndices[1])
    @test typeof(c2) == typeof(dest.constraintIndices[2])
end

@testset "Filtering copy: check based on index" begin
    # Create a basic model.
    src = MOIU.Model{Float64}()
    x = MOI.add_variable(src)
    c1 = MOI.add_constraint(src, x, MOI.LessThan{Float64}(1.0))
    c2 = MOI.add_constraint(src, x, MOI.GreaterThan{Float64}(0.0))

    # Filtering function: the default case where this function always returns
    # true is already well-tested by the above cases.
    # Only keep the constraint c1.
    f = (cidx) -> cidx == c1

    # Perform the copy.
    dst = OrderConstrainedVariablesModel()
    index_map = MOI.copy_to(dst, src, filter_constraints=f)

    @test typeof(c1) == typeof(dst.constraintIndices[1])
    @test length(dst.constraintIndices) == 1
end

mutable struct BoundModel <: MOI.ModelLike
    # Type of model that only supports ≤ bound constraints. In particular, it does not support integrality constraints.
    inner::MOIU.Model{Float64}
    BoundModel() = new(MOIU.Model{Float64}())
end

MOI.add_variable(model::BoundModel) = MOI.add_variable(model.inner)
MOI.add_constraint(model::BoundModel, f::MOI.AbstractFunction, s::MOI.LessThan{Float64}) = MOI.add_constraint(model.inner, f, s)
MOI.supports_constraint(::BoundModel, ::Type{MOI.SingleVariable}, ::MOI.LessThan{Float64}) = true

MOIU.supports_default_copy_to(::BoundModel, ::Bool) = true
MOI.copy_to(dest::BoundModel, src::MOI.ModelLike; kws...) = MOIU.automatic_copy_to(dest, src; kws...)
MOI.empty!(model::BoundModel) = MOI.empty!(model.inner)

MOI.supports(::BoundModel, ::Type{MOI.NumberOfConstraints}) = true
MOI.get(model::BoundModel, attr::MOI.NumberOfConstraints) = MOI.get(model.inner, attr)

@testset "Filtering copy: check based on constraint type" begin
    # Create a basic model.
    src = MOIU.Model{Float64}()
    x = MOI.add_variable(src)
    c1 = MOI.add_constraint(src, x, MOI.LessThan{Float64}(10.0))
    c2 = MOI.add_constraint(src, x, MOI.Integer())

    # Filtering function: get rid of integrality constraint.
    f = (cidx) -> MOI.get(src, MOI.ConstraintSet(), cidx) != MOI.Integer()

    # Perform the unfiltered copy. This should throw an error (i.e. the implementation of BoundModel
    # should be correct).
    dst = BoundModel()
    @test_throws MOI.UnsupportedConstraint{MOI.SingleVariable, MOI.Integer} MOI.copy_to(dst, src)

    # Perform the filtered copy. This should not throw an error.
    dst = BoundModel()
    MOI.copy_to(dst, src, filter_constraints=f)
    @test MOI.get(dst, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.LessThan{Float64}}()) == 1
    @test MOI.get(dst, MOI.NumberOfConstraints{MOI.SingleVariable, MOI.Integer}()) == 0
end


type Grid
	obj::Ptr{Void}
end

function Grid()
    obj = Ref{Ptr{Void}}(C_NULL)
        err = ccall(("ElDefaultGrid", libEl), Cuint,
            (Ref{Ptr{Void}},), obj)
    err == 0 || throw(ElError(err))
    return Grid(obj[])
end


using Revise, LForaging
##

ispath("/home/beatriz/Documents/") ? (main_path ="/home/beatriz/Documents/") : (main_path = "/Users/dariosarra/Documents/Lab/Walton/LForaging")
fig_dir = joinpath(main_path,"Figures")
# pokes = DataFrame(CSV.read(joinpath(main_path,"Miscellaneous/Poke_data.csv")))#, DataFrame)
# pokes2 = DataFrame(CSV.read(joinpath(main_path,"Miscellaneous/New_Poke_data.csv")))#, DataFrame)
pokes = DataFrame(CSV.read(joinpath(main_path,"Miscellaneous/States_Poke_data.csv")))#, DataFrame)
pokes = preprocess_pokes(pokes)[:,[:MOUSE,:DATE,:SIDE,:KIND,:TRAVEL,:BOUT,:TRIAL,:BOUT_TRIAL,:POKE_TRIAL,:REWARD,:LEAVE,
    :IN, :OUT, :IN_TRIAL, :OUT_TRIAL, :IN_BOUT, :OUT_BOUT, :DURATION,:TIME]];
##
pokes[!,:BIN] = Int64.(round.(pokes.DURATION ./ 0.1))
filter!(r-> r.BIN >= 1, pokes)
pokes[!,:PatchRewRate] = get_rew_rate(pokes.REWARD,pokes.BIN, 0.1, 2)
transform!(groupby(pokes,[:MOUSE,:DATE]), [:REWARD,:BIN] => ((R,B) -> get_rew_rate(R,B, 0.1, 2)) => :PatchRewRate)
open_html_table(pokes[1:2000,:])

##
@df pokes density(:DURATION, group = :KIND, xrotation = 45)
savefig(joinpath(fig_dir,"Poke_Duration_density.png"))

@df pokes histogram(:DURATION, xrotation = 45, bins = 100)
savefig(joinpath(fig_dir,"Poke_Duration_histogram.png"))
##
fdf = filter(r-> r.SIDE != "travel", pokes)
fdf.MOUSE = categorical(fdf.MOUSE)
fdf.KIND = levels!(categorical(fdf.KIND),["poor", "medium", "rich"])
fdf.SIDE = categorical(fdf.SIDE)
fdf.TRAVEL = levels!(categorical(fdf.TRAVEL),["short", "long"])
fdf.REWARD = categorical(Bool.(fdf.REWARD))
f1 =  @formula(LEAVE ~ 1 + OUT_TRIAL+OUT_BOUT+DURATION+REWARD+TRAVEL+KIND + (1|MOUSE))
gm = fit(MixedModel, f1,fdf, Bernoulli())

##
pltdf = filter(r->r.DURATION <=2,fdf)
poke_rich, poke_rich_df =
    function_analysis(pltdf,:DURATION, cumulative_algorythm; grouping = :KIND, calc = :bootstrapping, xaxis = 0:0.1:2)
plot(poke_rich, ylabel = "Cumulative", xlabel = "Poke duration")
savefig(joinpath(fig_dir,"CumPokeRich.png"))
plt_trav = filter(r-> r.SIDE == "travel" && r.DURATION <=2, pokes)
dropmissing!(plt_trav)
poke_trav, poke_trav_df =
    function_analysis(plt_trav,:DURATION, cumulative_algorythm; grouping = :TRAVEL, calc = :bootstrapping, xaxis = 0:0.1:2)
plot(poke_trav, ylabel = "Cumulative", xlabel = "Poke duration")
savefig(joinpath(fig_dir,"CumPokeTrav.png"))
##
gd = groupby(filter(r->r.SIDE != "travel",pokes),[:MOUSE,:DATE,:TRIAL])
pre_trials = combine(gd) do dd
    DataFrame(
        DURATION_FULL = dd[end, :OUT_TRIAL],
        DURATION_SUM = round.(sum(dd.DURATION),digits =1),
        LAST_BOUT_FULL = dd[dd.LEAVE,:OUT_BOUT],
        LAST_BOUT_SUM = round.(sum(dd[dd.BOUT .== dd[dd.LEAVE,:BOUT],:DURATION]),digits =1),
        REWARDS = maximum(dd.BOUT),
        LAST_REWARD = Bool(dd[end,:REWARD]),
        SIDE = dd[1,:SIDE],
        KIND = dd[1,:KIND]
    )
end
open_html_table(pre_trials)

gd_tr = groupby(filter(r->r.SIDE == "travel",pokes),[:MOUSE,:DATE,:TRIAL])
trials_tr = combine(gd_tr) do dd
    DataFrame(
        TRAVEL_FULL = dd[end, :OUT_TRIAL],
        TRAVEL_SUM = round.(sum(dd.DURATION),digits = 1),
        TRAVEL_KIND = dd[1,:KIND]
    )
end
trials_tr.TRIAL = trials_tr.TRIAL .+1

trials = leftjoin(pre_trials, trials_tr, on = [:MOUSE,:DATE,:TRIAL])
open_html_table(pre_trials)
##
bout_sum_rich, bout_sum_rich_df =
    function_analysis(trials,:LAST_BOUT_SUM, cumulative_algorythm; grouping = :KIND, calc = :bootstrapping, xaxis = 0:0.5:10)
plot(bout_sum_rich, legend = :bottomright, fillalpha = 0.3, ylabel = "Cumulative", xlabel = "Last Bout duration (SUM)")
savefig(joinpath(fig_dir,"CumBoutSumRich.png"))
bout_full_rich, bout_full_rich_df =
    function_analysis(trials,:LAST_BOUT_FULL, cumulative_algorythm; grouping = :KIND, calc = :bootstrapping, xaxis = 0:1:40)
plot(bout_full_rich, legend = :bottomright, fillalpha = 0.3, ylabel = "Cumulative", xlabel = "Last Bout duration (FULL)")
savefig(joinpath(fig_dir,"CumBoutFullRich.png"))
##
trv_pltdf = filter(r->!ismissing(r.TRAVEL_KIND),trials)
bout_sum_rich, bout_sum_rich_df =
    function_analysis(trv_pltdf,:LAST_BOUT_SUM, cumulative_algorythm; grouping = :TRAVEL_KIND, calc = :bootstrapping, xaxis = 0:0.5:10)
plot(bout_sum_rich, legend = :bottomright, fillalpha = 0.3, ylabel = "Cumulative", xlabel = "Last Bout duration (SUM)")
savefig(joinpath(fig_dir,"CumBoutSumTrav.png"))
bout_full_rich, bout_full_rich_df =
    function_analysis(trv_pltdf,:LAST_BOUT_FULL, cumulative_algorythm; grouping = :TRAVEL_KIND, calc = :bootstrapping, xaxis = 0:1:40)
plot(bout_full_rich, legend = :bottomright, fillalpha = 0.3, ylabel = "Cumulative", xlabel = "Last Bout duration (FULL)")
savefig(joinpath(fig_dir,"CumBoutFullTrav.png"))
##
trial_sum_rich, trial_sum_rich_df =
    function_analysis(trials,:DURATION_SUM, cumulative_algorythm; grouping = :KIND, calc = :bootstrapping, xaxis = 0:1:30)
plot(trial_sum_rich, legend = :bottomright, fillalpha = 0.3, ylabel = "Cumulative", xlabel = "Trial duration (SUM)")
savefig(joinpath(fig_dir,"CumTrialSumRich.png"))
trial_full_rich, trial_full_rich_df =
    function_analysis(trials,:DURATION_FULL, cumulative_algorythm; grouping = :KIND, calc = :bootstrapping, xaxis = 0:5:100)
plot(trial_full_rich, legend = :bottomright, fillalpha = 0.3, ylabel = "Cumulative", xlabel = "Trial duration (FULL)")
savefig(joinpath(fig_dir,"CumTrialFullRich.png"))
##
trv_pltdf = filter(r->!ismissing(r.TRAVEL_KIND),trials)
trial_sum_rich, trial_sum_rich_df =
    function_analysis(trv_pltdf,:DURATION_SUM, cumulative_algorythm; grouping = :TRAVEL_KIND, calc = :bootstrapping, xaxis = 0:1:30)
plot(trial_sum_rich, legend = :bottomright, fillalpha = 0.3, ylabel = "Cumulative", xlabel = "Trial duration (SUM)")
savefig(joinpath(fig_dir,"CumTrialSumTrav.png"))
trial_full_rich, trial_full_rich_df =
    function_analysis(trv_pltdf,:DURATION_FULL, cumulative_algorythm; grouping = :TRAVEL_KIND, calc = :bootstrapping, xaxis = 0:5:100)
plot(trial_full_rich, legend = :bottomright, fillalpha = 0.3, ylabel = "Cumulative", xlabel = "Trial duration (FULL)")
savefig(joinpath(fig_dir,"CumTrialFullTrav.png"))
##
gs0 = trials
gs0.DURATION_FULL = round.(gs0.DURATION_FULL)
filter!(r->r.DURATION_FULL <=60, gs0)
gs1 = combine(groupby(gs0,[:MOUSE,:DURATION_FULL,:KIND]),
    :LAST_BOUT_FULL	.=> [mean,sem]
)

gs2 = combine(groupby(gs1,[:DURATION_FULL,:KIND]),
    :LAST_BOUT_FULL_mean .=> [mean,sem] .=> [:LAST_BOUT_FULL_mean,:LAST_BOUT_FULL_sem]
)

sort!(gs2,:DURATION_FULL)
@df gs2 plot(:DURATION_FULL, :LAST_BOUT_FULL_mean, ribbon = :LAST_BOUT_FULL_sem, group = :KIND,
    xlabel = "Trial Duration (Full)", ylabel = "Last Bout Duration (Full)")
savefig(joinpath(fig_dir,"Full(BoutxTrav)_Kind.png"))
##
gs0 = trials
gs0.DURATION_FULL = round.(gs0.DURATION_FULL)
filter!(r->r.DURATION_FULL <=60, gs0)
gs1 = combine(groupby(gs0,[:MOUSE,:DURATION_FULL,:TRAVEL_KIND]),
    :LAST_BOUT_FULL	.=> [mean,sem]
)

gs2 = combine(groupby(gs1,[:DURATION_FULL,:TRAVEL_KIND]),
    :LAST_BOUT_FULL_mean .=> [mean,sem] .=> [:LAST_BOUT_FULL_mean,:LAST_BOUT_FULL_sem]
)

sort!(gs2,:DURATION_FULL)
filter!(r->!ismissing(r.TRAVEL_KIND), gs2)
@df gs2 plot(:DURATION_FULL, :LAST_BOUT_FULL_mean, ribbon = :LAST_BOUT_FULL_sem, group = :TRAVEL_KIND,
    xlabel = "Trial Duration (Full)", ylabel = "Last Bout Duration (Full)")
savefig(joinpath(fig_dir,"Full(BoutxTrav)_Travel.png"))
##
sum(trials.LAST_REWARD)/nrow(trials)


import JSON
import MCTS: AbstractTreeVisualizer, node_tag, tooltip_tag, create_json, blink

type POMCPTreeVisualizer <: AbstractTreeVisualizer
    node::BeliefNode
end

blink(n::BeliefNode) = blink(POMCPTreeVisualizer(n))

typealias NodeDict Dict{Int, Dict{String, Any}}

function create_json(v::POMCPTreeVisualizer)
    node_dict = NodeDict()
    dict = recursive_push!(node_dict, v.node)
    json = JSON.json(node_dict)
    return (json, 1)
end

function recursive_push!(nd::NodeDict, n::BeliefNode, parent_id=-1)
    id = length(nd) + 1
    if parent_id > 0
        push!(nd[parent_id]["children_ids"], id)
    end
    nd[id] = Dict("id"=>id,
                  "type"=>:belief, # maybe this should be :state - it doesn't matter for now
                  "children_ids"=>Array(Int,0),
                  "tag"=>node_tag(n.label),
                  "tt_tag"=>tooltip_tag(n.label),
                  "N"=>n.N
                  )
    for (a,c) in n.children
        recursive_push!(nd, c, id)
    end
    return nd
end

function recursive_push!(nd::NodeDict, n::RootNode, parent_id=-1)
    id = length(nd) + 1
    if parent_id > 0
        push!(nd[parent_id]["children_ids"], id)
    end
    nd[id] = Dict("id"=>id,
                  "type"=>:belief, # maybe this should be :state - it doesn't matter for now
                  "children_ids"=>Array(Int,0),
                  "tag"=>node_tag(n.B),
                  "tt_tag"=>tooltip_tag(n.B),
                  "N"=>n.N
                  )
    for (a,c) in n.children
        recursive_push!(nd, c, id)
    end
    return nd
end

function recursive_push!(nd::NodeDict, n::AbstractActNode, parent_id=-1)
    id = length(nd) + 1
    if parent_id > 0
        push!(nd[parent_id]["children_ids"], id)
    end
    nd[id] = Dict("id"=>id,
                  "type"=>:action,
                  "children_ids"=>Array(Int,0),
                  "tag"=>node_tag(n.label),
                  "tt_tag"=>tooltip_tag(n.label),
                  "N"=>n.N,
                  "Q"=>n.V
                  )
    for (o,c) in n.children
        recursive_push!(nd, c, id)
    end
    return nd
end