#=##############################################################################
# DESCRIPTION
    Class of wind turbine geometry.
# AUTHORSHIP
  * Author    : Eduardo J Alvarez
  * Email     : Edo.AlvarezR@gmail.com
  * Created   : Jul 2018
  * License   : MIT License
=###############################################################################

################################################################################
# WIND FARM
################################################################################

# import JLD
# import CSV
# import Dierckx
# import PyPlot
# using PyCall
#
# const plt = PyPlot

# ------------ FLOW MODULES ----------------------------------------------------
# https://github.com/byuflowlab/GeometricTools.jl
# import GeometricTools
# const gt = GeometricTools

# include("WFVisual_turbine.jl")

using PyCall

"""
`generate_layout(D::Array{T,1}, H::Array{T,1}, N::Array{Int64,1},
                          x::Array{T,1}, y::Array{T,1}, z::Array{T,1},
                          glob_yaw::Array{T,1};
                          # TURBINE GEOMETRY OPTIONS
                          hub::Array{String,1}=String[],
                          tower::Array{String,1}=String[],
                          blade::Array{String,1}=String[],
                          data_path::String=def_data_path,
                          # FILE OPTIONS
                          save_path=nothing, file_name="windfarm",
                          paraview=true
                         ) where{T<:Real}`

  Generates and returns a wind farm layout consisting of a MultiGrid object
containing every turbine at the indicated position `x,y,z`, and the given
geometry `D,H,N` and orientation `glob_yaw`.

  **Arguments**
  * `D::Array{Float64,1}`         : Rotor diameter of every turbine.
  * `H::Array{Float64,1}`         : Tower height of every turbine.
  * `N::Array{Float64,1}`         : Number of blades of every turbine.
  * `x::Array{Float64,1}`         : x-position of the every turbine base.
  * `y::Array{Float64,1}`         : y-position of the every turbine base.
  * `z::Array{Float64,1}`         : z-position of the every turbine base.
  * `glob_yaw::Array{Float64,1}`  : Angle of the plane of rotation relative to
                                    the x-axis of the global coordinate system
                                    IN DEGREES.

"""
function generate_layout(D::Array{T,1}, H::Array{T,1}, N::Array{Int64,1},
                          x::Array{T,1}, y::Array{T,1}, z::Array{T,1},
                          glob_yaw::Array{T,1};
                          # TURBINE GEOMETRY OPTIONS
                          hub::Array{String,1}=String[],
                          tower::Array{String,1}=String[],
                          blade::Array{String,1}=String[],
                          data_path::String=def_data_path,
                          # FILE OPTIONS
                          save_path=nothing, file_name="windfarm",
                          paraview=true
                         ) where{T<:Real}

  nturbines = size(D, 1)        # Number of turbines

  # Default turbine geometry
  if size(hub,1)==0
    hub = ["hub" for i in 1:nturbines]
  end
  if size(tower,1)==0
    tower = ["tower1" for i in 1:nturbines]
  end
  if size(blade,1)==0
    blade = ["NREL5MW" for i in 1:nturbines]
  end

  # Generates layout
  windfarm = gt.MultiGrid(3)

  for i in 1:nturbines
    # Generate wind turbine geometry
    turbine = generate_windturbine(D[i]/2, H[i], blade[i], hub[i], tower[i];
                                    nblades=N[i], data_path=data_path,
                                    save_path=nothing)

    # Places it at the location and orientation
    Oaxis = gt.rotation_matrix(glob_yaw[i], 0, 0)
    gt.lintransform!(turbine, Oaxis, [x[i], y[i], z[i]])

    # Adds it to the farm
    gt.addgrid(windfarm, "turbine$i", turbine)
  end

  if save_path!=nothing
    gt.save(windfarm, file_name; path=save_path)

    if paraview
      strn = ""
      for i in 1:nturbines
        strn *= file_name*"_turbine$(i)_tower.vtk;"
        strn *= file_name*"_turbine$(i)_rotor_hub.vtk;"
        for j in 1:N[i]
          strn *= file_name*"_turbine$(i)_rotor_blade$(j).vtk;"
        end
      end

      run(`paraview --data=$save_path/$strn`)
    end

  end

  return windfarm::gt.MultiGrid
end

"""
`generate_perimetergrid(perimeter::Array{Array{T, 1}, 1},
                                  NDIVSx, NDIVSy, NDIVSz;
                                  z_min::Real=0, z_max::Real=0,
                                  # SPLINE OPTIONS
                                  verify_spline::Bool=true,
                                  spl_s=0.001, spl_k="automatic",
                                  # FILE OPTIONS
                                  save_path=nothing, file_name="perimeter",
                                  paraview=true
                                )`

  Generates the perimeter grid with `perimeter` the array of points of the
  contour (must be a closed contour), and `NDIVS_` the number of cells in
  each parametric dimension (give it `NDIVSz=0` for a flat surface, otherwise
  it'll generate a volumetric grid between `z_min` and `z_max`).
"""
function generate_perimetergrid(perimeter::Array{Array{T, 1}, 1},
                                  NDIVSx, NDIVSy, NDIVSz;
                                  z_min::Real=0, z_max::Real=0,
                                  # SPLINE OPTIONS
                                  verify_spline::Bool=true,
                                  spl_s=0.001, spl_k="automatic", atol=1.0,
                                  # FILE OPTIONS
                                  save_path=nothing, file_name="perimeter",
                                  paraview=true
                                ) where{T<:Real}
  # Error cases
  multidiscrtype = Array{Tuple{Float64,Int64,Float64,Bool},1}
  if typeof(NDIVSx)==Int64
    nz = NDIVSz
  elseif typeof(NDIVSz)==multidiscrtype
    nz = 0
    for sec in NDIVSz
      nz += sec[2]
    end
  else
    error("Expected `NDIVSz` to be type $(Int64) or $MultiDiscrType,"*
            " got $(typeof(NDIVSz)).")
  end

  # --------- REPARAMETERIZES THE PERIMETER ---------------------------
  org_x = [p[1] for p in perimeter]
  org_y = [p[2] for p in perimeter]
  # Separate upper and lower sides to make the contour injective in x
  upper, lower = gt.splitcontour(org_x, org_y)

  # # Parameterize both sides independently
  # fun_upper = gt.parameterize(upper[1], upper[2], zeros(upper[1]); inj_var=1,
  #                                                     s=spl_s, kspl=spl_k)
  # fun_lower = gt.parameterize(lower[1], lower[2], zeros(lower[1]); inj_var=1,
  #                                                     s=spl_s, kspl=spl_k)
  # # Discretizes both sides
  # if NDIVSx==multidiscrtype
  #   new_upper = gt.multidiscretize(fun_upper, 0, 1, NDIVSx)
  #   new_lower = gt.multidiscretize(fun_lower, 0, 1, NDIVSx)
  # else
  #   new_upper = gt.discretize(fun_upper, 0, 1, NDIVSx, 1.0)
  #   new_lower = gt.discretize(fun_lower, 0, 1, NDIVSx, 1.0)
  # end

  splt_up = Int(ceil((size(upper[1],1)/2)))
  splt_low = Int(ceil((size(lower[1],1)/2)))

  # Splits the perimeter into four faces
  upper1 = [[x for x in upper[1][1:splt_up]], [y for y in upper[2][1:splt_up]]]
  upper2 = [[x for x in upper[1][splt_up:end]], [y for y in upper[2][splt_up:end]]]
  lower1 = [[x for x in lower[1][1:splt_low]], [y for y in lower[2][1:splt_low]]]
  lower2 = [[x for x in lower[1][splt_low:end]], [y for y in lower[2][splt_low:end]]]

  # Parameterize both sides independently
  fun_upper1 = gt.parameterize(upper1[1], upper1[2], zeros(upper1[1]); inj_var=1,
                                              s=spl_s, kspl=spl_k, atol=atol)
  fun_upper2 = gt.parameterize(upper2[1], upper2[2], zeros(upper2[1]); inj_var=1,
                                              s=spl_s, kspl=spl_k, atol=atol)
  fun_lower1 = gt.parameterize(lower1[1], lower1[2], zeros(lower1[1]); inj_var=1,
                                              s=spl_s, kspl=spl_k, atol=atol)
  fun_lower2 = gt.parameterize(lower2[1], lower2[2], zeros(lower2[1]); inj_var=1,
                                              s=spl_s, kspl=spl_k, atol=atol)
  # Discretizes both sides
  if NDIVSx==multidiscrtype
    new_upper1 = gt.multidiscretize(fun_upper1, 0, 1, NDIVSx)
    new_upper2 = gt.multidiscretize(fun_upper2, 0, 1, NDIVSy)
    new_lower1 = gt.multidiscretize(fun_lower1, 0, 1, NDIVSy)
    new_lower2 = gt.multidiscretize(fun_lower2, 0, 1, NDIVSx)
  else
    new_upper1 = gt.discretize(fun_upper1, 0, 1, NDIVSx, 1.0)
    new_upper2 = gt.discretize(fun_upper2, 0, 1, NDIVSy, 1.0)
    new_lower1 = gt.discretize(fun_lower1, 0, 1, NDIVSy, 1.0)
    new_lower2 = gt.discretize(fun_lower2, 0, 1, NDIVSx, 1.0)
  end

  # ----------------- SPLINE VERIFICATION --------------------------------------

  if verify_spline
  #   new_points = vcat(reverse(new_upper), new_lower)
    # new_x = [p[1] for p in new_points]
    # new_y = [p[2] for p in new_points]
    plt.plot(org_x, org_y, "--^k", label="Original", alpha=0.5)
    # plt.plot(new_x, new_y, ":.b", label="Parameterized")

    plt.plot(upper1[1], upper1[2], "*b", label="upper1", alpha=0.75)
    plt.plot(upper2[1], upper2[2], "^b", label="upper2", alpha=0.75)
    plt.plot(lower1[1], lower1[2], "*r", label="lower1", alpha=0.75)
    plt.plot(lower2[1], lower2[2], "^r", label="lower2", alpha=0.75)

    new_points = vcat(new_upper1, new_upper2)
    new_x = [p[1] for p in new_points]
    new_y = [p[2] for p in new_points]
    plt.plot(new_x, new_y, ":.b", label="Parameterized Upper", alpha=0.75)
    new_points = vcat(new_lower1, new_lower2)
    new_x = [p[1] for p in new_points]
    new_y = [p[2] for p in new_points]
    plt.plot(new_x, new_y, ":.r", label="Parameterized Lower", alpha=0.75)
    plt.xlabel(plt.L"x")
    plt.ylabel(plt.L"y")
    plt.legend(loc="best")
    plt.grid(true, color="0.8", linestyle="--")
  end

  # --------- GRIDS THE INSIDE OF THE PERIMETER ---------------------
  # Parametric grid
  P_min = zeros(3)
  P_max = [1, 1, 1*(nz!=0)]
  param_grid = gt.Grid(P_min, P_max, [NDIVSx, NDIVSy, NDIVSz])

  # function my_space_transform(X, ind)
  #     i = ind[1]                      # Arc length point
  #     w = X[2]                        # Weight
  #     z = z_min + X[3]*(z_max-z_min)  # z-position
  #
  #     Y = new_lower[i] + w*(new_upper[i]-new_lower[i])
  #     Y[3] = z
  #
  #     return Y
  # end

  rev_new_upper1 = reverse(new_upper1) # Left edge
  rev_new_upper2 = reverse(new_upper2) # Upper edge
  rev_new_lower1 = reverse(new_lower1) # Lower edge
  rev_new_lower2 = reverse(new_lower2) # Right edge

  function my_space_transform(X, ind)
      beta = 1e-2*pi/2                 # Warping parameter

      z = z_min + X[3]*(z_max-z_min)   # z-position

      xw = X[2]                        # x weight
      yw = X[1]                        # y weight

      # Linearly interpolate the edges
      x = new_upper1[ind[1]]*xw + new_lower2[ind[1]]*(1-xw) # Left-right edges
      y = rev_new_upper2[ind[2]]*yw + rev_new_lower1[ind[2]]*(1-yw) # Upper-lower edges
      # Y = x + y

      # A clever way of merging the two interpolations
      # First weight
      yw = abs(X[1]-0.5)/0.5
      yw = tan(yw*pi/2 - beta)/tan(pi/2 - beta)
      # Second weight
      xw = abs(X[2]-0.5)/0.5
      xw = tan(xw*pi/2 - beta)/tan(pi/2 - beta)
      # Merged weight
      w = ( (1-xw) + yw ) /2
      # Merged point
      Y = x*(1-w) + y*w

      Y[3] = z

      return Y
  end

  # Applies the space transformation to the parametric grid
  gt.transform3!(param_grid, my_space_transform)

  if save_path!=nothing
    gt.save(param_grid, file_name; path=save_path)

    if paraview
      strn = file_name*".vtk"
      run(`paraview --data=$save_path/$strn`)
    end

  end

  return param_grid::gt.Grid
end

function generate_windfarm(D::Array{T,1}, H::Array{T,1}, N::Array{Int64,1},
                          x::Array{T,1}, y::Array{T,1}, z::Array{T,1},
                          glob_yaw::Array{T,1}, perimeter::Array{T, 2},
                          wake; optargs...
                         ) where{T<:Real}

    _perimeter = M2arr(perimeter)
    return generate_windfarm(D, H, N, x, y, z, glob_yaw, _perimeter, wake;
                                                                    optargs...)
end

function generate_windfarm(D::Array{T,1}, H::Array{T,1}, N::Array{Int64,1},
                          x::Array{T,1}, y::Array{T,1}, z::Array{T,1},
                          glob_yaw::Array{T,1}, perimeter::Array{Array{T, 1}},
                          wake;
                          # TURBINE GEOMETRY OPTIONS
                          hub::Array{String,1}=String[],
                          tower::Array{String,1}=String[],
                          blade::Array{String,1}=String[],
                          data_path::String=def_data_path,
                          # PERIMETER AND FLUID DOMAIN OPTIONS
                          NDIVSx=50, NDIVSy=50, NDIVSz=50,
                          z_min="automatic", z_max="automatic",
                          z_off=0.0,
                          fdom_perimeter::Union{Array{Array{T, 1}}, Void}=nothing,
                          # PERIMETER SPLINE OPTIONS
                          verify_spline::Bool=true,
                          spl_s=0.001, spl_k="automatic",
                          # FILE OPTIONS
                          save_path=nothing, file_name="mywindfarm",
                          paraview=true, num=nothing
                         ) where{T<:Real}

  windfarm = generate_layout(D, H, N, x, y, z, glob_yaw;
                                  hub=hub, tower=tower, blade=blade,
                                  data_path=data_path, save_path=nothing)

  perimeter_grid = generate_perimetergrid(perimeter, NDIVSx, NDIVSy, 0;
                                     z_min=z_off, z_max=z_off,
                                      verify_spline=verify_spline, spl_s=spl_s,
                                      spl_k=spl_k, save_path=nothing)

  _zmin = z_min=="automatic" ? 0 : z_min
  _zmax = z_max=="automatic" ? maximum(H) + 1.25*maximum(D)/2 : z_max
  fdom = generate_perimetergrid(fdom_perimeter != nothing ? fdom_perimeter : perimeter,
                                    NDIVSx, NDIVSy, NDIVSz;
                                    z_min=_zmin+z_off, z_max=_zmax+z_off,
                                    verify_spline=false,
                                    spl_s=spl_s, spl_k=spl_k,
                                    save_path=nothing,
                                  )

  gt.calculate_field(fdom, wake, "wake", "vector", "node")

  if save_path!=nothing
    gt.save(windfarm, file_name; path=save_path, num=num)
    gt.save(perimeter_grid, file_name*"_perimeter"; path=save_path, num=num)
    gt.save(fdom, file_name*"_fdom"; path=save_path, num=num)

    if paraview
      strn = ""
      for i in 1:size(D,1)
        strn *= file_name*"_turbine$(i)_tower.vtk;"
        strn *= file_name*"_turbine$(i)_rotor_hub.vtk;"
        for j in 1:N[i]
          strn *= file_name*"_turbine$(i)_rotor_blade$(j).vtk;"
        end
      end

      strn *= file_name*"_perimeter.vtk;"
      strn *= file_name*"_fdom.vtk;"

      run(`paraview --data=$save_path/$strn`)
    end

  end

  return (windfarm, perimeter, fdom)
end

function M2arr(M::Array{T, 2}) where{T<:Real}
  return [ [p for p in M[i, :]] for i in 1:size(M,1) ]
end

# ------------ END OF WIND FARM ------------------------------------------------


import RLCore

using Plots

import ArcadeLearningEnvironment
const ALE = ArcadeLearningEnvironment

"""
    Atari

An interface adapted from https://github.com/JuliaML/AtariAlgos.jl/blob/master/src/AtariAlgos.jl with a backend
implemented by https://github.com/JuliaReinforcementLearning/ArcadeLearningEnvironment.jl. Because we want to have
some better fidelity with settings, reimplementing is easier than writting a wrapper around a wrapper around a wrapper...
"""
mutable struct Atari{S} <: RLCore.AbstractEnvironment
    ale::ALE.ALEPtr
    lives::Int
    died::Bool
    reward::Float64
    score::Float64
    nframes::Int
    width::Int
    height::Int
    rawscreen::Vector{Cuchar}  # raw screen data from the most recent frame
    state::Array{S, 3}  # the game state... raw screen data converted to Float64
    screen::Matrix{RGB{Float64}}

    function Atari{S}(gamename::AbstractString) where {S<:Number}
        ale = ALE.ALE_new()
        ALE.loadROM(ale, gamename)
        w = ALE.getScreenWidth(ale)
        h = ALE.getScreenHeight(ale)
        rawscreen = Array{Cuchar}(undef, w * h * 3)
        screen = fill(RGB{Float64}(0,0,0), h, w)
        state = fill(zero(S), h, w, 3)
        new{S}(ale, 0, false, 0., 0., 0, w, h, rawscreen, state, screen)
    end
end
Atari(gamename::AbstractString) = Atari{Float32}(gamename)

function Base.close(env::Atari)
    env.state = typeof(env.state)(undef, 0, 0, 0)
    ALE.ALE_del(env.ale)
end

RLCore.get_actions(env::Atari) = ALE.getLegalActionSet(env.ale)
valid_action(env::Atari, action) = action in RLCore.get_actions(env)

function update_screen(env::Atari)
    for i in 1:env.height, j in 1:env.width
        env.screen[i,j] = RGB{Float64}(env.state[i, j, 1], env.state[i, j, 2], env.state[i, j, 3])
    end
    env.screen
end

@recipe function f(env::Atari)
    ticks := nothing
    foreground_color_border := nothing
    grid := false
    legend := false
    aspect_ratio := 1

    # convert to Image
    update_screen(env)
end

function update_state(env::Atari)
    # get the raw screen data
    ALE.getScreenRGB!(env.ale, env.rawscreen)
    idx = 1
    for i in 1:env.height, j in 1:env.width
        env.state[i,j,1] = env.rawscreen[idx] // 256
        env.state[i,j,2] = env.rawscreen[idx+1] // 256
        env.state[i,j,3] = env.rawscreen[idx+2] // 256
        idx += 3
    end
    env.lives = ALE.lives(env.ale)
    return
end

# Set seed default to 0
function RLCore.reset!(env::Atari; seed::Int64=0, kwargs...)
    ALE.reset_game(env.ale)
    ALE.setInt(env.ale, "random_seed", seed)
    env.lives = 0
    env.died = false
    env.reward = 0
    env.score = 0
    env.nframes = 0
    update_state(env)
end

function RLCore.environment_step!(env::Atari, action; kwargs...)
    # act and get the reward and new state
    env.reward = ALE.act(env.ale, action)
    env.score += env.reward
    update_state(env)
    return
end

RLCore.get_reward(env::Atari) = env.reward
RLCore.is_terminal(env::Atari) = ALE.game_over(env.ale)
RLCore.get_state(env::Atari) = env.state


module QJuliaInterface

  import QJuliaEnums

#WARNING: static array dims MUST coincide with corresponding ones from QUDA
  const QJULIA_MAX_DWF_LS      = 32
  const QJULIA_MAX_MULTI_SHIFT = 32
  const QJULIA_MAX_MG_LEVEL    = 4
  const QJULIA_MAX_DIM         = 6

  mutable struct QJuliaGaugeParam_qj

    # The location of the gauge field
    location::QJuliaEnums.QJuliaFieldLocation_qj

    # The local space-time dimensions (without checkboarding)
    X::NTuple{4, Cint}

    # Used for Wilson and Wilson-clover
    anisotropy::Cdouble
    # Used for staggered only
    tadpole_coeff::Cdouble
    # Used by staggered long links
    scale::Cdouble

    # The link type of the gauge field (e.g., Wilson, fat, long, etc.)
    gtype::QJuliaEnums.QJuliaLinkType_qj
    # The ordering on the input gauge field
    gauge_order::QJuliaEnums.QJuliaGaugeFieldOrder_qj
    # The temporal boundary condition that will be used for fermion fields
    t_boundary::QJuliaEnums.QJuliaTboundary_qj

    # The precision used by the caller
    cpu_prec::QJuliaEnums.QJuliaPrecision_qj

    # The precision of the cuda gauge field
    cuda_prec::QJuliaEnums.QJuliaPrecision_qj
    # The reconstruction type of the cuda gauge field
    reconstruct::QJuliaEnums.QJuliaReconstructType_qj

    # The precision of the sloppy gauge field
    cuda_prec_sloppy::QJuliaEnums.QJuliaPrecision_qj
    # The recontruction type of the sloppy gauge field
    reconstruct_sloppy::QJuliaEnums.QJuliaReconstructType_qj

    # The precision of the sloppy gauge field for the refinement step in multishift
    cuda_prec_refinement_sloppy::QJuliaEnums.QJuliaPrecision_qj
    # The recontruction type of the sloppy gauge field for the refinement step in multishift
    reconstruct_refinement_sloppy::QJuliaEnums.QJuliaReconstructType_qj

    # The precision of the preconditioner gauge field
    cuda_prec_precondition::QJuliaEnums.QJuliaPrecision_qj
    # The recontruction type of the preconditioner gauge field
    reconstruct_precondition::QJuliaEnums.QJuliaReconstructType_qj

    # Whether the input gauge field is in the axial gauge or not
    gauge_fix::QJuliaEnums.QJuliaGaugeFixed_qj

    # The pad size that the cudaGaugeField will use (default=0)
    ga_pad::Cint

    # Used by link fattening and the gauge and fermion forces
    site_ga_pad::Cint

    # Used by link fattening
    staple_pad::Cint
    # Used by link fattening
    llfat_ga_pad::Cint
    # Used by the gauge and fermion forces
    mom_ga_pad::Cint

    # Set the staggered phase type of the links
    staggered_phase_type::QJuliaEnums.QJuliaStaggeredPhase_qj
    # Whether the staggered phase has already been applied to the links
    staggered_phase_applied::Cint

    # Imaginary chemical potential
    i_mu::Cdouble

    # Width of overlapping domains
    overlap::Cint

    # When computing momentum, should we overwrite it or accumulate to
    overwrite_mom::Cint

    # Use the resident gauge field as input
    use_resident_gauge::Cint
    # Use the resident momentum field as input
    use_resident_mom::Cint
    # Make the result gauge field resident
    make_resident_gauge::Cint
    # Make the result momentum field resident
    make_resident_mom::Cint
    # Return the result gauge field
    return_result_gauge::Cint
    # Return the result momentum field
    return_result_mom::Cint

    # Offset into MILC site struct to the gauge field (only if gauge_order=MILC_SITE_GAUGE_ORDER)
    gauge_offset::Csize_t
    # Offset into MILC site struct to the momentum field (only if gauge_order=MILC_SITE_GAUGE_ORDER)
    mom_offset::Csize_t
    # Size of MILC site struct (only if gauge_order=MILC_SITE_GAUGE_ORDER)
    site_size::Csize_t

    #defualt constructor (remarkable: no need to indicate enum name!)
    QJuliaGaugeParam_qj() = new(QJuliaEnums.QJULIA_CPU_FIELD_LOCATION,
			     NTuple{4, Cint}(ntuple(i->16, 4)),
                             1.0,
			     0.0,
			     1.0,
                             QJuliaEnums.QJULIA_WILSON_LINKS,
			     QJuliaEnums.QJULIA_QDP_GAUGE_ORDER,
			     QJuliaEnums.QJULIA_PERIODIC_T,
			     QJuliaEnums.QJULIA_DOUBLE_PRECISION,
			     QJuliaEnums.QJULIA_SINGLE_PRECISION,
			     QJuliaEnums.QJULIA_RECONSTRUCT_NO,
			     QJuliaEnums.QJULIA_SINGLE_PRECISION,
			     QJuliaEnums.QJULIA_RECONSTRUCT_NO,
			     QJuliaEnums.QJULIA_SINGLE_PRECISION,
			     QJuliaEnums.QJULIA_RECONSTRUCT_NO,
			     QJuliaEnums.QJULIA_SINGLE_PRECISION,
			     QJuliaEnums.QJULIA_RECONSTRUCT_NO,
			     QJuliaEnums.QJULIA_GAUGE_FIXED_NO,
			     0,
			     0,
			     0,
			     0,
			     0,
                             QJuliaEnums.QJULIA_STAGGERED_PHASE_NO,
                             0,
                             0.0,
                             0,
                             0,
			     0,
			     0,
			     0,
			     0,
			     0,
			     0,
                             0,
			     0,
			     0)

  end #QJuliaGaugeParam

  mutable struct QJuliaInvertParam_qj

    # The location of the input field
    input_location::QJuliaEnums.QJuliaFieldLocation_qj
    # The location of the output field
    output_location::QJuliaEnums.QJuliaFieldLocation_qj

    # The Dirac Dslash type that is being used
    dslash_type::QJuliaEnums.QJuliaDslashType_qj
    # Which linear solver to use
    inv_type::QJuliaEnums.QJuliaInverterType_qj

    # Used for staggered only
    mass::Cdouble
    # Used for Wilson and Wilson-clover
    kappa::Cdouble
    # Domain wall height
    m5::Cdouble
    # Extent of the 5th dimension (for domain wall)
    Ls::Cint

    # MDWF coefficients
    b_5::NTuple{QJULIA_MAX_DWF_LS, ComplexF64}
    # will be used only for the mobius type of Fermion
    c_5::NTuple{QJULIA_MAX_DWF_LS, ComplexF64}

    # Twisted mass parameter
    mu::Cdouble
    # Twisted mass parameter
    epsilon::Cdouble

    # Twisted mass flavor
    twist_flavor::QJuliaEnums.QJuliaTwistFlavorType_qj

    # Solver tolerance in the L2 residual norm
    tol::Cdouble
    # Solver tolerance in the L2 residual norm (used to restart InitCG)
    tol_restart::Cdouble
    # Solver tolerance in the heavy quark residual norm
    tol_hq::Cdouble

    # Whether to compute the true residual post solve
    compute_true_res::Cint
    # Actual L2 residual norm achieved in solver
    true_res::Cdouble
    # Actual heavy quark residual norm achieved in solver
    true_res_hq::Cdouble
    # Maximum number of iterations in the linear solver
    maxiter::Cint
    # Reliable update tolerance
    reliable_delta::Cdouble
    # Reliable update tolerance used in post multi-shift solver refinement
    reliable_delta_refinement::Cdouble
	# Whether to use alternative reliable updates
    use_alternative_reliable::Cint
    # Whether to keep the partial solution accumuator in sloppy precision
    use_sloppy_partial_accumulator::Cint

    solution_accumulator_pipeline::Cint

    max_res_increase::Cint

    max_res_increase_total::Cint

    # After how many iterations shall the heavy quark residual be updated
    heavy_quark_check::Cint
    # Whether to use a pipelined solver with less global sums
    pipeline::Cint
    # Number of offsets in the multi-shift solver
    num_offset::Cint
    # Number of sources in the multiple source solver
    num_src::Cint

    # Width of domain overlaps
    overlap::Cint

    # Offsets for multi-shift solver
    offset::NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}

    # Solver tolerance for each offset
    tol_offset::NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}

    # Solver tolerance for each shift when refinement is applied using the heavy-quark residual
    tol_hq_offset::NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}

    # Actual L2 residual norm achieved in solver for each offset
    true_res_offset::NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}

    # Iterated L2 residual norm achieved in multi shift solver for each offset
    iter_res_offset::NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}

    # Actual heavy quark residual norm achieved in solver for each offset
    true_res_hq_offset::NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}

    # Residuals in the partial faction expansion
    residue::NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}

    # Whether we should evaluate the action after the linear solver*/
    compute_action::Cint

    action::NTuple{2, Cdouble}

    # Type of system to solve
    solution_type::QJuliaEnums.QJuliaSolutionType_qj
    # How to solve it
    solve_type::QJuliaEnums.QJuliaSolveType_qj
    # The preconditioned matrix type
    matpc_type::QJuliaEnums.QJuliaMatPCType_qj
    # Whether we are using the Hermitian conjugate system or not
    dagger::QJuliaEnums.QJuliaDagType_qj
    # The mass normalization is being used by the caller
    mass_normalization::QJuliaEnums.QJuliaMassNormalization_qj
    # The normalization desired in the solver
    solver_normalization::QJuliaEnums.QJuliaSolverNormalization_qj
    # Preserve the source or not in the linear solver (deprecated)
    preserve_source::QJuliaEnums.QJuliaPreserveSource_qj

    # The precision used by the input fermion fields
    cpu_prec::QJuliaEnums.QJuliaPrecision_qj
    # The precision used by the QJULIA solver
    cuda_prec::QJuliaEnums.QJuliaPrecision_qj
    # The precision used by the QJULIA sloppy operator
    cuda_prec_sloppy::QJuliaEnums.QJuliaPrecision_qj
    # The precision of the sloppy gauge field for the refinement step in multishift
    cuda_prec_refinement_sloppy::QJuliaEnums.QJuliaPrecision_qj
    # The precision used by the QJULIA preconditioner
    cuda_prec_precondition::QJuliaEnums.QJuliaPrecision_qj

    # The order of the input and output fermion fields
    dirac_order::QJuliaEnums.QJuliaDiracFieldOrder_qj
    # Gamma basis of the input and output host fields
    gamma_basis::QJuliaEnums.QJuliaGammaBasis_qj

    # The location of the clover field
    clover_location::QJuliaEnums.QJuliaFieldLocation_qj
    # The precision used for the input clover field
    clover_cpu_prec::QJuliaEnums.QJuliaPrecision_qj
    # The precision used for the clover field in the QJULIA solver
    clover_cuda_prec::QJuliaEnums.QJuliaPrecision_qj
    # The precision used for the clover field in the QJULIA solver
    clover_cuda_prec_sloppy::QJuliaEnums.QJuliaPrecision_qj
    # The precision of the sloppy clover field for the refinement step in multishift
    clover_cuda_prec_refinement_sloppy::QJuliaEnums.QJuliaPrecision_qj
    # The precision used for the clover field in the QJULIA preconditioner
    clover_cuda_prec_precondition::QJuliaEnums.QJuliaPrecision_qj

    # The order of the input clover field
    clover_order::QJuliaEnums.QJuliaCloverFieldOrder_qj
    # Whether to use an initial guess in the solver or not
    use_init_guess::QJuliaEnums.QJuliaUseInitGuess_qj

    # Coefficient of the clover term
    clover_coeff::Cdouble
    # Real number added to the clover diagonal (not to inverse)
    clover_rho::Cdouble

    # Whether to compute the trace log of the clover term
    compute_clover_trlog::Cint
    # The trace log of the clover term (even/odd computed separately)
    trlogA::NTuple{2, Cdouble}

    # Whether to compute the clover field
    compute_clover::Cint
    # Whether to compute the clover inverse field
    compute_clover_inverse::Cint
    # Whether to copy back the clover matrix field
    return_clover::Cint
    # Whether to copy back the inverted clover matrix field
    return_clover_inverse::Cint
    # The verbosity setting to use in the solver
    verbosity::QJuliaEnums.QJuliaVerbosity_qj
    # The padding to use for the fermion fields
    sp_pad::Cint
    # The padding to use for the clover fields
    cl_pad::Cint
    # The number of iterations performed by the solver
    iter::Cint
    # The Gflops rate of the solver
    gflops::Cdouble
    # The time taken by the solver
    secs::Cdouble
    # Enable auto-tuning? (default = QJULIA_TUNE_YES)
    tune::QJuliaEnums.QJuliaTune_qj

    # Number of steps in s-step algorithms
    Nsteps::Cint

    # Maximum size of Krylov space used by solver
    gcrNkrylov::Cint

    inv_type_precondition::QJuliaEnums.QJuliaInverterType_qj

    # Preconditioner instance, e.g., multigrid
    preconditioner::Ptr{Cvoid}

    # Deflation instance
    deflation_op::Ptr{Cvoid}

    # Dirac Dslash used in preconditioner
    dslash_type_precondition::QJuliaEnums.QJuliaDslashType_qj
    # Verbosity of the inner Krylov solver
    verbosity_precondition::QJuliaEnums.QJuliaVerbosity_qj
    # Tolerance in the inner solver
    tol_precondition::Cdouble

    # Maximum number of iterations allowed in the inner solver
    maxiter_precondition::Cint

    # Relaxation parameter used in GCR-DD (default = 1.0)
    omega::Cdouble
#new!
	# Basis for CA algorithms
    ca_basis::QJuliaEnums.QJuliaCABasis_qj

    # Minimum eigenvalue for Chebyshev CA basis
    ca_lambda_min::Cdouble

    # Maximum eigenvalue for Chebyshev CA basis
    ca_lambda_max::Cdouble

    # Number of preconditioner cycles to perform per iteration
    precondition_cycle::Cint

    # Whether to use additive or multiplicative Schwarz preconditioning
    schwarz_type::QJuliaEnums.QJuliaSchwarzType_qj

    residual_type::QJuliaEnums.QJuliaResidualType_qj

    #Parameters for deflated solvers
    # The precision of the Ritz vectors
    cuda_prec_ritz::QJuliaEnums.QJuliaPrecision_qj
    # How many vectors to compute after one solve
    #  for eigCG recommended values 8 or 16

    nev::Cint
    # EeigCG  : Search space dimension
    # GMResDR : Krylov subspace dimension
    max_search_dim::Cint
    #For systems with many RHS: current RHS index
    rhs_idx::Cint
    # Specifies deflation space volume: total number of eigenvectors is nev*deflation_grid
    deflation_grid::Cint
    # eigCG: selection criterion for the reduced eigenvector set
    eigenval_tol::Cdouble
    # mixed precision eigCG tuning parameter:  minimum search vector space restarts
    eigcg_max_restarts::Cint
    #initCG tuning parameter:  maximum restarts
    max_restart_num::Cint
    # initCG tuning parameter:  tolerance for cg refinement corrections in the deflation stage
    inc_tol::Cdouble

    # Whether to make the solution vector(s) after the solve
    make_resident_solution::Cint

    # Whether to use the resident solution vector(s)
    use_resident_solution::Cint

    # Whether to use the solution vector to augment the chronological basis
    chrono_make_resident::Cint

    # Whether the solution should replace the last entry in the chronology
    chrono_replace_last::Cint

    # Whether to use the resident chronological basis
    chrono_use_resident::Cint

    # The maximum length of the chronological history to store
    chrono_max_dim::Cint

    # The index to indicate which chrono history we are augmenting
    chrono_index::Cint

    # Precision to store the chronological basis in
    chrono_precision::QJuliaEnums.QJuliaPrecision_qj

    # Which external library to use in the linear solvers (MAGMA or Eigen)
    extlib_type::QJuliaEnums.QJuliaExtLibType_qj

    #defualt constructor
    QJuliaInvertParam_qj() = new(QJuliaEnums.QJULIA_CPU_FIELD_LOCATION,
				 QJuliaEnums.QJULIA_CPU_FIELD_LOCATION,
				 QJuliaEnums.QJULIA_WILSON_DSLASH,
				 QJuliaEnums.QJULIA_CG_INVERTER,
				 0.0,
				 0.0,
				 0.0,
				 1,
				 NTuple{QJULIA_MAX_DWF_LS, ComplexF64}(ntuple(i->ComplexF64(0.0, 0.0), QJULIA_MAX_DWF_LS)),
				 NTuple{QJULIA_MAX_DWF_LS, ComplexF64}(ntuple(i->ComplexF64(0.0, 0.0), QJULIA_MAX_DWF_LS)),
				 0.0,
				 0.0,
				 QJuliaEnums.QJULIA_TWIST_INVALID,
				 1e-10,
				 5e-3,
				 1e-2,
				 1,
				 0.0,
				 0.0,
				 256,
				 1e-2,
				 1e-2,
                 0,
				 0,
				 1,
				 10,
				 10,
				 64,
				 1,
				 8,
				 32,
				 0,
				 NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}(ntuple(i->0, QJULIA_MAX_MULTI_SHIFT)),
				 NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}(ntuple(i->0, QJULIA_MAX_MULTI_SHIFT)),
				 NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}(ntuple(i->0, QJULIA_MAX_MULTI_SHIFT)),
				 NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}(ntuple(i->0, QJULIA_MAX_MULTI_SHIFT)),
				 NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}(ntuple(i->0, QJULIA_MAX_MULTI_SHIFT)),
				 NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}(ntuple(i->0, QJULIA_MAX_MULTI_SHIFT)),
				 NTuple{QJULIA_MAX_MULTI_SHIFT, Cdouble}(ntuple(i->0, QJULIA_MAX_MULTI_SHIFT)),
				 0,
				 NTuple{2, Cdouble}(ntuple(i->0, 2)),
				 QJuliaEnums.QJULIA_MAT_SOLUTION,
				 QJuliaEnums.QJULIA_NORMOP_PC_SOLVE,
				 QJuliaEnums.QJULIA_MATPC_EVEN_EVEN,
				 QJuliaEnums.QJULIA_DAG_NO,
				 QJuliaEnums.QJULIA_KAPPA_NORMALIZATION,
				 QJuliaEnums.QJULIA_DEFAULT_NORMALIZATION,
				 QJuliaEnums.QJULIA_PRESERVE_SOURCE_YES,
				 QJuliaEnums.QJULIA_DOUBLE_PRECISION,
				 QJuliaEnums.QJULIA_SINGLE_PRECISION,
				 QJuliaEnums.QJULIA_SINGLE_PRECISION,
				 QJuliaEnums.QJULIA_SINGLE_PRECISION,
				 QJuliaEnums.QJULIA_SINGLE_PRECISION,
				 QJuliaEnums.QJULIA_DIRAC_ORDER,
				 QJuliaEnums.QJULIA_DEGRAND_ROSSI_GAMMA_BASIS,
				 QJuliaEnums.QJULIA_CPU_FIELD_LOCATION,
				 QJuliaEnums.QJULIA_INVALID_PRECISION,
				 QJuliaEnums.QJULIA_INVALID_PRECISION,
				 QJuliaEnums.QJULIA_INVALID_PRECISION,
				 QJuliaEnums.QJULIA_INVALID_PRECISION,
				 QJuliaEnums.QJULIA_INVALID_PRECISION,
				 QJuliaEnums.QJULIA_INVALID_CLOVER_ORDER,
				 QJuliaEnums.QJULIA_USE_INIT_GUESS_NO,
				 0.0,
				 0.0,
				 0,
				 NTuple{2, Cdouble}(ntuple(i->0, 2)),
				 0,
				 0,
				 0,
				 0,
				 QJuliaEnums.QJULIA_VERBOSE,
				 0,
				 0,
				 256,
				 0.0,
				 0.0,
				 QJuliaEnums.QJULIA_TUNE_YES,
				 8,
				 16,
				 QJuliaEnums.QJULIA_INVALID_INVERTER,
				 C_NULL, C_NULL,
				 QJuliaEnums.QJULIA_INVALID_DSLASH,
				 QJuliaEnums.QJULIA_SILENT,
				 0.1,
				 8,
				 1.0,
				 QJuliaEnums.QJULIA_INVALID_BASIS,
				 0.0,
				 1.0,
				 1,
				 QJuliaEnums.QJULIA_ADDITIVE_SCHWARZ,
				 QJuliaEnums.QJULIA_L2_RELATIVE_RESIDUAL,
				 QJuliaEnums.QJULIA_SINGLE_PRECISION,
				 8,
				 64,
				 0,
				 8,
				 1e-3,
				 4,
				 3,
				 1e-2,
				 0,
				 0,
				 0,
				 0,
				 0,
				 0,
				 0,
				 QJuliaEnums.QJULIA_INVALID_PRECISION,
				 QJuliaEnums.QJULIA_EIGEN_EXTLIB)

  end #QJuliaInvertParam

  mutable struct QJuliaMultigridParam_qj
      # Reference to invert param structure
	  invert_param::QJuliaInvertParam_qj

	  # Number of multigrid levels
	  n_level::Cint

	  # Geometric block sizes to use on each level
	  geo_block_size::NTuple{QJULIA_MAX_DIM*QJULIA_MAX_MG_LEVEL, Cint} # [QUDA_MAX_MG_LEVEL][QUDA_MAX_DIM] -> QJULIA_MAX_DIM*QJULIA_MAX_MG_LEVEL

	  # Spin block sizes to use on each level
	  spin_block_size::NTuple{QJULIA_MAX_MG_LEVEL, Cint}

	  # Number of null-space vectors to use on each level
	  n_vec::NTuple{QJULIA_MAX_MG_LEVEL, Cint}

	  # Precision to store the null-space vectors in (post block orthogonalization)
	  precision_null::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaPrecision_qj}

	  # Verbosity on each level of the multigrid
	  verbosity::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaVerbosity_qj}

	  # Inverter to use in the setup phase
	  setup_inv_type::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaInverterType_qj}

	  # Number of setup iterations
	  num_setup_iter::NTuple{QJULIA_MAX_MG_LEVEL, Cint}

	  # Tolerance to use in the setup phase
	  setup_tol::NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}

	  # Maximum number of iterations for each setup solver
	  setup_maxiter::NTuple{QJULIA_MAX_MG_LEVEL, Cint}

	  # Maximum number of iterations for refreshing the null-space vectors
	  setup_maxiter_refresh::NTuple{QJULIA_MAX_MG_LEVEL, Cint}

	  # Null-space type to use in the setup phase
	  setup_type::QJuliaEnums.QJuliaSetupType_qj

	  # Pre orthonormalize vectors in the setup phase
	  pre_orthonormalize::QJuliaEnums.QJuliaBoolean_qj

	  # Post orthonormalize vectors in the setup phase
	  post_orthonormalize::QJuliaEnums.QJuliaBoolean_qj

	  # The solver that wraps around the coarse grid correction and smoother
	  coarse_solver::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaInverterType_qj}

	  # Tolerance for the solver that wraps around the coarse grid correction and smoother
	  coarse_solver_tol::NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}

	  # Maximum number of iterations for the solver that wraps around the coarse grid correction and smoother
	  coarse_solver_maxiter::NTuple{QJULIA_MAX_MG_LEVEL, Cdouble} #Warning : will be changed to Cint!

	  # Smoother to use on each level
	  smoother::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaInverterType_qj}

	  # Tolerance to use for the smoother / solver on each level
	  smoother_tol::NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}

	  # Number of pre-smoother applications on each level
	  nu_pre::NTuple{QJULIA_MAX_MG_LEVEL, Cint}

	  # Number of post-smoother applications on each level
	  nu_post::NTuple{QJULIA_MAX_MG_LEVEL, Cint}

	  # Over/under relaxation factor for the smoother at each level
	  omega::NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}

	  # Precision to use for halo communication in the smoother
	  smoother_halo_precision::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaPrecision_qj}

	  # Whether to use additive or multiplicative Schwarz preconditioning in the smoother
	  smoother_schwarz_type::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaSchwarzType_qj}

	  # Number of Schwarz cycles to apply
	  smoother_schwarz_cycle::NTuple{QJULIA_MAX_MG_LEVEL, Cint}

      # The type of residual to send to the next coarse grid, and thus the
  	  # type of solution to receive back from this coarse grid
      coarse_grid_solution_type::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaSolutionType_qj}

      # The type of smoother solve to do on each grid (e/o preconditioning or not)
      smoother_solve_type::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaSolveType_qj}

      # The type of multigrid cycle to perform at each level
      cycle_type::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaMultigridCycleType_qj}

      # Whether to use global reductions or not for the smoother / solver at each level
      global_reduction::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaBoolean_qj}

      # Location where each level should be done
      location::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaFieldLocation_qj}

      # Location where the coarse-operator construction will be computedn
      setup_location::NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaFieldLocation_qj}

      # Minimize device memory allocations during the adaptive setup,
        #  placing temporary fields in mapped memory instad of device
         # memory
      setup_minimize_memory::QJuliaEnums.QJuliaBoolean_qj

      # Whether to compute the null vectors or reload them
      compute_null_vector::QJuliaEnums.QJuliaComputeNullVector_qj

      # Whether to generate on all levels or just on level 0
      generate_all_levels::QJuliaEnums.QJuliaBoolean_qj

      # Whether to run the verification checks once set up is complete
      run_verify::QJuliaEnums.QJuliaBoolean_qj

      # Filename prefix where to load the null-space vectors (currently just placeholders)
      vec_infile::NTuple{256, Int8}

      # Filename prefix for where to save the null-space vectors (currently just placeholders)
      vec_outfile::NTuple{256, Int8}

      # The Gflops rate of the multigrid solver setup
      gflops::Cdouble

      # The time taken by the multigrid solver setup
      secs::Cdouble

      # Multiplicative factor for the mu parameter
      mu_factor::NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}

      #defualt constructor
      function QJuliaMultigridParam_qj( invert_param::QJuliaInvertParam_qj )
               # default number of levels
			   nlevel = Cint(3)

		       # setup geom block size
		       geo_block_size = NTuple{QJULIA_MAX_DIM*QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(1), QJULIA_MAX_DIM*QJULIA_MAX_MG_LEVEL))
			   # fill up default values (remember column major format):
			   for i in 0:3; [geo_block_size[QJULIA_MAX_DIM*i+j] = Cint(4) for j = 1:4];end

			   # setup default spin block size
			   spin_block_size    = NTuple{QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(1), QJULIA_MAX_MG_LEVEL))
			   spin_block_size[1] = Cint(2)

			   # setup default null space vectors for each level
			   n_vec    = NTuple{QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(1), QJULIA_MAX_MG_LEVEL))
			   n_vec[1] = Cint(24); n_vec[2] = Cint(24)

			   # setup null space precisions
			   prec_null= NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaPrecision_qj}(ntuple(i->QJuliaEnums.QJULIA_INVALID_PRECISION, QJULIA_MAX_MG_LEVEL))

			   # setup verbosity on each level of the multigrid
			   verbosity= NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaVerbosity_qj}(ntuple(i->QJuliaEnums.QJULIA_SILENT, QJULIA_MAX_MG_LEVEL))

			   # select inverters to use in the setup phase
		       setup_inv_type = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaInverterType_qj}(ntuple(i->QJuliaEnums.QJULIA_INVALID_INVERTER, QJULIA_MAX_MG_LEVEL))

		       # fill up number of setup iterations
		       num_setup_iter = NTuple{QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(100), QJULIA_MAX_MG_LEVEL))

		       # fill up tolerance to use in the setup phase
		       setup_tol = NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}(ntuple(i->1e-4, QJULIA_MAX_MG_LEVEL))

		       # fill up maximum number of iterations for each setup solver
		       setup_maxiter = NTuple{QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(100), QJULIA_MAX_MG_LEVEL))

		       # fill up maximum number of iterations for refreshing the null-space vectors *
		       setup_maxiter_refresh = NTuple{QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(1), QJULIA_MAX_MG_LEVEL))

		       # Null-space type to use in the setup phase
		       setup_type = QJuliaEnums.QJULIA_NULL_VECTOR_SETUP

		       # Pre orthonormalize vectors in the setup phase
		       pre_orthonormalize = QJuliaEnums.QJULIA_BOOLEAN_YES

		       # Post orthonormalize vectors in the setup phase
		       post_orthonormalize= QJuliaEnums.QJULIA_BOOLEAN_YES

		       # The solver that wraps around the coarse grid correction and smoother
		       coarse_solver = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaInverterType_qj}(ntuple(i->QJuliaEnums.QJULIA_INVALID_INVERTER, QJULIA_MAX_MG_LEVEL))

		       # Tolerance for the solver that wraps around the coarse grid correction and smoother
		       coarse_solver_tol = NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}(ntuple(i->1e-2, QJULIA_MAX_MG_LEVEL))

		       # Maximum number of iterations for the solver that wraps around the coarse grid correction and smoother
		       coarse_solver_maxiter = NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}(ntuple(i->100.0, QJULIA_MAX_MG_LEVEL))

		       # Set default smoother to use on each level
		       smoother = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaInverterType_qj}(ntuple(i->QJuliaEnums.QJULIA_INVALID_INVERTER, QJULIA_MAX_MG_LEVEL))

		       # Set tolerance to use for the smoother / solver on each level
		       smoother_tol = NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}(ntuple(i->1e-2, QJULIA_MAX_MG_LEVEL))

		       # Set number of pre-smoother applications on each level
		       nu_pre = NTuple{QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(4), QJULIA_MAX_MG_LEVEL))

		       # Set number of post-smoother applications on each level
		       nu_post = NTuple{QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(4), QJULIA_MAX_MG_LEVEL))

		       # Over/under relaxation factor for the smoother at each level
		       omega = NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}(ntuple(i->1.0, QJULIA_MAX_MG_LEVEL))

		       # Precision to use for halo communication in the smoother
		       smoother_halo_precision = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaPrecision_qj}(ntuple(i->QJuliaEnums.QJULIA_INVALID_PRECISION, QJULIA_MAX_MG_LEVEL))

		       # Whether to use additive or multiplicative Schwarz preconditioning in the smoother
		       smoother_schwarz_type  = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaSchwarzType_qj}(ntuple(i->QJuliaEnums.QJULIA_INVALID_SCHWARZ, QJULIA_MAX_MG_LEVEL))

		       # Number of Schwarz cycles to apply
		       smoother_schwarz_cycle = NTuple{QJULIA_MAX_MG_LEVEL, Cint}(ntuple(i->Cint(1), QJULIA_MAX_MG_LEVEL))

		       # The type of residual to send to the next coarse grid, and thus the
		   	  # type of solution to receive back from this coarse grid
		       coarse_grid_solution_type = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaSolutionType_qj}(ntuple(i->QJuliaEnums.QJULIA_MAT_SOLUTION, QJULIA_MAX_MG_LEVEL))

		       # The type of smoother solve to do on each grid (e/o preconditioning or not)
		       smoother_solve_type = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaSolveType_qj}(ntuple(i->QJuliaEnums.QJULIA_DIRECT_SOLVE, QJULIA_MAX_MG_LEVEL))

		       # The type of multigrid cycle to perform at each level
		       cycle_type = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaMultigridCycleType_qj}(ntuple(i->QJuliaEnums.QJULIA_MG_CYCLE_RECURSIVE, QJULIA_MAX_MG_LEVEL))

		       # Whether to use global reductions or not for the smoother / solver at each level
		       global_reduction = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaBoolean_qj}(ntuple(i->QJuliaEnums.QJULIA_BOOLEAN_YES, QJULIA_MAX_MG_LEVEL))

		       # Location where each level should be done
		       location = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaFieldLocation_qj}(ntuple(i->QJuliaEnums.QJULIA_CPU_FIELD_LOCATION, QJULIA_MAX_MG_LEVEL))

		       # Location where the coarse-operator construction will be computedn
		       setup_location = NTuple{QJULIA_MAX_MG_LEVEL, QJuliaEnums.QJuliaFieldLocation_qj}(ntuple(i->QJuliaEnums.QJULIA_CPU_FIELD_LOCATION, QJULIA_MAX_MG_LEVEL))
			   # Minimize device memory allocations during the adaptive setup,
		       #  placing temporary fields in mapped memory instad of device
		       # memory
		       setup_minimize_memory::QJuliaEnums.QJULIA_BOOLEAN_YES

		       # Whether to compute the null vectors or reload them
		       compute_null_vector::QJuliaEnums.QJULIA_COMPUTE_NULL_VECTOR_YES

		       # Whether to generate on all levels or just on level 0
		       generate_all_levels::QJuliaEnums.QJULIA_BOOLEAN_NO

		       # Whether to run the verification checks once set up is complete
		       run_verify::QJuliaEnums.QJULIA_BOOLEAN_YES

		       # Filename prefix where to load the null-space vectors
		       #char vec_infile[256];

		       # Filename prefix for where to save the null-space vectors
		       #char vec_outfile[256];

		       # The Gflops rate of the multigrid solver setup
		       gflops = 0.0

		       # The time taken by the multigrid solver setup
		       secs   = 0.0

		       # Multiplicative factor for the mu parameter
		       mu_factor = NTuple{QJULIA_MAX_MG_LEVEL, Cdouble}(ntuple(i->1.0, QJULIA_MAX_MG_LEVEL))

               new( invert_param,
			   		nlevel,
					geo_block_size,
					spin_block_size,
					n_vec,
					prec_null,
					verbosity,
					setup_inv_type,
					num_setup_iter,
					setup_tol,
					setup_maxiter,
					setup_maxiter_refresh,
					setup_type,
					pre_orthonormalize,
					post_orthonormalize,
					coarse_solver,
					coarse_solver_tol,
					coarse_solver_maxiter,
					smoother,
					smoother_tol,
					nu_pre,
					nu_post,
					omega,
					smoother_halo_precision,
					smoother_schwarz_type,
					smoother_schwarz_cycle,
					coarse_grid_solution_type,
					smoother_solve_type,
					cycle_type,
					global_reduction,
					location,
					setup_location,
					setup_minimize_memory,
					compute_null_vector,
					generate_all_levels,
					run_verify,
					NTuple{256, Int8}(ntuple(i->Int8(0), 256)),
					NTuple{256, Int8}(ntuple(i->Int8(0), 256)),
					glops,
					secs,
					mu_factor
			   )
         end #function QJuliaMultigridParam_qj

      end#QJuliaMultigridParam

end #QJuliaInterface


#=
LCM Server: an LCM interface to Caesar.jl

=#

using Caesar, RoME
using TransformUtils, Rotations, CoordinateTransformations
using Distributions
using PyCall, PyLCM
# using LibBSON
# using CloudGraphs # for sorryPedro

function gen_bindings()
    @show lcmtpath = joinpath(dirname(@__FILE__),"lcmtypes")
    run(`lcm-gen -p --ppath $(lcmtpath) $(lcmtpath)/rome_point_cloud_t.lcm`)
    run(`lcm-gen -p --ppath $(lcmtpath) $(lcmtpath)/rome_pose_node_t.lcm`)
    run(`lcm-gen -p --ppath $(lcmtpath) $(lcmtpath)/rome_pose_pose_nh_t.lcm`)
    run(`lcm-gen -p --ppath $(lcmtpath) $(lcmtpath)/rome_pose_pose_xyh_t.lcm`)
    run(`lcm-gen -p --ppath $(lcmtpath) $(lcmtpath)/rome_prior_zpr_t.lcm`)
    println("Adding lcmtypes dir to Python path: $(lcmtpath)")
    unshift!(PyVector(pyimport("sys")["path"]),lcmtpath)
end

println("[Caesar.jl] (re)generating LCM bindings")
gen_bindings()

println("[Caesar.jl] Importing LCM message types")
@pyimport rome

## ========================================================================== ##

wTx = Vector{AffineMap}(5)
wTx[1] = Translation(8.7481, 7.51, 4.5662) ∘ LinearMap(Quat(0.9939, -0.0280019, -0.00824962, -0.106355))
wTx[2] = Translation(9.43, 11.0879, 4.9) ∘ LinearMap(Quat(0.992387, 0.0575732, -0.00698004, -0.108645))
wTx[3] = Translation(9.94, 15.1484, 4.8792) ∘ LinearMap(Quat(0.997597, 0.0563353, -0.0115003, -0.0386678))
wTx[4] = Translation(9.93104, 14.5831, 5.8) ∘ LinearMap(Quat(0.997626, 0.0336346, -0.011705, -0.0589371))
wTx[5] = Translation(9.12, 10.1615, 5.84927) ∘ LinearMap(Quat(0.998351, 0.0362801, -0.00973871, -0.0434122))

wTx1 = convert(SE3, wTx[1])
wTx2 = convert(SE3, wTx[2])
wTx3 = convert(SE3, wTx[3])
wTx4 = convert(SE3, wTx[4])
wTx5 = convert(SE3, wTx[5])
# get rotation to world frame for local level
wEx1 = convert(Euler, wTx1.R);  wEx1.Y = 0.0;
wRlx1 = SE3(zeros(3), wEx1)
wEx2 = convert(Euler, wTx2.R);  wEx2.Y = 0.0;
wRlx2 = SE3(zeros(3), wEx2)
wEx3 = convert(Euler, wTx3.R);  wEx3.Y = 0.0;
wRlx3 = SE3(zeros(3), wEx3)
wEx4 = convert(Euler, wTx4.R);  wEx4.Y = 0.0;
wRlx4 = SE3(zeros(3), wEx4)
wEx5 = convert(Euler, wTx5.R);  wEx5.Y = 0.0;
wRlx5 = SE3(zeros(3), wEx5)
# Odometries
wRlx1Tx2 = wRlx1 * (wTx1\wTx2)
wRlx2Tx3 = wRlx2 * (wTx2\wTx3)
wRlx3Tx4 = wRlx3 * (wTx3\wTx4)
wRlx4Tx5 = wRlx4 * (wTx4\wTx5)

vEx1_2 = veeEuler(wRlx1Tx2)
vEx2_3 = veeEuler(wRlx2Tx3)
vEx3_4 = veeEuler(wRlx3Tx4)
vEx4_5 = veeEuler(wRlx4Tx5)

XYH1_2 = [vEx1_2[1], vEx1_2[2], vEx1_2[6]]
XYH2_3 = [vEx2_3[1], vEx2_3[2], vEx2_3[6]]
XYH3_4 = [vEx3_4[1], vEx3_4[2], vEx3_4[6]]
XYH4_5 = [vEx4_5[1], vEx4_5[2], vEx4_5[6]]

zrp2 = veeEuler(wTx2)[[3;4;5]]
zrp3 = veeEuler(wTx3)[[3;4;5]]
zrp4 = veeEuler(wTx4)[[3;4;5]]
zrp5 = veeEuler(wTx5)[[3;4;5]]

lcm_node = LCM()

# send the first pose
msg = rome.pose_node_t()
msg[:utime] = 0
msg[:id] = 1
msg[:mean_dim] = 7
q1 = convert(Quaternion, wTx1.R)
msg[:mean] = Float64[wTx1.t..., q1.s, q1.v...]
msg[:covar_dim] = 6
msg[:covar] = Float64[0.001, 0.001, 0.001, 0.001, 0.001, 0.001]
publish(lcm_node, "ROME_POSES", msg)
sleep(0.01)

# send the second pose
msg = rome.pose_node_t()
msg[:utime] = 0
msg[:id] = 2
msg[:mean_dim] = 7
q2 = convert(Quaternion, wTx2.R)
msg[:mean] = Float64[wTx2.t..., q2.s, q2.v...]
msg[:covar_dim] = 6
msg[:covar] = Float64[0.001, 0.001, 0.001, 0.001, 0.001, 0.001]
publish(lcm_node, "ROME_POSES", msg)
sleep(0.01)

msg = rome.prior_zpr_t()
msg[:utime] = 0
msg[:id] = 2
msg[:z] = zrp2[1]
msg[:roll] = zrp2[2]
msg[:pitch] = zrp2[3]
msg[:var_z] = 0.01
msg[:var_pitch] = 0.001
msg[:var_roll] = 0.001
publish(lcm_node, "ROME_PARTIAL_ZPR", msg)
sleep(0.01)

msg = rome.pose_pose_xyh_t()
msg[:utime] = 0
msg[:node_1_utime] = 0
msg[:node_1_id] = 1
msg[:node_2_utime] = 0
msg[:node_2_id] = 2
msg[:delta_x] = XYH1_2[1]
msg[:delta_y] = XYH1_2[2]
msg[:delta_yaw] = XYH1_2[3]
msg[:var_x] = 0.001
msg[:var_y] = 0.001
msg[:var_yaw] = 0.001
publish(lcm_node, "ROME_PARTIAL_XYH", msg)

# send the third pose
msg = rome.pose_node_t()
msg[:utime] = 0
msg[:id] = 3
msg[:mean_dim] = 7
q3 = convert(Quaternion, wTx3.R)
msg[:mean] = Float64[wTx3.t..., q3.s, q3.v...]
msg[:covar_dim] = 6
msg[:covar] = Float64[0.001, 0.001, 0.001, 0.001, 0.001, 0.001]
publish(lcm_node, "ROME_POSES", msg)
sleep(0.01)

msg = rome.prior_zpr_t()
msg[:utime] = 0
msg[:id] = 3
msg[:z] = zrp3[1]
msg[:roll] = zrp3[2]
msg[:pitch] = zrp3[3]
msg[:var_z] = 0.01
msg[:var_pitch] = 0.001
msg[:var_roll] = 0.001
publish(lcm_node, "ROME_PARTIAL_ZPR", msg)
sleep(0.01)

msg = rome.pose_pose_xyh_t()
msg[:utime] = 0
msg[:node_1_utime] = 0
msg[:node_1_id] = 2
msg[:node_2_utime] = 0
msg[:node_2_id] = 3
msg[:delta_x] = XYH2_3[1]
msg[:delta_y] = XYH2_3[2]
msg[:delta_yaw] = XYH2_3[3]
msg[:var_x] = 0.001
msg[:var_y] = 0.001
msg[:var_yaw] = 0.001
publish(lcm_node, "ROME_PARTIAL_XYH", msg)

# send the forth pose
msg = rome.pose_node_t()
msg[:utime] = 0
msg[:id] = 4
msg[:mean_dim] = 7
q4 = convert(Quaternion, wTx4.R)
msg[:mean] = Float64[wTx4.t..., q4.s, q4.v...]
msg[:covar_dim] = 6
msg[:covar] = Float64[0.001, 0.001, 0.001, 0.001, 0.001, 0.001]
publish(lcm_node, "ROME_POSES", msg)
sleep(0.01)

msg = rome.prior_zpr_t()
msg[:utime] = 0
msg[:id] = 4
msg[:z] = zrp4[1]
msg[:roll] = zrp4[2]
msg[:pitch] = zrp4[3]
msg[:var_z] = 0.01
msg[:var_pitch] = 0.001
msg[:var_roll] = 0.001
publish(lcm_node, "ROME_PARTIAL_ZPR", msg)
sleep(0.01)

msg = rome.pose_pose_xyh_t()
msg[:utime] = 0
msg[:node_1_utime] = 0
msg[:node_1_id] = 3
msg[:node_2_utime] = 0
msg[:node_2_id] = 4
msg[:delta_x] = XYH3_4[1]
msg[:delta_y] = XYH3_4[2]
msg[:delta_yaw] = XYH3_4[3]
msg[:var_x] = 0.001
msg[:var_y] = 0.001
msg[:var_yaw] = 0.001
publish(lcm_node, "ROME_PARTIAL_XYH", msg)

# send the fifth pose
msg = rome.pose_node_t()
msg[:utime] = 0
msg[:id] = 5
msg[:mean_dim] = 7
q5 = convert(Quaternion, wTx4.R)
msg[:mean] = Float64[wTx4.t..., q5.s, q5.v...]
msg[:covar_dim] = 6
msg[:covar] = Float64[0.001, 0.001, 0.001, 0.001, 0.001, 0.001]
publish(lcm_node, "ROME_POSES", msg)
sleep(0.01)

msg = rome.prior_zpr_t()
msg[:utime] = 0
msg[:id] = 5
msg[:z] = zrp5[1]
msg[:roll] = zrp5[2]
msg[:pitch] = zrp5[3]
msg[:var_z] = 0.01
msg[:var_pitch] = 0.001
msg[:var_roll] = 0.001
publish(lcm_node, "ROME_PARTIAL_ZPR", msg)
sleep(0.01)

msg = rome.pose_pose_xyh_t()
msg[:utime] = 0
msg[:node_1_utime] = 0
msg[:node_1_id] = 4
msg[:node_2_utime] = 0
msg[:node_2_id] = 5
msg[:delta_x] = XYH4_5[1]
msg[:delta_y] = XYH4_5[2]
msg[:delta_yaw] = XYH4_5[3]
msg[:var_x] = 0.001
msg[:var_y] = 0.001
msg[:var_yaw] = 0.001
publish(lcm_node, "ROME_PARTIAL_XYH", msg)

#
# function handle_poses!(slam::SLAMWrapper,
#                        message_data)
#
#     message = rome.pose_node_t[:decode](message_data)
#     id = message[:id]
#     println("[Caesar.jl] Received pose message for x$(id)")
#
#     mean = message[:mean]
#     covar = message[:covar]
#     t = [mean[1], mean[2], mean[3]]
#     qw = mean[4]
#     qxyz = [mean[5], mean[6], mean[7]]
#     q = Quaternion(qw,qxyz) # why not a (w,x,y,z) constructor?
#     pose = SE3(t,q)
#     euler = Euler(q)
#
#     node_label = Symbol("x$(id)")
#     nothing
# end
#
# function handle_priors!(slam::SLAMWrapper,
#                          message_data)
#
#
#     message = rome.prior_zpr_t[:decode](message_data)
#     id = message[:id]
#     println("[Caesar.jl] Adding prior on RPZ to x$(id)")
#
#     @show node_label = Symbol("x$(id)")
#
#     @show message[:z]
#     @show message[:pitch]
#     @show message[:roll]
#
#     @show message[:var_z]
#     @show message[:var_pitch]
#     @show message[:var_roll]
#     nothing
# end
#
#
# function handle_partials!(slam::SLAMWrapper,
#                          message_data)
#     # add XYH factor
#
#     message = rome.pose_pose_xyh_t[:decode](message_data)
#
#     origin_id = message[:node_1_id]
#     destination_id = message[:node_2_id]
#     origin_label = Symbol("x$(origin_id)")
#     destination_label = Symbol("x$(destination_id)")
#
#     println("[Caesar.jl] Adding XYH odometry constraint betwee (x$(origin_id), x$(destination_id))")
#
#     @show message[:delta_x]
#     @show message[:delta_y]
#     @show message[:delta_yaw]
#
#     @show message[:var_x]
#     @show message[:var_y]
#     @show message[:var_yaw]
#
#     @show origin_label, destination_label
#     nothing
# end
#
#
#
# # Now test and evaluate if the lcm log is in fact correct
#
# lcm_pose_handler = (channel, message_data) -> handle_poses!(slam_client, message_data )
# lcm_odom_handler = (channel, message_data) -> handle_partials!(slam_client, message_data )
# lcm_prior_handler = (channel, message_data) -> handle_priors!(slam_client, message_data )

#


println("Hello world!");

@everywhere using Printf;

@show workdir = if length(ARGS) > 0
  ARGS[1];
else
  ".";
end

@everywhere fmt(x) = @sprintf("%07d", round(Int, 1e3*x));
@everywhere fmt_int(x) = @sprintf("%03d", x);

@everywhere function prefix(case)
  "E0-$(fmt(case[:E0]))_mu-$(fmt(case[:mu]))_kT-$(fmt(case[:kT]))_Fz-$(fmt(case[:Fz]))_Fx-$(fmt(case[:Fx]))_n-$(fmt(case[:n]))_b-$(fmt(case[:b]))";
end

cases = Any[];
E0s = [0.0; 1e-1; 1.0; 10.0];
μs = [1e-6; 1e-3; 1e-2; 1e-1; 0.5; 1.0; 2.0; 10.0];
kTs = [1.0];
Fzs = sort(vcat(-[0.25; 0.5; 0.75; 1.0; 2.0; 3.0; 4.0; 5.0; 10.0; 20.0],
                [0.0; 0.25; 0.5; 0.75; 1.0; 2.0; 3.0; 4.0; 5.0; 10.0; 20.0]));
Fxs = [0.0; 0.25; 0.5; 0.75; 1.0; 2.0; 3.0; 4.0; 5.0; 10.0; 20.0];
ns = Int[100; 200];
bs = [0.5; 1.0; 2.0];
for b in bs, n in ns, Fx in Fxs, Fz in Fzs, kT in kTs, E0 in E0s, μ in μs
    push!(cases, Dict(:E0 => E0, :mu => μ,
                      :kT => kT, :Fz => Fz,
                      :Fx => Fx, :n => n, :b => b));
end

@info "total number of cases to run: $(length(cases))";

pmap(case -> begin;
  outfile = joinpath(workdir, "$(prefix(case)).out");
  if !isfile(outfile)
    println("Running case: $case.");
    command = `julia -O 3 mcmc_eap_chain.jl --chain-type polar --energy-type interacting -b $(case[:b]) --E0 $(case[:E0]) --mu $(case[:mu]) --kT $(case[:kT]) --Fz $(case[:Fz]) --Fx $(case[:Fx]) -n $(case[:n]) --num-steps 1000000 -v 2 --prefix $(joinpath(workdir, prefix(case))) --numeric-type big`;
    output = read(command, String);
    write(outfile, output);
  else
    println("Case: $case has already been run.");
  end
end, cases);


"""
Run gibbs sampler.
"""
function gibbs_sample!(
    model::DistributedSeqModel,
    spikes::Vector{Spike},
    initial_assignments::Vector{Int64},
    num_samples::Int64,
    extra_split_merge_moves::Int64,
    split_merge_window::Float64,
    save_every::Int64;
    verbose=false
)

    if extra_split_merge_moves > 0
        @warn "Split merge not implemented for distributed model."
    end

    num_partitions = model.num_partitions
    max_time = model.primary_model.max_time

    # Partition spikes.
    split_points = Tuple(p * max_time / num_partitions for p in 0:num_partitions)
    spk_partition = Tuple(Spike[] for m in model.submodels)
    assgn_partition = [Int64[] for m in model.submodels]  # TODO -- Tuple?
    partition_ids = [Int64[] for m in model.submodels]    # TODO -- Tuple?

    for s = 1:length(spikes)
        for p = 1:model.num_partitions
            if split_points[p] <= spikes[s].timestamp <= split_points[p + 1]
                push!(spk_partition[p], spikes[s])
                push!(assgn_partition[p], initial_assignments[s])
                push!(partition_ids[p], s)
                break
            end
        end
    end

    # Dense rank assignments within each partition.
    for p = 1:model.num_partitions
        idx = assgn_partition[p] .> 0 # ignore background spikes.
        assgn_partition[p][idx] .= denserank(assgn_partition[p][idx])
    end

    # Pass assignments to submodels
    for p in 1:model.num_partitions
        recompute!(
            model.submodels[p],
            spk_partition[p],
            assgn_partition[p],
        )
    end

    # Save spike assignments over samples.
    assignments = initial_assignments
    collect_assignments!(model, assignments, assgn_partition, partition_ids) # updates assignments.

    # Order to iterate over spikes.
    spike_order_partition = Tuple(
        collect(1:length(subspikes)) for subspikes in spk_partition
    )

    # ======== THINGS TO SAVE ======== #

    # number of samples to save.
    n_saved_samples = Int(round(num_samples / save_every))

    # log likelihoods.
    log_p_hist = zeros(n_saved_samples)

    # parent assignments.
    assignment_hist = zeros(
        Int64, length(spikes), n_saved_samples
    )

    # latent event assignment ids, times, types, amplitudes.
    latent_event_hist = Vector{EventSummaryInfo}[]

    # global variables (offsets, response amplitudes, etc.)
    globals_hist = SeqGlobals[]

     # ======== MAIN LOOP ======== #

    # Draw samples.
    for s = 1:num_samples

        # Update spike assignments in random order.
        Threads.@threads for p in 1:model.num_partitions
            Random.shuffle!(spike_order_partition[p])
            for i in spike_order_partition[p]
                remove_datapoint!(
                    model.submodels[p],
                    spk_partition[p][i],
                    assgn_partition[p][i]
                )
                assgn_partition[p][i] = gibbs_add_datapoint!(
                    model.submodels[p],
                    spk_partition[p][i]
                )
            end
        end

        # Pass spike assignment updates and latent event updates to primary model.
        # Latent events.
        collect_assignments!(model, assignments, assgn_partition, partition_ids)

        # Update latent events.
        gibbs_update_latents!(model.primary_model)

        # Update globals
        gibbs_update_globals!(model.primary_model, spikes, assignments)

        # No need to pass updates to submodels, since the globals
        # and events point to the same objects.

        # Store results
        if (s % save_every) == 0
            j = Int(s / save_every)

            # Save log likelihood
            log_p_hist[j] = log_like(model, spikes)

            # Save assignments.
            assignment_hist[:, j] .= assignments

            # Save latent event information.
            push!(
                latent_event_hist,
                event_list_summary(model.primary_model)
            )

            # Save global variables.
            push!(globals_hist, deepcopy(model.primary_model.globals))

            verbose && print(s, "-")
        end
    end
    verbose && println("Done")

    return (
        assignments,
        assignment_hist,
        log_p_hist,
        latent_event_hist,
        globals_hist
    )
end

"""
Collects assignments from submodels into `assignments` vector
and populates `model.primary_model.sequence_events` with
submodel's events.
"""
function collect_assignments!(
        model::DistributedSeqModel,
        assignments::Vector{Int64},
        assgn_partition::Vector{Vector{Int64}},
        partition_ids::Vector{Vector{Int64}}
    )

    empty!(model.primary_model.sequence_events.events)
    empty!(model.primary_model.sequence_events.indices)
    event_id = 1
    event_map = Dict{Tuple{Int64,Int64},Int64}()

    # Create flat assignment index for each event, to be used
    # in `primary_model`.
    for p = 1:model.num_partitions

        # Iterate over event indices in submodel-p.
        for ind in model.submodels[p].sequence_events.indices
            event_map[(p, ind)] = event_id
            push!(
                model.primary_model.sequence_events.events,
                model.submodels[p].sequence_events[ind]
            )
            push!(
                model.primary_model.sequence_events.indices,
                event_id
            )
            event_id += 1
        end

        # Assign each spike in `primary_model` using the
        # event ids generated above.
        for (s, assgn) in enumerate(assgn_partition[p])
            if assgn == -1
                assignments[partition_ids[p][s]] = -1
            else
                assignments[partition_ids[p][s]] = event_map[(p, assgn)]
            end
        end

    end
end


using Base

# referred base types or modules
import Base:    Indices, CommonReduceResult, SmallSigned, SmallUnsigned,
                OneTo, Bottom, Generator, BitInteger_types, Broadcast,
                ReshapedArray, SparseArrays

# related to traits
import Base:    HasLength, HasShape, SizeUnknown, HasEltype, EltypeUnknown,
                IteratorSize

# they are used only
import Base:    scalarmin, scalarmax, uniontypes, arraylen, sizeof, safe_tail, tail,
                indices1, getindex, setindex!, setindex_shape_check, one, zero

# those are re-defined and overwritten
import Base:    sum, sum!, prod, prod!, minimum, minimum!, maximum, maximum!,
                all, all!, any, any!,
                convert, copy, copy!, size, length, _length,
                reducedim, mapreduce_impl,
                findmin, findmin!, findmax, findmax!

import Base.==

# macros
import Base:    @_inline_meta, @_noinline_meta, @_propagate_inbounds_meta


using Colors
using ShaderAbstractions: InstancedProgram, Program
using AbstractPlotting: Key, plotkey
using Colors: N0f8

AbstractPlotting.plotkey(::Nothing) = :scatter

function lift_convert(key, value, plot)
    val = lift(value) do value
        return wgl_convert(value, Key{key}(), Key{plotkey(plot)}())
    end
    if key == :colormap && val[] isa AbstractArray
        return ShaderAbstractions.Sampler(val)
    else
        return val
    end
end

function Base.pairs(mesh::GeometryBasics.Mesh)
    return (kv for kv in GeometryBasics.attributes(mesh))
end

function GeometryBasics.faces(x::VertexArray)
    return GeometryBasics.faces(getfield(x, :data))
end

tlength(T) = length(T)
tlength(::Type{<:Real}) = 1

serialize_three(val::Number) = val
serialize_three(val::Vec2f0) = convert(Vector{Float32}, val)
serialize_three(val::Vec3f0) = convert(Vector{Float32}, val)
serialize_three(val::Vec4f0) = convert(Vector{Float32}, val)
serialize_three(val::Quaternion) = convert(Vector{Float32}, collect(val.data))
serialize_three(val::RGB) = Float32[red(val), green(val), blue(val)]
serialize_three(val::RGBA) = Float32[red(val), green(val), blue(val), alpha(val)]
serialize_three(val::Mat4f0) = vec(val)
serialize_three(val::Mat3) = vec(val)

function serialize_three(observable::Observable)
    return Dict(:type => "Observable", :id => observable.id,
                :value => serialize_three(observable[]))
end

function serialize_three(array::AbstractArray)
    return serialize_three(flatten_buffer(array))
end

function serialize_three(array::Buffer)
    return serialize_three(flatten_buffer(array))
end

function serialize_three(array::AbstractArray{UInt8})
    return Dict(:type => "Uint8Array", :data => array)
end

function serialize_three(array::AbstractArray{Int32})
    return Dict(:type => "Int32Array", :data => array)
end

function serialize_three(array::AbstractArray{UInt32})
    return Dict(:type => "Uint32Array", :data => array)
end

function serialize_three(array::AbstractArray{Float32})
    return Dict(:type => "Float32Array", :data => array)
end

function serialize_three(array::AbstractArray{Float16})
    return Dict(:type => "Float32Array", :data => array)
end

function serialize_three(array::AbstractArray{Float64})
    return Dict(:type => "Float64Array", :data => array)
end

function serialize_three(color::Sampler{T,N}) where {T,N}
    tex = Dict(:type => "Sampler", :data => serialize_three(color.data),
               :size => [size(color.data)...], :three_format => three_format(T),
               :three_type => three_type(eltype(T)),
               :minFilter => three_filter(color.minfilter),
               :magFilter => three_filter(color.magfilter),
               :wrapS => three_repeat(color.repeat[1]), :anisotropy => color.anisotropic)
    if N > 1
        tex[:wrapT] = three_repeat(color.repeat[2])
    end
    if N > 2
        tex[:wrapR] = three_repeat(color.repeat[3])
    end
    return tex
end

function serialize_uniforms(dict::Dict)
    result = Dict{Symbol,Any}()
    for (k, v) in dict
        result[k] = serialize_three(to_value(v))
    end
    return result
end

three_format(::Type{<:Real}) = "RedFormat"
three_format(::Type{<:RGB}) = "RGBFormat"
three_format(::Type{<:RGBA}) = "RGBAFormat"

three_type(::Type{Float16}) = "FloatType"
three_type(::Type{Float32}) = "FloatType"
three_type(::Type{N0f8}) = "UnsignedByteType"

function three_filter(sym)
    sym == :linear && return "LinearFilter"
    return sym == :nearest && return "NearestFilter"
end

function three_repeat(s::Symbol)
    s == :clamp_to_edge && return "ClampToEdgeWrapping"
    s == :mirrored_repeat && return "MirroredRepeatWrapping"
    return s == :repeat && return "RepeatWrapping"
end

"""
    flatten_buffer(array::AbstractArray)
Flattens `array` array to be a 1D Vector of Float32 / UInt8.
If presented with AbstractArray{<: Colorant/Tuple/SVector}, it will flatten those
to their element type.
"""
function flatten_buffer(array::AbstractArray{<: Number})
    return array
end

function flatten_buffer(array::Buffer)
    return flatten_buffer(getfield(array, :data))
end

function flatten_buffer(array::AbstractArray{T}) where {T<:N0f8}
    return reinterpret(UInt8, array)
end

function flatten_buffer(array::AbstractArray{T}) where {T}
    return flatten_buffer(reinterpret(eltype(T), array))
end

lasset(paths...) = read(joinpath(@__DIR__, "..", "assets", paths...), String)

isscalar(x::StaticArrays.StaticArray) = true
isscalar(x::AbstractArray) = false
isscalar(x::Observable) = isscalar(x[])
isscalar(x) = true

function ShaderAbstractions.type_string(::ShaderAbstractions.AbstractContext,
                                        ::Type{<:AbstractPlotting.Quaternion})
    return "vec4"
end

function ShaderAbstractions.convert_uniform(::ShaderAbstractions.AbstractContext,
                                            t::Quaternion)
    return convert(Quaternion, t)
end

function wgl_convert(value, key1, key2)
    val = AbstractPlotting.convert_attribute(value, key1, key2)
    return if val isa AbstractArray{<:Float64}
        return AbstractPlotting.el32convert(val)
    else
        return val
    end
end

function wgl_convert(value::AbstractMatrix, ::key"colormap", key2)
    return ShaderAbstractions.Sampler(value)
end

function serialize_buffer_attribute(buffer::AbstractVector{T}) where {T}
    return Dict(:flat => serialize_three(buffer), :type_length => tlength(T))
end

function serialize_named_buffer(buffer)
    return Dict(map(pairs(buffer)) do (name, buff)
                    return name => serialize_buffer_attribute(buff)
                end)
end

function register_geometry_updates(update_buffer::Observable, named_buffers)
    for (name, buffer) in pairs(named_buffers)
        if buffer isa Buffer
            on(ShaderAbstractions.updater(buffer).update) do (f, args)
                # update to replace the whole buffer!
                if f === (setindex!) && args[1] isa AbstractArray && args[2] isa Colon
                    new_array = args[1]
                    flat = flatten_buffer(new_array)
                    update_buffer[] = [name, serialize_three(flat), length(new_array)]
                end
                return
            end
        end
    end
    return update_buffer
end

function register_geometry_updates(update_buffer::Observable, program::Program)
    return register_geometry_updates(update_buffer, program.vertexarray)
end

function register_geometry_updates(update_buffer::Observable, program::InstancedProgram)
    return register_geometry_updates(update_buffer, program.per_instance)
end

function uniform_updater(uniforms::Dict)
    updater = Observable(Any[:none, []])
    for (name, value) in uniforms
        if value isa Sampler
            on(ShaderAbstractions.updater(value).update) do (f, args)
                if args[2] isa Colon && f == setindex!
                    updater[] = [name, serialize_three(args[1])]
                end
                return
            end
        else
            value isa Observable || continue
            on(value) do value
                updater[] = [name, serialize_three(value)]
                return
            end
        end
    end
    return updater
end

function serialize_three(ip::InstancedProgram)
    program = serialize_three(ip.program)
    program[:instance_attributes] = serialize_named_buffer(ip.per_instance)
    register_geometry_updates(program[:attribute_updater], ip)
    return program
end

function serialize_three(program::Program)
    indices = GeometryBasics.faces(program.vertexarray)
    indices = reinterpret(UInt32, indices)
    uniforms = serialize_uniforms(program.uniforms)
    attribute_updater = Observable(["", [], 0])
    register_geometry_updates(attribute_updater, program)
    return Dict(:vertexarrays => serialize_named_buffer(program.vertexarray),
                :faces => indices, :uniforms => uniforms,
                :vertex_source => program.vertex_source,
                :fragment_source => program.fragment_source,
                :uniform_updater => uniform_updater(program.uniforms),
                :attribute_updater => attribute_updater)
end

function serialize_scene(scene::Scene, serialized_scenes=[])
    hexcolor(c) = "#" * hex(Colors.color(to_color(c)))
    pixel_area = lift(area -> [minimum(area)..., widths(area)...], pixelarea(scene))
    cam_controls = cameracontrols(scene)
    cam3d_state = if cam_controls isa Camera3D
        fields = (:lookat, :upvector, :eyeposition, :fov, :near, :far)
        Dict((f => serialize_three(getfield(cam_controls, f)[]) for f in fields))
    else
        nothing
    end
    serialized = Dict(:pixelarea => pixel_area,
                      :backgroundcolor => lift(hexcolor, scene.backgroundcolor),
                      :clearscene => scene.clear,
                      :camera => serialize_camera(scene),
                      :plots => serialize_plots(scene, scene.plots),
                      :cam3d_state => cam3d_state,
                      :visible => scene.visible,
                      :uuid => js_uuid(scene))

    push!(serialized_scenes, serialized)
    foreach(child -> serialize_scene(child, serialized_scenes), scene.children)

    return serialized_scenes
end

function serialize_plots(scene::Scene, plots::Vector{T}, result=[]) where {T<:AbstractPlot}
    for plot in plots
        # if no plots inserted, this truely is an atomic
        if isempty(plot.plots)
            plot_data = serialize_three(scene, plot)
            plot_data[:uuid] = js_uuid(plot)
            push!(result, plot_data)
        else
            serialize_plots(scene, plot.plots, result)
        end
    end
    return result
end

function serialize_three(scene::Scene, plot::AbstractPlot)
    program = create_shader(scene, plot)
    mesh = serialize_three(program)
    mesh[:name] = string(AbstractPlotting.plotkey(plot)) * "-" * string(objectid(plot))
    mesh[:visible] = plot.visible
    mesh[:uuid] = js_uuid(plot)
    uniforms = mesh[:uniforms]
    updater = mesh[:uniform_updater]

    delete!(uniforms, :lightposition)

    if haskey(plot, :lightposition)
        eyepos = scene.camera.eyeposition
        lightpos = lift(plot.lightposition, eyepos,
                        typ=Vec3f0) do pos, eyepos
            return ifelse(pos == :eyeposition, eyepos, pos)::Vec3f0
        end
        uniforms[:lightposition] = serialize_three(lightpos[])
        on(lightpos) do value
            updater[] = [:lightposition, serialize_three(value)]
            return
        end
    end
    if haskey(plot, :space)
        mesh[:space] = plot.space[]
    end

    return mesh
end

function serialize_camera(scene::Scene)
    cam = scene.camera
    return lift(cam.view, cam.projection, cam.resolution) do v, p, res
        # projectionview updates with projection & view
        pv = cam.projectionview[]
        # same goes for eyeposition, since an eyepos change will trigger
        # a view matrix change!
        ep = cam.eyeposition[]
        pixel_space = cam.pixel_space[]
        return [serialize_three.((v, p, pv, res, ep, pixel_space))...]
    end
end


module Test10

import Automa
import Automa.RegExp: @re_str
const re = Automa.RegExp
using Test

@testset "Test10" begin
    machine = Automa.compile(re.primitive(0x61))
    @test Automa.execute(machine, "a")[1] == 0
    @test Automa.execute(machine, "b")[1] < 0

    machine = Automa.compile(re.primitive(0x61:0x62))
    @test Automa.execute(machine, "a")[1] == 0
    @test Automa.execute(machine, "b")[1] == 0
    @test Automa.execute(machine, "c")[1] < 0

    machine = Automa.compile(re.primitive('a'))
    @test Automa.execute(machine, "a")[1] == 0
    @test Automa.execute(machine, "b")[1] < 0

    machine = Automa.compile(re.primitive('樹'))
    @test Automa.execute(machine, "樹")[1] == 0
    @test Automa.execute(machine, "儒")[1] < 0

    machine = Automa.compile(re.primitive("ジュリア"))
    @test Automa.execute(machine, "ジュリア")[1] == 0
    @test Automa.execute(machine, "パイソン")[1] < 0

    machine = Automa.compile(re.primitive([0x61, 0x62, 0x72]))
    @test Automa.execute(machine, "abr")[1] == 0
    @test Automa.execute(machine, "acr")[1] < 0

    machine = Automa.compile(re"[^A-Z]")
    @test Automa.execute(machine, "1")[1] == 0
    @test Automa.execute(machine, "A")[1] < 0
    @test Automa.execute(machine, "a")[1] == 0

    machine = Automa.compile(re"[A-Z]+" & re"FOO?")
    @test Automa.execute(machine, "FO")[1] == 0
    @test Automa.execute(machine, "FOO")[1] == 0
    @test Automa.execute(machine, "foo")[1] < 0

    machine = Automa.compile(re"[A-Z]+" \ re"foo")
    @test Automa.execute(machine, "FOO")[1] == 0
    @test Automa.execute(machine, "foo")[1] < 0

    machine = Automa.compile(!re"foo")
    @test Automa.execute(machine, "bar")[1] == 0
    @test Automa.execute(machine, "foo")[1] < 0

    machine = Automa.compile(re.rep("a"))
    @test Automa.execute(machine, "aaa")[1] == 0
    @test Automa.execute(machine, "aYa")[1] < 0
end

end


"""
PENBBScan2D()
Performs a scan moving in x-y axis
...
Function to perform an automate scan in 2D (x,y axis)
This function requires as input the starting point (x_start,y_start), and the end point (x_ends,y_ends) as well
as the step sizes in both axis(step_x,step_y). All values are in mm. The range specified muss be in the interval x[0.,10.], y[0.,10.]
In addition you can specify the name of the holder or sample and time of data taking for each position
PENBBScan2D(x_start, y_start, step_x, step_y, x_ends, y_ends, HolderName::String, time_per_point)
Example: PENBBScan2D(0.0,0.0,20.0,20.0,40.0,40.0,"small",2)
...
# Arguments
- x_start: intial point in x
- y_start: intial point in y
- step_x: step size in x
- step_y: step size in y
- x_ends: final point in x
- y_ends: final point in y
- HolderName::String: name of the holder of piece you are scanning
- time_per_point: time of data taking in each point
...
"""
function PENBBScan2D(x_start, y_start, step_x, step_y, x_ends, y_ends, HolderName::String, time_per_point)
    if x_start < 0.0 || y_start < 0.0 || x_ends > 100.0 || y_ends > 100.
        @info("Error: value out of range: you muss use values in the range x[0.,10.], y[0.,10.]")
    else
	motor = mymotor()
	Calibrate(motor)
        for i in  collect(x_start:step_x:x_ends)
            XMoveMM(i,motor)
            for j in collect(y_start:step_y:y_ends)
                @info("position: ",i,j)
                YMoveMM(j,motor)
                pos_x = string(PosX(motor))
                pos_y = string(PosY(motor))
                name_file = string("2D_PEN_Scan_Holder_",HolderName,"_x_",pos_x,"_y_",pos_y)
                settings = (fadc = "gelab-fadc08",
                    output_basename = name_file,
                    data_dir = "./",
                    conv_data_dir = "./",
                    measurement_time = time_per_point,
                    number_of_measurements = 1,
                    channels = [1,2,3,4,5,6,7],
                    trigger_threshold = 45,
                    trigger_pmt = 5,
                    peakTime = 2,
                    gapTime = 2,
                    nPreTrig = 70,
                    nSamples = 128,
                    saveEnergy = false,
                    delete_dat = true,
                    h5_filesize_limit = 1000,
                    filter_faulty_events = false,
                    coincidence_interval = 4e-9
                );
                println(name_file)
                take_struck_data(settings, calibration_data=false)
                sleep(2)
            end
        end
        @info("PEN BB 2D scan completed, see you soon!")
    end

end

"""
PENBBScan1DY()
Performs a scan moving in y axis
...
Function to perform an automate scan in 1D (y axis) keeping x constant
This function requires as input the starting point (x_start,y_start), and the end point (x_start,y_ends) as well
as the step size(step_y). All values are in mm. The range specified muss be in the interval x[0.,10.], y[0.,10.]
In addition you can specify the name of the holder or sample and time of data taking for each position
...
# Arguments
- x_start: intial point in x
- y_start: intial point in y
- step_y: step size in y
- y_ends: final point in y
- HolderName::String: name of the holder of piece you are scanning
- time_per_point: time of data taking in each point
...
"""
function PENBBScan1DY(x_start, y_start, step_y, y_ends, HolderName::String, time_per_point)
    if x_start < 0.0 || y_start < 0.0 || x_start > 100.0 || y_ends > 100.
        @info("Error: value out of range: you muss use soem value in the range x[0.,10.], y[0.,10.]")
    else
	motor = mymotor()
	Calibrate(motor)
        XMoveMM(x_start,motor)
        for j in collect(y_start:step_y:y_ends)
            @info("position: ",x_start,j)
            YMoveMM(j,motor)
            pos_x = string(PosX(motor))
            pos_y = string(PosY(motor))
            name_file = string("2D_PEN_Scan_Holder_",HolderName,"_x_",pos_x,"_y_",pos_y)
            settings = (fadc = "gelab-fadc08",
                    output_basename = name_file,
                    data_dir = "./",
                    conv_data_dir = "./",
                    measurement_time = time_per_point,
                    number_of_measurements = 1,
                    channels = [1,2,3,4,5,6,7],
                    trigger_threshold = 45,
                    trigger_pmt = 5,
                    peakTime = 2,
                    gapTime = 2,
                    nPreTrig = 70,
                    nSamples = 128,
                    saveEnergy = false,
                    delete_dat = true,
                    h5_filesize_limit = 1000,
                    filter_faulty_events = false,
                    coincidence_interval = 4e-9
            );
            println(name_file)
            take_struck_data(settings, calibration_data=false)
            sleep(2)
        end
        @info("PEN 1D BB scan completed, see you soon!")
    end

end

"""
PENBBScan1DX()
Performs a scan moving in x axis
...
Function to perform an automate scan in 1D (x axis) keeping y constant
This function requires as input the starting point (y_start,x_start), and the end point (y_start,x_ends) as well
as the step size(step_x). All values are in mm. The range specified muss be in the interval x[0.,10.], y[0.,10.]
In addition you can specify the name of the holder or sample and time of data taking for each position
...
# Arguments
- x_start: intial point in x
- y_start: intial point in y
- step_x: step size in y
- x_ends: final point in y
- HolderName::String: name of the holder of piece you are scanning
- time_per_point: time of data taking in each point
...
"""

function PENBBScan1DX(y_start, x_start, step_x, x_ends, HolderName::String, time_per_point)
    if x_start < 0.0 || y_start < 0.0 || x_start > 100.0 || y_start > 100.
        @info("Error: value out of range: you muss use soem value in the range x[0.,10.], y[0.,10.]")
    else
	motor = mymotor()
	Calibrate(motor)
        YMoveMM(y_start,motor)
        for i in collect(x_start:step_x:x_ends)
            @info("position: ",i,y_start)
            XMoveMM(i,motor)
            pos_x = string(PosX(motor))
            pos_y = string(PosY(motor))
            name_file = string("2D_PEN_Scan_Holder_",HolderName,"_x_",pos_x,"_y_",pos_y)
            settings = (fadc = "gelab-fadc08",
                    output_basename = name_file,
                    data_dir = "./",
                    conv_data_dir = "./",
                    measurement_time = time_per_point,
                    number_of_measurements = 1,
                    channels = [1,2,3,4,5,6,7],
                    trigger_threshold = 45,
                    trigger_pmt = 5,
                    peakTime = 2,
                    gapTime = 2,
                    nPreTrig = 70,
                    nSamples = 128,
                    saveEnergy = false,
                    delete_dat = true,
                    h5_filesize_limit = 1000,
                    filter_faulty_events = false,
                    coincidence_interval = 4e-9
            );
            println(name_file)
            take_struck_data(settings, calibration_data=false)
            sleep(2)
        end
        @info("PEN BB 1D scan completed, see you soon!")
    end

end


export add_negate, add_round_off

function add_negate(mgr::Ptr{Manager}, f::Ptr{Node})
    result = Cudd_addNegate(mgr, f)
    if result == C_NULL
        throw(OutOfMemoryError())
    end
    return result
end

function add_round_off(mgr::Ptr{Manager}, f::Ptr{Node}, n::Int)
    result = Cudd_addRoundOff(mgr, f, n)
    if result == C_NULL
        throw(OutOfMemoryError())
    end
    return result
end


## Exercise 2-2
## Repeating my advice from the previous chapter, whenever you learn a new feature, you should try it out in interactive mode and make errors on purpose to see what goes wrong.

## 1. We’ve seen that n = 42 is legal. What about 42 = n?
println("Ans 1:")
# 42 = n  # Error: syntax: invalid assignment location "42"

## 2. How about x = y = 1?
println("Ans 2:")
x = y = 1  # works fine x and y get assigned 1

## 3. In some languages every statement ends with a semi-colon, ;. What happens if you put a semi-colon at the end of a Julia statement?
println("Ans 3:")
x = 17;  # when in interactive environment it does not print the value, in script no change.

## 4. What if you put a period at the end of a statement?
println("Ans 4:")
x + 6.  # working fine
# x + y.  # Error syntax incomplete OR Load Error type Int64 has no field println

## In math notation you can multiply x and y like this: x y. What happens if you try that in Julia? What about 5x?
println("Ans 5:")
x = 3
y = 4
println(5x)  # works, 'yey
# println(xy)  # error UndefVarError, as xy will be different from x * y
# println(x y)  # error syntax, missing `,` or `)`
println(x * y)

println("End.")


devices = Dict{Symbol, DeviceModel}(:Generators => DeviceModel(PSY.ThermalStandard, ThermalDispatch),
                                    :Loads =>  DeviceModel(PSY.PowerLoad, StaticPowerLoad))
branches = Dict{Symbol, DeviceModel}(:L => DeviceModel(PSY.Line, StaticLineUnbounded))
services = Dict{Symbol, ServiceModel}()

@testset "Operation set ref models" begin
    model_ref = ModelReference(CopperPlatePowerModel, devices, branches, services);
    op_model = OperationModel(TestOptModel, model_ref, c_sys5)
    set_transmission_ref!(op_model, DCPLLPowerModel)
    @test op_model.model_ref.transmission == DCPLLPowerModel

    model_ref = ModelReference(CopperPlatePowerModel, devices, branches, services);
    op_model = OperationModel(TestOptModel, model_ref, c_sys5)
    new_devices = Dict{Symbol, DeviceModel}(:Generators => DeviceModel(PSY.ThermalStandard, ThermalBasicUnitCommitment),
                                            :Loads =>  DeviceModel(PSY.PowerLoad, StaticPowerLoad))
    set_devices_ref!(op_model, new_devices)
    @test op_model.model_ref.devices[:Generators].formulation == ThermalBasicUnitCommitment
    jump_model = op_model.canonical.JuMPmodel
    @test ((JuMP.VariableRef, MOI.ZeroOne) in JuMP.list_of_constraint_types(jump_model)) == true

    model_ref = ModelReference(DCPPowerModel, devices, branches, services);
    op_model = OperationModel(TestOptModel, model_ref, c_sys5)
    new_branches = Dict{Symbol, DeviceModel}(:L => DeviceModel(PSY.Line, StaticLine))
    set_branches_ref!(op_model, new_branches)
    @test op_model.model_ref.branches[:L].formulation == StaticLine
end

@testset "Operation set models" begin
    model_ref = ModelReference(CopperPlatePowerModel, devices, branches, services);
    op_model = OperationModel(TestOptModel, model_ref, c_sys5)
    set_device_model!(op_model, :Generators, DeviceModel(PSY.ThermalStandard, ThermalBasicUnitCommitment))
    @test op_model.model_ref.devices[:Generators].formulation == ThermalBasicUnitCommitment
    jump_model = op_model.canonical.JuMPmodel
    @test ((JuMP.VariableRef, MOI.ZeroOne) in JuMP.list_of_constraint_types(jump_model)) == true

    model_ref = ModelReference(DCPPowerModel, devices, branches, services);
    op_model = OperationModel(TestOptModel, model_ref, c_sys5)
    set_branch_model!(op_model, :L, DeviceModel(PSY.Line, StaticLine))
    @test op_model.model_ref.branches[:L].formulation == StaticLine
end


push!(LOAD_PATH, "/Users/megorov/Desktop/projects/stanford/sniper/egorov/sniperRepo/new-version")

using Collada_
using UrbanMap_
using SniperPOMDP_
using SniperMDP_
using ValueIterations
using Helpers
using RayCaster_
using Simulator
using SimServers
using PolicyAssignment

map = UrbanMap(1, [0.5], [0.5], [0.42], [0.22], 10, 10)
mapPath = "/Users/megorov/Desktop/projects/stanford/sniper/egorov/sniperRepo/new-version/maps/demo_map_border2D.dae"
coll = ColladaObjects(mapPath)
#map = UrbanMap(coll, 15, 15) # 15x15 map

#t   = [(2.0,8.0), (2.0,1.0), (5.0,2.0), (7.0,3.0)]
t   = [(2.0,8.0), (2.0,1.0)]
#t   = [(7.0,7.0), (2.0,14.0), (8.0,4.0), (11.0,13.0)]
mdp = SniperMDP(map, nSnipers = 1, nMonitors = 2, targets=t)

p1 = load_policy("policies/mdp-1.policy")
p2 = load_policy("policies/mdp-2.policy")
p3 = load_policy("policies/mdp-3.policy")
p4 = load_policy("policies/mdp-4.policy")

#policies = merge(p1.policy,p2.policy,p3.policy,p4.policy)
policies = merge(p1.policy,p2.policy)
#values   = merge(p1.V,p2.V,p3.V,p4.V)
values   = merge(p1.V,p2.V)

policy = AssignmentPolicy(2, policies, values, mdp)

# create the simulation type
#init  = 8_888_888_803 # sniper at (3,1), all monitors at (9,9) for 10x10 4 agent world
#init = 97 # sniper at (7,7), all monitors at (1,1) for 15x15 4 agent world
#init = 20_583_164_347 # sniper at (7,7), all monitors at (1,i) for 15x15 4 agent world
init = 480_872
sim  = MDPSimulation(mdp, init, 100)

# set up the server
socket = 4444
server = SniperServer(socket)

# begin simulation and send results to client
#simAndSend(server, sim, mdp, policy)

# begin batch sim
n = 10
rewards = zeros(n)
shot    = zeros(Int64, n)
obs     = zeros(Int64, n)
for i = 1:n
    res = simulate(policy, mdp, 100, initState=init)
    rewards[i] = sum(res.rewards)
    shot[i]    = res.nKills
    obs[i]     = res.nObs
end


function _container_spec(V::DataType, ax...; kwargs...)

    parameters = get(kwargs, :parameters, true)

    # While JuMP fixes the isassigned problems
     # While JuMP fixes the isassigned problems
    #=
    if parameters
            cont = JuMP.Containers.DenseAxisArray{PGAE{V}}(undef, ax...)
            _remove_undef!(cont.data)
        return cont
    else
            cont = JuMP.Containers.DenseAxisArray{GAE{V}}(undef, ax...)
            _remove_undef!(cont.data)
        return cont
    end
    =#

    if parameters
        return JuMP.Containers.DenseAxisArray{PGAE{V}}(undef, ax...)
    else
        return JuMP.Containers.DenseAxisArray{GAE{V}}(undef, ax...)
    end

end

function _canonical_model_init(bus_numbers::Vector{Int64},
                              optimizer::Union{Nothing,JuMP.OptimizerFactory},
                              transmission::Type{S},
                              time_range::UnitRange{Int64}; kwargs...) where {S <: PM.AbstractPowerFormulation}

    parameters = get(kwargs, :parameters, true)

    jump_model = _pass_abstract_jump(optimizer; kwargs...)
    V = JuMP.variable_type(jump_model)

    ps_model = CanonicalModel(jump_model,
                            Dict{Symbol, JuMP.Containers.DenseAxisArray}(),
                            Dict{Symbol, JuMP.Containers.DenseAxisArray}(),
                            zero(JuMP.GenericAffExpr{Float64, V}),
                            Dict{Symbol, JuMP.Containers.DenseAxisArray}(:var_active => _container_spec(V, bus_numbers, time_range; kwargs...),
                                                                         :var_reactive => _container_spec(V, bus_numbers, time_range; kwargs...)),
                            parameters ? Dict{Symbol,JuMP.Containers.DenseAxisArray}() : nothing,
                            Dict{Symbol,Array{InitialCondition}}(),
                            nothing);

    return ps_model

end

function _canonical_model_init(bus_numbers::Vector{Int64},
                               optimizer::Union{Nothing,JuMP.OptimizerFactory},
                               transmission::Type{S},
                               time_range::UnitRange{Int64}; kwargs...) where {S <: PM.AbstractActivePowerFormulation}

    parameters = get(kwargs, :parameters, true)

    jump_model = _pass_abstract_jump(optimizer; kwargs...)
    V = JuMP.variable_type(jump_model)

    ps_model = CanonicalModel(jump_model,
                              Dict{Symbol, JuMP.Containers.DenseAxisArray}(),
                              Dict{Symbol, JuMP.Containers.DenseAxisArray}(),
                              zero(JuMP.GenericAffExpr{Float64, V}),
                              Dict{Symbol, JuMP.Containers.DenseAxisArray}(:var_active => _container_spec(V, bus_numbers, time_range; kwargs...)),
                              parameters ? Dict{Symbol,JuMP.Containers.DenseAxisArray}() : nothing,
                              Dict{Symbol,Array{InitialCondition}}(),
                              nothing);

    return ps_model

end

function  build_canonical_model(transmission::Type{T},
                                devices::Dict{Symbol, DeviceModel},
                                branches::Dict{Symbol, DeviceModel},
                                services::Dict{Symbol, ServiceModel},
                                system::PSY.System,
                                optimizer::Union{Nothing,JuMP.OptimizerFactory}=nothing;
                                kwargs...) where {T <: PM.AbstractPowerFormulation}

time_range = 1:system.time_periods
bus_numbers = [b.number for b in system.buses]

ps_model = _canonical_model_init(bus_numbers, optimizer, transmission, time_range; kwargs...)

# Build Injection devices
for mod in devices
    construct_device!(ps_model, mod[2], transmission, system, time_range; kwargs...)
end

# Build Network
construct_network!(ps_model, transmission, system, time_range; kwargs...)

# Build Branches
for mod in branches
    construct_device!(ps_model, mod[2], transmission, system, time_range; kwargs...)
end

#Build Service
for mod in services
    construct_service!(ps_model, mod[2], transmission, system, time_range; kwargs...)
end

# Objective Function
JuMP.@objective(ps_model.JuMPmodel, Min, ps_model.cost_function)

return ps_model

end