# # # RAMI Benchmarking
#  The goal of the RAMI4PILPS experiment[^1] is to evaluate different approaches by which Land Surface Models in larger Earth System Models quantify the radiation transfer within vegetation canopies. The RAMI4PILPS can be interpreted as a quality control mechanism used to:
#
# 1) quantify the errors in the radiative transfer scheme;
#
# 2) identify the impact that structural and spectral sub-grid variability may have on radiative transfer; and
#
# 3) verify the conservation of energy at the level of the surface, as well as inconsistencies arising from different levels of assumptions/simplifications.
#
# This approach involves direct comparison with reference solutions obtained from highly accurate 3D models identified during the third phase of the RAMI benchmarking exercise[^2].
#
# A set of 3D experiments compares the partitioning of incident solar energy into an absorbed (A) flux, a transmitted (T) flux component and the surface reflectance (R). The overall canopy structure for these test cases is reminiscent of open forest canopies with randomly oriented foliage, confined to spherical volumes located at varying heights above the ground.
# In here, we use a total of 36 test cases including various canopy density, soil brightness, and illumination conditions for the visible (VIS) (400-700nm) and near infra-red (NIR) (700-3000nm) spectral ranges.
#
#  [^1]: J.L. Widlowski, B. Pinty, M. Clerici, Y. Dai, M. De Kauwe, K. de Ridder, A. Kallel, H. Kobayashi, T. Lavergne, W. Ni-Meister, A. Olchev, T. Quaife, S. Wang, W. Yang, Y. Yang, and H. Yuan (2011), RAMI4PILPS: An intercomparison of formulations for the partitioning of solar radiation in land surface models, Journal of Geophysical Research, 116, G02019, 25, DOI: 10.1029/2010JG001511.
#
#  [^2]: Widlowski, J-L., M. Taberner, B. Pinty, V. Bruniquel-Pinel, M. Disney, R. Fernandes, J.-P. Gastellu-Etchegorry, N. Gobron, A. Kuusk, T. Lavergne, S. Leblanc, P. Lewis, E. Martin, M. Mottus, P. J. R. North, W. Qin, M.Robustelli, N. Rochdi, R.Ruiloba, C.Soler, R.Thompson, W. Verhoef, M. M.Verstraete, and D. Xie (2007), 'The third RAdiation transfer Model Intercomparison (RAMI) exercise: Documenting progress in canopy reflectance modelling', Journal of Geophysical Research, 112, D09111, 28, DOI: 10.1029/2006JD007821.
#

#### Use Julia Plots package and switch to plotly js option:
using PyPlot

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

## First, we include Revise (good for debugging) and Parameters (tools for structures)

##using Revise
using Parameters
##----------------------------------------------------------------------------

## Now include the Land modules

using Land
using Land.CanopyRT
##----------------------------------------------------------------------------

##Defining all reference values for the Sparse case

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

const FT = Float32

wl_set = create_wl_para_set(FT)
leaf = create_leaf_bio(FT, wl_set.nwl, wl_set.nWlE, wl_set.nWlF);
canopy_rt = Canopy4RT{FT, 20, 3.0}()
canRad_rt = CanopyRadiation{FT, wl_set.nwl, wl_set.nWlF, length(canopy_rt.litab), length(canopy_rt.lazitab), canopy_rt.nlayers}()
canOpt_rt = create_canopy_optical(FT, wl_set.nwl, canopy_rt.nlayers, length(canopy_rt.lazitab), length(canopy_rt.litab); using_marray=false)
sunRad_rt = create_incoming_radiation(FT, wl_set.swl);

soil = SoilOpti{FT}(wl_set.wl, FT(0.2)*ones(FT, length(wl_set.wl)), FT[0.1], FT(290.0))
angles = SolarAngles{FT}()

arrayOfLeaves = [create_leaf_bio(FT, wl_set.nwl, wl_set.nWlE, wl_set.nWlF) for i in 1:canopy_rt.nlayers]
for i in 1:canopy_rt.nlayers
    fluspect!(arrayOfLeaves[i], wl_set)
end

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

RAMI_SZA = [27.,60.,83.]

RAMI_fabsRed_050_BLK =  [0.09380509999999999, 0.16259713, 0.53931207]
RAMI_frefRed_050_BLK =  [0.00330673, 0.00517598, 0.01626682]
RAMI_ftranRed_050_BLK =  [0.90288817, 0.83222689, 0.44442110999999995]

RAMI_fabsRed_050_MED =  [0.10897124, 0.17760124000000002, 0.54764719]
RAMI_frefRed_050_MED =  [0.09759354, 0.09107608, 0.06177913]
RAMI_ftranRed_050_MED =  [0.90337609, 0.83265704, 0.44469279]

RAMI_fabsRed_050_SNW =  [0.21471034, 0.28200132, 0.60564705]
RAMI_frefRed_050_SNW =  [0.7526521700000001, 0.6879087300000001, 0.37825442000000004]
RAMI_ftranRed_050_SNW =  [0.90659694, 0.83583194, 0.44718138]
#----------------------------------------------------------------------------

function RAMI_case(LAI, soil_albedo, clumping_index)

  soil.albedo_SW[:] .=soil_albedo;
  ##Clumping index
  canopy_rt.Ω = 1.0
  ##Viewing Zenith Angle in degrees
  angles.tto=0.0
  ##Leaf Area index
  canopy_rt.LAI=LAI

  reflRed_SZA = []
  absRed_SZA = []
  transRed_SZA = []

  for SZA=0.0:1:85
    angles.tts=SZA

    fluspect!(leaf, wl_set);
    compute_canopy_geometry!(canopy_rt, angles, canOpt_rt)
    compute_canopy_matrices!(arrayOfLeaves, canOpt_rt);

   ## leaf reflectance RED
   leaf.ρ_SW[28] = 0.0735
   ## leaf transmittance
   leaf.τ_SW[28]= 0.0566

    ##Setting all diffuse to zero
    sunRad_rt.E_diffuse[28] = 0.0

    simulate_short_wave!(canopy_rt, canOpt_rt, canRad_rt, sunRad_rt, soil);
    push!(reflRed_SZA, canRad_rt.alb_direct[28])
    push!(absRed_SZA, (sum(canRad_rt.netSW_shade,dims=2)[28,1].+sum(canRad_rt.netSW_sunlit,dims=2)[28,1])./(sunRad_rt.E_diffuse[28].+sunRad_rt.E_direct[28]))
    push!(transRed_SZA,  (canOpt_rt.Es_[28,end] .+ canRad_rt.E_down[28,end])./(sunRad_rt.E_diffuse[28].+sunRad_rt.E_direct[28]))
  end

  ######## Clumped Case

  reflRed_clump_SZA = []
  absRed_clump_SZA = []
  transRed_clump_SZA = []

  ##Clumping index
  canopy_rt.Ω = clumping_index

  for SZA=0.0:1:85
    angles.tts=SZA

    fluspect!(leaf, wl_set);
    compute_canopy_geometry!(canopy_rt, angles, canOpt_rt)
    compute_canopy_matrices!(arrayOfLeaves, canOpt_rt);

    simulate_short_wave!(canopy_rt, canOpt_rt, canRad_rt, sunRad_rt, soil);
    push!(reflRed_clump_SZA, canRad_rt.alb_direct[28])
    push!(absRed_clump_SZA, (sum(canRad_rt.netSW_shade,dims=2)[28,1].+sum(canRad_rt.netSW_sunlit,dims=2)[28,1])./(sunRad_rt.E_diffuse[28].+sunRad_rt.E_direct[28]))
    push!(transRed_clump_SZA,  (canOpt_rt.Es_[28,end] .+ canRad_rt.E_down[28,end])./(sunRad_rt.E_diffuse[28].+sunRad_rt.E_direct[28]))

  end

  return reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA

end;

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

#TODO nest those into a loop!

##Sparse case with black soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(0.50265, 0.0, 0.365864235);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_050_BLK)
scatter(RAMI_SZA, RAMI_fabsRed_050_BLK)
scatter(RAMI_SZA, RAMI_ftranRed_050_BLK)
title("050_BLK - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_050_BLK,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_050_BLK,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_050_BLK,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

##Sparse case with medium soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(0.50265, 0.1217, 0.365864235);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_050_MED)
scatter(RAMI_SZA, RAMI_fabsRed_050_MED)
scatter(RAMI_SZA, RAMI_ftranRed_050_MED)
title("050_MED - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_050_MED,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_050_MED,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_050_MED,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

##Sparse case with snowy soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(0.50265, 0.9640, 0.365864235);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_050_SNW)
scatter(RAMI_SZA, RAMI_fabsRed_050_SNW)
scatter(RAMI_SZA, RAMI_ftranRed_050_SNW)
title("050_SNW - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_050_SNW,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_050_SNW,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_050_SNW,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

##Defining all reference values for the Medium case

RAMI_SZA = [27.,60.,83.]

RAMI_fabsRed_150_BLK =  [0.28137804, 0.46514268999999997, 0.89063486]
RAMI_frefRed_150_BLK =  [0.00923676, 0.01379672, 0.02970703]
RAMI_ftranRed_150_BLK =  [0.7093851999999999, 0.52106059, 0.07965811]

RAMI_fabsRed_150_MED =  [0.31403827, 0.49003033, 0.89432051]
RAMI_frefRed_150_MED =  [0.06195053, 0.05151941, 0.03561715]
RAMI_ftranRed_150_MED =  [0.7104761399999999, 0.52197456, 0.07977039000000001]

RAMI_fabsRed_150_SNW =  [0.5431621799999999, 0.66519762, 0.9201217300000001]
RAMI_frefRed_150_SNW =  [0.43100610000000006, 0.31581022999999997, 0.07698033]
RAMI_ftranRed_150_SNW =  [0.71754777, 0.52755972, 0.08049832999999999]
#----------------------------------------------------------------------------

##Medium case with black soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(1.5017, 0.0, 0.405417644);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_150_BLK)
scatter(RAMI_SZA, RAMI_fabsRed_150_BLK)
scatter(RAMI_SZA, RAMI_ftranRed_150_BLK)
title("150_BLK - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_150_BLK,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_150_BLK,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_150_BLK,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

##Medium case with medium soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(1.5017, 0.1217, 0.405417644);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_150_MED)
scatter(RAMI_SZA, RAMI_fabsRed_150_MED)
scatter(RAMI_SZA, RAMI_ftranRed_150_MED)
title("150_MED - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_150_MED,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_150_MED,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_150_MED,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

##Medium case with black soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(1.5017, 0.9640, 0.405417644);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_150_SNW)
scatter(RAMI_SZA, RAMI_fabsRed_150_SNW)
scatter(RAMI_SZA, RAMI_ftranRed_150_SNW)
title("150_SNW - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_150_SNW,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_150_SNW,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_150_SNW,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

##Defining all reference values for the Dense case

RAMI_SZA = [27.,60.,83.]

RAMI_fabsRed_250_BLK =  [0.46852539, 0.70426097, 0.9461774300000001]
RAMI_frefRed_250_BLK =  [0.01445858, 0.02016963, 0.03477486]
RAMI_ftranRed_250_BLK =  [0.51701603, 0.2755694, 0.01904771]

RAMI_fabsRed_250_MED =  [0.50540545, 0.72429659, 0.94742381]
RAMI_frefRed_250_MED =  [0.03953053, 0.03315039, 0.03580858]
RAMI_ftranRed_250_MED =  [0.51811911, 0.27616192, 0.01909098]

RAMI_fabsRed_250_SNW =  [0.76512258, 0.86538802, 0.9562473199999999]
RAMI_frefRed_250_SNW =  [0.21595537, 0.124503, 0.043056080000000004]
RAMI_ftranRed_250_SNW =  [0.5256125, 0.280805, 0.01935]
#----------------------------------------------------------------------------

##Dense case with black soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(2.5007, 0.0, 0.45946608);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_250_BLK)
scatter(RAMI_SZA, RAMI_fabsRed_250_BLK)
scatter(RAMI_SZA, RAMI_ftranRed_250_BLK)
title("250_BLK - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_250_BLK,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_250_BLK,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_250_BLK,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

##Medium case with medium soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(2.5007, 0.1217, 0.45946608);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_250_MED)
scatter(RAMI_SZA, RAMI_fabsRed_250_MED)
scatter(RAMI_SZA, RAMI_ftranRed_250_MED)
title("250_MED - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_250_MED,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_250_MED,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_250_MED,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

##Dense case with snowy soil
reflRed_SZA,absRed_SZA,transRed_SZA,reflRed_clump_SZA,absRed_clump_SZA,transRed_clump_SZA=RAMI_case(2.5007, 0.9640, 0.45946608);

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

SZA=0:1:85

figure(figsize=(10,5))

subplot(1,2,1)
plot(SZA, reflRed_SZA,label=["reflectance"])
plot(SZA, absRed_SZA ,label=["absorptance"])
plot(SZA, transRed_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_250_SNW)
scatter(RAMI_SZA, RAMI_fabsRed_250_SNW)
scatter(RAMI_SZA, RAMI_ftranRed_250_SNW)
title("250_SNW - Default")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

subplot(1,2,2)
plot(SZA, reflRed_clump_SZA,label=["reflectance"])
plot(SZA, absRed_clump_SZA ,label=["absorptance"])
plot(SZA,transRed_clump_SZA,label=["transmittance"])
scatter(RAMI_SZA, RAMI_frefRed_250_SNW,label="RAMI reflectance")
scatter(RAMI_SZA, RAMI_fabsRed_250_SNW,label="RAMI absorptance")
scatter(RAMI_SZA, RAMI_ftranRed_250_SNW,label="RAMI transmittance")
title("Clumping")
ylabel("Radiation Partitioning")
xlabel("Sun Zenith Angle")
xlim(0.0, 90.)
ylim(-0.05, 1.0)
xticks(0:20:91.)
yticks(0:0.2:1.0)

legend()
gcf()

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

# # ### Test a VZA dependence in the principal plane with clumping

## Define a few wavelengths:
wl_blue = 450.0;
wl_red = 600.0;
wl_FarRed = 740.0;
wl_Red = 685.0;
ind_wle_blue  = argmin(abs.(wl_set.wle .-wl_blue));
ind_wle_red = argmin(abs.(wl_set.wle .-wl_red));
ind_wlf_FR  = argmin(abs.(wl_set.wlf .-wl_FarRed));
ind_wlf_R  = argmin(abs.(wl_set.wlf .-wl_Red));
ind_red = argmin(abs.(wl_set.wl .-wl_Red));
ind_NIR = argmin(abs.(wl_set.wl .-800));

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

SIF_FR = Float32[]
SIF_R = Float32[]
reflVIS = Float32[]
reflNIR = Float32[]

#### Just running the code over all geometries:
##MED
soil.albedo_SW[:] .=0.1217;
#### Set sun SZA to 27 degrees
angles.tts=27.
#### Set 0 azimuth (principal plane)
angles.psi=0

##Adding clumping
canopy_rt.Ω = 1.0
#### LAI of 3:
canopy_rt.LAI = 2.5007
#### Define VZA
VZA=collect(-89.5:0.5:89.5)

for VZA_ in VZA
    angles.tto=VZA_
    compute_canopy_geometry!(canopy_rt, angles, canOpt_rt)
    compute_canopy_matrices!(arrayOfLeaves, canOpt_rt);
    simulate_short_wave!(canopy_rt, canOpt_rt, canRad_rt, sunRad_rt, soil);
    computeSIF_Fluxes!(arrayOfLeaves, canOpt_rt, canRad_rt, canopy_rt, soil, wl_set);
    #### Handpicked indices in
    push!(reflVIS, canRad_rt.alb_obs[ind_red])
    push!(reflNIR, canRad_rt.alb_obs[ind_NIR])
    push!(SIF_R , canRad_rt.SIF_obs[ind_wlf_R])
    push!(SIF_FR, canRad_rt.SIF_obs[ind_wlf_FR ])
end

##Adding clumping
canopy_rt.Ω = 0.45946608

SIF_FR_clump = Float32[]
SIF_R_clump = Float32[]
reflVIS_clump = Float32[]
reflNIR_clump = Float32[]

for VZA_ in VZA
    angles.tto=VZA_
    compute_canopy_geometry!(canopy_rt, angles, canOpt_rt)
    compute_canopy_matrices!(arrayOfLeaves, canOpt_rt);
    simulate_short_wave!(canopy_rt, canOpt_rt, canRad_rt, sunRad_rt, soil);
    computeSIF_Fluxes!(arrayOfLeaves, canOpt_rt, canRad_rt, canopy_rt, soil, wl_set);
    #### Handpicked indices in
    push!(reflVIS_clump, canRad_rt.alb_obs[ind_red])
    push!(reflNIR_clump, canRad_rt.alb_obs[ind_NIR])
    push!(SIF_R_clump , canRad_rt.SIF_obs[ind_wlf_R])
    push!(SIF_FR_clump, canRad_rt.SIF_obs[ind_wlf_FR ])
end
#----------------------------------------------------------------------------

#### Plots Visible
figure()
plot(VZA, reflVIS, color=:black,label="Red Reflectance", lw=2)
plot(VZA, SIF_R/30, color=:orange,label="Red SIF (/30)", lw=2)
plot(VZA, reflVIS_clump, color=:black, ls="--", lw=2,label="Red Reflectance w/ Clumping")
plot(VZA, SIF_R_clump/30, color=:orange, ls="--", lw=2,label="Red SIF (/30) w/ Clumping")
xlabel("Viewing Zenith Angle")
legend()
gcf()
#----------------------------------------------------------------------------

#### Plots Visible
figure()
plot(VZA, reflNIR, color=:black,label="NIR Reflectance", lw=2)
plot(VZA, SIF_FR/6, color=:orange,label="Far Red SIF (/6)", lw=2)
plot(VZA, reflNIR_clump, color=:black, ls="--", lw=2,label="NIR Reflectance w/ Clumping")
plot(VZA, SIF_FR_clump/6, color=:orange, ls="--", lw=2,label="Far Red SIF (/6) w/ Clumping")
xlabel("Viewing Zenith Angle")
legend()
gcf()
#----------------------------------------------------------------------------

# # ## BRDF sampling
#
# By going through viewing and azimuth angles, we can construct a full BRDF for reflectance and SIF emissions at different wavelengths:
#

reflVIS = Float32[]
reflNIR = Float32[]
SIF_FR = Float32[]
SIF_R  = Float32[]

##MED
soil.albedo_SW[:] .=0.1217;
angles.tts=27.
angles.psi=0
canopy_rt.LAI=2.5007
canopy_rt.Ω = 1.0
for psi=0:360
    angles.psi=psi
    for VZA=0:1:85
        angles.tto=VZA

        compute_canopy_geometry!(canopy_rt, angles, canOpt_rt)
        compute_canopy_matrices!(arrayOfLeaves, canOpt_rt);
        simulate_short_wave!(canopy_rt, canOpt_rt, canRad_rt, sunRad_rt, soil);
        computeSIF_Fluxes!(arrayOfLeaves, canOpt_rt, canRad_rt, canopy_rt, soil, wl_set);
        push!(reflVIS, canRad_rt.alb_obs[28])
        push!(reflNIR, canRad_rt.alb_obs[52])
        push!(SIF_R , canRad_rt.SIF_obs[8])
        push!(SIF_FR, canRad_rt.SIF_obs[20])
    end
end

##Adding clumping
canopy_rt.Ω = 0.45946608

SIF_FR_clump = Float32[]
SIF_R_clump = Float32[]
reflVIS_clump = Float32[]
reflNIR_clump = Float32[]

for psi=0:360
    angles.psi=psi
    for VZA=0:1:85
        angles.tto=VZA

        compute_canopy_geometry!(canopy_rt, angles, canOpt_rt)
        compute_canopy_matrices!(arrayOfLeaves, canOpt_rt);
        simulate_short_wave!(canopy_rt, canOpt_rt, canRad_rt, sunRad_rt, soil);
        computeSIF_Fluxes!(arrayOfLeaves, canOpt_rt, canRad_rt, canopy_rt, soil, wl_set);
        push!(reflVIS_clump, canRad_rt.alb_obs[28])
        push!(reflNIR_clump, canRad_rt.alb_obs[52])
        push!(SIF_R_clump , canRad_rt.SIF_obs[8])
        push!(SIF_FR_clump, canRad_rt.SIF_obs[20])
    end
end

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

A = reshape(reflNIR, ( 86,361));
B = reshape(reflVIS, ( 86,361));
SIFFER = reshape(SIF_R, ( 86,361));
SIFFER_FR = reshape(SIF_FR, ( 86,361));

A_clump = reshape(reflNIR_clump, ( 86,361));
B_clump = reshape(reflVIS_clump, ( 86,361));
SIFFER_clump = reshape(SIF_R_clump, ( 86,361));
SIFFER_FR_clump = reshape(SIF_FR_clump, ( 86,361));
#----------------------------------------------------------------------------

figure(figsize=(10,5))
subplot(1,2,1, polar=true)
grid(false)
hm = contourf(deg2rad.(collect((0:360))),collect(0:1:85),  A,  cmap=:viridis, levels=collect(0.25:0.012:0.5))
title("NIR reflectance BRDF")
yticks([])
colorbar()
subplot(1,2,2, polar=true)
grid(false)
hm = contourf(deg2rad.(collect((0:360))),collect(0:1:85),  A_clump,  cmap=:viridis, levels=collect(0.25:0.012:0.5))
title("Clumping")
yticks([])
colorbar()
gcf()

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

figure(figsize=(10,5))
subplot(1,2,1, polar=true)
grid(false)
hm = contourf(deg2rad.(collect((0:360))),collect(0:1:85),  B,  cmap=:viridis, levels=collect(0.0:0.005:0.045))
title("Red reflectance BRDF")
yticks([])
colorbar()
subplot(1,2,2, polar=true)
grid(false)
hm = contourf(deg2rad.(collect((0:360))),collect(0:1:85),  B_clump,  cmap=:viridis, levels=collect(0.0:0.005:0.045))
title("Clumping")
yticks([])
colorbar()
gcf()

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

figure(figsize=(10,5))
subplot(1,2,1, polar=true)
grid(false)
hm = contourf(deg2rad.(collect((0:360))),collect(0:1:85),  SIFFER,  cmap=:viridis, levels=collect(0.3:0.05:0.8))
title("Red SIF emission BRDF")
yticks([])
colorbar()
subplot(1,2,2, polar=true)
grid(false)
hm = contourf(deg2rad.(collect((0:360))),collect(0:1:85),  SIFFER_clump,  cmap=:viridis, levels=collect(0.3:0.05:0.8))
title("Clumping")
yticks([])
colorbar()
gcf()

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

figure(figsize=(10,5))
subplot(1,2,1, polar=true)
grid(false)
hm = contourf(deg2rad.(collect((0:360))),collect(0:1:85),  SIFFER_FR,  cmap=:viridis, levels=collect(1.:0.1:3.5))
title("Far-Red SIF emission BRDF")
yticks([])
colorbar()
subplot(1,2,2, polar=true)
grid(false)
hm = contourf(deg2rad.(collect((0:360))),collect(0:1:85),  SIFFER_FR_clump,  cmap=:viridis, levels=collect(1.:0.1:3.5))
title("Clumping")
yticks([])
colorbar()
gcf()

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


using Pkg
Pkg.activate(".")

import DotEnv
DotEnv.config()

using MOSInterface

# Interface
interface = Interface()

# Delete model
delete_model_with_name(interface, "Stochastic Unit Commitment")

# New model
model = new_model(interface, "./examples/jump/unit_commitment/unit_commitment_model.jl")

# Get model
model = get_model_with_name(interface, "Stochastic Unit Commitment")

# Set inputs
set_interface_object(model, "n_g", 10)
set_interface_object(model, "n_gsl", 5)

# Show info
println(get_name(model))
@assert(get_system(model) == "jump")
println(get_status(model))

# Solve
MOSInterface.run(model)
println(get_status(model))


using MetaUtils
using Test

x = 10

@testset "MetaUtils.jl" begin
    @test (@teval (:call, :+, (:call, :*, 2, :x), 1)) == 21
    @test (@teval (:+, (:*, 2, :x), 1)) == 21
    @test sprint(show_expr, :(2x + 1)) ==
        "Expr(:call, :+, \n    Expr(:call, :*, 2, :x), 1)"
    @test sprint(show_texpr, :(2x + 1)) ==
        "(:call, :+, \n    (:call, :*, 2, :x), 1)"
    @test sprint(print_subtypes, AbstractFloat) ==
        "AbstractFloat\n├─ BigFloat\n├─ Float16\n├─ Float32\n└─ Float64\n"
    @test sprint(print_subtypes, Ref) ==
        "Ref\n├─ Base.CFunction\n├─ Base.RefArray\n├─ Base.RefValue\n├─ Core.Compiler.RefValue\n├─ Core.LLVMPtr\n└─ Ptr\n"
end


struct AdaptiveParameters{δτ⁻_T, δτ⁺_T, δΔτ⁻_T, δΔτ⁺_T, Nᵣ_T, fineupdate_T} <: AbstractAdaptiveParameters
    δτ⁻::δτ⁻_T
    δτ⁺::δτ⁺_T
    δΔτ⁻::δΔτ⁻_T
    δΔτ⁺::δΔτ⁺_T
    Nᵣ::Nᵣ_T
    fineupdate::fineupdate_T
end
AdaptiveParameters(; δτ⁻=0.5, δτ⁺=1.25, δΔτ⁻=0.25, δΔτ⁺=2.0, Nᵣ=10, fineupdate=true) = AdaptiveParameters(δτ⁻, δτ⁺, δΔτ⁻, δΔτ⁺, Nᵣ, fineupdate)


# =======================================================
#         CompleteBipartiteGraph struct
# =======================================================

"""
    CompleteBipartiteGraph <: AbstractGraph

A structure representing an undirected bipartite graph.

See also: [`LightGraphs.complete_bipartite_graph`](@ref)
"""
struct CompleteBipartiteGraph{T<:Integer} <: LG.AbstractGraph{T}
    m::T
    n::T

    function CompleteBipartiteGraph{T}(m, n) where {T}

        m = convert(T, m)
        n = convert(T, n)
        m >= zero(T) || throw(ArgumentError("m must be >= 0"))
        n >= zero(T) || throw(ArgumentError("n must be >= 0"))
        _, does_overflow = Base.Checked.add_with_overflow(m, n)
        does_overflow && throw(ArgumentError("m + n must not exceed typemax($T)"))

        return new{T}(m, n)
    end
end

function CompleteBipartiteGraph(m::Integer, n::Integer)
    m, n = promote(m, n)
    return CompleteBipartiteGraph{typeof(m)}(m, n)
end

# =======================================================
#         traits
# =======================================================

LG.is_directed(::Type{<:CompleteBipartiteGraph}) = false

# =======================================================
#        vertices
# =======================================================

Base.eltype(::Type{CompleteBipartiteGraph{T}}) where {T} = T
Base.eltype(g::CompleteBipartiteGraph) = eltype(typeof(g))

LG.nv(g::CompleteBipartiteGraph) = g.m + g.n

LG.vertices(g::CompleteBipartiteGraph) = Base.OneTo(nv(g))

LG.has_vertex(g::CompleteBipartiteGraph, v) = v in vertices(g)

# =======================================================
#        edges
# =======================================================

LG.edgetype(::Type{CompleteBipartiteGraph{T}}) where {T} = Edge{T}
LG.edgetype(g::CompleteBipartiteGraph{T}) where {T} = edgetype(typeof(g))

function LG.ne(g::CompleteBipartiteGraph)
    m = Int(g.m)
    n = Int(g.n)

    return m * n
end

function LG.has_edge(g::CompleteBipartiteGraph{T}, u, v) where {T}

    u, v = minmax(T(u), T(v))

    m = g.m
    n = g.n
    return (u ∈ Base.OneTo(m)) & ((v - m) ∈ (Base.OneTo(n)))
end

# ---- edges vector -----------------------------------

LG.edges(g::CompleteBipartiteGraph) = SimpleEdgeVector(g)

function Base.size(edgevec::SimpleEdgeVector{V, G}) where {V, G <: CompleteBipartiteGraph}
    g = edgevec.graph
    return (ne(g), )
end

@inline function Base.getindex(edgevec::SimpleEdgeVector{V, G}, i::Integer) where {V, G <: CompleteBipartiteGraph}

    @boundscheck i ∈ Base.OneTo(length(edgevec)) || throw(BoundsError(edgevec, i))

    g = edgevec.graph
    T = eltype(g)
    m::T = g.m
    n::T = g.n

    div, rem = T.(divrem(i - 1, n))

    u = div + one(T)
    v = rem + one(T) + m
    return Edge{T}(u, v)
end

Base.IndexStyle(::Type{<:SimpleEdgeVector{V, G}}) where {V, G <: CompleteBipartiteGraph} = IndexLinear()

# =======================================================
#        neighbors
# =======================================================

@inline function LG.outneighbors(g::CompleteBipartiteGraph, v::Integer)
    @boundscheck v ∈ vertices(g) || throw(BoundsError(g, v))
    return OutNeighborVector(g, eltype(g)(v))
end

LG.inneighbors(g::CompleteBipartiteGraph, v::Integer) = outneighbors(g, v)

# ---- neighbors vector -----------------------------------

function Base.size(nbs::OutNeighborVector{V, G}) where {V, G <: CompleteBipartiteGraph}

    g = nbs.graph
    T = eltype(g)
    m::T = g.m
    n::T = g.n
    v::T = nbs.vertex

    return (Int(ifelse(v <= m, n, m)), )
end

@inline function Base.getindex(nbs::OutNeighborVector{V, G}, i::Integer) where {V, G <: CompleteBipartiteGraph}

    @boundscheck i ∈ Base.OneTo(length(nbs)) || throw(BoundsError(nbs, i))

    g = nbs.graph
    T = eltype(g)
    m::T = g.m
    n::T = g.n
    v::T = nbs.vertex

    return ifelse(v <= m, T(i) + m, T(i))
end

Base.IndexStyle(::Type{<:OutNeighborVector{V, G}}) where {V, G <: CompleteBipartiteGraph} = IndexLinear()

# =======================================================
#         converting
# =======================================================

Base.convert(::Type{SimpleGraph}, g::CompleteBipartiteGraph{T}) where {T} = complete_bipartite_graph(g.m, g.n)
Base.convert(::Type{SimpleGraph{T}}, g::CompleteBipartiteGraph) where {T} = complete_bipartite_graph(T(g.m), T(g.n))


using GeoSimulation
using Meshes
using GeoStatsBase
using Variography
using Distributions
using LinearAlgebra
using Plots; gr(size=(600,400))
using ReferenceTests, ImageIO
using Test, Random

# workaround GR warnings
ENV["GKSwstype"] = "100"

# environment settings
isCI = "CI" ∈ keys(ENV)
islinux = Sys.islinux()
visualtests = !isCI || (isCI && islinux)
datadir = joinpath(@__DIR__,"data")

# dummy definitions
include("dummy.jl")

# list of tests
testfiles = [
  "lu.jl",
  "fft.jl",
  "seq.jl",
  "sgs.jl",
  "cookie.jl"
]

@testset "GeoSimulation.jl" begin
  for testfile in testfiles
    include(testfile)
  end
end


function StableRandomGenerator(arg0::RandomGenerator, arg1::jdouble, arg2::jdouble)
    return StableRandomGenerator((RandomGenerator, jdouble, jdouble), arg0, arg1, arg2)
end

function next_normalized_double(obj::StableRandomGenerator)
    return jcall(obj, "nextNormalizedDouble", jdouble, ())
end


"""
"""
struct DomainContribution <: GridapType
  dict::IdDict{Triangulation,AbstractArray}
end

DomainContribution() = DomainContribution(IdDict{Triangulation,AbstractArray}())

num_domains(a::DomainContribution) = length(a.dict)

get_domains(a::DomainContribution) = keys(a.dict)

function get_contribution(a::DomainContribution,trian::Triangulation)
  if haskey(a.dict,trian)
     return a.dict[trian]
  else
    @unreachable """\n
    There is not contribution associated with the given mesh in this DomainContribution object.
    """
  end
end

Base.getindex(a::DomainContribution,trian::Triangulation) = get_contribution(a,trian)

function add_contribution!(a::DomainContribution,trian::Triangulation,b::AbstractArray,op=+)

  S = eltype(b)
  if !(S<:AbstractMatrix || S<:AbstractVector || S<:Number)
    @unreachable """\n
    You are trying to add a contribution with eltype $(S).
    Only cell-wise matrices, vectors, or numbers are accepted.

    Make sure that you are defining the terms in your weak form correclty.
    """
  end

  if length(a.dict) > 0
    T = eltype(first(values(a.dict)))
    if T <: AbstractMatrix
      @assert S<:AbstractMatrix """\n
      You are trying to add a contribution with eltype $(S) to a DomainContribution that
      stores cell-wise matrices.

      Make sure that you are defining the terms in your weak form correclty.
      """
    elseif T <: AbstractVector
      @assert S<:AbstractVector """\n
      You are trying to add a contribution with eltype $(S) to a DomainContribution that
      stores cell-wise vectors.

      Make sure that you are defining the terms in your weak form correclty.
      """
    elseif T <: Number
      @assert S<:Number """\n
      You are trying to add a contribution with eltype $(S) to a DomainContribution that
      stores cell-wise numbers.

      Make sure that you are defining the terms in your weak form correclty.
      """
    end
  end

  if haskey(a.dict,trian)
    a.dict[trian] = lazy_map(Broadcasting(op),a.dict[trian],b)
  else
    if op == +
     a.dict[trian] = b
    else
     a.dict[trian] = lazy_map(Broadcasting(op),b)
    end
  end
  a
end

Base.sum(a::DomainContribution)= sum(map(sum,values(a.dict)))

Base.copy(a::DomainContribution) = DomainContribution(copy(a.dict))

function (+)(a::DomainContribution,b::DomainContribution)
  c = copy(a)
  for (trian,array) in b.dict
    add_contribution!(c,trian,array)
  end
  c
end

function (-)(a::DomainContribution,b::DomainContribution)
  c = copy(a)
  for (trian,array) in b.dict
    add_contribution!(c,trian,array,-)
  end
  c
end

function (*)(a::Number,b::DomainContribution)
  c = DomainContribution()
  for (trian,array_old) in b.dict
    s = size(get_cell_map(trian))
    array_new = lazy_map(Broadcasting(*),Fill(a,s),array_old)
    add_contribution!(c,trian,array_new)
  end
  c
end

(*)(a::DomainContribution,b::Number) = b*a

function get_array(a::DomainContribution)
  @assert num_domains(a) == 1 """\n
  Method get_array(a::DomainContribution) can be called only
  when the DomainContribution object involves just one domain.
  """
  a.dict[first(keys(a.dict))]
end

struct Measure <: GridapType
  quad::CellQuadrature
end

get_cell_points(a::Measure) = get_cell_points(a.quad)

Measure(args...) = Measure(CellQuadrature(args...))

function integrate(f,b::Measure)
  c = integrate(f,b.quad)
  cont = DomainContribution()
  add_contribution!(cont,b.quad.trian,c)
  cont
end

function (*)(a::Integrand,b::Measure)
  integrate(a.object,b)
end

(*)(b::Measure,a::Integrand) = a*b


# Test plotting
module TestPlots

using OpenStreetMap
using Base.Test
import Winston

MAP_FILENAME = "tech_square.osm"

# Load and crop map to file bounds
nodesLLA, hwys, builds, feats = getOSMData(MAP_FILENAME)
boundsLLA = getBounds(parseMapXML(MAP_FILENAME))
cropMap!(nodesLLA, boundsLLA, highways=hwys, buildings=builds, features=feats, delete_nodes=true)

roads = roadways(hwys)
peds = walkways(hwys)
cycles = cycleways(hwys)
bldg_classes = classify(builds)
feat_classes = classify(feats)

p = plotMap(nodesLLA, highways=hwys, buildings=builds, features=feats, bounds=boundsLLA, width=500, feature_classes=feat_classes, building_classes=bldg_classes, cycleways=cycles, walkways=peds, roadways=roads)

@test typeof(p) == Winston.FramedPlot
@test Winston.getattr(p, "xlabel") == "Longitude (deg)"
@test Winston.getattr(p, "ylabel") == "Latitude (deg)"
@test Winston.getattr(p.x1, "draw_axis") == true
@test Winston.getattr(p.x1, "draw_grid") == false
@test Winston.getattr(p, "xrange") == (-71.0939, -71.0891)
@test Winston.getattr(p, "yrange") == (42.3626, 42.3659)

lla_ref = center(boundsLLA)
nodesENU = ENU(nodesLLA, lla_ref)
boundsENU = ENU(boundsLLA, lla_ref)

p2 = plotMap(nodesENU, highways=hwys, buildings=builds, features=feats, bounds=boundsENU, width=500, feature_classes=feat_classes, building_classes=bldg_classes, cycleways=cycles, walkways=peds, roadways=roads, km=true, fontsize=4)

@test typeof(p2) == Winston.FramedPlot
@test Winston.getattr(p2, "xlabel") == "East (km)"
@test Winston.getattr(p2, "ylabel") == "North (km)"
@test Winston.getattr(p2.x1, "draw_axis") == true
@test Winston.getattr(p2.x1, "draw_grid") == false
@test_approx_eq_eps Winston.getattr(p2, "xrange")[1] -0.19770898045741428 1e-6
@test_approx_eq_eps Winston.getattr(p2, "xrange")[2] 0.19770898045628862 1e-6
@test_approx_eq_eps Winston.getattr(p2, "yrange")[1] -0.18328257005093138 1e-6
@test_approx_eq_eps Winston.getattr(p2, "yrange")[2] 0.18328541309049148 1e-6

end # module TestPlots


using ProjectMoana
using ColorTypes

color = RGB(1.0, 2.0, 3.0)
pigment = UniformPigment(color)

image = HdrImage(2, 2)
set_pixel(image, 1, 1, RGB(1.0, 2.0, 3.0))
set_pixel(image, 2, 1, RGB(2.0, 3.0, 1.0))
set_pixel(image, 1, 2, RGB(2.0, 1.0, 3.0))
set_pixel(image, 2, 2, RGB(3.0, 2.0, 1.0))

pigment2 = ImagePigment(image)

color1 = RGB(1.0, 2.0, 3.0)
color2 = RGB(10.0, 20.0, 30.0)

pigment3 = CheckeredPigment(color1, color2, 2)

# With num_of_steps == 2, the pattern should be the following:
        #
        #              (0.5, 0)
        #   (0, 0) +------+------+ (1, 0)
        #          |      |      |
        #          | col1 | col2 |
        #          |      |      |
        # (0, 0.5) +------+------+ (1, 0.5)
        #          |      |      |
        #          | col2 | col1 |
        #          |      |      |
        #   (0, 1) +------+------+ (1, 1)
        #              (0.5, 1)

@testset "Surface: Pigment tests" begin
    ## testing UniformPigment
    @test get_color(pigment, Vec2D(0.0, 0.0)) ≈ color
    @test get_color(pigment, Vec2D(1.0, 0.0)) ≈ color
    @test get_color(pigment, Vec2D(0.0, 1.0)) ≈ color
    @test get_color(pigment, Vec2D(1.0, 1.0)) ≈ color

    ## testing ImagePigment
    @test get_color(pigment2, Vec2D(0.0, 0.0)) ≈ RGB(1.0, 2.0, 3.0)
    @test get_color(pigment2, Vec2D(1.0, 0.0)) ≈ RGB(2.0, 3.0, 1.0)
    @test get_color(pigment2, Vec2D(0.0, 1.0)) ≈ RGB(2.0, 1.0, 3.0)
    @test get_color(pigment2, Vec2D(1.0, 1.0)) ≈ RGB(3.0, 2.0, 1.0)

    ## testing CheckeredPigment
    @test get_color(pigment3, Vec2D(0.25, 0.25)) ≈ color1
    @test get_color(pigment3, Vec2D(0.75, 0.25)) ≈ color2
    @test get_color(pigment3, Vec2D(0.25, 0.75)) ≈ color2
    @test get_color(pigment3, Vec2D(0.75, 0.75)) ≈ color1

end


module TestSimulation
using GrandGameOfLife
using Test

@testset "flashlight" begin
    state = to_cell.([(10, 10), (9, 10), (11, 10)])
    state = LifeCells(state)

    newstate = makestep(state)
    @test !has(newstate, to_cell(9, 10))
    @test !has(newstate, to_cell(11, 10))
    @test has(newstate, to_cell(10, 10))
    @test has(newstate, to_cell(10, 11))
    @test has(newstate, to_cell(10, 9))
end

end # module


type Vehicle
    speed::Int8
    position::Int64
    tipo::Int8
    change::Int8
    num::Int64
    len::Int64
end

type Highway1D
	count::Int64
	N::Int64
	highway::Vector{Vehicle}
end

function Highway1D(N::Int64)
    highway = Vehicle[]
    for i in 1:N
        cell = Vehicle(round(Int8, -1), i, round(Int8, -1), zero(Int8), 0, 0)
        push!(highway, cell)
    end
    return Highway1D(0, N, highway)
end

type Highway2D
  lanes::Int8
  highway::Vector{Highway1D}
end

function Highway2D(lanes::Int64, N::Int64)
    C = Highway1D[]
    if lanes > 1
        for i = 1:lanes
            push!(C, Highway1D(N))
        end
        return Highway2D(round(Int8,lanes), C)
    end
    if lanes == 1
        return Highway1D(N)
    end
    if lanes < 1
      println("There must be at least one lane!")
    end
end


"""
Memory-Map a set of 3 binary files created by convertDSVNet
Return Vector of nodes, Vector of the corresponding node's degree, Matrix of neighbours of each node
"""
function mmapNetU(file_dir::String, file_name::String, index::Int)
  file = open("$(file_dir)/$(file_name)/nodes$(index).bin")
  columns = read(file, Int)
  nodeArray = mmap_array(Int, (columns, 1), file)
  close(file)
  file = open("$(file_dir)/$(file_name)/degrees$(index).bin")
  degreeArray = mmap_array(Float64, (columns, 1), file)
  close(file)
  file = open("$(file_dir)/$(file_name)/neighbours$(index).bin")
  rows = read(file, Int)
  neighbourMatrix = mmap_array(Int, (rows, columns), file)
  close(file)
  return (nodeArray, degreeArray, neighbourMatrix)
end

"""
Memory-Map a set of 4 binary files created by convertDSVNet
Return Vector of nodes, Vector of the corresponding node's degree, Matrix of neighbours of each node, Matrix of corresponding neighbour's weight
"""
function mmapNetW(file_dir::String, file_name::String, index::Int)
  file = open("$(file_dir)/$(file_name)/nodes$(index).bin")
  columns = read(file, Int)
  nodeArray = mmap_array(Int, (columns, 1), file)
  close(file)
  file = open("$(file_dir)/$(file_name)/degrees$(index).bin")
  degreeArray = mmap_array(Float64, (columns, 1), file)
  close(file)
  file = open("$(file_dir)/$(file_name)/neighbours$(index).bin")
  rows = read(file, Int)
  neighbourMatrix = mmap_array(Int, (rows, columns), file)
  close(file)
  file = open("$(file_dir)/$(file_name)/weights$(index).bin")
  weightMatrix = mmap_array(Float64, (rows, columns), file)
  close(file)
  return (nodeArray, degreeArray, neighbourMatrix, weightMatrix)
end

"""
Converts the network file into sets of binary files, one set each time the maxMB threshold is reached
- *nodes.bin: vector to map internal to external node index
- *degrees.bin: vector of the node degee values
- *neighbours.bin: square array of the neighbours of each node
- *weights.bin: square array of the weight of the corresponding neighbour
Return number of file sets created
"""
function convertDSVNet(dsv_file::String, file_dir::String, file_name::String, delim::Char, maxMB::Int)
  fileCount = 0
  firstNode = -1
  lastNode = -1
  fileWeighted = 1
  # read in file as stream
  fstream = open(dsv_file)
  positionConvert = position(fstream)
  startConvert = false
  lineValues = 0
  while !startConvert || !eof(fstream)
    line = strip(readline(fstream), ['\n', '\r'])
    sline = split(line, delim)
    pline = parse(sline[1]; raise=false)
    if !isa(pline, Int)
      positionConvert = position(fstream)
      println("Skipped line: $(line)")
    else
      startConvert = true
      fstream = seek(fstream, positionConvert)
      if length(sline) == 2
        fileWeighted = 0
        fileCount, firstNode, lastNode = convertDSVunweighted(fstream, file_dir, file_name, delim, maxMB)
      elseif length(sline) == 3
        fileCount, firstNode, lastNode = convertDSVweighted(fstream, file_dir, file_name, delim, maxMB)
      else
        error("Wrong file format.")
      end
    end
  end
  file = open("$(file_dir)/$(file_name)/info.bin", "w")
  write(file, fileCount, firstNode, lastNode, fileWeighted)
  close(file)
  return fileCount, firstNode, lastNode, fileWeighted
end

function convertDSVweighted(fstream::IOStream, file_dir::String, file_name::String, delim::Char, maxMB::Int)
  fileCount = 0
  firstNode = typemax(Int)
  lastNode = 0
  # hashtable: key = node, value = (degree, neighbours, weights)
  data = Dict{Int, (Float64, Vector{Int}, Vector{Float64})}()
  totalNodes = 0
  maxNeighbours = 0
  while !eof(fstream)
    # parse stream line per line
    line = split(strip(readline(fstream), ['\n', '\r']), delim)
    node = int(line[1])
    neighbour = int(line[2])
    weight = float64(line[3])
    # update the data
    firstNode = min(firstNode, node, neighbour)
    lastNode = max(lastNode, node, neighbour)
    totalNodes, maxNeighbours = updateData!(data, node, neighbour, weight, totalNodes, maxNeighbours)
    totalNodes, maxNeighbours = updateData!(data, neighbour, node, weight, totalNodes, maxNeighbours)
    # check if no more data or size is greater than bound
    if eof(fstream) || checkMemSize(data, maxMB, totalNodes, maxNeighbours)
      # array to map internal to external node index
      nodeArray = collect(keys(data))
      degreeArray = Array(Float64, length(nodeArray))
      # pad neighbour vector with -1's and weight vector with 0's as empty neighbours
      neighbourMatrix = fill(-1, (maxNeighbours, length(nodeArray)))
      weightMatrix = fill(0.0, (maxNeighbours, length(nodeArray)))
      for i in 1:length(nodeArray)
        degreeArray[i] = data[nodeArray[i]][1]
        for j in 1:length(data[nodeArray[i]][2])
          neighbourMatrix[j, i] = data[nodeArray[i]][2][j]
          weightMatrix[j, i] = data[nodeArray[i]][3][j]
        end
      end
      fileCount += 1
      println("Writing components to file set $(fileCount)")
      file = open("$(file_dir)/$(file_name)/nodes$(fileCount).bin", "w")
      write(file, totalNodes, nodeArray)
      close(file)
      file = open("$(file_dir)/$(file_name)/degrees$(fileCount).bin", "w")
      write(file, degreeArray)
      close(file)
      file = open("$(file_dir)/$(file_name)/neighbours$(fileCount).bin", "w")
      write(file, maxNeighbours, neighbourMatrix)
      close(file)
      file = open("$(file_dir)/$(file_name)/weights$(fileCount).bin", "w")
      write(file, weightMatrix)
      close(file)
      empty!(data)
      gc()
      totalNodes = 0
      maxNeighbours = 0
    end
  end
  return fileCount, firstNode, lastNode
end

function convertDSVunweighted(fstream::IOStream, file_dir::String, file_name::String, delim::Char, maxMB::Int)
  fileCount = 0
  firstNode = typemax(Int)
  lastNode = 0
  # hashtable: key = node, value = neighbours
  data = Dict{Int, Vector{Int}}()
  totalNodes = 0
  maxNeighbours = 0
  while !eof(fstream)
    # parse stream line per line
    line = split(strip(readline(fstream), ['\n', '\r']), delim)
    node = int(line[1])
    neighbour = int(line[2])
    # update the data
    firstNode = min(firstNode, node, neighbour)
    lastNode = max(lastNode, node, neighbour)
    totalNodes, maxNeighbours = updateData!(data, node, neighbour, totalNodes, maxNeighbours)
    #totalNodes, maxNeighbours = updateData!(data, neighbour, node, totalNodes, maxNeighbours)
    # check if no more data or size is greater than bound
    if eof(fstream) || checkMemSize(data, maxMB, totalNodes, maxNeighbours)
      fileCount += 1
      println("Writing components to file set $(fileCount)")
      # array to map internal to external node index
      nodeArray = collect(keys(data))
      degreeArray = Array(Float64, length(nodeArray))
      # pad neighbour vector with -1's and weight vector with 0's as empty neighbours
      neighbourMatrix = fill(-1, (maxNeighbours, length(nodeArray)))
      for i in 1:length(nodeArray)
        degreeArray[i] = float(length(data[nodeArray[i]]))
        for j in 1:length(data[nodeArray[i]])
          neighbourMatrix[j, i] = data[nodeArray[i]][j]
        end
      end
      file = open("$(file_dir)/$(file_name)/nodes$(fileCount).bin", "w")
      write(file, totalNodes, nodeArray)
      close(file)
      file = open("$(file_dir)/$(file_name)/degrees$(fileCount).bin", "w")
      write(file, degreeArray)
      close(file)
      file = open("$(file_dir)/$(file_name)/neighbours$(fileCount).bin", "w")
      write(file, maxNeighbours, neighbourMatrix)
      close(file)
      empty!(data)
      totalNodes = 0
      maxNeighbours = 0
    end
  end
  return fileCount, firstNode, lastNode
end

"""
Update the data dictionary with the new node data
Return totalNodes and maxNeighbours
"""
function updateData!(data::Dict{Int, (Float64, Vector{Int}, Vector{Float64})}, node::Int, neighbour::Int, weight::Float64, totalNodes::Int, maxNeighbours::Int)
  # check if node already stored
  if haskey(data, node)
    # neighbour not yet stored
    if findfirst(data[node][2], neighbour) == 0
      # update the node degree and add the neighbour
      data[node] = (data[node][1]+weight, push!(data[node][2], neighbour), push!(data[node][3], weight))
      # check if maxNeighbours needs to be increased
      if length(data[node][2]) > maxNeighbours
        maxNeighbours = length(data[node][2])
      end
    end
  else
    data[node] = (weight, [neighbour], [weight])
    totalNodes += 1
  end
  return (totalNodes, maxNeighbours)
end

function updateData!(data::Dict{Int, Vector{Int}}, node::Int, neighbour::Int, totalNodes::Int, maxNeighbours::Int)
  # check if node already stored
  if haskey(data, node)
    # check if neighbour not yet stored
    if findfirst(data[node], neighbour) == 0
      # update the node degree and add the neighbour
      data[node] = push!(data[node], neighbour)
      # check if maxNeighbours needs to be increased
      if length(data[node]) > maxNeighbours
        maxNeighbours = length(data[node])
      end
    end
  else
    data[node] = [neighbour]
    totalNodes += 1
  end
  return (totalNodes, maxNeighbours)
end

"""
Check if the memory footprint is still smaller than the threshold
Return true if threshold reached
"""
function checkMemSize(data::Dict{Int, (Float64, Vector{Int}, Vector{Float64})}, maxMB::Int, totalNodes::Int, maxNeighbours::Int)
  # estimate current needed memory in Bytes: size of dictionary + arrays that need to be generated for storage
  size = sizeof(data) + (totalNodes + totalNodes*maxNeighbours)*sizeof(Int) + totalNodes*sizeof(Float64)
  # return true if size larger than maximum allowed MB threshold
  return (size < maxMB*1000000)?false:true
end

function checkMemSize(data::Dict{Int, Vector{Int}}, maxMB::Int, totalNodes::Int, maxNeighbours::Int)
  # estimate current needed memory in Bytes: size of dictionary + arrays that need to be generated for storage
  size = sizeof(data) + (totalNodes + totalNodes*maxNeighbours)*(sizeof(Int) + sizeof(Float64))
  # return true if size larger than maximum allowed MB threshold
  return (size < maxMB*1000000)?false:true
end

"""
Create an undirected network file with duplicate entries from the original network file
"""
function createMirrored(dsvFile::String, delim::Char, newFile::String)
  println("Creating mirrored network file")
  tic()
  # read in dsvfile as stream
  fstream = open(dsvFile)
  # write to newfile as stream
  nstream = open(newFile, "w")
  while !eof(fstream)
    # parse stream line per line
    line = readline(fstream)
    sline = split(strip(line, ['\n', '\r']), delim)
    pline = parse(sline[1]; raise=false)
    if !isa(pline, Int)
      println("Skipped line: $(line)")
    else
      if length(sline) == 2
        node = int(sline[1])
        neighbour = int(sline[2])
        write(nstream, "$(node)$(delim)$(neighbour)\n")
        write(nstream, "$(neighbour)$(delim)$(node)\n")
      elseif length(sline) == 3
        node = int(sline[1])
        neighbour = int(sline[2])
        weight = float(sline[3])
        write(nstream, "$(node)$(delim)$(neighbour)$(delim)$(weight)\n")
        write(nstream, "$(neighbour)$(delim)$(node)$(delim)$(weight)\n")
      end
    end
  end
  close(nstream)
  close(fstream)
  timeMirrored = toq()
  println("Done, time elapsed $(timeMirrored)s")
end


function rectilinear_grid(filename::AbstractString, x::AbstractVector,
                          y::AbstractVector, z::AbstractVector;
                          compress=true, append::Bool=true,
                          extent=nothing)
    Ni, Nj, Nk = length(x), length(y), length(z)
    Npts = Ni*Nj*Nk
    Ncls = num_cells_structured(Ni, Nj, Nk)
    ext = extent_attribute(Ni, Nj, Nk, extent)

    xvtk = XMLDocument()
    vtk = DatasetFile(xvtk, add_extension(filename, ".vtr"), "RectilinearGrid",
                      Npts, Ncls, compress, append)

    # VTKFile node
    xroot = vtk_xml_write_header(vtk)

    # RectilinearGrid node
    xGrid = new_child(xroot, vtk.grid_type)
    set_attribute(xGrid, "WholeExtent", ext)

    # Piece node
    xPiece = new_child(xGrid, "Piece")
    set_attribute(xPiece, "Extent", ext)

    # Coordinates node
    xPoints = new_child(xPiece, "Coordinates")

    # DataArray node
    data_to_xml(vtk, xPoints, x, "x")
    data_to_xml(vtk, xPoints, y, "y")
    data_to_xml(vtk, xPoints, z, "z")

    return vtk::DatasetFile
end

"""
    vtk_grid(filename::AbstractString,
             x::AbstractVector{T}, y::AbstractVector{T}, [z::AbstractVector{T}];
             kwargs...)

Create 2D or 3D rectilinear grid (`.vtr`) file.

Coordinates are specified by separate vectors `x`, `y`, `z`.

# Examples

```jldoctest
julia> vtk = vtk_grid("abc", [0., 0.2, 0.5], collect(-2.:0.2:3), [1., 2.1, 2.3])
VTK file 'abc.vtr' (RectilinearGrid file, open)
```

"""
vtk_grid(filename::AbstractString, x::AbstractVector{T},
         y::AbstractVector{T}, z::AbstractVector{T}; kwargs...) where T =
    rectilinear_grid(filename, x, y, z; kwargs...)

# 2D variant
vtk_grid(filename::AbstractString, x::AbstractVector{T},
         y::AbstractVector{T}; kwargs...) where T =
    rectilinear_grid(filename, x, y, zeros(T, 1); kwargs...)


function get_column_dimension(obj::AnyMatrix)
    return jcall(obj, "getColumnDimension", jint, ())
end

function get_row_dimension(obj::AnyMatrix)
    return jcall(obj, "getRowDimension", jint, ())
end

function is_square(obj::AnyMatrix)
    return jcall(obj, "isSquare", jboolean, ())
end


# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.5.2
#   kernelspec:
#     display_name: Julia 1.5.0
#     language: julia
#     name: julia-1.5
# ---

using LinearAlgebra

include("SOneTo.jl")

"""
    abstract type StaticArrays{S,T,N} <: AbstractArray{T, N} end
    StaticScalar{T} = StaticArray{Tuple{}, T, 0}
    StaticVector{N,T} = StaticArray{Tuple{N}, T, 1}
    StaticMatrix{N, T} = StaticArray{Tuple{N, M}, T, 2}
`StaticArray`s are Julia arrays with fixed, known size.
"""

abstract type StaticArray{S <: Tuple, T, N} <: AbstractArray{T, N} end
const StaticScalar{T} = StaticArray{Tuple{}, T, 0}
const StaticVector{N, T} = StaticArray{Tuple{N}, T, 1}
const StaticMatrix{N, M, T} = StaticArray{Tuple{N, M}, T, 2}
const StaticVecOrMat{T} = Union{StaticVector{<:Any, T}, StaticMatrix{<:Any, <:Any, T}}

# +
# Being a member of StaticMatrixLike, StaticVecOrMatLike, or StaticArrayLike implies that Size(A)
# returns a static Size instance (none of the dimensions are Dynamic). The converse may not be true.
# These are akin to aliases like StridedArray and in similarly bad taste, but the current approach
# in Base necessitates their existence.
const StaticMatrixLike{T} = Union{
    StaticMatrix{<:Any, <:Any, T},
    Transpose{T, <:StaticVecOrMat{T}},
    Adjoint{T, <:StaticVecOrMat{T}},
    Symmetric{T, <:StaticMatrix{<:Any, <:Any, T}},
    Hermitian{T, <:StaticMatrix{<:Any, <:Any, T}},
    Diagonal{T, <:StaticVector{<:Any, T}},
    # We specifically list *Triangular here rather than using
    # AbstractTriangular to avoid ambiguities in size() etc.
    UpperTriangular{T, <:StaticMatrix{<:Any, <:Any, T}},
    LowerTriangular{T, <:StaticMatrix{<:Any, <:Any, T}},
    UnitUpperTriangular{T, <:StaticMatrix{<:Any, <:Any, T}},
    UnitLowerTriangular{T, <:StaticMatrix{<:Any, <:Any, T}}
}
const StaticVecOrMatLike{T} = Union{StaticVector{<:Any, T}, StaticMatrixLike{T}}
const StaticArrayLike{T} = Union{StaticVecOrMatLike{T}, StaticArray{<:Tuple, T}}

const AbstractScalar{T} = AbstractArray{T, 0} # not exported, but useful none-the-less
const StaticArrayNoEltype{S, N, T} = StaticArray{S, T, N}

# +
# utils.jl
# -

# For convenience
TupleN{T,N} = NTuple{N,T}

@inline convert_ntuple(::Type{T}, d::T) where {T} = T # For zero-dimensional arrays
@inline convert_ntuple(::Type{T}, d::NTuple{N,T}) where {N,T} = d
@generated function convert_ntuple(::Type{T}, d::NTuple{N,Any}) where {N,T}
    exprs = ntuple(i->:(convert(T, d[$i])), Val(N))
    return quote
        @_inline_meta
        $(Expr(:tuple, exprs...))
    end
end

# Base gives up on tuples for promote_eltype... (TODO can we improve Base?)
@generated function promote_tuple_eltype(::Union{T,Type{T}}) where T <: Tuple
    t = Union{}
    for i = 1:length(T.parameters)
        tmp = T.parameters[i]
        if tmp <: Vararg
            tmp = tmp.parameters[1]
        end
        t = :(promote_type($t, $tmp))
    end
    return quote
        @_inline_meta
        $t
    end
end

# The ::Tuple variants exist to make sure anything that calls with a tuple
# instead of a Tuple gets through to the constructor, so the user gets a nice error message
Base.@pure tuple_length(T::Type{<:Tuple}) = length(T.parameters)
Base.@pure tuple_length(T::Tuple) = length(T)
Base.@pure tuple_prod(T::Type{<:Tuple}) = length(T.parameters) == 0 ? 1 : *(T.parameters...)
Base.@pure tuple_prod(T::Tuple) = prod(T)
Base.@pure tuple_minimum(T::Type{<:Tuple}) = length(T.parameters) == 0 ? 0 : minimum(tuple(T.parameters...))
Base.@pure tuple_minimum(T::Tuple) = minimum(T)

"""
    size_to_tuple(::Type{S}) where S<:Tuple
Convert a size given by `Tuple{N,M,...}` into a tuple `(N, M, ...)`
"""
Base.@pure function size_to_tuple(::Type{S}) where S<:Tuple
    return tuple(S.parameters...)
end

# +
# Something doesn't match up type wise
function check_array_parameters(Size, T, N, L)
    (!isa(Size, DataType) || (Size.name !== Tuple.name)) && throw(ArgumentError("Static Array parameter Size must be a Tuple type, got $Size"))
    !isa(T, Type) && throw(ArgumentError("Static Array parameter T must be a type, got $T"))
    !isa(N.parameters[1], Int) && throw(ArgumenError("Static Array parameter N must be an integer, got $(N.parameters[1])"))
    !isa(L.parameters[1], Int) && throw(ArgumentError("Static Array parameter L must be an integer, got $(L.parameters[1])"))
    # shouldn't reach here. Anything else should have made it to the function below
    error("Internal error. Please file a bug")
end

@generated function check_array_parameters(::Type{Size}, ::Type{T}, ::Type{Val{N}}, ::Type{Val{L}}) where {Size,T,N,L}
    if !all(x->isa(x, Int), Size.parameters)
        return :(throw(ArgumentError("Static Array parameter Size must be a tuple of Ints (e.g. `SArray{Tuple{3,3}}` or `SMatrix{3,3}`).")))
    end

    if L != tuple_prod(Size) || L < 0 || tuple_minimum(Size) < 0 || tuple_length(Size) != N
        return :(throw(ArgumentError("Size mismatch in Static Array parameters. Got size $Size, dimension $N and length $L.")))
    end

    return nothing
end

# +
"""
   TrivialView
Use to drop static dimensions to override dispatch
"""
struct TrivialView{A, T, N} <: AbstractArray{T, N}
    a::A
end

size(a::TrivialView) = size(a.a)
getindex(a::TrivialView) = getindex(a.a, inds...)
setindex!(a::TrivialView) = setindex!(a.a, inds...)
Base.IndexStyle(::Type{<:TrivialView{A}}) where {A} = IndexStyle{A}

# +
@inline drop_sdims(a::StaticArrayLike) = TrivialView(a)
@inline drop_sdims(a) = a

Base.@propagate_inbounds function invperm(p::StaticVector)
    # in difference to base, this does not check if p is a permutation (every value unique)
     ip = similar(p)
     ip[p] = 1:length(p)
     similar_type(p)(ip)
end


# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# Script creating monthly data files
# -------------------------------------------------------------------------------------------------------------------------------------------------------------

using DataFrames, CSV, XLSX, LinearAlgebra, Statistics

dir_io = "C:/Users/u0148308/data/comext/" # location of input/output (io)

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

# timespan
years = string.(2015:2021)
months = lpad.(1:12, 2, '0')

# initial cleaning of the data
function initial_cleaning(year::String, month::String)

    path = dir_io * "raw/" * "full" * year * month * ".dat"
    df = CSV.read(path, DataFrame)

    # correct column types
    # Notes:
    #   - treat PERIOD as numeric to allow sorting
    transform!(df, [:DECLARANT, :PARTNER, :PERIOD] .=> ByRow(Int64), renamecols=false)

    # different additional PROD_BEC column from 2017 onwards
    col_strings = [:DECLARANT_ISO, :PARTNER_ISO, :TRADE_TYPE, :PRODUCT_NC, :PRODUCT_SITC, :PRODUCT_CPA2002, :PRODUCT_CPA2008, :PRODUCT_CPA2_1,
                    :PRODUCT_BEC, :PRODUCT_SECTION, :FLOW, :STAT_REGIME]
    col_strings = ifelse(year in ["2015", "2016"], col_strings, [col_strings; :PRODUCT_BEC5])
    transform!(df,  col_strings .=> ByRow(string), renamecols=false)

    transform!(df, [:VALUE_IN_EUROS, :QUANTITY_IN_KG, :SUP_QUANTITY] .=> ByRow(Float64), renamecols=false)

    # rename some indicators
    df[:, :TRADE_TYPE] .= ifelse.(df[:, :TRADE_TYPE] .== "I", "intra", "extra")
    df[:, :FLOW] .= ifelse.(df[:, :FLOW] .== "1", "imports", "exports")
    df[:, :SUPP_UNIT] .= ifelse.(ismissing.(df[:, :SUPP_UNIT]), missing, string.(df[:, :SUPP_UNIT])) # to make sure type is of Union{Missing, String}

    # missing data
    # Notes:
    #   - if value or quanity is reported as 0 use missing instead
    transform!(df, [:VALUE_IN_EUROS, :QUANTITY_IN_KG, :SUP_QUANTITY] .=> ByRow(x->ifelse(x == 0.0, missing, x)), renamecols=false)

    # aggregate over STAT_REGIME, SUPP_UNIT
    # Notes:
    #   - only lose few rows ~1%
    #   - disregard SUPP_UNIT and SUP_QUANTITY for the time being
    cols_grouping = ["DECLARANT_ISO", "PARTNER_ISO", "TRADE_TYPE", "PRODUCT_NC", "FLOW", "PERIOD"]
    gdf = groupby(df, cols_grouping)
    df = combine(gdf, [:VALUE_IN_EUROS, :QUANTITY_IN_KG] .=> sum, renamecols=false)

    return df
end

function pct_change(input::AbstractVector)
    [i == 1 ? missing : (input[i]-input[i-1])/input[i-1]*100 for i in eachindex(input)]
end

function mom_change(period::AbstractVector, input::AbstractVector)
    [i == 1 ? missing : period[i]-period[i-1] != 1 ? missing : (input[i]-input[i-1])/input[i-1]*100 for i in eachindex(input)]
end

g(x) = x[end-1:end]

periods = repeat(years, inner=12) .* repeat(months, outer=length(years))
periods = parse.(Int64, periods) # convert to Int64 (actually needed? think we can do all with String)

# create monthly files
# Notes:
#   - import data for 3 months, compute prices and shares
#       + could use 2 months only but then we can only compute MOM. In this setting we obtain also UNIT_PRICE_CHANGE between Jan and Mar,
#         this might be useful later if we realize MOM is too restrictive
#   - subset to middle month (i.e. Feb if Jan, Feb, Mar) and export
#       + means we lose first and last month (i.e. cannot compute changes since no previous/following data)

for (i, time) in enumerate(periods[1:end-2])

    year = string.(time)[1:4]
    month = g.(string.(periods[[i, i+1, i+2]]))
    sub_month = month[2]

    df = DataFrame(DECLARANT_ISO=String[], PARTNER_ISO=String[], TRADE_TYPE=String[], PRODUCT_NC=String[], FLOW=String[], PERIOD=Int64[],
                VALUE_IN_EUROS=Union{Missing, Float64}[], QUANTITY_IN_KG=Union{Missing, Float64}[])

    append!(df, initial_cleaning(year, month[1]))
    append!(df, initial_cleaning(year, sub_month))
    append!(df, initial_cleaning(year, month[3]))

    sort!(df)

    # -------------------------------------------------------------------------------------------------------------------------------------------------------------
    # Unit prices
    # -------------------------------------------------------------------------------------------------------------------------------------------------------------

    # compute UNIT_PRICE
    transform!(df, [:VALUE_IN_EUROS, :QUANTITY_IN_KG] => ByRow((v,q) -> v/q) => :UNIT_PRICE)

    # compute UNIT_PRICE_CHANGE
    cols_grouping = ["DECLARANT_ISO", "PARTNER_ISO", "PRODUCT_NC", "FLOW"]
    gdf = groupby(df, cols_grouping)
    df = transform(gdf, :UNIT_PRICE => pct_change => :UNIT_PRICE_CHANGE)

    # compute MOM_PRICE_CHANGE
    gdf = groupby(df, cols_grouping)
    df = transform(gdf, [:PERIOD, :UNIT_PRICE] => mom_change => :MOM)

    # export data
    df_export = subset(df, :PERIOD => ByRow(x-> string(x) == year * sub_month))
    CSV.write(dir_io * "clean/" * "df_prices_" * year * sub_month * ".csv", df_export)

    println(" ✓ Price data for $sub_month/$year were successfully created.")

    # -------------------------------------------------------------------------------------------------------------------------------------------------------------
    # Share of Belgium
    # -------------------------------------------------------------------------------------------------------------------------------------------------------------

    # compute EU_WORLD (EU imports/exports to the entire world)
    cols_grouping = ["PRODUCT_NC", "FLOW", "PERIOD"]
    gdf = groupby(df, cols_grouping)
    df_sum_EU_WORLD = combine(gdf, [:VALUE_IN_EUROS, :QUANTITY_IN_KG] .=> (x -> sum(skipmissing(x))), renamecols=false)
    df_sum_EU_WORLD.TRADE_TYPE .= "total"

    # compute EU_EXTRA and EU_INTRA (EU imports/exports to extra/intra EU countries)
    cols_grouping = ["TRADE_TYPE", "PRODUCT_NC", "FLOW", "PERIOD"]
    gdf = groupby(df, cols_grouping)
    df_sum_EU_EXTRA_INTRA = combine(gdf, [:VALUE_IN_EUROS, :QUANTITY_IN_KG] .=> (x -> sum(skipmissing(x))), renamecols=false)

    # compute BE_WORLD (BE imports/exports to the entire world)
    df_BE = subset(df, :DECLARANT_ISO => ByRow(x -> x == "BE"))
    cols_grouping = ["PRODUCT_NC", "FLOW", "PERIOD"]
    gdf = groupby(df_BE, cols_grouping)
    df_sum_BE_WORLD = combine(gdf, [:VALUE_IN_EUROS, :QUANTITY_IN_KG] .=> (x -> sum(skipmissing(x))), renamecols=false)
    df_sum_BE_WORLD.TRADE_TYPE .= "total"

    # compute BE_EXTRA and BE_INTRA (BE imports/exports to extra/intra EU countries)
    cols_grouping = ["TRADE_TYPE", "PRODUCT_NC", "FLOW", "PERIOD"]
    gdf = groupby(df_BE, cols_grouping)
    df_sum_BE_EXTRA_INTRA = combine(gdf, [:VALUE_IN_EUROS, :QUANTITY_IN_KG] .=> (x -> sum(skipmissing(x))), renamecols=false)

    # merge tables
    df_sum_EU = vcat(df_sum_EU_EXTRA_INTRA, df_sum_EU_WORLD)
    df_sum_BE = vcat(df_sum_BE_EXTRA_INTRA, df_sum_BE_WORLD)

    # join, rename and compute shares in percentages (?)
    df_join = leftjoin(df_sum_EU, df_sum_BE, on=[:TRADE_TYPE, :PRODUCT_NC, :FLOW, :PERIOD], makeunique=true)
    rename!(df_join, ["TRADE_TYPE", "PRODUCT_NC", "FLOW", "PERIOD", "VALUE_EU", "QUANITY_EU", "VALUE_BE", "QUANITY_BE"])
    df_join.VALUE_SHARE = df_join.VALUE_BE ./ df_join.VALUE_EU .* 100
    df_join.QUANTITY_SHARE = df_join.QUANITY_BE ./ df_join.QUANITY_EU .* 100

    # possibly need to sort before grouping, seems that grouping sorts incorrectly sometimes?
    sort!(df_join, [:PRODUCT_NC, :FLOW, :TRADE_TYPE, :PERIOD])

    # compute SHARE_CHANGE
    cols_grouping = ["TRADE_TYPE", "PRODUCT_NC", "FLOW"]
    gdf = groupby(df_join, cols_grouping)
    df_share_BE = transform(gdf, [:VALUE_SHARE, :QUANTITY_SHARE] .=> pct_change .=> [:VALUE_SHARE_CHANGE, :QUANTITY_SHARE_CHANGE])

    # compute MOM_CHANGE
    gdf = groupby(df_share_BE, cols_grouping)
    df_share_BE = transform(gdf, [:PERIOD, :VALUE_SHARE] => mom_change => :MOM_VALUE,
                                 [:PERIOD, :QUANTITY_SHARE] => mom_change => :MOM_QUANTITY)

    # export data
    df_export = subset(df_share_BE, :PERIOD => ByRow(x-> string(x) == year * sub_month))
    CSV.write(dir_io * "clean/" * "df_share_BE_" * year * sub_month * ".csv", df_export)

    println(" ✓ Belgian shares data for $sub_month/$year were successfully created. \n")

end


export AbstractFeatureConstructor, create_features, feature_size

"""
    AbstractFeatureCreator
An abstract feature creator, for feature transformation from the states.
"""
abstract type AbstractFeatureConstructor end

"""
    create_features(fc::AbstractFeatureCreator, s)
Actually create the features
"""
function create_features(fc::AbstractFeatureConstructor, s; kwargs...)
    throw("Implement create features for $(typof(fc))")
end

"""
    feature_size(fc::AbstractFeatureCreator)
Get size of feature vector the features assume exists.
"""
function feature_size(fc::AbstractFeatureConstructor)
    throw("Implement feature size for $(typof(fc))")
end

# export TileCoder
# include("FeatureCreators/TileCoder.jl")

# export KenervaCoder
# include("FeatureCreators/KenervaCoder.jl")


#
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

# What is included in the file `FMI2_additional.jl` (FMU add functions)?
# - high-level functions, that are useful, but not part of the FMI-spec [exported]

using Base.Filesystem: mktempdir

using FMIImport: FMU2, fmi2ModelDescription
using FMIImport: fmi2Boolean, fmi2Real, fmi2Integer, fmi2Byte, fmi2String, fmi2FMUstate
using FMIImport: fmi2True, fmi2False
using FMIImport: fmi2StatusKind, fmi2Status
using FMIImport: fmi2DependencyKindDependent, fmi2DependencyKindFixed
using FMIImport: fmi2CallbackFunctions, fmi2Component
import FMIImport: fmi2VariableNamingConventionFlat, fmi2VariableNamingConventionStructured

"""
Returns how a variable depends on another variable based on the model description.
"""
function fmi2VariableDependsOnVariable(fmu::FMU2, vr1::fmi2ValueReference, vr2::fmi2ValueReference)
    i1 = fmu.modelDescription.valueReferenceIndicies[vr1]
    i2 = fmu.modelDescription.valueReferenceIndicies[vr2]
    return fmi2GetDependencies(fmu)[i1, i2]
end

"""
Returns the FMU's dependency-matrix for fast look-ups on dependencies between value references.

Entries are from type fmi2DependencyKind.
"""
function fmi2GetDependencies(fmu::FMU2)
    if !isdefined(fmu, :dependencies)
        dim = length(fmu.modelDescription.valueReferences)
        @info "fmi2GetDependencies: Started building dependency matrix $(dim) x $(dim) ..."

        if fmi2DependenciesSupported(fmu.modelDescription)
            fmu.dependencies = fill(nothing, dim, dim)

            for i in 1:dim
                modelVariable = fmi2ModelVariablesForValueReference(fmu.modelDescription, fmu.modelDescription.valueReferences[i])[1]

                if modelVariable.dependencies !== nothing
                    indicies = collect(fmu.modelDescription.valueReferenceIndicies[fmu.modelDescription.modelVariables[dependency].valueReference] for dependency in modelVariable.dependencies)
                    dependenciesKind = modelVariable.dependenciesKind

                    k = 1
                    for j in 1:dim
                        if j in indicies
                            if dependenciesKind[k] == "fixed"
                                fmu.dependencies[i,j] = fmi2DependencyKindFixed
                            elseif dependenciesKind[k] == "dependent"
                                fmu.dependencies[i,j] = fmi2DependencyKindDependent
                            else
                                @warn "Unknown dependency kind for index ($i, $j) = `$(dependenciesKind[k])`."
                            end
                            k += 1
                        end
                    end
                end
            end
        else
            fmu.dependencies = fill(nothing, dim, dim)
        end

        @info "fmi2GetDependencies: Building dependency matrix $(dim) x $(dim) finished."
    end

    fmu.dependencies
end

function fmi2PrintDependencies(fmu::FMU2)
    dep = fmi2GetDependencies(fmu)
    ni, nj = size(dep)

    for i in 1:ni
        str = ""
        for j in 1:nj
            str = "$(str) $(Integer(dep[i,j]))"
        end
        println(str)
    end
end

"""
Prints FMU related information.
"""
function fmi2Info(fmu::FMU2)
    println("#################### Begin information for FMU ####################")

    println("\tModel name:\t\t\t$(fmi2GetModelName(fmu))")
    println("\tFMI-Version:\t\t\t$(fmi2GetVersion(fmu))")
    println("\tGUID:\t\t\t\t$(fmi2GetGUID(fmu))")
    println("\tGeneration tool:\t\t$(fmi2GetGenerationTool(fmu))")
    println("\tGeneration time:\t\t$(fmi2GetGenerationDateAndTime(fmu))")
    print("\tVar. naming conv.:\t\t")
    if fmi2GetVariableNamingConvention(fmu) == fmi2VariableNamingConventionFlat
        println("flat")
    elseif fmi2GetVariableNamingConvention(fmu) == fmi2VariableNamingConventionStructured
        println("structured")
    else
        println("[unknown]")
    end
    println("\tEvent indicators:\t\t$(fmi2GetNumberOfEventIndicators(fmu))")

    println("\tInputs:\t\t\t\t$(length(fmu.modelDescription.inputValueReferences))")
    for vr in fmu.modelDescription.inputValueReferences
        println("\t\t$(vr) $(fmi2ValueReferenceToString(fmu, vr))")
    end

    println("\tOutputs:\t\t\t$(length(fmu.modelDescription.outputValueReferences))")
    for vr in fmu.modelDescription.outputValueReferences
        println("\t\t$(vr) $(fmi2ValueReferenceToString(fmu, vr))")
    end

    println("\tStates:\t\t\t\t$(length(fmu.modelDescription.stateValueReferences))")
    for vr in fmu.modelDescription.stateValueReferences
        println("\t\t$(vr) $(fmi2ValueReferenceToString(fmu, vr))")
    end

    println("\tSupports Co-Simulation:\t\t$(fmi2IsCoSimulation(fmu))")
    if fmi2IsCoSimulation(fmu)
        println("\t\tModel identifier:\t$(fmu.modelDescription.coSimulation.modelIdentifier)")
        println("\t\tGet/Set State:\t\t$(fmu.modelDescription.coSimulation.canGetAndSetFMUstate)")
        println("\t\tSerialize State:\t$(fmu.modelDescription.coSimulation.canSerializeFMUstate)")
        println("\t\tDir. Derivatives:\t$(fmu.modelDescription.coSimulation.providesDirectionalDerivative)")

        println("\t\tVar. com. steps:\t$(fmu.modelDescription.coSimulation.canHandleVariableCommunicationStepSize)")
        println("\t\tInput interpol.:\t$(fmu.modelDescription.coSimulation.canInterpolateInputs)")
        println("\t\tMax order out. der.:\t$(fmu.modelDescription.coSimulation.maxOutputDerivativeOrder)")
    end

    println("\tSupports Model-Exchange:\t$(fmi2IsModelExchange(fmu))")
    if fmi2IsModelExchange(fmu)
        println("\t\tModel identifier:\t$(fmu.modelDescription.modelExchange.modelIdentifier)")
        println("\t\tGet/Set State:\t\t$(fmu.modelDescription.modelExchange.canGetAndSetFMUstate)")
        println("\t\tSerialize State:\t$(fmu.modelDescription.modelExchange.canSerializeFMUstate)")
        println("\t\tDir. Derivatives:\t$(fmu.modelDescription.modelExchange.providesDirectionalDerivative)")
    end

    println("##################### End information for FMU #####################")
end


# This file was generated, do not modify it. # hide
cv3 = CV(; nfolds=3)
res = evaluate(rf_mdl, X, y, resampling=CV(shuffle=true),
               measure=rms, verbosity=0)


using NonstationaryProcesses
using Plots

function wallPlot(S, vars)
        # Square ones
        if vars[2] isa String
                vars, name = vars
        else
                name = getprocess(S)
        end
        mkpath(joinpath(dir, "Square"))
        p = plot(S; vars, colormode=:velocity, linecolor=colorgradient, background_color=:black, axis=nothing, framestyle=:none, size=(900, 900), aspect_ratio=:equal, margin=15Plots.mm, dpi=1000, linewidth=1.0);
        savefig(p, joinpath(dir, "Square", string(name)*".png"))

        # 16:9
        mkpath(joinpath(dir, "WallpaperSquare"))
        p = plot(S; vars, colormode=:velocity, linecolor=colorgradient, background_color=:black, axis=nothing, framestyle=:none, size=(1600, 900), aspect_ratio=:equal, margin=10Plots.mm, dpi=1000, linewidth=1.5);
        savefig(p, joinpath(dir, "WallpaperSquare", string(name)*".png"))

        # 16:9 fill
        mkpath(joinpath(dir, "WallpaperFill"))
        p = plot(S; vars, colormode=:velocity, linecolor=colorgradient, background_color=:black, axis=nothing, framestyle=:none, size=(1600, 900), left_margin=40Plots.mm, right_margin=40Plots.mm, dpi=1000, linewidth=1.5);
        savefig(p, joinpath(dir, "WallpaperFill", string(name)*".png"))
end

colorgradient = :turbo
dir = "../ProcessImages/"
mkpath(dir)

# Dict of system => variables to plot
𝒮 = Dict(
        simplestChaoticFlowVis()                => 2:3,
        thomasCyclicallySymmetricVis()          => 1:2,
        doubleScrollVis()                       => 1:3,
        diffusionlessLorenzVis()                => 1:3,
        piecewiseLinearHyperchaosVis()          => [1, 3],
        simplifiedLorenz4DVis()                 => [3, 2, 4],
        chensSystemVis()                        => [1, 2, 3],
        chensSystemVis(parameters=(46.0, 11.0, 29.0), tmax=500.0)
                                                => ([1, 2, 3], "chensSystem46"),
        cartesianDoublePendulumSim(profiles=(constant, constant, constant, constant),
                                   parameters=(0.5, 1.0, 1.0, 1.0),
                                   X0=[π/4, π, 0.0, 0.0],
                                   tmax=2000.0,
                                   savedt= 0.01)
                                                => [3, 4],
        lorenzVis()                             => 1:3,
)

for (S, vars) ∈ 𝒮
        wallPlot(S, vars)
end


using ImageComponentAnalysis
using Test, Images, JuliaDBMeta, IndexedTables

@testset "ImageComponentAnalysis.jl" begin
    include("test_images.jl")
    include("test_volumes.jl")
    include("test_ellipse_images.jl")
    include("contour_tracing.jl")
    include("measurement.jl")
    include("generic_labelling.jl")
    include("one_component_2d.jl")
    include("one_component_3d.jl")
    include("basic_topology.jl")
    include("region_ellipse.jl")
end


#
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

###############
# Prepare FMU #
###############

pathToFMU = "https://github.com/ThummeTo/FMI.jl/raw/main/model/" * ENV["EXPORTINGTOOL"] * "/IO.fmu"

myFMU = fmi2Load(pathToFMU)
comp = fmi2Instantiate!(myFMU; loggingOn=false)
@test comp != 0

@test fmi2SetupExperiment(comp, 0.0) == 0

@test fmi2EnterInitializationMode(comp) == 0

realValueReferences = ["p_real", "u_real"]
integerValueReferences = ["p_integer", "u_integer"]
booleanValueReferences = ["p_boolean", "u_boolean"]
stringValueReferences = ["p_string", "p_string"]

#########################
# Testing Single Values #
#########################

rndReal = 100 * rand()
rndInteger = round(Integer, 100 * rand())
rndBoolean = rand() > 0.5
rndString = Random.randstring(12)

cacheReal = 0.0
cacheInteger = 0
cacheBoolean = false
cacheString = ""

@test fmi2SetReal(comp, realValueReferences[1], rndReal) == 0
@test fmi2GetReal(comp, realValueReferences[1]) == rndReal
@test fmi2SetReal(comp, realValueReferences[1], -rndReal) == 0
@test fmi2GetReal(comp, realValueReferences[1]) == -rndReal

@test fmi2SetInteger(comp, integerValueReferences[1], rndInteger) == 0
@test fmi2GetInteger(comp, integerValueReferences[1]) == rndInteger
@test fmi2SetInteger(comp, integerValueReferences[1], -rndInteger) == 0
@test fmi2GetInteger(comp, integerValueReferences[1]) == -rndInteger

@test fmi2SetBoolean(comp, booleanValueReferences[1], rndBoolean) == 0
@test fmi2GetBoolean(comp, booleanValueReferences[1]) == rndBoolean
@test fmi2SetBoolean(comp, booleanValueReferences[1], !rndBoolean) == 0
@test fmi2GetBoolean(comp, booleanValueReferences[1]) == !rndBoolean

@test fmi2SetString(comp, stringValueReferences[1], rndString) == 0
@test fmi2GetString(comp, stringValueReferences[1]) == rndString

if ENV["EXPORTINGTOOL"] != "OpenModelica/v1.17.0"
    fmi2Set(comp,
            [realValueReferences[1], integerValueReferences[1], booleanValueReferences[1], stringValueReferences[1]],
            [rndReal,                rndInteger,                rndBoolean,                rndString])
    @test fmi2Get(comp,
                  [realValueReferences[1], integerValueReferences[1], booleanValueReferences[1], stringValueReferences[1]]) ==
                  [rndReal,                rndInteger,                rndBoolean,                rndString]
end

##################
# Testing Arrays #
##################

rndReal = [100 * rand(), 100 * rand()]
rndInteger = [round(Integer, 100 * rand()), round(Integer, 100 * rand())]
rndBoolean = [(rand() > 0.5), (rand() > 0.5)]
tmp = Random.randstring(8)
rndString = [tmp, tmp]

cacheReal = [0.0, 0.0]
cacheInteger =  [fmi2Integer(0), fmi2Integer(0)]
cacheBoolean = [fmi2Boolean(false), fmi2Boolean(false)]
cacheString = [pointer(""), pointer("")]

@test fmi2SetReal(comp, realValueReferences, rndReal) == 0
@test fmi2GetReal(comp, realValueReferences) == rndReal
fmi2GetReal!(comp, realValueReferences, cacheReal)
@test cacheReal == rndReal
@test fmi2SetReal(comp, realValueReferences, -rndReal) == 0
@test fmi2GetReal(comp, realValueReferences) == -rndReal
fmi2GetReal!(comp, realValueReferences, cacheReal)
@test cacheReal == -rndReal

@test fmi2SetInteger(comp, integerValueReferences, rndInteger) == 0
@test fmi2GetInteger(comp, integerValueReferences) == rndInteger
fmi2GetInteger!(comp, integerValueReferences, cacheInteger)
@test cacheInteger == rndInteger
@test fmi2SetInteger(comp, integerValueReferences, -rndInteger) == 0
@test fmi2GetInteger(comp, integerValueReferences) == -rndInteger
fmi2GetInteger!(comp, integerValueReferences, cacheInteger)
@test cacheInteger == -rndInteger

@test fmi2SetBoolean(comp, booleanValueReferences, rndBoolean) == 0
@test fmi2GetBoolean(comp, booleanValueReferences) == rndBoolean
fmi2GetBoolean!(comp, booleanValueReferences, cacheBoolean)
@test cacheBoolean == rndBoolean
not_rndBoolean = collect(!b for b in rndBoolean)
@test fmi2SetBoolean(comp, booleanValueReferences, not_rndBoolean) == 0
@test fmi2GetBoolean(comp, booleanValueReferences) == not_rndBoolean
fmi2GetBoolean!(comp, booleanValueReferences, cacheBoolean)
@test cacheBoolean == not_rndBoolean

@test fmi2SetString(comp, stringValueReferences, rndString) == 0
@test fmi2GetString(comp, stringValueReferences) == rndString
fmi2GetString!(comp, stringValueReferences, cacheString)
@test unsafe_string.(cacheString) == rndString

# this is not suppoerted by OMEdit-FMUs in the repository
if ENV["EXPORTINGTOOL"] != "OpenModelica/v1.17.0"
    # Testing input/output derivatives
    dirs = fmi2GetRealOutputDerivatives(comp, ["y_real"], ones(Int, 1))
    @test dirs == -Inf # at this point, derivative is undefined
    @test fmi2SetRealInputDerivatives(comp, ["u_real"], ones(Int, 1), zeros(1)) == 0

    @test fmi2ExitInitializationMode(comp) == 0
    @test fmi2DoStep(comp, 0.1) == 0

    dirs = fmi2GetRealOutputDerivatives(comp, ["y_real"], ones(Int, 1))
    @test dirs == 0.0
else
    @test fmi2ExitInitializationMode(comp) == 0
    @test fmi2DoStep(comp, 0.1) == 0
end

############
# Clean up #
############

fmi2Unload(myFMU)


module TestCausalAlgorithms
using Base.Test
using Causality
using DataFrames
using Requests

sample_data = replace(replace(Requests.get("http://data.princeton.edu/wws509/datasets/smoking.dat").data, r" +", "\t"), r"\r", "")
io = IOBuffer(sample_data)
df = DataFrames.readtable(io, separator='\t', makefactors=true, header=true)
vars = names(df)

# each of these


"""
GAR fileformat
See EUROCONTROL NEST Manual for Gar fileformat description

Dictionary with point list (latitude + longitude [deg]) for each airblock ID (key)
"""
module Gar

export read

using Format
using CSV

const fileformat = Dict(1=>String, 2=>String, 3=>String)

const header_format = ["AorP", "LAT_DEG", "LON_DEG"]

function read(file)
    csviterator = CSV.File(file, delim=";", types=fileformat, header=header_format,
    datarow=2)
    return splitintodict(csviterator)
end

function splitintodict(iterator)
    dict = Dict{String, Matrix{Float64}}()
    points = Matrix{Float64}(undef, 0, 2)
    j = 1
    n_points = 1000
    airblockname = ""
    for (i, row) in enumerate(iterator)
        if row.AorP == "P"
            points = vcat(points,[parse(Float64, row.LAT_DEG) parse(Float64, row.LON_DEG)])
            if j == n_points
                dict[airblockname] = points
            end
            j += 1
        elseif row.AorP == "A"
            airblockname = row.LAT_DEG
            n_points = parse(Int64, row.LON_DEG)
            points = Matrix{Float64}(undef, 0, 2)
            j = 1
        else
        end
    end
    return dict
end

end # module


mutable struct SwitchAssessState{T}
    weight::Float64
    retval::T
    SwitchAssessState{T}(weight::Float64) where T = new{T}(weight)
end

function process!(gen_fn::Switch{C, N, K, T},
                  index::Int,
                  args::Tuple,
                  choices::ChoiceMap,
                  state::SwitchAssessState{T}) where {C, N, K, T}
    (weight, retval) = assess(getindex(gen_fn.branches, index), args, choices)
    state.weight = weight
    state.retval = retval
end

@inline process!(gen_fn::Switch{C, N, K, T}, index::C, args::Tuple, choices::ChoiceMap, state::SwitchAssessState{T}) where {C, N, K, T} = process!(gen_fn, getindex(gen_fn.cases, index), args, choices, state)

function assess(gen_fn::Switch{C, N, K, T},
                args::Tuple,
                choices::ChoiceMap) where {C, N, K, T}
    index = args[1]
    state = SwitchAssessState{T}(0.0)
    process!(gen_fn, index, args[2 : end], choices, state)
    return state.weight, state.retval
end


using nGraph
using CxxWrap
using Test
using Flux
#using Random, Distributions
#using Flux, BenchmarkTools, Zygote

# Testing Strategy
# 1) Type Tests
# 2) Basic op tests without the Flux backend
#    These tests will be pretty basic because constructing graphps without the Flux backend
#    is pretty tedious and error prone.
#
# 3) Flux Backend

# Set OMP_NUM_THREADS to 1 because the sizes of everything used in the test suite is small
# enough that the overhead of parallelization on large systems hurts performance.
ENV["OMP_NUM_THREADS"] = 1

include("types.jl")
include("ops.jl")
# include("loss.jl")
# include("flux.jl")
# include("backprop.jl")
# include("train.jl")
# include("models/mnist.jl")
# include("models/inception.jl")

# include("models/resnet.jl")
# include("models/inception_v4.jl")


module DynamicPressures

struct PressureTube
    L::Vector{Float64}
    D::Vector{Float64}
    V::Vector{Float64}
    ϕ::Vector{Float64}

end