__precompile__()

module Screen
	include("./Types.jl")
	include("./GPU.jl")

	using PyCall
	using OpenCL
	using .Types
	using .GPU

	struct ScreenConfig
		sct::PyObject
		checkHeight::Int
		monitor::Int
		sectors::Array{Int}
		gpuContext::GPUContext
		gpuOpts::GPUOpts
		sectorCount::Int
		sectorSize::Array{Int}
	end

	function config_screen(monitor, height, sectors)::ScreenConfig
		sct = pyimport("mss").mss()
		monitorSize = sct.monitors[monitor]

		gpuOpts = GPUOpts(
			Int32(monitorSize["width"]), # Screen width
			Int32(monitorSize["height"]), # Screen height
			Int32(height), # Ignore min vertical
			Int32(monitorSize["height"] - height), # Ignore max vertical
			Int32(height), # Ignore min horizontal
			Int32(monitorSize["width"] - height), # Ignore max horizontal
			Int32(floor(monitorSize["width"] / sectors[1])), # Sector width top
			Int32(floor((monitorSize["height"] - 2 * height) / sectors[2])), # Sector height right
			Int32(floor(monitorSize["width"] / sectors[3])), # Sector width bottom
			Int32(floor((monitorSize["height"] - 2 * height) / sectors[4])), # Sector height left
			0, # Sector offset top
			Int32(sectors[1]), # Sector offset right
			Int32(sum(sectors[1:2])), # Sector offset bottom
			Int32(sum(sectors[1:3])), # Sector offset left
			Int32(sectors[3]), # Sector bottom count
			Int32(sectors[4]), # Sector left count
			Int32(sum(sectors))
		)

		sectorSize = [
			gpuOpts.sector_width_top * height,
			gpuOpts.sector_width_right * height,
			gpuOpts.sector_width_bottom * height,
			gpuOpts.sector_width_left * height
		]

		gpuContext = init_kernel()

		return ScreenConfig(
			sct,
			height,
			monitor,
			sectors,
			gpuContext,
			gpuOpts,
			sum(sectors),
			sectorSize
		)
	end

	function get_sector(config::ScreenConfig, index::Int)
		if index > config.gpuOpts.sector_offset_left
			return 4
		elseif index > config.gpuOpts.sector_offset_bottom
			return 3
		elseif index > config.gpuOpts.sector_offset_right
			return 2
		else
			return 1
		end
	end

	function hsv_to_rgb(h::Float64, s::Float64, v::Float64)::RGB
		c = v * s
		x = c * (1 - abs((h % 2) - 1))
		m = v - c
		col = [0, 0, 0]

		if h < 1
			col = (c, x, 0)
		elseif h < 2
			col = (x, c, 0)
		elseif h < 3
			col = (0, c, x)
		elseif h < 4
			col = (0, x, c)
		elseif h < 5
			col = (x, 0, c)
		else
			col = (c, 0, x)
		end

		return UInt8.(round.((col .+ m) .* 255))
	end

	function parse_data(config::ScreenConfig, raw::Array{Int32})
		avgColors = Array{RGB}(undef, config.sectorCount)

		@Threads.threads for i in 1:config.sectorCount
			offset = i * 4 - 3
			satWeight = raw[offset + 3]

			if satWeight == 0
				avgColors[i] = (0,0,0)
				continue
			end

			r = @fastmath(raw[offset] / satWeight / 255)
			g = @fastmath(raw[offset + 1] / satWeight / 255)
			b = @fastmath(raw[offset + 2] / satWeight / 255)

			cMin = min(r,g,b)
			cMax = max(r,g,b)
			delta = cMax - cMin
			sat = @fastmath((delta / cMax) ^ (1 / 2.5))

			hue = 0

			if cMax == r
				hue = @fastmath(((g - b) / delta) % 6)
			elseif cMax == g
				hue = @fastmath((b - r) / delta + 2)
			elseif cMax == b
				hue = @fastmath((r - g) / delta + 4)
			end

			avgColors[i] = hsv_to_rgb(abs(hue), sat, cMax)
		end

		return avgColors
	end

	function process_screen_gpu(config::ScreenConfig)
		screenshot = config.sct.grab(config.sct.monitors[config.monitor])
		rawData = process_data(config.gpuContext, config.gpuOpts, screenshot.raw)
		return parse_data(config, rawData)
	end

	export ScreenConfig, config_screen, process_screen_gpu
end


#module CUDNN

include("cudnn_retry.jl")       # TODO: export this to Ops20_gpu, auto-add unsafe_cudnn?
include("common.jl")
include("activation.jl")
include("dropout.jl")
#include("multiheadattn.jl")

#end

## grep cudnnHandle_t
# add8: cudnnBackendExecute
# add8: cudnnBuildRNNDynamic
# add8: cudnnCTCLoss_v8
# add8: cudnnGetFoldedConvBackwardDataDescriptors
# add8: cudnnGetNormalizationBackwardWorkspaceSize
# add8: cudnnGetNormalizationForwardTrainingWorkspaceSize
# add8: cudnnGetNormalizationTrainingReserveSpaceSize
# add8: cudnnGetRNNTempSpaceSizes
# add8: cudnnGetRNNWeightParams
# add8: cudnnGetRNNWeightSpaceSize
# add8: cudnnNormalizationBackward
# add8: cudnnNormalizationForwardInference
# add8: cudnnNormalizationForwardTraining
# add8: cudnnRNNBackwardData_v8
# add8: cudnnRNNBackwardWeights_v8
# add8: cudnnRNNForward
# cudnnActivationBackward
# cudnnActivationForward
# cudnnAddTensor
# cudnnBatchNormalizationBackward
# cudnnBatchNormalizationBackwardEx
# cudnnBatchNormalizationForwardInference
# cudnnBatchNormalizationForwardTraining
# cudnnBatchNormalizationForwardTrainingEx
# cudnnConvolutionBackwardBias
# cudnnConvolutionBackwardData
# cudnnConvolutionBackwardFilter
# cudnnConvolutionBiasActivationForward
# cudnnConvolutionForward
# cudnnCreate
# cudnnCTCLoss
# cudnnDestroy
# cudnnDivisiveNormalizationBackward
# cudnnDivisiveNormalizationForward
# cudnnDropoutBackward
# cudnnDropoutForward
# cudnnDropoutGetStatesSize
# cudnnFindConvolutionBackwardDataAlgorithm
# cudnnFindConvolutionBackwardDataAlgorithmEx
# cudnnFindConvolutionBackwardFilterAlgorithm
# cudnnFindConvolutionBackwardFilterAlgorithmEx
# cudnnFindConvolutionForwardAlgorithm
# cudnnFindConvolutionForwardAlgorithmEx
# cudnnFusedOpsExecute
# cudnnGetBatchNormalizationBackwardExWorkspaceSize
# cudnnGetBatchNormalizationForwardTrainingExWorkspaceSize
# cudnnGetBatchNormalizationTrainingExReserveSpaceSize
# cudnnGetConvolutionBackwardDataAlgorithm_v7
# cudnnGetConvolutionBackwardDataAlgorithmMaxCount
# cudnnGetConvolutionBackwardDataWorkspaceSize
# cudnnGetConvolutionBackwardFilterAlgorithm_v7
# cudnnGetConvolutionBackwardFilterAlgorithmMaxCount
# cudnnGetConvolutionBackwardFilterWorkspaceSize
# cudnnGetConvolutionForwardAlgorithm_v7
# cudnnGetConvolutionForwardAlgorithmMaxCount
# cudnnGetConvolutionForwardWorkspaceSize
# cudnnGetCTCLossWorkspaceSize
# cudnnGetCTCLossWorkspaceSize_v8
# cudnnGetDropoutDescriptor
# cudnnGetMultiHeadAttnBuffers
# cudnnGetMultiHeadAttnWeights
# cudnnGetReductionIndicesSize
# cudnnGetReductionWorkspaceSize
# cudnnGetStream
# cudnnIm2Col
# cudnnLRNCrossChannelBackward
# cudnnLRNCrossChannelForward
# cudnnMakeFusedOpsPlan
# cudnnMultiHeadAttnBackwardData
# cudnnMultiHeadAttnBackwardWeights
# cudnnMultiHeadAttnForward
# cudnnOpTensor
# cudnnPoolingBackward
# cudnnPoolingForward
# cudnnQueryRuntimeError
# cudnnReduceTensor
# cudnnReorderFilterAndBias
# cudnnRestoreDropoutDescriptor
# cudnnScaleTensor
# cudnnSetDropoutDescriptor
# cudnnSetStream
# cudnnSetTensor
# cudnnSoftmaxBackward
# cudnnSoftmaxForward
# cudnnSpatialTfGridGeneratorBackward
# cudnnSpatialTfGridGeneratorForward
# cudnnSpatialTfSamplerBackward
# cudnnSpatialTfSamplerForward
# cudnnTransformFilter
# cudnnTransformTensor
# cudnnTransformTensorEx
# dep8: cudnnFindRNNBackwardDataAlgorithmEx
# dep8: cudnnFindRNNBackwardWeightsAlgorithmEx
# dep8: cudnnFindRNNForwardInferenceAlgorithmEx
# dep8: cudnnFindRNNForwardTrainingAlgorithmEx
# dep8: cudnnGetAlgorithmSpaceSize
# dep8: cudnnGetRNNBackwardDataAlgorithmMaxCount
# dep8: cudnnGetRNNBackwardWeightsAlgorithmMaxCount
# dep8: cudnnGetRNNDescriptor_v6
# dep8: cudnnGetRNNForwardInferenceAlgorithmMaxCount
# dep8: cudnnGetRNNForwardTrainingAlgorithmMaxCount
# dep8: cudnnGetRNNLinLayerBiasParams
# dep8: cudnnGetRNNLinLayerMatrixParams
# dep8: cudnnGetRNNParamsSize
# dep8: cudnnGetRNNProjectionLayers
# dep8: cudnnGetRNNTrainingReserveSize
# dep8: cudnnGetRNNWorkspaceSize
# dep8: cudnnRestoreAlgorithm
# dep8: cudnnRNNBackwardData
# dep8: cudnnRNNBackwardDataEx
# dep8: cudnnRNNBackwardWeights
# dep8: cudnnRNNBackwardWeightsEx
# dep8: cudnnRNNForwardInference
# dep8: cudnnRNNForwardInferenceEx
# dep8: cudnnRNNForwardTraining
# dep8: cudnnRNNForwardTrainingEx
# dep8: cudnnRNNGetClip
# dep8: cudnnRNNSetClip
# dep8: cudnnSaveAlgorithm
# dep8: cudnnSetRNNAlgorithmDescriptor
# dep8: cudnnSetRNNDescriptor_v6
# dep8: cudnnSetRNNProjectionLayers


#local_dir = "/home/awise/Desktop/RvSpectML/RvLineList"
#local_dir = "/storage/work/afw5465/RvSpectML"

#using Pkg
# Pkg.activate(".")
using RvLineList, DataFrames, CSV, ArgParse

local_dir = pkgdir(RvLineList)
cd(local_dir)

#load param file
include(joinpath(local_dir,"inputs","param.jl"))

s = ArgParseSettings()

@add_arg_table s begin
   "--allowBlends"
      help = "number of blends allowed with each line, e.g. 0 for single lines only, 1 for doublets only, [0,1] for singlets and doublets"
      arg_type = Int
   "--overlap_cutoff"
      help = "distance between lines required for them to be categorized as a blend, expressed as a fraction of the speed of light"
      arg_type = Float64
   "--rejectTelluricSlope"
      help = "derivative of spectrum required for telluric line rejection, in units of normalized depth per fraction of speed of light - a value of 0 turns off telluric rejection, 2000-10000 is recommended range in values"
      arg_type = Float64
   "--badLineFilter"
      help = "which mask to use to filter out bad lines. Tested Options: none, ESPRESSOG2"
      arg_type = String
   "--quant"
      help = "quantile for stability of fit params for empirical masks - BROKEN in command line - works in param file" # BROKEN in command line - works in param file
      arg_type = String
   "--nbin"
      help = "number of mask subdivisions to make"
      arg_type = Int
   "--output_dir"
      help = "directory to save mask outputs in"
      arg_type = String
   "--VALD_output"
      help = "whether or not to carry VALD line data through to the final mask"
      arg_type = Bool
end

   parsed_args = parse_args(s)

if parsed_args["allowBlends"] !== nothing
   println("Found command-line arg allowBlends. Overwriting param file definition of this arg.")
   global allowBlends = parsed_args["allowBlends"]
end

if parsed_args["overlap_cutoff"] !== nothing
   println("Found command-line arg overlap_cutoff. Overwriting param file definition of this arg.")
   global overlap_cutoff = parsed_args["overlap_cutoff"]
end

if parsed_args["rejectTelluricSlope"] !== nothing
   println("Found command-line arg rejectTelluricSlope. Overwriting param file definition of this arg.")
   global rejectTelluricSlope = parsed_args["rejectTelluricSlope"]
end

if parsed_args["badLineFilter"] !== nothing
   println("Found command-line arg badLineFilter. Overwriting param file definition of this arg.")
   global badLineFilter = parsed_args["badLineFilter"]
end

if parsed_args["quant"] !== nothing
   println("Found command-line arg quant. Overwriting param file definition of this arg.")
   global quant = parsed_args["quant"]
end

if parsed_args["nbin"] !== nothing
   println("Found command-line arg nbin. Overwriting param file definition of this arg.")
   global nbin = parsed_args["nbin"]
end

if parsed_args["output_dir"] !== nothing
   println("Found command-line arg output_dir. Overwriting param file definition of this arg.")
   global output_dir = parsed_args["output_dir"]
end

if parsed_args["VALD_output"] !== nothing
   println("Found command-line arg VALD_output. Overwriting param file definition of this arg.")
   global VALD_output = parsed_args["VALD_output"]
end

#generate empirical NEID mask

#set up output directories
if !isdir(joinpath(output_dir,"clean_masks"))
   mkdir(joinpath(output_dir,"clean_masks"))
end
if !isdir(joinpath(output_dir,"VALD_masks"))
   mkdir(joinpath(output_dir,"VALD_masks"))
end
if !isdir(joinpath(output_dir,"mask_bins"))
   mkdir(joinpath(output_dir,"mask_bins"))
end
if !isdir(joinpath(output_dir,"linefinder"))
   mkdir(joinpath(output_dir,"linefinder"))
end

pipeline_plan = RvLineList.PipelinePlan()
RvLineList.RvSpectMLBase.Pipeline.save_data!(pipeline_plan, :fit_lines) #tell the pipeline plan to save the line fits

@time empirical_mask = generateEmpiricalMask(output_dir=output_dir, pipeline_plan=pipeline_plan) #TODO: figure out why I can't run this with eval(param.jl) statement commented out in empriical_line_lists.jl (note param file was loaded earlier into global scope) - this eval statement resets quant

using PyCall
try
   pyimport("pandas") #make sure pandas is installed
catch e
   pyimport_conda("pandas", "pandas") #if the module fails to load, install it to Conda.jl environment
   pyimport("pandas") #make sure pandas is installed
end
@pyinclude("src/make_VALD_line_list.py")
@time VALD_masks, VALD_masks_long = py"getVALDmasks"(overlap_cutoff=overlap_cutoff, depth_cutoff=depth_cutoff, iron1Only=iron1Only, badLineFilter=badLineFilter, allowBlends=allowBlends)
VALD_mask = VALD_masks[0]
VALD_mask_long = VALD_masks_long[0]

import Pandas.DataFrame as pd_df

empirical_mask_2col = select(empirical_mask,[:median_λc, :median_depth])
rename!(empirical_mask_2col, [:lambda, :depth])
#debug empirical_mask_2col = CSV.read(joinpath(output_dir,"RvLineList_species=all_depthcutoff=0.05_overlapcutoff=1.0e-5_allowBlends=0_badLineFilter=none_rejectTelluricSlope=0.0_nbin=1_DP=true_binParam=depth_n=0_VACUUM.csv"), DataFrame)
#debug empirical_mask_2col = Dict(pairs(eachcol(empirical_mask_2col)))
empirical_mask_pd = pd_df(empirical_mask_2col)

combined_mask = py"mask_intersection"(empirical_mask_pd, VALD_output ? VALD_mask_long : VALD_mask, threshold=500.0)

function pd_df_to_df(df_pd)
   df = DataFrame()
   for col in df_pd.columns
      df[!,col] = getproperty(df_pd,col).values
   end
   df
end

combined_mask_df = pd_df_to_df(combined_mask)

telluric_indices = py"getTelluricIndices"(combined_mask, true, overlap_cutoff, vel_slope_threshold=rejectTelluricSlope, RV_offset = 0.0, RV_range = 1e-4)

mask = combined_mask_df[map(!,telluric_indices),:]

mask.weight = mask.depth

#binned_masks = binMask(rename(mask,[:lambda,:weight]), nbin, binParam=:weight)
binned_masks = binMask(mask, nbin, binParam=binParam)
#rename!(binned_masks[1], [:lambda, :depth])

for bin_n in 1:nbin
   saveStr = "RvLineList" * "_allowBlends="*string(allowBlends) * "_overlapcutoff="*string(overlap_cutoff) * "_rejectTelluricSlope="*string(rejectTelluricSlope) * "_badLineFilter="*badLineFilter* "_quant="*quant * "_nbin="*string(nbin) * "_DP="*string(depthPercentile) * "_binParam="*string(binParam) * "_n="*string(bin_n) * "_VALD_output="*string(VALD_output) * "_VACUUM" * ".csv"
   CSV.write(joinpath(output_dir,"clean_masks",saveStr),binned_masks[bin_n])
end

#julia --project=RvSpectML/RvLineList RvSpectML/RvLineList/examples/NEID_test_script.jl --allowBlends=0 --overlap_cutoff=1e-5 --rejectTelluricSlope=2000 --badLineFilter="none", --nbin=1 --output_dir="/home/awise/Desktop/neid_masks"

#julia --project=RvSpectML/RvLineList RvSpectML/RvLineList/examples/NEID_test_script.jl --allowBlends=0 --overlap_cutoff=2e-5 --rejectTelluricSlope=2000 --badLineFilter="none", --nbin=1 --output_dir="/home/awise/Desktop/neid_masks"

#julia --project=RvSpectML/RvLineList RvSpectML/RvLineList/examples/NEID_test_script.jl --allowBlends=0 --overlap_cutoff=1e-5 --rejectTelluricSlope=2000 --badLineFilter="ESPRESSOG2", --nbin=1 --output_dir="/home/awise/Desktop/neid_masks"

#julia --project=RvSpectML/RvLineList RvSpectML/RvLineList/examples/NEID_test_script.jl --allowBlends=0 --overlap_cutoff=2e-5 --rejectTelluricSlope=2000 --badLineFilter="ESPRESSOG2", --nbin=1 --output_dir="/home/awise/Desktop/neid_masks"


function get_a(obj::DSSTThirdBodyContext)
    return jcall(obj, "getA", jdouble, ())
end

function get_alpha(obj::DSSTThirdBodyContext)
    return jcall(obj, "getAlpha", jdouble, ())
end

function get_ao_r3_pow(obj::DSSTThirdBodyContext)
    return jcall(obj, "getAoR3Pow", Vector{jdouble}, ())
end

function get_bb(obj::DSSTThirdBodyContext)
    return jcall(obj, "getBB", jdouble, ())
end

function get_bbb(obj::DSSTThirdBodyContext)
    return jcall(obj, "getBBB", jdouble, ())
end

function get_beta(obj::DSSTThirdBodyContext)
    return jcall(obj, "getBeta", jdouble, ())
end

function get_bo_a(obj::DSSTThirdBodyContext)
    return jcall(obj, "getBoA", jdouble, ())
end

function get_bo_a_bpo(obj::DSSTThirdBodyContext)
    return jcall(obj, "getBoABpo", jdouble, ())
end

function get_gamma(obj::DSSTThirdBodyContext)
    return jcall(obj, "getGamma", jdouble, ())
end

function get_hxxx(obj::DSSTThirdBodyContext)
    return jcall(obj, "getHXXX", jdouble, ())
end

function get_kxxx(obj::DSSTThirdBodyContext)
    return jcall(obj, "getKXXX", jdouble, ())
end

function get_m2ao_a(obj::DSSTThirdBodyContext)
    return jcall(obj, "getM2aoA", jdouble, ())
end

function get_m_co2ab(obj::DSSTThirdBodyContext)
    return jcall(obj, "getMCo2AB", jdouble, ())
end

function get_max_ar3_pow(obj::DSSTThirdBodyContext)
    return jcall(obj, "getMaxAR3Pow", jint, ())
end

function get_max_ecc_pow(obj::DSSTThirdBodyContext)
    return jcall(obj, "getMaxEccPow", jint, ())
end

function get_max_freq_f(obj::DSSTThirdBodyContext)
    return jcall(obj, "getMaxFreqF", jint, ())
end

function get_mean_motion(obj::DSSTThirdBodyContext)
    return jcall(obj, "getMeanMotion", jdouble, ())
end

function get_muo_r3(obj::DSSTThirdBodyContext)
    return jcall(obj, "getMuoR3", jdouble, ())
end

function get_oo_ab(obj::DSSTThirdBodyContext)
    return jcall(obj, "getOoAB", jdouble, ())
end

function get_qns(obj::DSSTThirdBodyContext)
    return jcall(obj, "getQns", Vector{Vector{jdouble}}, ())
end

function get_x(obj::DSSTThirdBodyContext)
    return jcall(obj, "getX", jdouble, ())
end

function getb(obj::DSSTThirdBodyContext)
    return jcall(obj, "getb", jdouble, ())
end


module NeuroCore

using ImageCore, ImageAxes, Unitful, ImageMetadata, Markdown
using StaticArrays, Rotations, CoordinateTransformations

export CoordinateList,
       NeuroMetadata,
       NeuroMetaArray,
       BIDSMetadata,
       # methods
       calmax,
       calmax!,
       calmin,
       calmin!,
       description,
       description!,
       freqdim,
       freqdim!,
       is_anatomical,
       is_functional,
       is_electrophysiology,
       neurohelp,
       phasedim,
       phasedim!,
       stream_offset,
       slicedim,
       slicedim!,
       slice_end,
       slice_start,
       spatial_offset,
       spatial_units,
       time_units

const OneF64Sec = 1.0u"s"

"F64Sec - A `Float64` type with seconds units."
const F64Sec = typeof(OneF64Sec)

const OneF64Tesla = 1.0u"T"

"F64Tesla - A `Float64` type with tesla units."
const F64Tesla = typeof(OneF64Tesla)

"F64Hertz - A `Float64` type with hertz units."
const OneF64Hz = 1.0u"Hz"
const F64Hz = typeof(OneF64Hz)

"IntHertz - A `Float64` type with degree units."
const OneIntDeg = 1.0u"°"
const IntDeg = typeof(OneIntDeg)

const OnekOhm = 1.0u"kΩ"
const F64kOhm = typeof(OnekOhm)

const CoordinateList = Dict{Symbol,NTuple{3,Float64}}

const NeuroAffine{R} = AffineMap{R,SArray{Tuple{3},Float64,1,3}}

include("enums.jl")
include("properties.jl")
include("coordinates.jl")
include("bids.jl")
include("getproperty.jl")
include("metadata.jl")
include("array.jl")
include("traits.jl")

"""
    neurohelp(func[;])

"""
neurohelp(func) = neurohelp(stdout, func)
neurohelp(io::IO, input::Symbol) = neurohelp(io, getproperty(NeuroCore, input))
function neurohelp(io::IO, input)
    buffer = IOBuffer()
    println(buffer, Base.Docs.doc(input))
    Markdown.parse(String(take!(buffer)))
end

end


@testset ExtendedTestSet "Metric function" begin
    @testset ExtendedTestSet "`accuracy`" begin
        @test accuracy([1 0; 0 1], [1 0; 0 1]) == 1
        @test accuracy([0.9 0.1; 0.1 0.9], [1 0; 0 1]) == 1
    end

end


IDmm = DataFrame(NPS = [0.125,
	0.25,
	0.375,
	0.5,
	0.75,
	1,
	1.25,
	1.5,
	2,
	2.5,
	3,
	3.5,
	4,
	5,
	6,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	22,
	24,
	26,
	28,
	30,
	32,
	36,
	42,
	48,
	0.125,
	0.25,
	0.375,
	0.5,
	0.75,
	1,
	1.25,
	1.5,
	2,
	2.5,
	3,
	3.5,
	4,
	5,
	6,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	22,
	24,
	26,
	28,
	30,
	32,
	36,
	42,
	48,
	0.125,
	0.25,
	0.375,
	0.5,
	0.75,
	1,
	1.25,
	1.5,
	2,
	2.5,
	3,
	3.5,
	4,
	5,
	6,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	22,
	24,
	26,
	28,
	30,
	32,
	36,
	42,
	48,
	0.5,
	0.75,
	1,
	1.25,
	1.5,
	2,
	2.5,
	3,
	4,
	5,
	6,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	22,
	24,
	26,
	28,
	30,
	32,
	36,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	22,
	24,
	26,
	28,
	30,
	32,
	36,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	22,
	24,
	26,
	28,
	30,
	32,
	36,
	0.125,
	0.25,
	0.375,
	0.5,
	0.75,
	1,
	1.25,
	1.5,
	2,
	2.5,
	3,
	3.5,
	4,
	5,
	6,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	24,
	32,
	36,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	22,
	24,
	0.125,
	0.25,
	0.375,
	0.5,
	0.75,
	1,
	1.25,
	1.5,
	2,
	2.5,
	3,
	3.5,
	4,
	5,
	6,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	22,
	24,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	24,
	4,
	5,
	6,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	24,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	24,
	0.5,
	0.75,
	1,
	1.25,
	1.5,
	2,
	2.5,
	3,
	4,
	5,
	6,
	8,
	10,
	12,
	14,
	16,
	18,
	20,
	24],
Schedule = ["OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"OD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"STD",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"XXS",
"SCH10",
"SCH10",
"SCH10",
"SCH10",
"SCH10",
"SCH10",
"SCH10",
"SCH10",
"SCH10",
"SCH10",
"SCH10",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH20",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH30",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH40",
"SCH60",
"SCH60",
"SCH60",
"SCH60",
"SCH60",
"SCH60",
"SCH60",
"SCH60",
"SCH60",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH80",
"SCH100",
"SCH100",
"SCH100",
"SCH100",
"SCH100",
"SCH100",
"SCH100",
"SCH100",
"SCH120",
"SCH120",
"SCH120",
"SCH120",
"SCH120",
"SCH120",
"SCH120",
"SCH120",
"SCH120",
"SCH120",
"SCH120",
"SCH140",
"SCH140",
"SCH140",
"SCH140",
"SCH140",
"SCH140",
"SCH140",
"SCH140",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160",
"SCH160"],
Idmm = [10.287,
	13.716,
	17.145,
	21.336,
	26.67,
	33.401,
	42.164,
	48.26,
	60.325,
	73.025,
	88.9,
	101.6,
	114.3,
	141.3002,
	168.275,
	219.075,
	273.05,
	323.85,
	355.6,
	406.4,
	457.2,
	508,
	558.8,
	609.6,
	660.4,
	711.2,
	762,
	812.8,
	914.4,
	1066.8,
	1219.2,
	6.84,
	9.22,
	11.56,
	15.7,
	20.9,
	26.6,
	35,
	40.9,
	52.5,
	62.6,
	77.9,
	90.2,
	102.3,
	128.1,
	154.1,
	202.7,
	254.5,
	304.9,
	336.54,
	387.34,
	438.14,
	488.94,
	539.74,
	590.54,
	641.34,
	692.14,
	742.94,
	793.74,
	895.34,
	1047.74,
	1200.14,
	5.48,
	7.66,
	10.7,
	13.9,
	18.9,
	24.4,
	32.4,
	38.1,
	49.3,
	59,
	73.7,
	85.4,
	97.1,
	122.3,
	146.3,
	193.7,
	247.7,
	298.5,
	330.2,
	381,
	431.8,
	482.6,
	533.4,
	584.2,
	635,
	685.8,
	736.6,
	787.4,
	889,
	1041.4,
	1193.8,
	6.3,
	11.1,
	15.2,
	22.8,
	27.9,
	38.1,
	45,
	58.5,
	80.1,
	103.1,
	124.5,
	174.7,
	222.3,
	273.1,
	342.9,
	393.7,
	444.5,
	495.3,
	546.1,
	596.9,
	644.56,
	695.36,
	746.16,
	796.96,
	898.56,
	206.3,
	260.3,
	311.1,
	339.76,
	390.56,
	441.36,
	488.94,
	539.74,
	590.54,
	635,
	685.8,
	736.6,
	787.4,
	889,
	205.1,
	257.5,
	307.1,
	336.54,
	387.34,
	434.94,
	482.6,
	533.4,
	581.06,
	628.64,
	679.44,
	730.24,
	781.04,
	882.64,
	6.84,
	9.22,
	11.56,
	15.7,
	20.9,
	26.6,
	35,
	40.9,
	52.5,
	62.6,
	77.9,
	90.2,
	102.3,
	128.1,
	154.1,
	202.7,
	254.5,
	303.3,
	333.34,
	381,
	428.66,
	477.82,
	574.64,
	777.84,
	876.3,
	198.5,
	247.7,
	295.3,
	325.42,
	373.08,
	419.1,
	466.76,
	514.34,
	560.38,
	5.48,
	7.66,
	10.7,
	13.9,
	18.9,
	24.4,
	32.4,
	38.1,
	49.3,
	59,
	73.7,
	85.4,
	97.1,
	122.3,
	146.3,
	193.7,
	242.9,
	288.9,
	317.5,
	363.52,
	409.54,
	455.62,
	501.6,
	547.68,
	188.9,
	236.5,
	281.1,
	307.94,
	354.02,
	398.48,
	442.92,
	531.82,
	92.1,
	115.9,
	139.7,
	182.5,
	230.3,
	273.1,
	300.02,
	344.48,
	387.36,
	431.8,
	517.56,
	177.9,
	222.3,
	266.7,
	292.1,
	333.36,
	377.82,
	419.1,
	504.82,
	11.7,
	15.5,
	20.6,
	29.4,
	34.1,
	42.9,
	54,
	66.7,
	87.3,
	109.5,
	131.7,
	173.1,
	215.9,
	257.3,
	284.18,
	325.42,
	366.72,
	407.98,
	490.52] )


#jl #! format: off
# # Large Scale Production Cost Modeling with [PowerSimulations.jl](https://github.com/NREL-SIIP/PowerSimulations.jl)

# **Originally Contributed by**: Clayton Barrows

# ## Introduction

# This example shows a basic PCM simulation using the system data assembled in the
# [US-System example](https://nbviewer.jupyter.org/github/NREL-SIIP/SIIPExamples.jl/blob/master/notebook/2_PowerSystems_examples/08_US_system.ipynb).

# ### Dependencies
using SIIPExamples
using PowerSystems
using PowerSimulations
using PowerGraphics
using Logging
using Dates

pkgpath = pkgdir(SIIPExamples)
PSI = PowerSimulations
plotlyjs()

# ### Optimization packages
# You can use the free solvers as in one of the other PowerSimulations.jl examples, but on
# large problems it's useful to have a solver with better performance. I'll use the Xpress
# solver since I have a license, but others such as Gurobi or CPLEX work well too.
using Xpress
solver = optimizer_with_attributes(Xpress.Optimizer, "MIPRELSTOP" => 0.1, "OUTPUTLOG" => 1)

# ### Load the US `System`.
# If you have run the
# [US-System example](https://nbviewer.jupyter.org/github/NREL-SIIP/SIIPExamples.jl/blob/master/notebook/2_PowerSystems_examples/08_US-System.ipynb), the data will
# be serialized in the json and H5 format, so we can efficiently deserialize it:

sys = System(joinpath(pkgpath, "US-System", "SIIP", "sys.json"))
horizon = 24;
interval = Dates.Hour(24);
transform_single_time_series!(sys, horizon, interval);

# ### Selecting flow limited lines
# Since PowerSimulations will apply constraints by component type (e.g. Line), we need to
# change the component type of the lines on which we want to enforce flow limits. So, let's
# change the device type of certain branches from Line to MonitoredLine differentiate
# treatment when we build the model. Here, we can select inter-regional lines, or lines
# above a voltage threshold.

for line in get_components(Line, sys)
    if (get_base_voltage(get_from(get_arc(line))) >= 230.0) &&
       (get_base_voltage(get_to(get_arc(line))) >= 230.0)
        #if get_area(get_from(get_arc(line))) != get_area(get_to(get_arc(line)))
        @info "Changing $(get_name(line)) to MonitoredLine"
        convert_component!(MonitoredLine, line, sys)
    end
end

# ### Create a `template`
# Now we can create a `template` that applies an unbounded formulation to `Line`s and the standard
# flow limited formulation to `MonitoredLine`s.
template = ProblemTemplate(DCPPowerModel)
set_device_model!(template, Line, StaticBranchUnbounded)
set_device_model!(template, TapTransformer, StaticBranchUnbounded)
set_device_model!(template, MonitoredLine, StaticBranch)
set_device_model!(template, ThermalStandard, ThermalStandardUnitCommitment)
set_device_model!(template, RenewableDispatch, RenewableFullDispatch)
set_device_model!(template, PowerLoad, StaticPowerLoad)
set_device_model!(template, HydroDispatch, FixedOutput)

# ### Build and execute single step problem
op_problem = DecisionModel(template, sys; optimizer = solver, horizon = 24)

build!(op_problem, output_dir = mktempdir(), console_level = Logging.Info)

solve!(op_problem)

# ### Analyze results
res = ProblemResults(op_problem)
plot_fuel(res)

# ## Sequential Simulation
# In addition to defining the formulation template, sequential simulations require
# definitions for how information flows between problems.
sim_folder = mkpath(joinpath(pkgpath, "Texas-sim"))
models = SimulationModels(
    decision_models = [
        DecisionModel(
            template,
            sys,
            name = "UC",
            optimizer = solver,
            system_to_file = false,
        ),
    ],
)
DA_sequence =
    SimulationSequence(models = models, ini_cond_chronology = IntraProblemChronology())

# ### Define and build a simulation
sim = Simulation(
    name = "Texas-test",
    steps = 3,
    models = models,
    sequence = DA_sequence,
    simulation_folder = "Texas-sim",
)

build!(
    sim,
    console_level = Logging.Info,
    file_level = Logging.Debug,
    recorders = [:simulation],
)

# ### Execute the simulation
#nb execute!(sim)

# ### Load and analyze results
#nb results = SimulationResults(sim);
#nb uc_results = get_decision_problem_results(results, "UC");

#nb plot_fuel(uc_results)


########

"""
    Fields

This `baremodule` allows scoped enum-like access to electric field components `Ex`, `Ey`,
and `Ez`.

# Examples

```jldoctest; setup=:(using LongwaveModePropagator.Fields)
julia> Fields.Ex
Ex::Field = 0
julia> Fields.Ey
Ey::Field = 1
```
"""
baremodule Fields
using Base: @enum
@enum Field Ex Ey Ez
end

########

"""
    AbstractSampler

Abstract supertype for sampling fields in the waveguide.

Subtypes of AbstractSampler have a position in the guide and a `Fields.Field`.
"""
abstract type AbstractSampler{T} end

function distancechecks(d, stype; earthradius=LMPParams().earthradius)
    !issorted(d) && error("$stype distance must be sorted")
    minimum(d) < 0 && error("$stype distance must be positive")
    maximum(d) > (π*earthradius - 500e3) && @warn "antipode focusing effects not modeled"

    return true
end

"""
    Sampler{T} <: AbstractSampler{T}

`Sampler` types sample (measure) the electromagnetic field in the waveguide.

# Fields

- `distance::T`: ground distance from the transmitter in meters.
- `fieldcomponent::Fields.Field`: field component measured by the `Sampler`.
- `altitude::Float64`: height above the ground in meters.
"""
struct Sampler{T} <: AbstractSampler{T}
    distance::T
    fieldcomponent::Fields.Field
    altitude::Float64

    Sampler(d::T, fc, a) where T = distancechecks(d, Sampler) && new{T}(d, fc, a)
end
distance(s::Sampler) = s.distance
distance(s::Sampler,t::Transmitter) = s.distance
fieldcomponent(s::Sampler) = s.fieldcomponent
altitude(s::Sampler) = s.altitude

"""
    GroundSampler{T} <: AbstractSampler{T}

`GroundSamplers` are `Sampler` types with an altitude of zero.

# Fields

- `distance::T`: ground distance from the transmitter in meters.
- `fieldcomponent::Fields.Field`: field component measured by the `GroundSampler`.
"""
struct GroundSampler{T} <: AbstractSampler{T}
    distance::T  # T is usually <: AbstractVector but could be a scalar
    fieldcomponent::Fields.Field

    GroundSampler(d::T, fc) where T = distancechecks(d, GroundSampler) && new{T}(d, fc)
end
distance(g::GroundSampler) = g.distance
distance(g::GroundSampler,t::Transmitter) = g.distance
fieldcomponent(g::GroundSampler) = g.fieldcomponent
altitude(g::GroundSampler) = 0.0

"""
    Receiver{<:Antenna}

Represent a physical receiver.

A default `Receiver{VerticalDipole}` is returned with `Receiver()`.

# Fields

- `name::String = ""`: receiver name.
- `latitude::Float64 = 0.0`: geographic latitude in degrees.
- `longitude::Float64 = 0.0`: geographic longitude in degrees.
- `altitude::Float64 = 0.0`: receiver altitude in meters above the ground.
- `antenna::Antenna = VerticalDipole()`: receiver antenna.
"""
struct Receiver{A<:Antenna}
    name::String
    latitude::Float64
    longitude::Float64
    altitude::Float64
    antenna::A
end

Receiver() = Receiver{VerticalDipole}("", 0.0, 0.0, 0.0, VerticalDipole())

altitude(r::Receiver) = r.altitude
distance(r::Receiver,t::Transmitter) = nothing   # TODO proj4 stuff
fieldcomponent(r::Receiver) = fieldcomponent(r.antenna)


module PABASTO
### Mailboxes ###
import Base: put!, wait, isready, take!, fetch
using IntervalTrees
using Parameters

type IntervalMailbox <: AbstractChannel
	data::Dict
	lock::ReentrantLock
	function IntervalMailbox()
		new(Dict(),ReentrantLock())
	end
end

function put!{T}(m::IntervalMailbox,v::T,low::Real,high::Real)
	lock(m.lock)
	if !haskey(m.data,T)
		m.data[T]=IntervalMap{Real,T}()
	end
	push!(m.data[T],IntervalValue{Real,T}(low,high,v))
	unlock(m.lock)
	return m
end

function take!(m::IntervalMailbox,position)
	lock(m.lock)
	n=length(values(m.data))
	all_channels=shuffle(collect(m.data))
	for (T,channel) in all_channels
		i=IntervalTrees.firstintersection(channel,Interval{Real}(position-0.1,position+0.1))

		if i.index!=0
			leafnode=i.node
			interval=leafnode.entries.data[i.index]
			c=channel.n
			IntervalTrees.deletefirst!(channel,Interval{Real}(interval.first,interval.last))
			@assert channel.n==c-1
			unlock(m.lock)
			return interval.value
		end
	end
	unlock(m.lock)
	return nothing
end

type Mailbox <: AbstractChannel
	data::Dict
	function Mailbox()
		new(Dict())
	end
end

function put!{T}(m::Mailbox, v::T)
	if !haskey(m.data,T)
		m.data[T]=Channel(100)
	end
	put!(m.data[T],v)
	return m
end

function take!(m::Mailbox)
	n=length(values(m.data))
	all_channels=filter(x->isready(x[2]),collect(m.data))
	if length(all_channels)>0
		priorities=map(x->priority(x[1],length(x[2].data)),all_channels)
		mtype,c=all_channels[indmax(priorities)]
		return take!(c)
	else
		return nothing
	end
end

### Parameters ###
abstract Parameter
abstract Gradient
type ConcreteParameter <: Parameter
end

type ConcreteGradient <: Gradient
end

### Messages ###

type GradientUpdateMessage
	gradient::Future
end

function GradientUpdateMessage(gradient::Gradient)
	GradientUpdateMessage(@spawnat myid() gradient)
end

type ParameterUpdateRequestMessage
	worker_mailbox::RemoteChannel
end

type ParameterUpdateMessage
	parameters
end

type ParameterRequestMessage
end

type ExampleIndicesMessage
	indices::Array{Int}
end
type TestExampleIndicesMessage
	indices::Array{Int}
end
type TestLossMessage
	loss::Real
end

type ExamplesRequestMessage
	id::Int
	worker_mailbox::RemoteChannel
end

type CeaseOperationMessage
end

type CeasedOperationMessage
	id::Int
end

type FinishOperationMessage
end

type FinishedOperationMessage
	id::Int
end

type AdaptiveControlPolicyMessage
	tau::Float64
	num_workers::Int
	num_paramservers::Int
	example_batch_size::Int
	gossip_time::Float64
end

function update(p::Parameter, g::Gradient)
	#update p with gradient g
end

function add_procs(count)
	return fetch(@spawnat 1 Main.add_pabasto_procs(count))
end

##Priorities##
#The default priority of a message type is just the number
#of messages of that type

#To define priority of a new type, follow the example of
#ParameterUpdateMessage
function priority(message_type,queue_length)
	return queue_length
end
function priority(x::Type{TestExampleIndicesMessage},queue_length)
	return 5*queue_length
end

function priority(x::Type{ParameterUpdateMessage},queue_length)
	return 10*queue_length
end

function priority(x::Type{FinishOperationMessage},queue_length)
	return -1
end

# todo: split into separate modules
include("master.jl")
include("paramserver.jl")
include("worker.jl")
end


const HEADER_SIZE = 227
const DATA_OFFSET = 227
const SIZE_DATARECORD = 26

# """
# save file .las
# """
# function save_new_las(filename::String,h::LasIO.LasHeader,p::LasIO.Array{LasPoint,1})
#     if ispath(filename) #overwrite
#         rm(filename)
#     end
#     LasIO.FileIO.save(filename,h,p)
# end

"""
create a header
"""
function newHeader(aabb::AABB, software::String, sizePointRecord, npoints=0; scale=0.001)

	file_source_id=UInt16(0)
	global_encoding=UInt16(0)
	guid_1=UInt32(0)
	guid_2=UInt16(0)
	guid_3=UInt16(0)
	guid_4=""
	version_major=UInt8(1)
	version_minor=UInt8(2)
	system_id=""
	software_id = software
	creation_dayofyear = UInt16(Dates.dayofyear(today()))
	creation_year = UInt16(Dates.year(today()))
	header_size=UInt16(HEADER_SIZE) # valore fisso
	data_offset=UInt16(DATA_OFFSET) #valore fisso
	n_vlr=UInt32(0)
	data_format_id=UInt8(2)
	data_record_length=UInt16(sizePointRecord) #valore variabile
	records_count=UInt32(npoints)
	point_return_count=UInt32[0,0,0,0,0]
	x_scale=scale
	y_scale=scale
	z_scale=scale
	x_offset = aabb.x_min
	y_offset = aabb.y_min
	z_offset = aabb.z_min
	x_max = aabb.x_max
	x_min = aabb.x_min
	y_max = aabb.y_max
	y_min = aabb.y_min
	z_max = aabb.z_max
	z_min = aabb.z_min
	variable_length_records=Vector{LasVariableLengthRecord}()
	user_defined_bytes=Vector{UInt8}()

	return LasIO.LasHeader(file_source_id,
    global_encoding,
    guid_1,
    guid_2,
    guid_3,
    guid_4,
    version_major,
    version_minor,
    system_id,
    software_id,
    creation_dayofyear,
    creation_year,
    header_size,
    data_offset,
    n_vlr,
    data_format_id,
    data_record_length,
    records_count,
    point_return_count,
    x_scale,
    y_scale,
    z_scale,
    x_offset,
    y_offset,
    z_offset,
    x_max,
    x_min,
    y_max,
    y_min,
    z_max,
    z_min,
    variable_length_records,
    user_defined_bytes
	)
end

"""
create a new point record for las file.
"""
function newPointRecord(laspoint::LasIO.LasPoint, header::LasIO.LasHeader, type::DataType, mainHeader::LasIO.LasHeader; affineMatrix = Matrix{Float64}(Common.I,4,4))

	point = Common.apply_matrix(affineMatrix,[xcoord(laspoint,header), ycoord(laspoint,header), zcoord(laspoint,header)])
	x = LasIO.xcoord(point[1],mainHeader)
	y = LasIO.ycoord(point[2],mainHeader)
	z = LasIO.zcoord(point[3],mainHeader)
	intensity = laspoint.intensity
	flag_byte = laspoint.flag_byte
	raw_classification = laspoint.raw_classification
	scan_angle = laspoint.scan_angle
	user_data = laspoint.user_data
	pt_src_id = laspoint.pt_src_id

	if type == LasIO.LasPoint0
		return type(x, y, z,
					intensity, flag_byte, raw_classification,
					scan_angle, user_data, pt_src_id
					)

	elseif type == LasIO.LasPoint1
		gps_time = laspoint.gps_time
		return type(x, y, z,
					intensity, flag_byte, raw_classification,
					scan_angle, user_data, pt_src_id, gps_time
					)

	elseif type == LasIO.LasPoint2
		red = laspoint.red
		green = laspoint.green
		blue = laspoint.blue
		return type(x, y, z,
					intensity, flag_byte, raw_classification,
					scan_angle, user_data, pt_src_id,
					red, green, blue
					)

	elseif type == LasIO.LasPoint3
		gps_time = laspoint.gps_time
		red = laspoint.red
		green = laspoint.green
		blue = laspoint.blue
		return type(x, y, z,
					intensity, flag_byte, raw_classification,
					scan_angle, user_data, pt_src_id, gps_time,
					red, green, blue
					)

	end

end

function newPointRecord(point::Array{Float64,1}, rgb::Array{LasIO.N0f16,1} , type::LasIO.DataType, mainHeader::LasIO.LasHeader) #crea oggetto laspoint con vertici e colori

	x = LasIO.xcoord(point[1],mainHeader)
	y = LasIO.ycoord(point[2],mainHeader)
	z = LasIO.zcoord(point[3],mainHeader)
	intensity = UInt16(0)
	flag_byte = UInt8(0)
	raw_classification = UInt8(0)
	scan_angle = Int8(0)
	user_data = UInt8(0)
	pt_src_id = UInt16(0)

	if type == LasIO.LasPoint0
		return type(x, y, z,
					intensity, flag_byte, raw_classification,
					scan_angle, user_data, pt_src_id
					)

	elseif type == LasIO.LasPoint1
		gps_time = Float64(0)
		return type(x, y, z,
					intensity, flag_byte, raw_classification,
					scan_angle, user_data, pt_src_id, gps_time
					)

	elseif type == LasIO.LasPoint2
		red = rgb[1]
		green = rgb[2]
		blue = rgb[3]
		return type(x, y, z,
					intensity, flag_byte, raw_classification,
					scan_angle, user_data, pt_src_id,
					red, green, blue
					)

	elseif type == LasIO.LasPoint3
		gps_time = Float64(0)
		red = rgb[1]
		green = rgb[2]
		blue = rgb[3]
		return type(x, y, z,
					intensity, flag_byte, raw_classification,
					scan_angle, user_data, pt_src_id, gps_time,
					red, green, blue
					)

	end

end

"""
Save a point cloud in las format
"""
function save_pointcloud(filename::String, pc::PointCloud,  software::String)

	if pc.dimension == 2
		points = vcat(pc.coordinates,zeros(pc.n_points)')
	else
		points = pc.coordinates
	end

	rgbs = pc.rgbs
	npoints = pc.n_points

	aabb = AABB(points)
	header = newHeader(aabb,software,SIZE_DATARECORD,npoints)

	pvec = Array{LasPoint,1}(undef,npoints)
	for i in 1:npoints
		point = newPointRecord(points[:,i], rgbs[:,i], LasIO.LasPoint2, header)
		pvec[i] = point
	end

	LasIO.FileIO.save(filename,header,pvec)
end

#
# """
#  	createlasdata(p,h,header)
#
# create LasPoint from coordinates.
# """
# function createlasdata(p,h::LasIO.LasHeader,mainHeader::LasIO.LasHeader)
# 	type = pointformat(h)
#
# 	x = LasIO.xcoord(xcoord(p,h),mainHeader)
# 	y = LasIO.ycoord(ycoord(p,h),mainHeader)
# 	z = LasIO.zcoord(zcoord(p,h),mainHeader)
# 	intensity = p.intensity
# 	flag_byte = p.flag_byte
# 	raw_classification = p.raw_classification
# 	scan_angle = p.scan_angle
# 	user_data = p.user_data
# 	pt_src_id = p.pt_src_id
#
# 	if type == LasIO.LasPoint0
# 		return type(x, y, z,
# 					intensity, flag_byte, raw_classification,
# 					scan_angle, user_data, pt_src_id
# 					)
#
# 	elseif type == LasIO.LasPoint1
# 		gps_time = p.gps_time
# 		return type(x, y, z,
# 					intensity, flag_byte, raw_classification,
# 					scan_angle, user_data, pt_src_id, gps_time
# 					)
#
# 	elseif type == LasIO.LasPoint2
# 		red = p.red
# 		green = p.green
# 		blue = p.blue
# 		return type(x, y, z,
# 					intensity, flag_byte, raw_classification,
# 					scan_angle, user_data, pt_src_id,
# 					red, green, blue
# 					)
#
# 	elseif type == LasIO.LasPoint3
# 		gps_time = p.gps_time
# 		red = p.red
# 		green = p.green
# 		blue = p.blue
# 		return type(x, y, z,
# 					intensity, flag_byte, raw_classification,
# 					scan_angle, user_data, pt_src_id, gps_time,
# 					red, green, blue
# 					)
#
# 	end
# end

# """
# 	bbincremental!(coordpoint,bb)
#
# """
# function bbincremental!(coordpoint,bb)
#
# 	for i in 1:length(coordpoint)
# 		if coordpoint[i] < bb[1][i]
# 			bb[1][i] = coordpoint[i]
# 		end
# 		if coordpoint[i] > bb[2][i]
# 			bb[2][i] = coordpoint[i]
# 		end
# 	end
#
# 	return true
# end

# """
# .
# """
#
# function set_z_zero(points::Array{LasPoint2,1},header::LasIO.LasHeader)
# 	type = pointformat(header)
# 	pvec = Vector{LasPoint}()
# 	for p in points
# 		x = p.x
# 		y = p.y
# 		z = 0
# 		intensity = p.intensity
# 		flag_byte = p.flag_byte
# 		raw_classification = p.raw_classification
# 		scan_angle = p.scan_angle
# 		user_data = p.user_data
# 		pt_src_id = p.pt_src_id
#
# 		if type == LasIO.LasPoint0
# 			laspoint = type(x, y, z,
# 						intensity, flag_byte, raw_classification,
# 						scan_angle, user_data, pt_src_id
# 						)
#
# 		elseif type == LasIO.LasPoint1
# 			gps_time = p.gps_time
# 			laspoint = type(x, y, z,
# 						intensity, flag_byte, raw_classification,
# 						scan_angle, user_data, pt_src_id, gps_time
# 						)
#
# 		elseif type == LasIO.LasPoint2
# 			red = p.red
# 			green = p.green
# 			blue = p.blue
# 			laspoint = type(x, y, z,
# 						intensity, flag_byte, raw_classification,
# 						scan_angle, user_data, pt_src_id,
# 						red, green, blue
# 						)
#
# 		elseif type == LasIO.LasPoint3
# 			gps_time = p.gps_time
# 			red = p.red
# 			green = p.green
# 			blue = p.blue
# 			laspoint = type(x, y, z,
# 						intensity, flag_byte, raw_classification,
# 						scan_angle, user_data, pt_src_id, gps_time,
# 						red, green, blue
# 						)
#
# 		end
# 		push!(pvec,laspoint)
# 	end
# 	return pvec
# end

# """
# 	mergelas(headers,pointdata,bb,scale)
#
# Merge more file .las.
# """
# function mergelas(headers,pointdata)
# 	@assert length(headers) == length(pointdata) "mergelas: inconsistent data"
#
# 	# header of merging las
# 	hmerge = createheader(headers,pointdata)
# 	data = LasIO.LasPoint[]
#
# 	# Las point data merge
# 	for i in 1:length(pointdata)
# 		for p in pointdata[i]
# 			laspoint = createlasdata(p,headers[i],hmerge)
# 			push!(data,laspoint)
# 		end
# 	end
#
# 	return hmerge,data
# end

# """
#  	createheader(headers,pointdata,bb,scale)
#
# crea header coerente con i miei punti.
# """
# function createheader(headers,pointdata)
# 	type = pointformat(headers[1])
# 	h = deepcopy(headers[1])
# 	h.records_count = sum(length.(pointdata))
# 	return h
# end


function molecule(::Molecule"methyl")
  return """
    C           -0.000010438834    -0.000032040912    -0.000093850261
    H           -0.407478649408    -0.969388634215     0.251402951509
    H           -0.662923620356     0.817562451381    -0.247286128865
    H            1.070526563167     0.152207688478    -0.002999363660
  """
end


using ModelingToolkit: operation, istree, arguments, variable
import DomainSets

# Method of lines discretization scheme
struct MOLFiniteDifference{T,T2} <: DiffEqBase.AbstractDiscretization

end

MOLFiniteDifference(args...) = throw(ErrorException("MOLFiniteDifference has been moved to the MethodOfLines packgage, please use MethodOfLines.MOLFiniteDifference instead"))

# Constructors. If no order is specified, both upwind and centered differences will be 2nd order
MOLFiniteDifference(dxs, time; upwind_order = 1, centered_order = 2, grid_align=center_align) = throw(ErrorException("MOLFiniteDifference has been moved to the MethodOfLines packgage, please install with `Pkg.add(\"MethodOfLines\")` and use MethodOfLines.MOLFiniteDifference instead"))

function SciMLBase.symbolic_discretize(pdesys::ModelingToolkit.PDESystem,discretization::DiffEqOperators.MOLFiniteDifference)
    throw(ErrorException("Symbolic Method of Lines discretization has been moved to the MethodOfLines packgage, please install this instead with `Pkg.add(\"MethodOfLines\")`"))
end

function SciMLBase.discretize(pdesys::ModelingToolkit.PDESystem,discretization::DiffEqOperators.MOLFiniteDifference)
    throw(ErrorException("Symbolic Method of Lines discretization has been moved to the MethodOfLines packgage, please install this instead with `Pkg.add(\"MethodOfLines\")`"))
end


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

@testset "Operation set ref models" begin
    template = OperationsProblemTemplate(CopperPlatePowerModel, devices, branches, services)
    c_sys5 = build_system("c_sys5")
    op_problem = OperationsProblem(TestOpProblem, template, c_sys5)
    set_transmission_model!(op_problem, DCPLLPowerModel)
    @test op_problem.template.transmission == DCPLLPowerModel

    template = OperationsProblemTemplate(CopperPlatePowerModel, devices, branches, services)
    op_problem = OperationsProblem(TestOpProblem, template, c_sys5)
    new_devices = Dict{Symbol, DeviceModel}(
        :Generators => DeviceModel(ThermalStandard, ThermalBasicUnitCommitment),
        :Loads => DeviceModel(PowerLoad, StaticPowerLoad),
    )
    set_devices_template!(op_problem, new_devices)
    @test op_problem.template.devices[:Generators].formulation == ThermalBasicUnitCommitment
    jump_model = op_problem.psi_container.JuMPmodel
    @test ((JuMP.VariableRef, MOI.ZeroOne) in JuMP.list_of_constraint_types(jump_model)) ==
          true

    template = OperationsProblemTemplate(DCPPowerModel, devices, branches, services)
    op_problem = OperationsProblem(TestOpProblem, template, c_sys5)
    new_branches = Dict{Symbol, DeviceModel}(:L => DeviceModel(Line, StaticLine))
    set_branches_template!(op_problem, new_branches)
    @test op_problem.template.branches[:L].formulation == StaticLine
end

@testset "Operation set models" begin
    template = OperationsProblemTemplate(CopperPlatePowerModel, devices, branches, services)
    c_sys5 = build_system("c_sys5")
    op_problem = OperationsProblem(TestOpProblem, template, c_sys5)
    set_device_model!(
        op_problem,
        :Generators,
        DeviceModel(ThermalStandard, ThermalBasicUnitCommitment),
    )
    @test op_problem.template.devices[:Generators].formulation == ThermalBasicUnitCommitment
    jump_model = op_problem.psi_container.JuMPmodel
    @test ((JuMP.VariableRef, MOI.ZeroOne) in JuMP.list_of_constraint_types(jump_model)) ==
          true

    template = OperationsProblemTemplate(DCPPowerModel, devices, branches, services)
    op_problem = OperationsProblem(TestOpProblem, template, c_sys5)
    set_branch_model!(op_problem, :L, DeviceModel(Line, StaticLine))
    @test op_problem.template.branches[:L].formulation == StaticLine
    services_filled = Dict{Symbol, ServiceModel}(
        :Reserve => ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
    )
    template_s =
        OperationsProblemTemplate(DCPPowerModel, devices, branches, services_filled)
    op_problem_s = OperationsProblem(TestOpProblem, template_s, c_sys5)
    PSI.set_services_model!(
        op_problem_s,
        :Reserve,
        ServiceModel(VariableReserve{ReserveUp}, RangeReserve),
    )
    @test op_problem_s.template.services[:Reserve].formulation == RangeReserve
    @test_throws IS.ConflictingInputsError set_branch_model!(
        op_problem,
        :Reserve,
        DeviceModel(Line, StaticLine),
    )
    #@test_throws IS.ConflictingInputsError set_services_model!(op_problem_s, collect(keys(branches))[1], ServiceModel(VariableReserve{ReserveUp}, RangeReserve))
end


######################################################################
# data structures
######################################################################

"""
Keep track of spell durations using an interval type.
"""
typealias DateInterval ClosedInterval{Date}

"""
Define a constructor for `T`, which accepts DateInterval fields as two
arguments.
"""
macro constructor_DateInterval(t::Symbol)
    T = eval(t)
    @assert isa(T, DataType) "$t needs to be a type."
    outer = []
    inner = []
    for name in fieldnames(T)
        ft = fieldtype(T, name)
        if ft == DateInterval
            arg_start = gensym(name)
            arg_end = gensym(name)
            push!(outer, :($arg_start::Date), :($arg_end::Date))
            push!(inner, :(DateInterval($arg_start, $arg_end)))
        else
            push!(outer, :($name::$ft))
            push!(inner, name)
        end
    end
    @eval $(t)($(outer...))=$(t)($(inner...))
end

"""
Information on (spells of) personal characteristics (nationality and
academic degree may change over time).

- based on HV data
- Smooting: No; Overlap: No
"""
@auto_hash_equals immutable PersonSpell
    birth_year::Int
    gender::Gender
    nationality::Int
    # NOTE ignoring education::String
    death_date::Nullable{Date}
    interval::DateInterval
    # NOTE ignoring timestamp::Date
end

@constructor_DateInterval PersonSpell

"""
Parser for person spells (`hv_pn`). Currently ignore education
(unreliable), infer death date availability from whether death date is
missing, ignore timestamp.
"""
const PersonSpellParser =
    LineParser(PersonSpell,
               1 => Int,        # 1 birth year
               2 => Gender,     # 2 gender
               3 => Int,        # 3 nationality
               # 4 education levels: ignored
               # 5 death date availability: ignored
               6 => MaybeEmpty{AMDB_Date}, # 6 death date
               7 => AMDB_Date,             # 7 spell start
               8 => AMDB_Date)             # 8 spell end

"""
A spell from the AMP datasets, eg `hv_epi_uni_roh`.
"""
@auto_hash_equals immutable AMPSpell
    employer::Int
    status::AMP.Spell
    interval::DateInterval
end

@constructor_DateInterval AMPSpell

"""
Parser for AMP spells (eg `hv_epi_uni_roh`). Ignore timestamp, and
last field (imputation indicator?).
"""
const AMPSpellParser =
    LineParser(AMPSpell,
               1 => Int,            # employed ID
               2 => AMP.spell_dict, # spell status
               3 => AMDB_Date,      # spell start
               4 => AMDB_Date)      # spell end
"""
A spell from the `vmz_v2_int` dataset.
"""
@auto_hash_equals immutable VMZSpell
    status::VMZ.Spell
    discontinuation::VMZ.Einstellgrund
    interval::DateInterval
end

@constructor_DateInterval VMZSpell

"""
Parser for VMZ spells (`vmz_v2_int`). Ignore case id (first field).
"""
const VMZSpellParser =
    LineParser(VMZSpell,
               3 => VMZ.spell_dict,         # spell status
               4 => VMZ.einstellgrund_dict, # einstellgrund
               1 => AMDB_Date,              # spell start
               2 => AMDB_Date)              # spell end

"""
Data matched for an individual, from various data files.
"""
@auto_hash_equals immutable IndividualData
    person_spells::Vector{PersonSpell}
    AMP_spells::Vector{AMPSpell}
    VMZ_spells::Vector{VMZSpell}
end

IndividualData() = IndividualData(Vector{PersonSpell}(),
                                  Vector{AMPSpell}(),
                                  Vector{VMZSpell}())


"""
Submodule of `SirenSeq`.
Used for writing MIDI files.
"""
module Midi

export

## from this midi.jl

MidiTrack, writeMidiFile, tim, timm,

## from chanmess.jl

progSel!, bankSel!, noteOn!, noteOff!, pitchWheel!, chanAfter!, polyAfter!, control14!, control7!,

## from sysmess.jl

timeSigSet!, tempoSet!, trackEnd!, trackMarker!

abstract MidiMes
abstract ChanMes <: MidiMes
abstract SysMes <: MidiMes

"a collection of midi messages in a track"
type MidiTrack
	ms::Vector{MidiMes}		# set of midi messages, order in `ms` is irrelevant
	tpq::Int				# ticks per quarter note
	ff::Int					# current track head offset in ticks

	function MidiTrack(tpq::Int)
		@assert tpq > 0
		new(Any[],tpq,0)
	end
end

"""
	tim(mt::MidiTrack, v::Rational{Int}) -> Int

converts note length `v` to midi ticks for `mt`
"""
function tim(mt::MidiTrack, v::Rational{Int})
	x = v*mt.tpq*4 ;  @assert x >= 0
	round(Int,x)
end

"""
	timm(mt::MidiTrack, v::Rational{Int}) -> Int

converts note length `v` to midi ticks for `mt` and adds offset of `mt`
"""
function timm(mt::MidiTrack, v::Rational{Int})
	x = v*mt.tpq*4 + mt.ff ;  @assert x >= 0
	round(Int,x)
end

include("inout.jl")
include("sysmess.jl")
include("chanmess.jl")

## if events occur on the same tick in the track
## these numbers are used to determine thier order

precValMidiMes(m::NoteOff) = 0
precValMidiMes(m::TrackMarker) = 1
precValMidiMes(m::TimeSigSet) = 2
precValMidiMes(m::TempoSet) = 3
precValMidiMes(m::BankSel) = 4
precValMidiMes(m::ProgSel) = 5
precValMidiMes(m::Control14) = 6
precValMidiMes(m::Control7) = 7
precValMidiMes(m::ChanAfter) = 8
precValMidiMes(m::NoteOn) = 9
precValMidiMes(m::PolyAfter) = 10
precValMidiMes(m::PitchWheel) = 11
precValMidiMes(m::TrackEnd) = 12

compMidiEvByType(m1::MidiMes, m2::MidiMes) = precValMidiMes(m1) < precValMidiMes(m2)

function compMidiMes(m1::MidiMes, m2::MidiMes)
	m1.time < m2.time && return true
	m1.time > m2.time && return false
	compMidiEvByType(m1,m2)
end

## used for verbose mode of `writeMidiFile`

function midiMesDesc(m::MidiMes)
	"$(typeof(m))"
end

function midiMesDesc(m::NoteOn)
	"$NoteOn    $(m.pitch)"
end

function midiMesDesc(m::NoteOff)
	"$NoteOff   $(m.pitch)"
end

function midiMesDesc(m::TrackMarker)
	"$TrackMarker   $(m.mes)"
end

function midiMessToBytes(mt::MidiTrack, verbose::Bool)
	ms::Vector{Any} = sort(mt.ms,lt=compMidiMes)
	if verbose
		println()
		for m::MidiMes in ms
			print("$(lpad(m.time,6))    $(lpad(round(10*m.time/(mt.tpq*4))/10,4))    $(midiMesDesc(m))\n")
		end
		println()
	end
	bs = UInt8[]
	tm = 0
	for m::MidiMes in ms
		@assert typeof(m) != TrackEnd
		append!(bs,bytesev(m,tm))
		tm = m.time
	end
	tm = max(tm,mt.ff)
	append!(bs,bytesev(TrackEnd(mt.ff+1),mt.ff))
	bs
end

"""
	writeMidiFile(out::IOStream, mt::MidiTrack, [verbose::Bool])

Writes `mt` as a midi file to `out`. If `verbose` is true, it will write a description of all events to standard output
"""
function writeMidiFile(out::IOStream, mt::MidiTrack, verbose::Bool)
	bs = midiMessToBytes(mt,verbose)
	writeFileHeaderT0(out,mt.tpq)
	writeTrackHeader(out,length(bs))
	write(out,bs)
end

end


function aggregate(obj::AggregatableStatistic, arg0::Iterable)
    return jcall(obj, "aggregate", void, (Iterable,), arg0)
end

function aggregate(obj::AggregatableStatistic, arg0::Vector{Object})
    return jcall(obj, "aggregate", void, (Vector{Object},), arg0)
end

function aggregate(obj::FourthMoment, arg0::SecondMoment)
    return jcall(obj, "aggregate", void, (SecondMoment,), arg0)
end

function clear(obj::FourthMoment)
    return jcall(obj, "clear", void, ())
end

function copy(obj::FourthMoment)
    return jcall(obj, "copy", FourthMoment, ())
end

function get_result(obj::FourthMoment)
    return jcall(obj, "getResult", jdouble, ())
end

function increment(obj::FourthMoment, arg0::jdouble)
    return jcall(obj, "increment", void, (jdouble,), arg0)
end