module DLagrangianFESpaces

using Gridap
using Gridap.Helpers
using Gridap.CellValuesGallery
using Gridap.CLagrangianFESpaces: _setup_reffe
using Gridap.CLagrangianFESpaces: _setup_cellbasis
using Gridap.CLagrangianFESpaces: _S
using Gridap.CLagrangianFESpaces: _compute_comp_to_dof
using Gridap.CLagrangianFESpaces: _setup_grid
using Gridap.CLagrangianFESpaces: _setup_masks
using Gridap.CLagrangianFESpaces: _eval
using Gridap.CLagrangianFESpaces: _eval_bc
using Gridap.ConformingFESpaces: _CellField
using Gridap.BoundaryGrids: _setup_tags

export DLagrangianFESpace

import Gridap: num_free_dofs
import Gridap: num_diri_dofs
import Gridap: diri_tags
import Gridap: apply_constraints
import Gridap: apply_constraints_rows
import Gridap: apply_constraints_cols
import Gridap: celldofids
import Gridap: interpolate_values
import Gridap: interpolate_diri_values
import Gridap: CellField
import Gridap: CellBasis
import Gridap: Triangulation

struct DLagrangianFESpace{D,Z,T,B} <: FESpace{D,Z,T}
  grid::Grid{D,Z}
  node_to_label::Vector{Int}
  tag_to_labels::Vector{Vector{Int}}
  diritags::Vector{Int}
  dirimasks::Vector{B}
  nfreedofs::Int
  ndiridofs::Int
  cell_to_dofs::CellVector{Int}
  reffe::LagrangianRefFE{Z,T}
  cellbasis::CellBasis{Z,T}
  diricells::Vector{Int}
end

function DLagrangianFESpace(
  ::Type{T},
  grid::Grid{D,Z},
  node_to_label::AbstractVector{<:Integer},
  tag_to_labels::Vector{Vector{Int}},
  diritags::Vector{Int},
  dirimasks::Vector{B}) where {D,Z,T,B}

  cell_to_nodes = cells(grid)
  reffe = _setup_reffe(T,celltypes(grid),cellorders(grid))
  cellbasis = _setup_cellbasis(reffe,grid)

  lnode_and_comp_to_ldof = reffe.dofbasis.node_and_comp_to_dof

  cell_to_dofs, nfreedofs, ndiridofs, diricells = _setup_cell_to_dofs(
    cell_to_nodes,
    node_to_label,
    tag_to_labels,
    diritags,
    dirimasks,
    lnode_and_comp_to_ldof,
    T)

  DLagrangianFESpace{D,Z,T,B}(
    grid,
    node_to_label,
    tag_to_labels,
    diritags,
    dirimasks,
    nfreedofs,
    ndiridofs,
    cell_to_dofs,
    reffe,
    cellbasis,
    diricells)

end

function DLagrangianFESpace(
  ::Type{T},
  model::DiscreteModel,
  order::Integer,
  diritags=Int[],
  dirimasks=nothing) where T
  DLagrangianFESpace(T,model,FaceLabels(model),order,diritags,dirimasks)
end

function DLagrangianFESpace(
  ::Type{T},
  model::DiscreteModel,
  facelabels::FaceLabels,
  order::Integer,
  diritags=Int[],
  dirimasks=nothing) where T

  _diri_tags = _setup_tags(facelabels,diritags)
  _diri_masks = _setup_masks(T,_diri_tags,dirimasks)

  grid, node_to_label, tag_to_labels = _setup_grid(model,facelabels,order)

  DLagrangianFESpace(
    T,grid,node_to_label,tag_to_labels,_diri_tags,_diri_masks)

end

num_free_dofs(fesp::DLagrangianFESpace) = fesp.nfreedofs

num_diri_dofs(fesp::DLagrangianFESpace) = fesp.ndiridofs

diri_tags(fesp::DLagrangianFESpace) = fesp.diritags

apply_constraints(
  ::DLagrangianFESpace, cellvec::CellVector, cellids::CellNumber) = cellvec

apply_constraints_rows(
  ::DLagrangianFESpace, cellmat::CellMatrix, cellids::CellNumber) = cellmat

apply_constraints_cols(
  ::DLagrangianFESpace, cellmat::CellMatrix, cellids::CellNumber) = cellmat

celldofids(fesp::DLagrangianFESpace) = fesp.cell_to_dofs

function CellField(
  fesp::DLagrangianFESpace{D,Z,T},
  free_dofs::AbstractVector{E},
  diri_dofs::AbstractVector{E}) where {D,Z,T,E}

  _CellField(fesp, free_dofs, diri_dofs, T, E)
end

function interpolate_values(fesp::DLagrangianFESpace,fun::Function)
  _interpolate_values(fesp,fun)
end

function interpolate_values(fesp::DLagrangianFESpace{D,Z,T},val::T) where {D,Z,T}
  _interpolate_values(fesp,val)
end

function _interpolate_values(fesp::DLagrangianFESpace,fun)

  zh = zero(fesp)
  fdof_to_val = free_dofs(zh)
  ddof_to_val = diri_dofs(zh)
  node_to_coords = points(fesp.grid)
  lnode_and_comp_to_ldof = fesp.reffe.dofbasis.node_and_comp_to_dof
  cell_to_dofs = fesp.cell_to_dofs
  cell_to_nodes = cells(fesp.grid)
  node_comp_to_val = _eval_bc(fun,node_to_coords)

  _fill_interpolated_vals!(
    fdof_to_val,
    ddof_to_val,
    node_comp_to_val,
    cell_to_nodes,
    cell_to_dofs,
    lnode_and_comp_to_ldof)

  (fdof_to_val, ddof_to_val)
end

function interpolate_diri_values(fesp::DLagrangianFESpace, funs::Vector{<:Function})
  _interpolate_diri_values(fesp, funs)
end

function interpolate_diri_values(fesp::DLagrangianFESpace{D,Z,T}, vals::Vector{T}) where {D,Z,T}
  _interpolate_diri_values(fesp, vals)
end

function _interpolate_diri_values(fesp::DLagrangianFESpace, funs)

  T = value_type(fesp)
  E = eltype(T)
  ndiri = num_diri_dofs(fesp)
  ddof_to_val = zeros(E,ndiri)
  node_to_label = fesp.node_to_label
  tag_to_labels = fesp.tag_to_labels
  diricells = fesp.diricells
  lnode_and_comp_to_ldof = fesp.reffe.dofbasis.node_and_comp_to_dof
  cell_to_dofs = fesp.cell_to_dofs
  cell_to_nodes = cells(fesp.grid)
  node_to_coords = points(fesp.grid)

  for (fun,diritag,dirimask) in zip(funs,fesp.diritags,fesp.dirimasks)
    _fill_diri_values!(
      ddof_to_val,
      fun,
      diritag,
      dirimask,
      diricells,
      node_to_label,
      tag_to_labels,
      node_to_coords,
      cell_to_nodes,
      cell_to_dofs,
      lnode_and_comp_to_ldof)
  end

  ddof_to_val

end

CellBasis(fesp::DLagrangianFESpace{D,Z,T}) where {D,Z,T} = fesp.cellbasis

Triangulation(fesp::DLagrangianFESpace) = Triangulation(fesp.grid)

# Helpers

function _fill_diri_values!(
  ddof_to_val,
  fun,
  diritag,
  dirimask,
  diricells,
  node_to_label,
  tag_to_labels,
  node_to_coords,
  cell_to_nodes,
  cell_to_dofs,
  lnode_and_comp_to_ldof)

  for cell in diricells

    lnode_to_node = cell_to_nodes[cell]
    ldof_to_dof = cell_to_dofs[cell]

    for (lnode,node) in enumerate(lnode_to_node)
      label = node_to_label[node]
      if label in tag_to_labels[diritag]

        coords = node_to_coords[node]
        comp_to_val = _eval(fun,coords)

        for comp in eachindex(dirimask)
          mask = dirimask[comp]
          if mask
            val = comp_to_val[comp]
            ldof = lnode_and_comp_to_ldof[lnode,comp]
            dof = ldof_to_dof[ldof]
            ddof = -dof
            ddof_to_val[ddof] = val
          end
        end

      end
    end

  end

end

function  _fill_interpolated_vals!(
  fdof_to_val,
  ddof_to_val,
  node_comp_to_val,
  cell_to_nodes,
  cell_to_dofs,
  lnode_and_comp_to_ldof)

  ncells = length(cell_to_nodes)

  nlnodes, ncomps = size(lnode_and_comp_to_ldof)

  for cell in 1:ncells

    lnode_to_node = cell_to_nodes[cell]
    ldof_to_dof = cell_to_dofs[cell]

    for (lnode,node) in enumerate(lnode_to_node)
      comp_to_val = node_comp_to_val[node]
      for comp in 1:ncomps
        ldof = lnode_and_comp_to_ldof[lnode,comp]
        dof = ldof_to_dof[ldof]
        val = comp_to_val[comp]
        if dof > 0
          fdof_to_val[dof] = val
        elseif dof < 0
          ddof_to_val[-dof] = val
        else
          @unreachable
        end
      end
    end

  end

end

function _setup_cell_to_dofs(
  cell_to_nodes,
  node_to_label,
  tag_to_labels,
  diritags,
  dirimasks,
  lnode_and_comp_to_ldof,
  ::Type{T}) where T

  ncells = length(cell_to_nodes)
  nlnodes, ncomp = size(lnode_and_comp_to_ldof)
  nldofs = nlnodes * ncomp

  ndata = ncells*nldofs

  cell_to_dofs_data = zeros(Int,ndata)
  diricells = Int[]

  nfreedofs, ndiridofs = _fill_cell_to_dofs!(
    cell_to_dofs_data,
    diricells,
    cell_to_nodes,
    node_to_label,
    tag_to_labels,
    diritags,
    dirimasks,
    lnode_and_comp_to_ldof,
    T)

  cell_to_dofs = CellVectorFromDataAndStride(cell_to_dofs_data,nldofs)

  (cell_to_dofs, nfreedofs, ndiridofs, diricells)
end

function _fill_cell_to_dofs!(
  cell_to_dofs_data,
  diricells,
  cell_to_nodes,
  node_to_label,
  tag_to_labels,
  diritags,
  dirimasks,
  lnode_and_comp_to_ldof,
  ::Type{T}) where T

  ncells = length(cell_to_nodes)
  nlnodes, ncomp = size(lnode_and_comp_to_ldof)
  nldofs = nlnodes * ncomp
  S = _S(T)

  fdof = 1
  ddof = -1
  k = 0

  for cell in 1:ncells

    nodes = cell_to_nodes[cell]
    isdiricell = false

    for (lnode,node) in enumerate(nodes)

      label = node_to_label[node]
      comp_to_dof, fdof, ddof, isdirinode =
        _compute_comp_to_dof(
        S,label,tag_to_labels,diritags,dirimasks,fdof,ddof)

      for comp in eachindex(comp_to_dof)
        ldof = lnode_and_comp_to_ldof[lnode,comp]
        dof = comp_to_dof[comp]
        cell_to_dofs_data[k+ldof] = dof
      end

      if isdirinode
        isdiricell = true
      end

    end

    if isdiricell
      push!(diricells,cell)
    end

    k += nldofs

  end

  (fdof-1, -(ddof+1))

end

end # module


abstract type abstract_alg_history end

export abstract_alg_history, get_col, generic_alg_history, alg_history2

using NLPModels, JuMP

struct alg_history2 <: abstract_alg_history
    t::Int64
    step_type::String
    eta::Class_reduction_factors
    ls_info_num_steps::Int64
    ls_info_step_size_P::Float64
    ls_info_step_size_D::Float64
    dir_x_norm::Float64
    dir_y_norm::Float64
    dir_s_norm::Float64
    kkt_ratio::Float64
    mu::Float64
    fval::Float64
    dual_scaled::Float64
    norm_grad_lag::Float64
    primal_residual::Float64
    con_vio::Float64
    comp::Float64
    sy_inf::Float64
    comp_ratio::Float64
    sy_mean::Float64
    farkas::Float64
    delta::Float64
    eval_merit_function::Float64
    eval_phi::Float64
    eval_grad_phi::Float64
    y_norm::Float64
    x_norm::Float64
    tot_num_fac::Int64
    num_fac_inertia::Int64
    strict_comp::Float64

    #=function alg_history()
      return new()
  end=#
end

function major_its_only(hist::Array{alg_history2,1})
    # reduce the history list so it only includes major iterations
    its = get_col(hist,:t);
    keep_these = Array{Int64,1}()
    for i = 1:(length(its)-1)
        if its[i+1] != its[i]
            push!(keep_these,i)
        end
    end
    push!(keep_these,length(its))
    return hist[keep_these]
end

mutable struct generic_alg_history <: abstract_alg_history
  t::Int64
  #mu::Float64
  fval::Float64
  #dual_scaled::Float64
  norm_grad_lag::Float64
  sy_inf::Float64
  #comp_ratio::Float64
  #sy_mean::Float64
  #primal_residual::Float64
  #farkas::Float64
  #delta::Float64
  #eval_merit_function::Float64
  #eval_phi::Float64
  #eval_grad_phi::Float64
  mu::Float64
  primal_residual::Float64
  con_vio::Float64
  y_norm::Float64
  x_norm::Float64
  strict_comp::Float64
end

function get_col(arr::Array,colname::Symbol)
    col = nothing
    for i = 1:length(arr)
        val = getfield(arr[i],colname)
        if i == 1
            col = Array{typeof(val),1}();
        end
        push!(col,val)
    end
    return col
end

function head_progress()
  println( pd("it",3), pd("step", 6), pd("eta",8), pd("α_P"), pd("α_D"), pd("ls",3), pd("|dx|"), pd("|dy|"), pd("N err"), "|", pd("mu"), pd("dual"), pd("primal"), pd("cmp scaled"), pd("infeas?"), "|", pd("delta"), pd("#fac",6), pd("|x|"), pd("|y|"), pd("∇phi"), pd("phi") )
end

function record_progress_first_it!(progress::Array{alg_history2,1}, iter::Class_iterate, kkt_solver::abstract_KKT_system_solver, par::Class_parameters, display::Bool)
    record_progress!(progress, 0, "it0", iter, kkt_solver, Blank_ls_info(), Class_reduction_factors(),  0, 0, par, display)
end

function record_progress!(progress::Array{alg_history2,1}, t::Int64, step_type::String, iter::Class_iterate, kss::abstract_KKT_system_solver,  ls_info::abstract_ls_info, eta::Class_reduction_factors, num_fac_inertia::Int64, tot_num_fac::Int64, par::Class_parameters, display::Bool)
    dir_y_norm = LinearAlgebra.norm(kss.dir.y,Inf);
    dir_x_norm = LinearAlgebra.norm(kss.dir.x,Inf);
    dir_s_norm = norm(kss.dir.s,Inf);
    ls_info_num_steps = ls_info.num_steps
    ls_info_step_size_P = ls_info.step_size_P
    ls_info_step_size_D = ls_info.step_size_D
    kkt_err = kss.kkt_err_norm;
    mu = get_mu(iter)
    fval = get_fval(iter)
    sy_mean = dot(iter.point.s,iter.point.y)/length(iter.point.s)
    norm_grad_lag = LinearAlgebra.norm(eval_grad_lag(iter, 0.0),Inf)
    norm_grad_lag_mod = LinearAlgebra.norm(eval_grad_lag(iter, iter.point.mu),Inf)
    dual_scaled = scaled_dual_feas(iter, par)
    primal_residual = LinearAlgebra.norm(get_primal_res(iter),Inf)
    con_vio = get_max_vio(iter)
    val_comp = LinearAlgebra.norm(comp(iter),Inf)
    sy_inf = LinearAlgebra.norm(iter.point.s .* iter.point.y,Inf)
    comp_ratio = comp_ratio_max(iter) * par.ls.comp_feas_agg #LinearAlgebra.norm(comp(iter),Inf)
    farkas = eval_farkas(iter)
    delta = get_delta(iter)
    val_merit_function = eval_merit_function(iter, par)
    val_phi = eval_phi(iter,iter.point.mu)
    val_grad_phi = LinearAlgebra.norm(eval_grad_phi(iter, iter.point.mu),Inf)
    y_norm = LinearAlgebra.norm(get_y(iter),Inf)
    x_norm = LinearAlgebra.norm(get_x(iter),Inf)
    tot_num_fac = tot_num_fac
    num_fac_inertia = num_fac_inertia
    kkt_ratio = kss.kkt_err_norm.ratio
    strict_comp = minimum(iter.point.s+iter.point.y)

    hist = alg_history2(t::Int64,
    step_type::String,
    eta::Class_reduction_factors,
    ls_info_num_steps::Int64,
    ls_info_step_size_P::Float64,
    ls_info_step_size_D::Float64,
    dir_x_norm::Float64,
    dir_y_norm::Float64,
    dir_s_norm::Float64,
    kkt_ratio::Float64,
    mu::Float64,
    fval::Float64,
    dual_scaled::Float64,
    norm_grad_lag::Float64,
    primal_residual::Float64,
    con_vio::Float64,
    val_comp::Float64,
    sy_inf::Float64,
    comp_ratio::Float64,
    sy_mean::Float64,
    farkas::Float64,
    delta::Float64,
    val_merit_function::Float64,
    val_phi::Float64,
    val_grad_phi::Float64,
    y_norm::Float64,
    x_norm::Float64,
    tot_num_fac::Int64,
    num_fac_inertia::Int64,
    strict_comp::Float64)

    push!(progress, hist)

    if display
      println(pd(t,5), pd(step_type[1], 3), rd(eta.mu),  rd(ls_info_step_size_P),  rd(ls_info_step_size_D), pd(ls_info_num_steps,2), rd(dir_x_norm), rd(dir_y_norm), rd(kkt_ratio), "|", rd(mu), rd(dual_scaled), rd(primal_residual), rd(comp_ratio), rd(farkas), "|", rd(delta), pd(num_fac_inertia,3), pd(tot_num_fac,3), rd(x_norm), rd(y_norm), rd(val_grad_phi), rd(val_merit_function, 5))
    end
end


export get_element,
       number_of_patterns,
       number_of_pixels,
       get_pixel,
       get_element!,
       spatial_position,
      pattern_dimension,
    get_element,
    assign!

using ColorTypes

function pattern_dimension(X)
  return length(get_element(X,1))
end

"""
```julia
function get_element!(o::Matrix,  img::Matrix{C}, i::Vector{Integer}) where C<:Colorant

```
"""
function get_element!(o::D, img::Matrix{C}, i::Vector{<:Integer}) where D<:AbstractArray  where C<:Colorant
    car_inds = CartesianIndices(img)[i]
    values = img[car_inds]
    for (j, pos) in enumerate(car_inds)
        @inbounds o[1, j] = pos[2]
        @inbounds o[2, j] = pos[1]
    end
    N = length(C)
    component = N >= 3 ? (comp1, comp2, comp3, alpha) : (comp1, alpha)
    for j=1:length(C)
        @inbounds o[2+j,:] = component[j].(values)
    end
end

function get_element!(o::D, data::Matrix{C}, i::Integer) where D<:AbstractArray  where C<:Number
    o[:] = data[:, i]
end

"""
```julia
function assign!(vec::T, val::C) where T<:AbstractArray where C<:Colorant
```
This function assigns the components of the color component val to a vector v
"""
function assign!(vec::T, val::C) where T<:AbstractArray where C<:Colorant
   N = length(C)
   component = N >= 3 ? (comp1, comp2, comp3, alpha) : (comp1, alpha)
    for j=1:length(C)
        @inbounds vec[j] = component[j](val)
    end
end

"""
```@julia
get_element!{T<:AbstractArray}(vec::T,  img::Matrix{Gray}, i::Integer)
```

Return through```vec``` the intensity image element  [x,y, i], where \$x,y\$ are the spatial
position of the pixel and the value i of the pixel \$(x,y)\$.
"""
function get_element!(vec::T,  img::Matrix{C}, i::Integer) where T<:AbstractArray where C<:Colorant
    ind    = spatial_position(img,i)
    @inbounds vec[1] = ind[2]
    @inbounds vec[2] = ind[1]
    assign!(view(vec,3:length(vec)), img[i])
end

"""
```
function get_element( img::Matrix{RGB}, i::Vector)
```
"""
function get_element(img::Matrix{T}, i::Vector)  where T<:Colorant
    m = zeros(length(T)+2,length(i))
    get_element!(m,img,i)
    return m
end

function get_element(img::Matrix{T}, i::Integer)  where T<:Colorant
   m = zeros(length(T)+2)
    get_element!(m,img,i)
    return m
end

"""
```@julia
number_of_patterns{T<:Any}(X::Matrix{T,3})
```

Return the number of pixels in the image
"""
number_of_patterns(X::Matrix{T}) where T<:Colorant = size(X,1)*size(X,2)

"""
```@julia
 spatial_position(X::Matrix, i::Int)
```
Returns the sub indexes from the linear index ```i```
"""
spatial_position(img::Matrix, i) = CartesianIndices(img)[i]

get_element(X::T,i) where T<:AbstractArray= view(X,:,i)
number_of_patterns(X::T) where T<:AbstractArray    = size(X,2)

get_element(X::Vector,i) = X[i]
number_of_patterns(X::Vector)     = length(X)

number_of_patterns(X::Array{T, 3}) where T = size(X, 2) * size(X, 3)
"""
```
function get_element(data::Array{T, 3}, i::Vector)  where T<:Number
```
"""
function get_element(data::Array{T, 3}, i::Vector)  where T<:Number
    m = zeros(size(data,1)+2,length(i))
    get_element!(m, data, i)
    return m
end

get_element(data::Array{T, 3}, i::Integer)  where T<:Number = get_element(data, [i])
get_element!(o::AbstractArray, data::Array{T,3}, i::Integer) where T<:Number = get_element!(o, data, [i])

function get_element!(o::AbstractArray, data::Array{T,3}, i::Vector) where T<:Number
    (_, nr, nc) = size(data)
    cart_indices = CartesianIndices((nr,nc))[i]
    @inbounds for (i, pos) in enumerate(cart_indices)
        o[3:end, i] = data[:, pos[1], pos[2]]
        o[1,i] = pos[2]
        o[2,i] = pos[1]
    end
end


function run_model!(; model::Agents.AgentBasedModel, steps::Int64)
    agent_df, model_df = Agents.run!(
        model, agent_step!, model_step!, steps,
        adata=[:pos, :socialnorm, :conscientiousness, :littering],
        mdata=[:new_trash, :trash_aggregated]
    )
    return agent_df, model_df
end

function run_experiment(; config::Config, replicates::Int64, rnd_seed::Int64)
    Random.seed!(rnd_seed)
    agent_df_list = DataFrames.DataFrame[]
    model_df_list = DataFrames.DataFrame[]
    networks = Dict()
    for rep in 1:replicates
        space = create_space(config)
        model = initialize_model(config, space)
        networks[rep] = deepcopy(model.space.graph)
        agent_df, model_df = run_model!(model=model, steps=config.n_iter)
        agent_df[!, :replicate] .= deepcopy(rep)
        model_df[!, :replicate] .= deepcopy(rep)
        push!(agent_df_list, deepcopy(agent_df))
        push!(model_df_list, deepcopy(model_df))
    end
    agent_df = reduce(vcat, agent_df_list)
    model_df = reduce(vcat, model_df_list)
    return networks, agent_df, model_df
end

function run_batch(; configs::Dict, replicates::Int64, batchname::String, compress::Bool)
    make_folders(batchname)
    path = joinpath("experiments", batchname)
    progress_bar = ProgressMeter.Progress(length(keys(configs)) * 2, 1, "Running simulations...")
    for config_key in keys(configs)
        networks, agent_df, model_df = run_experiment(config=configs[config_key], replicates=replicates, rnd_seed=0)
        adata_filename = "config_" * lpad(string(config_key), 2, "0") * "_adata.feather"
        mdata_filename = "config_" * lpad(string(config_key), 2, "0") * "_mdata.feather"
        ProgressMeter.next!(progress_bar)
        names_to_camelcase!(agent_df)
        names_to_camelcase!(model_df)
        write_dataframe(df=agent_df, filename=adata_filename, path=path)
        write_dataframe(df=model_df, filename=mdata_filename, path=path)
        write_networks(networks, path=path, config_key=config_key)
        ProgressMeter.next!(progress_bar)
    end
    println("Simulations done.")
    configs_to_dataframe(configs, path=path, write=true)
    if compress
        patterns = ["_graph.txt", "_mdata.feather", "_adata.feather"]
        archive_filenames = ["graphs.7z", "mdata.7z", "adata.7z"]
        for (pattern, archive_filename) in zip(patterns, archive_filenames)
            compress_data(path=path, pattern=pattern, archive_filename=archive_filename);
        end
    end
    println("Batch $batchname run successfully.")
    return "successful"
end


module TestExamples2DPart3

using Test
using Trixi

include("test_trixi.jl")

# pathof(Trixi) returns /path/to/Trixi/src/Trixi.jl, dirname gives the parent directory
EXAMPLES_DIR = joinpath(pathof(Trixi) |> dirname |> dirname, "examples", "2d")

# Start with a clean environment: remove Trixi output directory if it exists
outdir = "out"
isdir(outdir) && rm(outdir, recursive=true)

@testset "2D-Part3" begin

# Run basic tests
@testset "Examples 2D" begin
  # Acoustic perturbation
  include("test_examples_2d_ape.jl")

  # Curved mesh
  include("test_examples_2d_curved.jl")

  # Unstructured curved mesh
  include("test_examples_2d_unstructured_quad.jl")

  # P4estMesh
  include("test_examples_2d_p4est.jl")

  # FDSBP methods on the TreeMesh
  include("test_tree_2d_fdsbp.jl")
end

# Clean up afterwards: delete Trixi output directory
@test_nowarn rm(outdir, recursive=true)

end # 2D-Part3

end #module


import GPUArrays: allowscalar, @allowscalar

function _getindex(xs::CuArray{T}, i::Integer) where T
  buf = Mem.view(buffer(xs), (i-1)*sizeof(T))
  return Mem.download(T, buf)[1]
end

function _setindex!(xs::CuArray{T}, v::T, i::Integer) where T
  buf = Mem.view(buffer(xs), (i-1)*sizeof(T))
  Mem.upload!(buf, T[v])
end

## logical indexing

Base.getindex(xs::CuArray, bools::AbstractArray{Bool}) = getindex(xs, CuArray(bools))

function Base.getindex(xs::CuArray{T}, bools::CuArray{Bool}) where {T}
  bools = reshape(bools, prod(size(bools)))
  indices = cumsum(bools)  # unique indices for elements that are true

  n = _getindex(indices, length(indices))  # number that are true
  ys = CuArray{T}(undef, n)

  if n > 0
    num_threads = min(n, 256)
    num_blocks = ceil(Int, length(indices) / num_threads)

    function kernel(ys::CuDeviceArray{T}, xs::CuDeviceArray{T}, bools, indices)
        i = threadIdx().x + (blockIdx().x - 1) * blockDim().x

        if i <= length(xs) && bools[i]
            b = indices[i]   # new position
            ys[b] = xs[i]

        end

        return
    end

    @cuda blocks=num_blocks threads=num_threads kernel(ys, xs, bools, indices)
  end

  return ys
end


# test/runtests.jl

using LinearMapsAA
using Test: @test, detect_ambiguities

@test LinearMapAA(:test)

@test length(detect_ambiguities(LinearMapsAA)) == 0


# Functions for handling and triggering events

"""
    trigger(event::Event, state, domain=nothing; kwargs...)
    trigger(events::Vector{Event}, state, domain=nothing; kwargs...)

Trigger an `event` or list of `events` if their preconditions hold in the given
`state` and `domain`. See [`execute`](@ref) for keyword arguments.
"""
function trigger(event::Event, state::State,
                 domain::Union{Domain,Nothing}=nothing;
                 as_dist::Bool=false, as_diff::Bool=false)
    # Check whether preconditions hold
    sat, subst = satisfy([event.precond], state, domain; mode=:all)
    if !sat
        @debug "Precondition $(event.precond) does not hold."
        return as_diff ? no_effect(as_dist) : state
    end
    # Update state with effects for each matching substitution
    effects = [substitute(event.effect, s) for s in subst]
    if as_dist
        # Compute product distribution
        eff_dists = [effect_dist(e, state) for e in effects]
        diff = combine(eff_dists...)
    else
        # Accumulate effect diffs
        diff = combine([effect_diff(e, state) for e in effects]...)
    end
    # Return either the difference or the updated state
    return as_diff ? diff : update(state, diff)
end

function trigger(events::Vector{Event}, state::State,
                 domain::Union{Domain,Nothing}=nothing;
                 as_dist::Bool=false, as_diff::Bool=false)
    if length(events) == 0
        diff = as_dist ? DiffDist() : Diff()
    else
        diffs = [trigger(e, state, domain; as_dist=as_dist, as_diff=true)
                 for e in events]
        filter!(d -> !isnothing(d), diffs)
        diff = combine(diffs...)
    end
    # Return either the difference or the updated state
    return as_diff ? diff : update(state, diff)
end


@testset "cb" begin
    @test cb() == []

    f = @formula(y ~ cb(1))
    @test f.rhs.forig(f.rhs.args_parsed...) == [1]

    f = @formula(y ~ cb(1,2))
    @test f.rhs.forig(f.rhs.args_parsed...) == [1,2]

    f = @formula(y ~ cb(1,x))
    @test_throws ArgumentError f.rhs.forig(f.rhs.args_parsed...)
end

@testset "unpack" begin
    @test unpack(term(1)) == 1
    @test unpack(term(:x)) == :x
    @test unpack(term(:Unconditional)) == Unconditional()
    f = @formula(y ~ cb)
    @test unpack(f.rhs) == []
    f = @formula(y ~ cb(1,2))
    @test unpack(f.rhs) == [1,2]
end

@testset "exampledata" begin
    @test exampledata() == (:hrs, :nsw, :mpdta)
    hrs = exampledata("hrs")
    @test size(hrs) == (3280, 11)
    @test hrs[1] isa Vector
    @test size(exampledata(:nsw)) == (32834, 11)
    @test size(exampledata(:mpdta)) == (2500, 5)
end

@testset "checktable" begin
    @test_throws ArgumentError checktable(rand(3,3))
    @test_throws ArgumentError checktable([(a=1, b=2), (a=1, b=2)])
end


using SpinMC
using Test

include("UnitCell.jl")
include("InteractionMatrix.jl")
include("Lattice.jl")
include("Observables.jl")
include("Spin.jl")
include("MonteCarlo.jl")
include("IntegrationTest.jl")

using MPI
nprocs = 2
testdir = @__DIR__
@testset "$f" for f in ["Helper_MPI.jl", "IntegrationTest_MPI.jl"]
    mpiexec() do cmd
        run(`$cmd -n $nprocs $(Base.julia_cmd()) $(joinpath(testdir, f))`)
        @test true
    end
end


# This code is heavily based on the floating point sort code in Base

missings2end!(v::AbstractVector, o::Base.Sort.ForwardOrdering) = missings2right!(v,o)
missings2end!(v::AbstractVector, o::Base.Sort.ReverseOrdering) = nas2left!(v,o)
missings2end!(v::AbstractVector{Int}, o::Base.Order.Perm{O}) where {O<:Base.Order.ForwardOrdering} = missings2right!(v,o)
missings2end!(v::AbstractVector{Int}, o::Base.Order.Perm{O}) where {O<:Base.Order.ReverseOrdering} = nas2left!(v,o)

myismissing(o::Base.Order.Ordering, chunks, i::Int) = Base.unsafe_bitgetindex(chunks, i)

swap(o::Base.Order.DirectOrdering, data, i, j) = setindex!(data, data[i], j)
function swap(o::Base.Order.Perm, data, i, j)
    data[j], data[i] = data[i], data[j]
end

datachunks(o::Base.Order.Perm, v::AbstractVector{Int}) = (v, o.data.na.chunks)
datachunks(o::Base.Order.DirectOrdering, v::DataVector) = (v.data, v.na.chunks)

function nas2left!(v::Union{AbstractVector{Int}, DataVector}, o::Base.Order.Ordering, lo::Int=1, hi::Int=length(v))
    data, chunks = datachunks(o, v)

    i = lo
    @inbounds while i <= hi && myismissing(o, chunks, i)
        i += 1
    end
    j = i + 1
    @inbounds while j <= hi
        if myismissing(o, chunks, j)
            swap(o, data, i, j)
            i += 1
        end
        j += 1
    end
    if isa(o, Base.Order.DirectOrdering)
        v.na[lo:i-1] = true
        v.na[i:hi] = false
    end
    return i, hi
end

function missings2right!(v::Union{AbstractVector{Int}, DataVector}, o::Base.Order.Ordering, lo::Int=1, hi::Int=length(v))
    data, chunks = datachunks(o, v)

    i = hi
    @inbounds while lo <= i && myismissing(o, chunks, i)
        i -= 1
    end
    j = i - 1
    @inbounds while lo <= j
        if myismissing(o, chunks, j)
            swap(o, data, i, j)
            i -= 1
        end
        j -= 1
    end
    if isa(o, Base.Order.DirectOrdering)
        v.na[lo:i] = false
        v.na[i+1:hi] = true
    end
    return lo, i
end

function dasort!(v::DataVector, a::Base.Sort.Algorithm, o::Base.Order.DirectOrdering)
    lo, hi = missings2end!(v, o)
    sort!(v.data, lo, hi, a, o)
    v
end

function dapermsort!(v::AbstractVector{Int}, a::Base.Sort.Algorithm, o::Base.Order.Perm{O,T}) where {O<:Base.Order.DirectOrdering,T<:DataVector}
    lo, hi = missings2end!(v, o)
    sort!(v, lo, hi, a, Base.Order.Perm(o.order, o.data.data))
end

Base.sort!(v::DataVector, a::Base.Sort.Algorithm, o::Base.Order.DirectOrdering) = dasort!(v,a,o)
Base.sort!(v::Vector{Int}, a::Base.Sort.Algorithm, o::Base.Order.Perm{O,T}) where {O<:Base.Order.DirectOrdering,T<:DataVector} = dapermsort!(v,a,o)


using Reactive

import Base: IndexStyle, size, getindex, setindex!, _unsafe_getindex!, push!, similar

using Base.Broadcast: map_newindexer, _broadcast_eltype, broadcast_indices

struct ArrayNode{T, N, F, AT <: AbstractArray} <: AbstractArray{T, N}
    signal::Signal{AT}
    convert::F
end
const AbstractNode{T} = Union{ArrayNode{T}, Node{T}}

function Base.show(io::IO, node::AbstractNode{T}) where T
    print(io, "<Node: ")
    show(io, to_value(node))
    print(io, ">")
end

function children(x::Signal)
    filter(x-> x != nothing, (x-> x.value).(Reactive.nodes[Reactive.edges[x.id]]))
end
children(x::AbstractNode) = children(to_signal(x))

function disconnect!(x::Signal, toremove::Signal)
    x.parents = (Iterators.filter(x-> x != toremove, x.parents)...,)
end

disconnect_all_children!(x::AbstractNode) = disconnect_all_children!(to_signal(x))

function disconnect_all_children!(x::Signal)
    for child in children(x)
        disconnect!(child, x)
    end
end

function closenode!(x::AbstractNode)
    s = to_signal(x)
    disconnect_all_children!(s)
    close(s, false)
end

"""
Lift nodes of a scene by supplying fetching nodes via keys into the scene.
A tuple of symboles will be interpreted as `scene[:tupl_elem_1, :tupl_elem_2, ...]`
"""
function lift_node(f, scene::Scene, keys::Union{Symbol, Tuple}...)
    args = map(x-> getindex(scene, x), keys)
    lift_node(f, args...)
end

"""
Registers a callback to `nodes`, which calls function `f` whenever any node in `nodes` updates.
`f` will get the values of each `node` as an argument, so basically `f(to_value.(nodes))`.
Returns a new node which is the result of `f` applied to the updated `nodes`.
"""
function lift_node(f, nodes::AbstractNode...)
    args = to_signal.(nodes)
    s = foreach(f, args...)
    to_node(s)
end

function push!(x::AbstractNode, value)
    val = x.convert(to_value(value))
    s = to_signal(x)
    Reactive.set_value!(s, val) # make the value available already!
    push!(s, val)
    val
end

IndexStyle(x::ArrayNode{T, N, F, AT}) where {T, N, F, AT} = IndexStyle(AT)
Array(x::ArrayNode) = value(to_signal(x))
similar(A::ArrayNode) = to_node(similar(Array(A)))
similar(A::ArrayNode, ::Type{T}) where T = to_node(similar(Array(A), T))
similar(A::ArrayNode, dims::NTuple{N, Int}) where N = to_node(similar(Array(A), dims))
similar(A::ArrayNode, ::Type{T}, dims::NTuple{N, Int}) where {T, N} = to_node(similar(Array(A), T, dims))

size(x::ArrayNode) = size(value(to_signal(x)))

# Index Linear
function getindex(x::ArrayNode, i::Integer)
    Array(x)[i]
end
function setindex!(x::ArrayNode{T, N}, val, i::Integer) where {T, N}
    Array(x)[i] = val
    push!(to_signal(x), Array(x)) # update array
    val
end
function getindex(x::ArrayNode, i::Vararg{Int, N}) where N
    Array(x)[i...]
end

function setindex!(x::ArrayNode{T, N}, val, i::Vararg{Int, N}) where {T, N}
    Array(x)[i...] = val
    push!(to_signal(x), Array(x))
    val
end

function _unsafe_getindex!(dest::ArrayNode, src::AbstractArray, Is::Union{Real, AbstractArray}...)
    Array(dest)[Is...] = Array(src)
    push!(to_signal(dest), Array(dest))
    return dest
end

to_node(obj::AbstractNode) = obj
function to_node(obj::AbstractNode, f)
    to_node(map(f, to_signal(obj)), f)
end
to_node(obj, f = identity) = to_node(Signal(f(obj)), f)
to_node(obj::Signal, f = identity) = Node(map(f, obj), f)
function to_node(obj::Signal{AT}, f::F = identity) where {AT <: AbstractArray, F}
    A = value(obj)
    ArrayNode{eltype(A), ndims(A), F, AT}(obj, f)
end

to_value(obj::AbstractNode) = value(to_signal(obj))
to_value(obj) = obj

to_signal(obj::AbstractNode) = obj.signal
to_signal(obj) = obj

# Called by Base broadcasting mechanisms (in place and out of place)
Base.Broadcast._containertype(::Type{<:ArrayNode}) = ArrayNode
Base.Broadcast.promote_containertype(::Type{Any}, ::Type{ArrayNode}) = ArrayNode
Base.Broadcast.promote_containertype(::Type{ArrayNode}, ::Type{Any}) = ArrayNode

@inline function Base.Broadcast.broadcast_c!(f, ::Type{ArrayNode}, ::Type, C, A, Bs::Vararg{Any, N}) where N
    lift_node(to_node(C), to_node(A), to_node.(Bs)...) do c, a, bs...
        broadcast!(f, c, a, bs)
    end
    C
end

@inline function Base.Broadcast.broadcast_c(f, ::Type{ArrayNode}, A, Bs...)
    lift_node(to_node(A), to_node.(Bs)...) do a, bs...
        broadcast(f, a, bs...)
    end
end
function Base.append!(node::ArrayNode, values)
    v = to_value(node)
    append!(v, values)
    push!(node, v)
    node
end


# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder, Pkg

name = "QuantReg"
version = v"0.1.2"

# Collection of sources required to complete build
sources = [
    GitSource("https://github.com/fogarty-ben/QuantReg.jl-Builder.git", "7800e5d4e4c2dfd89e8d9076d764b11a51f55081")
]

# Bash recipe for building across all platforms
script = raw"""
cd $WORKSPACE/srcdir
cd QuantReg.jl-Builder/
mkdir -p ${libdir}
gfortran -fPIC -shared -std=legacy rqbr.f -o ${libdir}/librqbr.${dlext}
gfortran -fPIC -shared -std=legacy rqfnb.f -o ${libdir}/librqfnb.${dlext}
install_license LICENSE
exit
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = supported_platforms()
platforms = expand_gfortran_versions(platforms)

# The products that we will ensure are always built
products = [
    LibraryProduct("librqfnb", :librqfnb),
    LibraryProduct("librqbr", :librqbr)
]

# Dependencies that must be installed before this package can be built
dependencies = [
    Dependency(PackageSpec(name="CompilerSupportLibraries_jll", uuid="e66e0078-7015-5450-92f7-15fbd957f2ae"))
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies)


abstract type Triangle{T <: Real} end

struct Incremental{T} <: Triangle{T}
    claims::Matrix{T}
    function Incremental(claims::Matrix{T}) where T <: Real
        if size(claims)[1] != size(claims)[2]
            error("The number of rows and the number of columns must be the same.")
        elseif siniestros_bajo_diagonal(claims::Matrix{T})
            error("Having claims under the diagonal is not supported.")
        end
        new{T}(claims)
    end
end

struct Accumulated{T} <: Triangle{T}
    claims::Matrix{T}
    function Accumulated(claims::Matrix{T}) where T <: Real
        if size(claims)[1] != size(claims)[2]
            error("The number of rows and the number of columns must be the same.")
        elseif siniestros_bajo_diagonal(claims::Matrix{T})
            error("NHaving claims under the diagonal is not supported.")
        end
        new{T}(claims)
    end
end

struct IncrementalCompleted{T} <: Triangle{T}
    claims::Matrix{T}
    function IncrementalCompleted(claims::Matrix{T}) where T <: Real
        if size(claims)[1] != size(claims)[2]
            error("The number of rows and the number of columns must be the same.")
        end
        new{T}(claims)
    end
end

struct AccumulatedCompleted{T} <: Triangle{T}
    claims::Matrix{T}
    function AccumulatedCompleted(claims::Matrix{T}) where T <: Real
        if size(claims)[1] != size(claims)[2]
            error("The number of rows and the number of columns must be the same.")
        end
        new{T}(claims)
    end
end


using Genie, Genie.Renderer.Html, Stipple, StipplePlotly

Genie.config.log_requests = false

xx = ["start", "1d", "1w", "2w", "6w"]
spaces = "       "

# Data:

y1 = [98.5, 96.0, 95.1, 94.4, 94.0]
pb1 = PlotData(
    x = xx, y = y1, xaxis="x", yaxis="y",
    error_y = ErrorBar(3 .* ones(Float64,size(y1)); color="rgba(0,0,0,0.6)"),
    plot = StipplePlotly.Charts.PLOT_TYPE_BAR, name = "Product 1",
    text = spaces .* string.(y1), textposition="auto"
)

y2 = [99.5, 94.0, 93.1, 92.4, 92.0]
pb2 = PlotData(
    x = xx, y = y2, xaxis="x", yaxis="y",
    error_y = ErrorBar(3 .* ones(Float64,size(y1)); color="rgba(0,0,0,0.6)"),
    plot = StipplePlotly.Charts.PLOT_TYPE_BAR, name = "Product 2",
    text = spaces .* string.(y2), textposition="auto"
)

y3 = [97.5, 92.0, 92.1, 91.4, 91.0]
pb3 = PlotData(
    x = xx, y = y3, xaxis="x", yaxis="y",
    error_y = ErrorBar(3 .* ones(Float64,size(y1)); color="rgba(0,0,0,0.6)"),
    plot = StipplePlotly.Charts.PLOT_TYPE_BAR, name = "Product 3",
    text = spaces .* string.(y3), textposition="auto"
)

y4 = [92.5, 88.1, 87.1, 85.9, 84.0]
pb4 = PlotData(
    x = xx, y = y4, xaxis="x", yaxis="y",
    error_y = ErrorBar(3 .* ones(Float64,size(y1)); color="rgba(0,0,0,0.6)"),
    plot = StipplePlotly.Charts.PLOT_TYPE_BAR, name = "Product 4",
    text = spaces .* string.(y4), textposition="auto"
)

plotdata = [pb1, pb2, pb3, pb4]

layout = PlotLayout(barmode="group", font=Font(16),
    title = PlotLayoutTitle(text="Product comparison", font=Font(24)),
    xaxis = [PlotLayoutAxis(xy = "x", index = 1, title="duration", font=Font(size=24), showline = true, zeroline = false)],
    yaxis = [PlotLayoutAxis(xy = "y", index = 1, ticks = "outside", title="ratio (%)", font=Font(size=24), autorange=false, range=[70.0,100.0], showline = true, zeroline = false)]
)

Base.@kwdef mutable struct Model <: ReactiveModel
  data::R{Vector{PlotData}} = plotdata, READONLY
  layout::R{PlotLayout} = layout, READONLY
  config::R{PlotConfig} = PlotConfig(), READONLY
end

model = Stipple.init(Model())

function ui()
  page(
    vm(model), class="container", [
        plot(:data, layout = :layout, config = :config)
    ]
  ) |> html
end

route("/", ui)

up()


################################################################################
#
#	ABSTRACT TYPE
#
#   ReciprocalPath{P}
#   --> P is the reciprocal point type (<: AbstractReciprocalPoint{D})
#       --> D is the dimension of embedding space
#
#   FILE CONTAINS
#       - abstract type definition
#       - interface definition
#       - TODO interface testing
#
################################################################################

################################################################################
#
#   ABSTRACT TYPE DEFINITION
#
################################################################################
abstract type AbstractReciprocalPath{P <: AbstractReciprocalPoint{D} where {D}} <: AbstractArray{P,1} end

# export the type
export AbstractReciprocalPath

# Array functionality for path type

# Indexing
function Base.IndexStyle(::Type{<:AbstractReciprocalPath})
    return IndexLinear()
end

# size
function Base.size(path::P) where {RP, P<:AbstractReciprocalPath{RP}}
    return size(points(path))
end

# getindex
function Base.getindex(path::P, i::Int64) where {RP, P<:AbstractReciprocalPath{RP}}
    return getindex(points(path), i)
end

# setindex
function Base.setindex!(path::P, v, i::Int64) where {RP, P<:AbstractReciprocalPath{RP}}
    return setindex!(points(path), v, i)
end

################################################################################
#
#	INTERFACING / ACCESSING RECIPROCAL PATHS
#	(functions have to be overwritten by concrete types)
#
################################################################################

# default constructor interface
# used for creation of new reciprocal paths
function newReciprocalPath(
            ::Type{RPA},
            points :: Vector{<:RPO}
        ) :: RPA where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'newReciprocalPath' for concrete reciprocal path type " *
            string(RPA) * " with reciprocal point type " * string(RPO) *
            " in " * string(D) * " spatial dimensions" )
end

# export the newUnitcell interface
export newReciprocalPath

# access the list of points
function points(
            p :: RPA
        ) :: Vector{RPO} where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'points' for concrete reciprocal path type " * string(RPA) )
end
# set the list of points
function points!(
            p :: RPA,
            points :: Vector{<:RPO}
        ) where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'points!' for concrete reciprocal path type " * string(RPA) )
end

# export access to points
export points, points!

function saveReciprocalPath(
        rp :: RPA,
        fn :: AbstractString,
        group :: AbstractString = "reciprocal_path"
        ;
        append :: Bool = false
    ) where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

end

function loadReciprocalPath(
        ::Type{RPA},
        fn :: AbstractString,
        group :: AbstractString = "reciprocal_path"
    ) where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

end

# export access to points
export saveReciprocalPath, loadReciprocalPath

# some more beauty interface
# builds on interface defined above but can also be overwritten

# number of reciprocal points
function numPoints(
            p :: RPA
        ) :: Int64 where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

    # return length of the point array that the reciprocal path type implements
    return length(points(p))
end

# export the function
export numPoints

# access a specific reciprocal point
function point(
            path  :: RPA,
            index :: Int64
        ) :: RPO where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

    # return the respective reciprocal point
    return points(path)[index]
end

# export the function
export point

# splatted constructor interface
# used for creation of new reciprocal paths based on number of points
# (does not have to be overwritten)
function newReciprocalPath(
            ::Type{RPA},
            points :: RPO ...
        ) :: RPA where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

    # return the usual constructor
    return newReciprocalPath(RPA, RPO[p for p in points])
end
# set the list of points with splatted input
function points!(
            p :: RPA,
            points :: RPO ...
        ) where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}

    # return the fitting function
    return points!(p, RPO[p for p in points])
end

################################################################################
#
#	OVERWRITING BASE.SHOW FOR RECIPROCAL PATHS
#
################################################################################
function show(io::IO, p::RPA) where {D, RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO}}
    print(io, "Reciprocal Path of type ", string(RPA), " including ", numPoints(p), " points.\n")
    print(io, "1) ", point(p,1))
    for i in 2:numPoints(p)
        print("\n    |\n")
        print(io, i, ") ", point(p,i))
    end
end

################################################################################
#
#	INTERFACING FOR BUILDING RECIPROCAL PATHS
#
################################################################################

# CONSTRUCT A PATH BY PASSED POINTS (as Symbols)
function getReciprocalPath(
            ::Type{RPA},
            unitcell :: U,
            points :: Symbol ...
        ) :: RPA where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO},S,B,U<:AbstractUnitcell{S,B}}

    # pass to the specific function
    return newReciprocalPath(RPA, [getReciprocalPoint(RPO, unitcell, p) for p in points])
end
function getReciprocalPath(
            ::Type{RPA},
            unitcell :: U,
            points :: Tuple{Symbol,Int64} ...
        ) :: RPA where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO},S,B,U<:AbstractUnitcell{S,B}}

    # pass to the specific function
    return newReciprocalPath(RPA, [getReciprocalPoint(RPO, unitcell, p[1],p[2]) for p in points])
end
function getReciprocalPath(
            ::Type{RPA},
            unitcell :: U,
            points :: Union{Symbol, Tuple{Symbol,Int64}} ...
        ) :: RPA where {D,RPO<:AbstractReciprocalPoint{D}, RPA<:AbstractReciprocalPath{RPO},S,B,U<:AbstractUnitcell{S,B}}

    # pass to the specific function
    return newReciprocalPath(RPA, [getReciprocalPoint(RPO, unitcell, p) for p in points])
end

function getReciprocalPath(
            unitcell :: U,
            points :: Symbol ...
        ) :: ReciprocalPath{ReciprocalPoint{D}} where {LS,D,S<:AbstractSite{LS,D},B,U<:AbstractUnitcell{S,B}}

    # pass to the specific function
    return newReciprocalPath(ReciprocalPath{ReciprocalPoint{D}}, [getReciprocalPoint(ReciprocalPoint{D}, unitcell, p) for p in points])
end
function getReciprocalPath(
            unitcell :: U,
            points :: Tuple{Symbol,Int64} ...
        ) :: ReciprocalPath{ReciprocalPoint{D}} where {LS,D,S<:AbstractSite{LS,D},B,U<:AbstractUnitcell{S,B}}

    # pass to the specific function
    return newReciprocalPath(ReciprocalPath{ReciprocalPoint{D}}, [getReciprocalPoint(ReciprocalPoint{D}, unitcell, p[1],p[2]) for p in points])
end
function getReciprocalPath(
            unitcell :: U,
            points :: Union{Symbol, Tuple{Symbol,Int64}} ...
        ) :: ReciprocalPath{ReciprocalPoint{D}} where {LS,D,S<:AbstractSite{LS,D},B,U<:AbstractUnitcell{S,B}}

    # pass to the specific function
    return newReciprocalPath(ReciprocalPath{ReciprocalPoint{D}}, [getReciprocalPoint(ReciprocalPoint{D}, unitcell, p) for p in points])
end

# export the reciprocal path
export getReciprocalPath


export Freq

"""
    Freq(min, max, [ndec])
    Freq(<keyword arguments>)

`Freq` objects `show` a FastHenry .freq command.

Keyword arguments are `min`, `max`, `ndec`.
"""
struct Freq <: Element
  min :: Float64
  max :: Float64
  ndec :: Float64
  function Freq(min, max, ndec)
    if min>max
      throw(ArgumentError("max must be >= min"))
    end
    if min<0 || max<0 || ndec<0
      throw(ArgumentError("all parameters must be >0"))
    end
    if isnan(max)
      throw(ArgumentError("must specify max frequency"))
    end
    return new(min,max,ndec)
  end
end

Freq(min,max) = Freq(min,max,NaN)
Freq(;min=0, max=NaN, ndec=NaN) = Freq(min,max,ndec)


abstract type AbstractValEdge{V <: Integer, E_VALS} <: AbstractEdge{V} end

"""
    ValEdge{V, E_VALS} <: AbstractValEdge{V}

A datastructure representing an undirected edge with multiple values.
"""
struct ValEdge{V<:Integer, E_VALS} <: AbstractValEdge{V, E_VALS}
    src::V
    dst::V
    vals::E_VALS

    function ValEdge(src::V, dst::V, vals::E_VALS) where {V, E_VALS}
        src, dst = minmax(src, dst) # TODO maybe use a branchless operator
        return new{V, E_VALS}(src, dst, vals)
    end
end

# TODO update docstring and ValDiEdge
"""
    ValEdge(s, d, v)
Create a `ValEdge` with source `s`, destination `d` and value `v`.
# Examples
```
julia> e = ValEdge(4, 2, 'A')
Edge 4 => 2 with value 'A'
```
"""
function ValEdge end

"""
    ValDiEdge{V, E_VALS} <: AbstractValEdge{V}

A datastructure representing a directed edge with values.
"""
struct ValDiEdge{V<:Integer, E_VALS} <: AbstractValEdge{V, E_VALS}
    src::V
    dst::V
    vals::E_VALS

    function ValDiEdge(src::V, dst::V, vals::E_VALS) where {V, E_VALS}
        return new{V, E_VALS}(src, dst, vals)
    end
end

src(e::AbstractValEdge) = e.src
dst(e::AbstractValEdge) = e.dst

"""
    vals(e::ValEdge)
Returns the value attached to the edge `e`.
# Examples
```
julia> g = EdgeValGraph(3, String)

julia> add_edge!(g, 2, 3, ("xyz",))

julia> first(edges(g)) |> vals
"xyz"
```
"""
vals(e::AbstractValEdge) = e.vals

# TODO Maybe that should be declared somewhere else
const SingleValTuple{T} = Union{Tuple{T}, NamedTuple{S, Tuple{T}} where {S}}

val(e::AbstractValEdge; key::Union{Integer, Symbol, NoKey}=nokey) =
    _val(e, key)

_val(e::AbstractValEdge{V, E_VAL}, ::NoKey) where {V, E_VAL <: SingleValTuple} =
    e.vals[1]

_val(e::AbstractValEdge, key::Union{Integer, Symbol}) = e.vals[key]

reverse(e::ValEdge) = e
reverse(e::ValDiEdge) = ValDiEdge(dst(e), src(e), vals(e))

is_directed(::Type{<:ValEdge}) = false
is_directed(::Type{<:ValDiEdge}) = true
is_directed(e::AbstractValEdge) = is_directed(typeof(e))

function show(io::IO, e::AbstractValEdge)
    isdir = is_directed(e)
    e_keys = keys(vals(e))
    has_symbol_keys = eltype(e_keys) === Symbol

    print(io, isdir ? "ValDiEdge" : "ValEdge")
    arrow = isdir ? "->" : "--"
    print(io, " $(src(e)) $arrow $(dst(e))")

    if length(e_keys) == 1
        print(io, " with value " * (has_symbol_keys ? "$(e_keys[1]) = $(val(e))" :
                                                      "$(val(e))"))
    elseif length(e_keys) > 1
        print(io, " with values $(vals(e))")
    end

    println(io)
end


@time @testset "utils" begin
    @testset "check_sizes" begin
        a = Octavian.StaticInt{1}()
        b = Octavian.StaticInt{2}()
        @test Octavian._select(a, a) === a
        @test Octavian._select(a, 1) === a
        @test Octavian._select(1, a) === a
        @test Octavian._select(1, 1) === 1
        @test_throws AssertionError Octavian.matmul_sizes(rand(3,2), rand(3,4), rand(5,2))
        @test_throws AssertionError Octavian.matmul_sizes(rand(3,2), rand(3,4), rand(4,5))
        @test_throws AssertionError Octavian.matmul_sizes(rand(3,2), rand(5,4), rand(4,2))
    end
end


export SimpleDeterministicCompositeModel

using MLJBase

"""
    SimpleDeterministicCompositeModel(;regressor=ConstantRegressor(),
                              transformer=FeatureSelector())

Construct a composite model consisting of a transformer
(`Unsupervised` model) followed by a `Deterministic` model. Mainly
intended for internal testing .

"""
mutable struct SimpleDeterministicCompositeModel{L<:Deterministic,
                             T<:Unsupervised} <: DeterministicComposite
    model::L
    transformer::T

end

function SimpleDeterministicCompositeModel(;
                      model=DeterministicConstantRegressor(),
                      transformer=FeatureSelector())

    composite =  SimpleDeterministicCompositeModel(model, transformer)

    message = MLJBase.clean!(composite)
    isempty(message) || @warn message

    return composite

end

MLJBase.is_wrapper(::Type{<:SimpleDeterministicCompositeModel}) = true

function MLJBase.fit(composite::SimpleDeterministicCompositeModel,
                     verbosity::Integer, Xtrain, ytrain)
    X = source(Xtrain) # instantiates a source node
    y = source(ytrain)

    t = machine(composite.transformer, X)
    Xt = transform(t, X)

    l = machine(composite.model, Xt, y)
    yhat = predict(l, Xt)

    mach = machine(Deterministic(), X, y; predict=yhat)
    fit!(mach, verbosity=verbosity)

    return mach()
end

MLJBase.load_path(::Type{<:SimpleDeterministicCompositeModel}) =
    "MLJBase.SimpleDeterministicCompositeModel"
MLJBase.package_uuid(::Type{<:SimpleDeterministicCompositeModel}) = ""
MLJBase.package_url(::Type{<:SimpleDeterministicCompositeModel}) =
    "https://github.com/alan-turing-institute/MLJBase.jl"
MLJBase.is_pure_julia(::Type{<:SimpleDeterministicCompositeModel}) = true
MLJBase.input_scitype(::Type{<:SimpleDeterministicCompositeModel{L,T}}) where {L,T} =
    MLJBase.input_scitype(T)
MLJBase.target_scitype(::Type{<:SimpleDeterministicCompositeModel{L,T}}) where {L,T} =
    MLJBase.target_scitype(L)