include("impl.jl")

## NNPACK supports only Float32
for (front_name, backend) in (
        :conv          => :_nnpack,
        :∇conv_data    => :_nnpack,
        :∇conv_filter  => :_nnpack,
    )
    @eval begin
        function $(Symbol("$(front_name)$(backend)!"))(
                        out::Array{T1,4}, in1::Array{T2,4}, in2::Array{T3,4},
                        cdims::ConvDims; kwargs...) where {T1, T2, T3}
            @warn "Automatically converting input tensor to Float32. This will have performance implications" maxlog=1
            # Output must of the same type as in the function signature
            T1.($(Symbol("$(front_name)$(backend)!"))(Float32.(out), Float32.(in1),
                                                      Float32.(in2), cdims; kwargs...))
        end
    end
end

function maxpool_nnpack!(y::Array{T1, 4}, x::Array{T2, 4}, pdims::PoolDims;
                         kwargs...) where {T1, T2}
    @warn "Automatically converting input tensor to Float32. This will have performance implications" maxlog=1
    # We want the output to be of the same type as desired
    T1.(maxpool_nnpack!(Float32.(y), Float32.(x), pdims; kwargs...))
end

"""
    nnpack_supported_operation(cdims::ConvDims)
    nnpack_supported_operation(pdims::PoolDims)

Returns `true` if nnpack supports the convolution/pooling operation for the given parameters.
"""
function nnpack_supported_operation(pdims::PoolDims{2, K, S, P, (1, 1)}) where {K, S, P}
    val = input_size(pdims)[1:2] .+ (P[1] + P[2], P[3] + P[4]) .- K
    return val .% S == (0, 0) ? true : false
end

function nnpack_supported_operation(cdims::ConvDims{2, K, (1, 1), P, (1, 1)}) where {K, S, P}
    return true
end

# Return false for everything else
nnpack_supported_operation(dims) = false


using BSON
using PyCall
using StatsBase
using Statistics
using OrderedCollections

using .Evaluation: compute_stats, _get_anomaly_class

const BAYES_CACHE = "bayes_cache.bson"
"""
	BayesianHyperOpt

Wrapper around `skotp.optimizer.Optimizer`.
Uses default params from `skopt.gp_minimize`.
Constructed using a list/array of `skotp.space`'s classes.
"""
struct BayesianHyperOpt
	opt
	function BayesianHyperOpt(dimensions)
		py"""
		import numpy as np
		from skopt import Optimizer
		from skopt.utils import cook_estimator, normalize_dimensions
		from sklearn.utils import check_random_state

		def construct_optimizer(dimensions):
			rng = check_random_state(7) 			# fix seed to fit always the same model
			space = normalize_dimensions(dimensions)

			base_estimator = cook_estimator(
							"GP", space=space,
							random_state=rng.randint(0, np.iinfo(np.int32).max),
							noise="gaussian")

			# default from gp_minimize
			acq_optimizer_kwargs = {"n_points": 10000, "n_restarts_optimizer": 5, "n_jobs": 1}
			acq_func_kwargs = {"xi": 0.01, "kappa": 1.96}

			optimizer = Optimizer(
				space,
				base_estimator,
				n_initial_points=0, 				# should be zero in order to skip the random generation
				initial_point_generator="random", 	# it fallbacks into random even if it is None, so here you go
				n_jobs=-1,
				acq_func="gp_hedge", 				# default from gp_minimize
				acq_optimizer="auto", 				# default from gp_minimize
				random_state=rng,
				model_queue_size=None,				# default from gp_minimize
				acq_optimizer_kwargs=acq_optimizer_kwargs,
				acq_func_kwargs=acq_func_kwargs)

			return optimizer
		"""
		new(py"construct_optimizer"(dimensions))
	end
end

"""
	function tell(bho::BayesianHyperOpt, x0, y0)

Fits the optimizer based on multiple observations of x0 (hyperparameter)
and y0 (objective). Be careful about the input types here as conversions
to Python are tricky. By default `x0` is passed as list of lists, however
vector of values `y0` is converted to `numpy.ndarray`, which cannot
be processed by `skopt` (!! What a nice ecosystem !!).
"""
function tell(bho::BayesianHyperOpt, x0, y0)
	bho.opt.tell(x0, Tuple(y0))
end

function ask(bho::BayesianHyperOpt)
	bho.opt.ask()
end

"""
	skopt_parse(dimension, entry::T)
Converts and entry of a hyperparameter from skotp dimension to named tuple.
Dimension's name have to following this convention:

```py
	Integer(1, 3, name="log2_{name}") # will convert to 2^p
	Integer(1, 3, name="log10_{name}") # will convert to 10^p
	Categorical(categories = [:dense, :all, :none], name="sym_{name}") # will convert to and from Symbol
	Categorical(categories = [identity], name="fun_{name}") # will convert to and from a function name
```
Other cases of `T` such as Bool, Real, Int are handled without any specific treatment.
"""
skopt_parse(dimension, entry::Bool) = (;[Symbol(dimension.name) => entry]...)
skopt_parse(dimension, entry::T) where {T <: AbstractFloat} = (;[Symbol(dimension.name) => entry]...)

function skopt_parse(dimension, entry::T) where {T <: Integer}
	if startswith(dimension.name, "log2_")
		return (;[Symbol(dimension.name[6:end]) => 2^entry]...)
	elseif startswith(dimension.name, "log10_")
		return (;[Symbol(dimension.name[7:end]) => 10^entry]...)
	else
		return (;[Symbol(dimension.name) => entry]...)
	end
end

function skopt_parse(dimension, entry::T) where {T <: AbstractString}
	if startswith(dimension.name, "sym_")
		return (;[Symbol(dimension.name[5:end]) => Symbol(entry)]...)
	elseif startswith(dimension.name, "fun_")
		return (;[Symbol(dimension.name[5:end]) => eval(:($(Symbol(entry))))]...)
	else
		return (;[Symbol(dimension.name) => entry]...)
	end
end

"""
	from_skopt(space, p)
Converts an array of hyperparameters `p` from skotp to a named tuple
based on provided metadata in skotp's Space class instance `space`.
The order of entries in `p` coincides with the order of dimensions in `space`,
if that is not the case something broke down.
"""
function from_skopt(space, p)
	merge([skopt_parse(dim, entry) for (dim, entry) in zip(space, p)]...)
end

tuple_parse(dimension, entry::Bool) = entry
tuple_parse(dimension, entry::T) where {T <: AbstractString} = entry
tuple_parse(dimension, entry::T) where {T <: AbstractFloat} = entry
tuple_parse(dimension, entry::Symbol) = string(entry)
tuple_parse(dimension, entry::Function) = string(entry) 	# SPTN has identity function in hyperparameters

function tuple_parse(dimension, entry::T) where {T <: Integer}
	if startswith(dimension.name, "log2")
		return Int(log2(entry))
	elseif startswith(dimension.name, "log10")
		return Int(log10(entry))
	else
		return entry
	end
end

"""
	to_skopt(space, p)
Converts named tuple of hyperparameters `p` from Julia to a list of hyperparameters in skopt
based on provided metadata in skotp's Space class instance `space`.
The order of entries in `p` does not have to coincide with the order of dimensions in `space`,
but it will be imposed by keys of the `space` tuple.
"""
function to_skopt(space, p)
	[tuple_parse(dim, p[name]) for (name, dim) in pairs(space)]
end

"""
	objective_value(runs;; run_agg=mean, score_agg=max, fail_value=0.0)

Validates entry from bayesian cache and returns aggregated score, which is optimized by `BayesianHyperOpt`.
For empty entries (no model trained) we return `fail_value`. In case of images `run_agg` allows to set
the aggregation over anomaly_classes to be also maximum.
"""
function objective_value(runs; run_agg=mean, score_agg=maximum, fail_value=0.0)
	if length(runs) > 0 # should we be more strict?
		return run_agg([score_agg(v) for v in values(runs)])
	else
		return fail_value
	end
end

"""
	function bayes_params(space, folder, fallback; add_model_seed=false, kwargs...)
Tries to load a cache of results to seed the optimizer with. The file should be named by `BAYES_CACHE`
global constant and located in `folder`. If the cache has not been created, the method calls `fallback`.
Additional keyword arguments are passed into `objective_value` function allowing to modify it's behavior.
"""
function bayes_params(space, folder, fallback; add_model_seed=false, kwargs...)
	cache = load_bayes_cache(folder)
	if cache !== nothing
		x0 = [v[:parameters] for v in values(cache)]
		y0 = [objective_value(v[:runs], kwargs...) for v in values(cache)]
		@info("Loaded $(length(cache)) cached results from $(folder).")
		# conversion to tuples this makes skopt happy
		# as it cannot parse list of numpy arrays as valid "2D" input
		x0 = [(to_skopt(space, x)..., ) for x in x0]
		opt = BayesianHyperOpt(collect(space))
		tell(opt, x0, y0)
		@info("Fitted GP with $(length(x0)) samples.")
		p = from_skopt(space, ask(opt))

		# registers only parameters with empty arrays and store cache
		register_run!(cache, (;parameters=p); fit_results=false)
		@info("Updated cache size: $(length(cache)). Saving.")
		save_bayes_cache(folder, cache)

		@info("Fetched new hyperparameters: $(p).")
		return add_model_seed ? merge((;init_seed=rand(1:Int(1e8))), p) : p
	else
		@warn("Could not find bayesian cache file at $(folder), calling fallback.")
		return fallback()
	end
end

"""
	load_bayes_cache(folder)

Tries to load serialized results of Bayesian optimization from `folder`.
"""
function load_bayes_cache(folder)
    file = joinpath(folder, BAYES_CACHE)
	isfile(file) ? BSON.load(file) : OrderedDict{UInt64, Any}()
end

"""
	save_bayes_cache(folder)

Saves serialized results of Bayesian optimization to `folder`.
"""
function save_bayes_cache(folder, cache)
    file = joinpath(folder, BAYES_CACHE)
	wsave(file, cache)
end

"""
	update_bayes_cache(folder, results; kwargs...)

Loads bayesian cache from folder and updates it with results.
Additional keyword arguments are passed into `register_run!`.
"""
function update_bayes_cache(folder, results; kwargs...)
	cache = load_bayes_cache(folder)
	@info("Loaded cache size: $(length(cache)). Updating.")
	for r in results
		register_run!(cache, r; kwargs...)
	end
	@info("Updated cache size: $(length(cache)). Saving.")
	save_bayes_cache(folder, cache)
end

"""
	register_run!(cache, r; fit_results=true, metric=:val_auc, flip_sign=true, ignore=Set([:init_seed]))

Updates `cache` with new result from `r`(named tuple or symbol indexed dictionary).
All basic metrics from `.Evaluation` module are supported and their column name
can be passed in the `metric` argument.
Set of hyperparameters that are not optimized is given by the `ignore` argument,
which by default filters `:init_seed`.
If `fit_results` is false register_run! will write an empty entry under a given parameters. This is used
to indicate that a traing has begun for such parameters and if there are not filled, we assume that it failed.
Flag `flip_sign` is used for aligning sign of the metric with minimization objective.
"""
function register_run!(cache, r; fit_results=true, metric=:val_auc, flip_sign=true, ignore=Set([:init_seed]))
	metric_value = try
		fit_results ? compute_stats(r, top_metrics=false)[metric] : []
	catch e
		@warn("Cache has not been updated. Stats computation failed due to $e")
		return
	end

	# compute hash from all parameters (used for filtering during evaluation)
	phash = hash(r[:parameters])
	# filter only those parameters that are being optimized
	parameters = (;[p for p in pairs(r[:parameters]) if !(p[1] in ignore)]...)
	ophash = hash(parameters)   # corresponds only to optimizable parameters
								# there may be colisions (e.g. parameter being run with two seeds)

	if ophash in keys(cache) && fit_results				# add to existing entry
		@info("Adding run to existing entry $(repr(ophash)) - $(parameters).")
		entry = cache[ophash]
		seed = r[:seed]
		anomaly_class = _get_anomaly_class(r)

		runs = entry[:runs]
		phashes = vcat(entry[:phashes], phash)

		key = (seed, anomaly_class)
		if key in keys(runs)	# this is true if for some `ophash` we have multiple outputs (scores)
			runs[key] = vcat(runs[key], flip_sign ? -metric_value : metric_value)
		else
			runs[key] = flip_sign ? -metric_value : metric_value
		end

		cache[ophash] = merge(entry, (;runs=runs, phashes=phashes))
	elseif !(ophash in keys(cache)) && fit_results		# or create new entry if there are results (mainly used during manual cache creation)
		@info("Creating new non-empty entry $(repr(ophash)) - $(parameters).")
		seed = r[:seed]
		anomaly_class = _get_anomaly_class(r)
		runs = Dict((seed, anomaly_class) => flip_sign ? -metric_value : metric_value)
		phashes = [phash]

		cache[ophash] = (;
			parameters = parameters,
			runs = runs,
			phashes=phashes)
	elseif !(ophash in keys(cache)) && fit_results == false 	# add an empty entry (this is to indicate that such parameters are about to be trained)
		@info("Creating new empty entry $(repr(ophash)) - $(parameters).")
		entry = (;
			parameters = parameters,
			runs = Dict{Tuple{Int,Int}, Any}(),
			phashes = UInt64[])

		cache[ophash] = entry
	else 		# otherwise warn the user that the chosen parameters already exist in the cache
		@warn("Entry $(repr(ophash)) - $(parameters) already exist in the cache.")
	end
end


function get_ae(obj::Normalizer)
    return jcall(obj, "getAe", jdouble, ())
end

function get_max_degree(obj::Normalizer)
    return jcall(obj, "getMaxDegree", jint, ())
end

function get_max_order(obj::Normalizer)
    return jcall(obj, "getMaxOrder", jint, ())
end

function get_mu(obj::Normalizer)
    return jcall(obj, "getMu", jdouble, ())
end

function get_offset(obj::Normalizer, arg0::AbsoluteDate)
    return jcall(obj, "getOffset", jdouble, (AbsoluteDate,), arg0)
end

function get_reference_date(obj::Normalizer)
    return jcall(obj, "getReferenceDate", AbsoluteDate, ())
end

function get_tide_system(obj::Normalizer)
    return jcall(obj, "getTideSystem", TideSystem, ())
end

function on_date(obj::Normalizer, arg0::AbsoluteDate)
    return jcall(obj, "onDate", NormalizedSphericalHarmonicsProvider_NormalizedSphericalHarmonics, (AbsoluteDate,), arg0)
end


using FileIO
using IntervalSets
using WAV
import FileIO: save, load
import DSP: resample
import Base: setindex!, getindex

export duration, nchannels, nframes, save, leftright, left, right,
  .., ends, Sound, ismono, isstereo, asstereo, asmono, resample

@require SampledSignals begin
  import SampledSignals: nframes, nchannels
end

struct Sound{R,T,C,N} <: AbstractArray{T,N}
  data::AbstractArray{T,N}
  function Sound(R::Int,C::Int,x::AbstractArray{T,N}) where {T,N}
    @assert(1 <= C <= 2, "Number of sound channels ($C) must be 1 or 2.")
    @assert(size(x,2) == C,
            "Sounds channels ($C) and data columns ($size(x,2)) must match.")
    @assert(1 <= ndims(x) <= 2,
            "Sound arrays must have 1 or 2 dimensions.")
    @assert(!(T <: Integer),
            "Sounds cannot hold integer value ($T). "*
            "Use `FixedPointNumbers` instead.")
    new{R,T,C,N}(x)
  end
end

MonoSound{R,T,N} = Sound{R,T,1,N}
StereoSound{R,T,N} = Sound{R,T,2,N}

"""
    Sound(x::AbstractArray;[rate=samplerate(x)])

Creates a sound object from an array.

Assumes 1 is the loudest and -1 the softest. The array should be 1d for mono
signals, or an array of size (N,2) for stereo sounds. If `samplerate`
is defined for this type of array, it will be used. Otherewise the
default sampling rate is used.
"""
function Sound(x::AbstractArray;rate=samplerate())
  R = floor(Int,ustrip(inHz(rate)))
  Sound(R,size(x,2),x)
end

function Sound(x::Sound;rate=samplerate(x))
  if rate == samplerate(x)
    x
  else
    resample(x,rate)
  end
end

Base.convert(to::Type{T},x::Array) where {T <: Sound} =
  convert(to,Sound(x,rate=rtype(T)*Hz))

function Base.convert(to::Type{Sound{R1,T1,C1,N1}},
                      x::Sound{R2,T2,C2,N2}) where {R1,R2,T1,T2,C1,C2,N1,N2}
  if R1 != R2
    convert(to,resample(x,R1*Hz))
  elseif C1 != C2
    if C1 == 1
      convert(to,asmono(x))
    else
      convert(to,asstereo(x))
    end
  elseif T1 != T2
    Sound(R1,C1,convert(Array{T1},x.data))
  elseif N1 != N2
    if N1 == 1
      Sound(R1,C1,vec(x.data))
    elseif N1 == 2
      Sound(R1,C1,reshape(x.data,:,1))
    else
      error("Unexpected number array dimensions of $N1.")
    end
  else
    x
  end
end

"""

    resample(x::Sound,new_rate;warn=true)

Returns a new sound representing `x` at the given sampling rate (in Hertz).

If you reduce the sampling rate, you will loose all frequencies in `x` that are
above `new_rate/2`. Reducing the sampling rate will produce a warning unless
`warn` is false.

"""

function resample(x::Sound,new_rate::Quantity;warn=true)
  R = ustrip(inHz(Int,samplerate(x)))
  new_rate = floor(Int,ustrip(inHz(new_rate)))
  if new_rate < R && warn
    Base.warn("The function `resample` reduced the sample rate, high freqeuncy"*
              " information above $(new_rate/2) will be lost.",
              bt=backtrace())
  elseif new_rate == R
    return x
  end

  T = eltype(x)
  if isstereo(x)
    Sound(new_rate,2,hcat(T.(resample(x.data[:,1],new_rate // R)),
                          T.(resample(x.data[:,2],new_rate // R))))
  else
    Sound(new_rate,1,T.(resample(x.data[:],new_rate // R)))
  end
end

function Base.Array(x::Sound{R,T,C}) where {R,T,C}
  if C == 2
    Array(x.data)
  else # C == 1
    vec(x.data)
  end
end

@require SampledSignals begin
  import SampledSignals: SampleBuf
  """
      Sound(x::SampledSignals.SampleBuf)

  Convert `SampleBuf` to `Sound` object, without copying data.
  """
  Sound(x::SampleBuf) = Sound(x.data,rate=samplerate(x)*Hz)
  SampleBuf(x::Sound) = SampleBuf(x.data,float(ustrip(samplerate(x))))
end

"""
    Sound(file)

Load a specified file as a `Sound` object.
"""
function Sound(file::Union{AbstractString,IO})
  data,fs = wavread(file)
  Sound(data,rate=fs*Hz)
end

save(file::Union{AbstractString,IO},sound::Sound) =
  wavwrite(sound,file,Fs=ustrip(samplerate(sound)))

@require AxisArray begin
  using AxisArrays
  """
      Sound(x::AxisArray)

  Convert `AxisArray` to `Sound` object. Avoids copying data when possible
  (e.g. when underlying data is an `Array` object).
  """
  Sound(x::AxisArray) = Sound(x.data,rate=samplerate(x))

  function AxisArrays.AxisArray(x::Sound)
    time_axis = Axis{:time}(((1:nframes(x))-1)/samplerate(x))
    ismono(x) ? AxisArray(x,time_axis) :
      AxisArray(x,time_axis,Axis{:channel}([:left,:right]))
  end
end

"""
    Sound(fn,len;asseconds=true,rate=samplerate(),offset=0s)

Creates monaural sound where `fn(t)` returns the amplitudes for a given `Range`
of time points (in seconds as a `Float64`). The function `fn(t)` should return
values ranging between -1 and 1 as an iterable object, and should be just
as long as `t`.

If `asseconds` is false, `fn(i)` returns the amplitudes for a given `Range` of
sample indices (rather than time points).

If `offset` is specified, return the section of the sound starting
from `offset` (rather than starting from 0 seconds).
"""
function Sound(fn::Function,len;asseconds=true,
               offset=0s,rate=samplerate())
  rate_Hz = inHz(rate)

  n = ustrip(inframes(offset,rate_Hz))
  m = ustrip(inframes(len,rate_Hz))
  R = floor(Int,ustrip(rate_Hz))
  Sound(!asseconds ? fn(n+1:m) : fn((linspace(n,(m-1),m-n))/R),rate=rate)
end

"""
    duration(x;rate=samplerate(x))

Returns the duration of the sound.

The rate keyword is only avaiable for generic array-like objects. It is an error
to pass a different rate to this function for a `Sound`
"""
function duration(x;rate=samplerate(x))
  uconvert(s,nframes(x) / inHz(rate))
end
duration(x::Sound{R}) where R = uconvert(s,nframes(x) / (R*Hz))

rtype(::Type{<:Sound{R}}) where R = R
rtype(x::Sound{R}) where {R} = R
Base.length(x::Sound) = length(x.data)

"""
    asstereo(x)

Returns a stereo version of a sound (wether it is stereo or monaural).
"""
asstereo(x::Sound{R,T,1}) where {R,T} = leftright(x,x)
asstereo(x::Sound{R,T,2}) where {R,T} = x

"""
    asmono(x)

Returns a monaural version of a sound (whether it is stereo or monaural).
"""
asmono(x::Sound{R,T,1}) where {R,T} = x
asmono(x::Sound{R,T,2}) where {R,T} = mix(left(x),right(x))

"""
    ismono(x)

True if the sound is monaural.
"""
ismono(x::MonoSound) = true
ismono(x::StereoSound) = false

"""
    isstereo

True if the sound is stereo.
"""
isstereo(x::Sound) = !ismono(x)

function Base.promote_rule(::Type{A},::Type{B}) where {A<:Sound,B<:Sound}
  R = max(rtype(A),rtype(B))
  T = promote_type(eltype(A),eltype(B))
  C = max(nchannels(A),nchannels(B))
  N = max(ndims(A),ndims(B))

  Sound{R,T,C,N}
end

function Base.vcat(xs::Sound...)
  ys = promote(xs...)
  R = rtype(ys[1])
  C = nchannels(ys[1])
  Sound(R,C,vcat(map(x -> x.data,ys)...))
end

function Base.:(*)(x::Number,y::Sound)
  z = similar(y)
  z .= x .* y
end

function Base.:(*)(y::Sound,x::Number)
  z = similar(y)
  z .= x .* y
end

Base.:(/)(y::Sound,x::Number) = y * (1/x)

"""
    nchannels(sound)

Return the number of channels (1 for mono, 2 for stereo) in this sound.
"""
nchannels(x::Sound{R,T,C,N}) where {R,T,C,N} = C
nchannels(::Type{<:Sound{R,T,C,N}}) where {R,T,C,N} = C

"""
    nframes(sound)

Returns the number of samples in the sound.

The number of samples is not always the same as the `length` of the sound.
Stereo sounds have a length of 2 x nframes(sound).
"""
nframes(x::Sound) = size(x.data,1)
nframes(x::AbstractArray) = size(x,1)

"""
    left(sound)

Extract the left channel of a sound. For monaural sounds, `left` and `right`
return the same value.
"""
left(sound::Sound) = sound[:left]

"""
    right(sound)

Extract the right channel of a sound. For monaural sounds, `left` and `right`
return the same value.
"""
right(sound::Sound) = sound[:right]

# adapted from:
# https://github.com/JuliaAudio/SampledSignals.jl/blob/0a31806c3f7d382c9aa6db901a83e1edbfac62df/src/SampleBuf.jl#L109-L139
rounded_time(x,rate::Quantity) = rounded_time(x,floor(Int,ustrip(inHz(rate))))
rounded_time(x,rate::Int) = round(ustrip(inseconds(x,rate)),floor(Int,log(10,rate)))*s

function Base.show(io::IO, x::Sound{R,T,N}) where {R,T,N}
  seconds = rounded_time(duration(x),R)
  typ = eltype(x)
  channel = size(x.data,2) == 1 ? "mono" : "stereo"

  println(io, "$seconds $typ $channel sound")
  print(io, "Sampled at $(R*Hz)")
  nframes(x) > 0 && showchannels(io, x)
end
Base.show(io::IO, ::MIME"text/plain", x::Sound) = show(io,x)

const ticks = ['_','▁','▂','▃','▄','▅','▆','▇']
function showchannels(io::IO, x::Sound, widthchars=80)
  # number of samples per block
  blockwidth = round(Int, nframes(x)/widthchars, RoundUp)
  nblocks = round(Int, nframes(x)/blockwidth, RoundUp)
  blocks = Array{Char}(nblocks, nchannels(x))
  for blk in 1:nblocks
    i = (blk-1)*blockwidth + 1
    n = min(blockwidth, nframes(x)-i+1)
    peaks = sqrt.(mean(float(x.data[(1:n)+i-1,:]).^2,1))
    # clamp to -60dB, 0dB
    peaks = clamp.(20log10.(peaks), -60.0, 0.0)
    idxs = trunc.(Int, (peaks+60)/60 * (length(ticks)-1)) + 1
    blocks[blk, :] = ticks[idxs]
  end
  for ch in 1:nchannels(x)
    println(io)
    print(io, convert(String, blocks[:, ch]))
  end
end

########################################
# AbstractArray interface:

Base.size(x::Sound) = size(x.data)
Base.IndexStyle(::Type{Sound}) = IndexCartesian()

@inline @Base.propagate_inbounds getindex(x::Sound,ixs::Int...) =
    getindex(x.data,ixs...)

@inline @Base.propagate_inbounds setindex!(x::Sound,v,ixs::Int...) =
    setindex!(x.data,v,ixs...)

# special casing of single sample of a stereo sound across channels (we need to
# represent this with time as dim 1 and channel as dim 2, while a normal array
# would return a vector.)
@inline function getindex(x::StereoSound{R,T},i::Int,
                          js::Union{Colon,Range}) where {R,T}
  @boundscheck checkbounds(x.data,i,js)
  @inbounds return Sound(R,2,getindex(x.data,i:i,js))
end

############################################################
# custom indexing:

########################################
# indexing by :left and :right

@inline @Base.propagate_inbounds getindex(x::Sound,js::Symbol) =
  getindex(x,:,js)
@inline @Base.propagate_inbounds function getindex(x::Sound,ixs,js::Symbol)
  if js == :left
    getindex(x,ixs,1)
  elseif js == :right
    if nchannels(x) == 1
      getindex(x,ixs,1)
    else
      getindex(x,ixs,2)
    end
  else
    throw(BoundsError(x,js))
  end
end

@inline @Base.propagate_inbounds setindex!(x::Sound,vals,js::Symbol) =
  setindex!(x,vals,:,js)
@inline @Base.propagate_inbounds function setindex!(x::Sound,vals,ixs,js::Symbol)
  if js == :left
    setindex!(x,vals,ixs,1)
  elseif js == :right
    if nchannels(x) == 1
      setindex!(x,vals,ixs,1)
    else
      setindex!(x,vals,ixs,2)
    end
  else
    throw(BoundsError(x,js))
  end
end

@inline function setindex!(x::Sound,i::Int,ixs::Union{AbstractVector,Colon})
  @boundscheck checkbounds(x.data,ixs)
  setindex!(x.data,i,ixs)
end

########################################
# indexing by time intervals (e.g. x[1s .. 3s])

struct EndSecs; end
const ends = EndSecs()

struct ClosedIntervalEnd{N}
  from::Quantity{N}
end
Base.minimum(x::ClosedIntervalEnd) = x.from

IntervalSets.:(..)(x::Time,::EndSecs) = ClosedIntervalEnd(x)
IntervalSets.:(..)(x::FrameQuant,::EndSecs) = ClosedIntervalEnd(x)
IntervalSets.:(..)(x,::EndSecs) = error("Unexpected quantity $x in interval.")

function checktime(time)
  if ustrip(time) < 0
    throw(BoundsError("Unexpected negative time."))
  end
end

const Index = Union{Integer,AbstractVector,Colon}
const IntervalType = Union{ClosedInterval,ClosedIntervalEnd}

function asrange(x::Sound{R},ixs::ClosedInterval) where R
  checktime(minimum(ixs))
  from = max(1,inframes(minimum(ixs),R*Hz)+1)
  to = inframes(maximum(ixs),R*Hz)
  checkbounds(x.data,from,:)
  checkbounds(x.data,to,:)
  from:to
end

function asrange(x::Sound{R},ixs::ClosedIntervalEnd) where R
  checktime(minimum(ixs))
  from = max(1,inframes(minimum(ixs),R*Hz)+1)
  checkbounds(x.data,from,:)
  from:nframes(x)
end

getindex(x::MonoSound,interval::IntervalType) = getindex(x,asrange(x,interval))
getindex(x::StereoSound,interval::IntervalType) = getindex(x,asrange(x,interval),:)
getindex(x::Sound,interval::IntervalType,js::Index) =
  getindex(x,asrange(x,interval),js)

setindex!(x::MonoSound,vals,interval::IntervalType) =
  setindex!(x,vals,asrange(x,interval))
setindex!(x::StereoSound,vals,interval::IntervalType) =
  setindex!(x,vals,asrange(x,interval),:)
setindex!(x::Sound,vals,interval::IntervalType,js::Index) =
  setindex!(x,vals,asrange(x,interval),js)

########################################

function Base.similar(x::Sound{R,T,C},::Type{S},
                      dims::Tuple{Vararg{Int64,N}}) where {R,T,C,S,N}
  C2 = length(dims) >= 2 ? dims[2] : 1
  Sound(R,C2,similar(x.data,S,dims))
end

"""
    leftright(left,right)

Create a stereo sound from two monaural sounds.
"""
function leftright(x::T,y::T) where T <: Sound
  @assert size(x,2) == size(y,2) == 1 "Expected two monaural sounds."
  len = maximum(nframes.((x,y)))
  z = similar(x,(len,2))
  z .= zero(x[1])
  z[1:nframes(x),1] = x
  z[1:nframes(y),2] = y
  z
end
leftright(x,y) = leftright(promote(x,y)...)


using RunTests
using Base.Test

@testmodule KNeighborsTest begin
  using MLLibrary

  function test_trainKMeans()
      # path = joinpath(Pkg.dir("MLLibrary"),"resources","datasets","test.csv")
      # seed=123
      # trainingSet, testSet = MLLibrary.DataSampling.ThresholdSampling.getSets(path, ",",.1,seed)
      #
      # trainingSet = MLLibrary.DataManipulation.DataTransform.convertToFloat(trainingSet)
      #
      # println(trainingSet)
      # println(MLLibrary.LearningAlgorithms.KMeans.trainKmeans(trainingSet, 4))
      #
      # @test 1==1

  end
end


push!(LOAD_PATH, "/Users/thomasriddick/Documents/workspace/Dynamic_HD_Code/Dynamic_HD_Julia_Code/")
using Profile
using ArgParse
using HDDriverModule: drive_hd_model,drive_hd_and_lake_model
using FieldModule: UnstructuredField,Field,repeat
using IOModule: load_river_parameters, load_river_initial_values,get_ncells
using IOModule: load_lake_initial_values,load_lake_parameters
using IOModule: get_additional_grid_information
using GridModule: UnstructuredGrid

function pass_arguments()
  settings = ArgParseSettings()
  @add_arg_table settings begin
  "--hd-para-file", "-p"
    help = "Filepath to HD parameters file"
    required = true
  "--hd-init-file", "-i"
    help = "Filepath to HD initialisation file (optional)"
  "--lake-para-file", "-l"
    help = "Filepath to lake model parameters file (optional)"
  "--lake-init-file", "-n"
    help = "Filepath to lake model initialisation file (optional)"
  "--drainage-file", "-d"
    help = "Filepath to file containing drainage values"
  "--runoff-file", "-r"
    help = "Filepath to file containing runoff values"
  "--lake-evaporation-file", "-e"
    help = "Filepath to file containing values for evaporation from lakes"
  "--timesteps", "-t"
    help = "Number of timesteps to run"
    arg_type = Int
    default=100
  "--step-length","-s"
    help = "Length of each timestep in seconds"
    arg_type = Float64
    default = 86400.0
  end
  return parse_args(settings)
end

function main()
  args = pass_arguments()
  timesteps = args["timesteps"]
  step_length = args["step-length"]
  ncells = get_ncells(args["hd-para-file"])
  local clat::Array{Float64,1}
  local clon::Array{Float64,1}
  local clat_bounds::Array{Float64,2}
  local clon_bounds::Array{Float64,2}
  clat,clon,clat_bounds,clon_bounds = get_additional_grid_information(args["hd-para-file"])
  grid = UnstructuredGrid(ncells,clat,clon,clat_bounds,clon_bounds)
  lake_grid = UnstructuredGrid(ncells,clat,clon,clat_bounds,clon_bounds)
  surface_model_grid = UnstructuredGrid(ncells,clat,clon,clat_bounds,clon_bounds)
  river_parameters = load_river_parameters(args["hd-para-file"],grid;
                                           day_length=86400.0,step_length=step_length)
  local drainages::Array{Field{Float64},1}
  local runoffs::Array{Field{Float64},1}
  local lake_evaporations::Array{Field{Float64},1}
  if args["drainage-file"] != nothing
    drainages = load_drainage_fields(args["drainage-file"],grid,
                                     last_timestep=12)
    drainages = [divide(x,30.0) for x in drainages]
  else
    drainage::Field{Float64} = UnstructuredField{Float64}(river_parameters.grid,
                                                          100*0.0000000227*step_length*2.6*10000000000)
    #drainages = repeat(drainage,Int(round(timesteps/30)+1))
    drainages = [drainage]
  end
  if args["runoff-file"] != nothing
    runoffs = load_runoff_fields(args["runoff-file"],grid,
                                 last_timestep=12)
    runoffs = [divide(x,30.0) for x in runoffs]
  else
    runoffs = deepcopy(drainages)
  end
  if args["lake-evaporation-file"] != nothing
    lake_evaporations = load_lake_evaporation_fields(args["lake-evaporation-file"],grid,
                                                     last_timestep=12)
    lake_evaporations = [divide(x,30.0) for x in lake_evaporations]
  else
    lake_evaporation_none ::Field{Float64} = UnstructuredField{Float64}(river_parameters.grid,0.0)
    lake_evaporation_high ::Field{Float64} =
      UnstructuredField{Float64}(river_parameters.grid,
                                 100000*0.0000000227*step_length*2.6*10000000000)
    lake_evaporations_none = repeat(lake_evaporation_none,Int(round(timesteps/60)+1))
    lake_evaporations_high = repeat(lake_evaporation_high,Int(round(timesteps/60)+1))
    lake_evaporations = vcat(lake_evaporations_none,lake_evaporations_high)
  end
  if args["lake-para-file"] != nothing
    lake_parameters = load_lake_parameters(args["lake-para-file"],lake_grid,grid,surface_model_grid)
    drainages_copy = deepcopy(drainages)
    runoffs_copy = deepcopy(runoffs)
    if args["hd-init-file"] != nothing
      river_fields = load_river_initial_values(args["hd-init-file"],grid,river_parameters)
    end
    if args["lake-init-file"] != nothing
      initial_water_to_lake_centers::UnstructuredField{Float64},
      initial_spillover_to_rivers::UnstructuredField{Float64} =
        load_lake_initial_values(args["lake-init-file"],lake_grid,grid)
      if args["hd-init-file"] != nothing
        drive_hd_and_lake_model(river_parameters,river_fields,
                                lake_parameters,drainages,runoffs,
                                lake_evaporations,timesteps,true,
                                initial_water_to_lake_centers,
                                initial_spillover_to_rivers;print_timestep_results=false)
        # Profile.clear()
        # Profile.init(delay=0.01)
        # @time drive_hd_and_lake_model(river_parameters,river_fields,
        #                               lake_parameters,drainages_copy,runoffs_copy,
        #                               timesteps,true,initial_water_to_lake_centers,
        #                               initial_spillover_to_rivers;print_timestep_results=true)
        # Profile.print()
        # r = Profile.retrieve();
        # f = open("/Users/thomasriddick/Downloads/profile.bin", "w")
        # Serialization.serialize(f, r)
        # close(f)
      else
        drive_hd_and_lake_model(river_parameters,lake_parameters,
                                drainages,runoffs,
                                lake_evaporations,timesteps,true,
                                initial_water_to_lake_centers,
                                initial_spillover_to_rivers;
                                print_timestep_results=true)
      end
    end
  else
    if args["hd-init-file"] != nothing
      river_fields = load_river_initial_values(args["hd-init-file"],grid,river_parameters)
      drive_hd_model(river_parameters,river_fields,
                     drainages,runoffs,timesteps;
                     print_timestep_results=false,
                     output_timestep=160)
      # @time drive_hd_model(river_parameters,river_fields,
      #                      drainages_copy,runoffs_copy,timesteps;
      #                      print_timestep_results=false)
    else
      # drainages_copy = deepcopy(drainages)
      # runoffs_copy = deepcopy(runoffs)
      drive_hd_model(river_parameters,drainages,
                     runoffs,timesteps;print_timestep_results=false)
      # Profile.clear()
      # Profile.init(delay=0.0001)
      # @time drive_hd_model(river_parameters,drainages_copy,
      #                      runoffs_copy,timesteps;print_timestep_results=false)
      # Profile.print()
      # r = Profile.retrieve();
      # f = open("/Users/thomasriddick/Downloads/profile.bin", "w")
      # Serialization.serialize(f, r)
      # close(f)
    end
  end
end

empty!(ARGS)
# push!(ARGS,"-p/Users/thomasriddick/Documents/data/ICONHDdata/hdparafiles/hd_para_icon_r2b9_30_20191452_retuned_v3.nc")
# push!(ARGS,"-i/Users/thomasriddick/Documents/data/ICONHDdata/hdstartfiles/hdrestart_R02B09_015_G_241019_1337_v2.nc")
# push!(ARGS,"-t1920")
# push!(ARGS,"-s45")
push!(ARGS,"-p/Users/thomasriddick/Documents/data/temp/icon_lake_model_test/hdpara_icon.nc")
push!(ARGS,"-i/Users/thomasriddick/Documents/data/temp/icon_lake_model_test/hdrestart_R02B04_013_G_231019_1242_v2.nc")
push!(ARGS,"-l/Users/thomasriddick/Documents/data/temp/icon_lake_model_test/lakeparams.nc")
push!(ARGS,"-n/Users/thomasriddick/Documents/data/temp/icon_lake_model_test/lakestart.nc")
push!(ARGS,"-t3600")
push!(ARGS,"-s86400")
main()


module TestExamplesMPIP4estMesh3D

using Test
using Trixi

include("test_trixi.jl")

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

@testset "P4estMesh MPI 3D" begin

# Run basic tests
@testset "Examples 3D" begin
  # Linear scalar advection
  @trixi_testset "elixir_advection_basic.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"),
      # Expected errors are exactly the same as with TreeMesh!
      l2   = [0.00016263963870641478],
      linf = [0.0014537194925779984])
  end

  @trixi_testset "elixir_advection_restart.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"),
      l2   = [0.002590388934758452],
      linf = [0.01840757696885409])
  end

  @trixi_testset "elixir_advection_cubed_sphere.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_cubed_sphere.jl"),
      l2   = [0.002006918015656413],
      linf = [0.027655117058380085])
  end

  # Compressible Euler
  @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic.jl"),
      l2   = [0.0015106060984283647, 0.0014733349038567685, 0.00147333490385685, 0.001473334903856929, 0.0028149479453087093],
      linf = [0.008070806335238156, 0.009007245083113125, 0.009007245083121784, 0.009007245083102688, 0.01562861968368434],
      tspan = (0.0, 1.0))
  end

  @trixi_testset "elixir_euler_ec.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"),
      l2   = [0.010380390326164493, 0.006192950051354618, 0.005970674274073704, 0.005965831290564327, 0.02628875593094754],
      linf = [0.3326911600075694, 0.2824952141320467, 0.41401037398065543, 0.45574161423218573, 0.8099577682187109],
      tspan = (0.0, 0.2),
      coverage_override = (polydeg=3,)) # Prevent long compile time in CI
  end

  @trixi_testset "elixir_euler_source_terms_nonperiodic_hohqmesh.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms_nonperiodic_hohqmesh.jl"),
      l2   = [0.0042023406458005464, 0.004122532789279737, 0.0042448149597303616, 0.0036361316700401765, 0.007389845952982495],
      linf = [0.04530610539892499, 0.02765695110527666, 0.05670295599308606, 0.048396544302230504, 0.1154589758186293])
  end
end

end # P4estMesh MPI

end # module


# Business Calendars (adapted from Ito.jl and BusinessDays.jl)
using Base.Dates

abstract BusinessCalendar

abstract WesternCalendar <: BusinessCalendar
abstract OrthodoxCalendar <: BusinessCalendar

# target calendar
type TargetCalendar <: BusinessCalendar end

# for simply moving foward and backward in time
type NullCalendar <: BusinessCalendar end

type JointCalendar{B <: BusinessCalendar, C <: BusinessCalendar} <: BusinessCalendar
  cal1::B
  cal2::C
end

# US Calendars
abstract UnitedStatesCalendar <: WesternCalendar

type USSettlementCalendar <: UnitedStatesCalendar; end
type USNYSECalendar <: UnitedStatesCalendar; end
type USNERCCalendar <: UnitedStatesCalendar; end
type USGovernmentBondCalendar <: UnitedStatesCalendar; end

# UK Calendars
abstract UnitedKingdomCalendar <: WesternCalendar

type UKSettlementCalendar <: UnitedKingdomCalendar end
type UKLSECalendar <: UnitedKingdomCalendar end
type UKLMECalendar <: UnitedKingdomCalendar end

abstract BusinessDayConvention
type Unadjusted <: BusinessDayConvention end
type ModifiedFollowing <: BusinessDayConvention end
type Following <: BusinessDayConvention end

# easter functions
function easter_rata(y::Int)

  c::Int64
	e::Int64
	p::Int64

   # Algo R only works after 1582
   if y < 1582
        # Are you using this? Send me a postcard!
        error("Year cannot be less than 1582. Provided: $(y).")
   end

	# Century
   c = div( y , 100) + 1

   # Shifted Epact
   e = mod(14 + 11*(mod(y, 19)) - div(3*c, 4) + div(5+8*c, 25), 30)

   # Adjust Epact
   if (e == 0) || ((e == 1) && ( 10 < mod(y, 19) ))
   	e += 1
   end

   # Paschal Moon
   p = Date(y, 4, 19).instant.periods.value - e

   # Easter: locate the Sunday after the Paschal Moon
   return p + 7 - mod(p, 7)
end

# Returns Date
function easter_date(y::Int)
	# Compute the gregorian date for Rata Die number
     return Date(Dates.rata2datetime( easter_rata(y) ))
end

# calendar functions
# advance{B <: BusinessDayConvention}(time_period::Day, cal::NullCalendar, dt::Date, ::B) = dt += time_period
# advance{B <: BusinessDayConvention}(time_period::Union{Week, Month, Year}, cal::NullCalendar, dt::Date, ::B) = dt += time_period

function advance(days::Day, cal::BusinessCalendar, dt::Date, biz_conv::BusinessDayConvention = Following())
  n = Int(days)
  if n > 0
    while n > 0
      dt += Day(1)
      while !is_business_day(cal, dt)
        dt += Day(1)
      end
      n -= 1
    end
  else
    while (n < 0)
      dt -= Day(1)
      while !is_business_day(cal, dt)
        dt -= Day(1)
      end
      n += 1
    end
  end

  return dt
end

function advance(time_period::Union{Week, Month, Year}, cal::BusinessCalendar, dt::Date, biz_conv::BusinessDayConvention = Following())
  dt += time_period
  return adjust(cal, biz_conv, dt)
end

is_business_day(cal::NullCalendar, ::Date) = true

function is_business_day(cal::BusinessCalendar, dt::Date)
  if dayofweek(dt) in [6, 7] || is_holiday(cal, dt)
    return false
  else
    return true
  end
end

# In the United States, if a holiday falls on Saturday, it's observed on the preceding Friday.
# If it falls on Sunday, it's observed on the next Monday.
function adjustweekendholidayUS(dt::Date)
	if dayofweek(dt) == 6
		return dt - Dates.Day(1)
	end

	if dayofweek(dt) == 7
		return dt + Dates.Day(1)
	end

	return dt
end

is_holiday(joint::JointCalendar, dt::Date) = is_holiday(joint.cal1, dt) || is_holiday(joint.cal2, dt)

function is_holiday(::USSettlementCalendar , dt::Date)

	const yy = year(dt)
	const mm = month(dt)
	const dd = day(dt)

	if (
			# New Year's Day
			adjustweekendholidayUS(Date(yy, 1, 1)) == dt
			||
			# New Year's Day on the previous year when 1st Jan is Saturday
			(mm == 12 &&  dd == 31 && dayofweek(dt) == Friday)
			||
			# Birthday of Martin Luther King, Jr.
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) ==3 && mm == 1)
			||
			# Washington's Birthday
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) ==3 && mm == 2)
			||
			# Memorial Day is the last Monday in May
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) == daysofweekinmonth(dt) && mm == 5)
			||
			# Independence Day
			adjustweekendholidayUS(Date(yy, 7, 4)) == dt
			||
			# Labor Day is the first Monday in September
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) == 1 && mm == 9)
			||
			# Columbus Day is the second Monday in October
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) == 2 && mm == 10)
			||
			# Veterans Day
			adjustweekendholidayUS(Date(yy, 11, 11)) == dt
			||
			# Thanksgiving Day is the fourth Thursday in November
			(dayofweek(dt) == 4 && dayofweekofmonth(dt) == 4 && mm == 11)
			||
			# Christmas
			adjustweekendholidayUS(Date(yy, 12, 25)) == dt
		)
		return true
	end

	return false
end

function is_holiday(::USGovernmentBondCalendar, dt::Date)
  const yy = year(dt)
	const mm = month(dt)
	const dd = day(dt)
	if (
			# New Year's Day
			adjustweekendholidayUS(Date(yy, 1, 1)) == dt
			||
			# New Year's Day on the previous year when 1st Jan is Saturday
			(mm == 12 &&  dd == 31 && dayofweek(dt) == Friday)
			||
			# Birthday of Martin Luther King, Jr.
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) ==3 && mm == 1)
			||
			# Washington's Birthday
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) ==3 && mm == 2)
			||
      # Good Friday
      easter_date(yy) - Day(2) == dt
      ||
			# Memorial Day is the last Monday in May
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) == daysofweekinmonth(dt) && mm == 5)
			||
			# Independence Day
			adjustweekendholidayUS(Date(yy, 7, 4)) == dt
			||
			# Labor Day is the first Monday in September
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) == 1 && mm == 9)
			||
			# Columbus Day is the second Monday in October
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) == 2 && mm == 10)
			||
			# Veterans Day
			adjustweekendholidayUS(Date(yy, 11, 11)) == dt
			||
			# Thanksgiving Day is the fourth Thursday in November
			(dayofweek(dt) == 4 && dayofweekofmonth(dt) == 4 && mm == 11)
			||
			# Christmas
			adjustweekendholidayUS(Date(yy, 12, 25)) == dt
		)
		return true
	end

	return false
end

function is_holiday(::USNYSECalendar, dt::Date)
  const yy = year(dt)
	const mm = month(dt)
	const dd = day(dt)
	if (
			# New Year's Day
			adjustweekendholidayUS(Date(yy, 1, 1)) == dt
			||
			# New Year's Day on the previous year when 1st Jan is Saturday
			(mm == 12 &&  dd == 31 && dayofweek(dt) == Friday)
			||
      # Birthday of Martin Luther King, Jr.
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) ==3 && mm == 1)
			||
			# Washington's Birthday
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) ==3 && mm == 2)
			||
      # Good Friday
      easter_date(yy) - Day(2) == dt
      ||
			# Memorial Day is the last Monday in May
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) == daysofweekinmonth(dt) && mm == 5)
			||
			# Independence Day
			adjustweekendholidayUS(Date(yy, 7, 4)) == dt
			||
			# Labor Day is the first Monday in September
			(dayofweek(dt) == 1 && dayofweekofmonth(dt) == 1 && mm == 9)
			||
			# Thanksgiving Day is the fourth Thursday in November
			(dayofweek(dt) == 4 && dayofweekofmonth(dt) == 4 && mm == 11)
			||
			# Christmas
			adjustweekendholidayUS(Date(yy, 12, 25)) == dt
		)
		return true
	end

  # Special Closings
  if (
    # Hurricane Sandy
    (yy == 2012 && mm == 10 && dd in (29, 30))
    ||
    # President Ford's funeral
    (yy == 2007 && mm == 1 && dd == 2)
    ||
    # President Reagan's funeral
    (yy == 2004 && mm == 6 && dd == 11)
    ||
    # 9/11
    (yy == 2001 && mm == 9 && dd in (11, 12, 13, 14))
    )
    return true
  end

	return false
end

function adjustweekendholidayUK(dt::Date)

  if dayofweek(dt) == 6
		return dt + Day(2)
	end

  if dayofweek(dt) == 7
		return dt + Day(1)
	end

	return dt
end

## UK Calendar functions
function is_holiday(c::Union{UKSettlementCalendar, UKLSECalendar}, dt::Date)
  const yy = year(dt)
	const mm = month(dt)
	const dd = day(dt)

	if (
    # New Year's Day
    adjustweekendholidayUK(Date(yy, 1, 1)) == dt
    ||
    # Good Friday
    easter_date(yy) - Day(2) == dt
    ||
    # Easter MONDAY
    easter_date(yy) + Day(1) == dt
    ||
    # first MONDAY of May (Early May Bank Holiday)
    (dayofweek(dt) == 1 && dayofweekofmonth(dt) == 1 && mm == 5)
    ||
    # last MONDAY of MAY (Spring Bank Holiday)
    (dayofweek(dt) == 1 && dayofweekofmonth(dt) == daysofweekinmonth(dt) && mm == 5)
    ||
    # last MONDAY of August (Summer Bank Holiday)
    (dayofweek(dt) == 1 && dayofweekofmonth(dt) == daysofweekinmonth(dt) && mm == 8)
    ||
    # Christmas (possibly moved to MONDAY or Tuesday)
    adjustweekendholidayUK(Date(yy, 12, 25)) == dt
    ||
    # Boxing Day (possibly moved to MONDAY or TUESDAY)
    adjustweekendholidayUK(adjustweekendholidayUK(Date(yy, 12, 25)) + Day(1)) == dt
    )
    return true
  end

  # Fixed holidays
  if (
    # Substitute date for Spring Bank Holiday
		(dt == Date(2012, 06, 04))
		||
		# Diamond Jubilee of Queen Elizabeth II.
		(dt == Date(2012, 06, 05))
		||
		# Golden Jubilee of Queen Elizabeth II.
		(dt == Date(2002, 06, 03))
		||
		# Substitute date for Spring Bank Holiday
		(dt == Date(2002, 06, 04))
		||
		# Wedding of Prince William and Catherine Middleton
		(dt == Date(2011, 04, 29))
    )
    return true
  end

  return false
end

function is_holiday(::TargetCalendar, dt::Date)
  const yy = year(dt)
	const mm = month(dt)
	const dd = day(dt)
  const easter_sun = easter_date(yy)

  if (
    # New Years Day
    (mm == 1 && dd == 1)
    ||
    # Good Friday
    easter_sun - Day(2) == dt
    ||
    # Easter Monday
    easter_sun + Day(1) == dt
    ||
    # Int'l Labor Day
    (mm == 5 && dd == 1)
    ||
    # Christmas
    (mm == 12 && dd == 25)
    ||
    # Day of Goodwill
    (mm == 12 && dd == 26)
    )
    return true
  else
    return false
  end
end

# adjustments
adjust(::BusinessCalendar, ::Unadjusted, d::Date) = d

function adjust(cal::BusinessCalendar, ::Union{ModifiedFollowing, Following}, d::Date)
  while !is_business_day(cal, d)
    d += Day(1)
  end

  return d
end

adjust(cal::BusinessCalendar, d::Date) = adjust(cal, Following(), d)


using AEMS
using POMDPs
using BeliefUpdaters
using POMDPModels
using POMDPTesting
using Test

#@requirements_info solver pomdp

######################################################################
# BABY POMDP Tests
######################################################################
pomdp = BabyPOMDP()
up = DiscreteUpdater(pomdp)

# testing solver with :clear root_manager
solver = AEMSSolver(max_time=0.1, updater=up)
test_solver(solver, pomdp)

# testing solver with :belief root_manager
solver = AEMSSolver(max_time=0.1, updater=up, root_manager=:belief)
test_solver(solver, pomdp)

# testing solver with :user root_manager
solver = AEMSSolver(max_time=0.1, updater=up, root_manager=:user)
test_solver(solver, pomdp)

######################################################################
# TIGER PROBLEM TESTS
######################################################################
pomdp = TigerPOMDP()
up = DiscreteUpdater(pomdp)

# testing solver with :clear root_manager
solver = AEMSSolver(max_time=0.1, updater=up)
test_solver(solver, pomdp)

# testing solver with :belief root_manager
solver = AEMSSolver(max_time=0.1, updater=up, root_manager=:belief)
test_solver(solver, pomdp)

# testing solver with :belief root_manager
solver = AEMSSolver(max_time=0.1, updater=up, root_manager=:user)
test_solver(solver, pomdp)


using DataFrames
using CSV
import Base.getindex
export Chromosome, print_chromosome, getindex, random_chromosome, mutate_chromosome!, mutate_all, PredType
export num_mutate_locations, set_active_to_false, fraction_active, check_recursive, node_values
export output_values, number_active, number_active_gates, hamming_distance, ihamming_distance, hamming, deactivate_chromosome!
export copy_chromosome!, mutational_robustness, fault_tolerance_fitness, number_active_old
export build_chromosome, Input_node, Int_node, Output_node, print_build_chromosome, circuit_code, circuit_int
export circuit, print_circuit
export circuit_distance, remove_inactive, count_circuits
export code_to_circuit   # outdate 9/13/21
export insert_gate!, delete_gate!
export enumerate_circuits, chromosome_to_int, gate_int, gate_int_list, int_to_gate, int_to_chromosome

PredType = Int64
#= Commented out.  The included definition is in aliases.jl.
mutable struct Chromosome
    params::Parameters
    inputs::Vector{InputNode}
    interiors::Vector{InteriorNode}
    outputs::Vector{OutputNode}
    fitness::Float64
    robustness::Union{Float64,PredType}
end
=#

CPopulation = Vector{Chromosome}

function Chromosome(p::Parameters; ident::PredType=PredType(0))
    inputs = Array{InputNode}( undef, p.numinputs)
    interiors = Array{InteriorNode}( undef, p.numinteriors )
    outputs = Array{OutputNode}( undef, p.numoutputs)
    fitness = 0.0
    if ident == PredType(0)
      robustness = 0.0
    else
      robustness = ident
    end
    return Chromosome(p, inputs, interiors, outputs, fitness, robustness )
end

function random_chromosome(p::Parameters; ident::PredType=PredType(0))
  funcs = default_funcs(p.numinputs)
  random_chromosome(p,funcs, ident=ident)
end

function random_chromosome(p::Parameters, funcs::Vector{Func}; ident::PredType=PredType(0))
    #println("Creating random Chromosome")
    c = Chromosome(p)

    for index = 1:p.numinputs
        c.inputs[index] = InputNode(index)
        c.inputs[index].active = false
        c.inputs[index].cache = 0
    end
    for index = (p.numinputs +1):(p.numinputs+p.numinteriors)
        maxindex = index - 1
        minindex = max(1,index-p.numlevelsback)
        #println("index: ",index,"  minindex: ",minindex,"  maxindex: ",maxindex)
        func = funcs[rand(1:end)]
        inputs = Array{Int64}(undef, func.arity)
        for i = 1:func.arity
            inputs[i] = rand(minindex:maxindex)
            #println("i: ",i,"  inputs[i]: ",inputs[i])
        end
        c.interiors[index - p.numinputs] = InteriorNode(func, inputs)
    end
    minindex = max(1,p.numinputs + p.numinteriors - p.numlevelsback + 1)
    maxindex = p.numinputs + p.numinteriors
    #println("output (minindex, maxindex): ",(minindex,maxindex))
    for i = 1:length(c.outputs)
        #index = rand(minindex:maxindex)
        index = p.numinputs + p.numinteriors + i - p.numoutputs   # use the last numoutputs interiors
        #println("output ",i," index: ",index)
        c.outputs[i] = OutputNode(index)
        c[index].active = true  #  Output nodes are always active
    end
    set_active_to_false(c)
    if ident != PredType(0)
      c.robustness = ident
    end
    return c
end

function set_active_to_false( c::Chromosome )
  for index = 1:(c.params.numinputs+c.params.numinteriors)
    c[index].active = false
    c[index].cache = MyInt(0)
  end
end

# mutates chromosome c by changing a random node or function
# if length(funcs) == 1, then only connections are mutated
# Returns new chromosome c and Bool active which is true if the node mutated was active
# BUG!! as of 3/3/21:  Does not modify c, instead returns the mutated c.  Probably OK:  3/8/21
function mutate_chromosome!( c::Chromosome, funcs::Vector{Func}, mutate_location::Int64=0;
    insert_gate_prob::Float64=0.0, delete_gate_prob::Float64=0.0 )
  #println("mutate_chromosome! insert_gate_prob: ",insert_gate_prob)
  if rand() < insert_gate_prob && c.params.numinteriors < MyIntBits( MyInt )
    return(insert_gate!(c),false)
  elseif rand() < delete_gate_prob && c.params.numinteriors > c.params.numinputs
    # Don't reduce number of gates less than number of interiors
    return (delete_gate!(c),true)  # active set to true, but has no meaning here
  end
  num_mutate_locs = num_mutate_locations( c, funcs )
  if mutate_location == 0
    mutate_location = rand(1:num_mutate_locs)
  end
  #println("num_mutate_locations: ",num_mutate_locs,"  mutate_location: ",mutate_location)
  interiors_inputs_list = [ c.interiors[i].inputs for i = 1:c.params.numinteriors ]
  num_inputs_list = map(length, interiors_inputs_list)  # A list of the number of inputs of the interior node functions
  #println("num_inputs_list: ",num_inputs_list)
  num_funcs_to_mutate = length(funcs) > 1 ? c.params.numinteriors : 0
  if mutate_location <= num_funcs_to_mutate   # mutate a func
    #println("mutate a func ml:",mutate_location)
    active = c[mutate_location].active
    new_func_index = rand(1:length(funcs))
    #println("new_funct_index: ",new_func_index,"  funcs[new_func_index].func: ",funcs[new_func_index].func)
    #println("  c.interiors[mutate_location].func: ",c.interiors[mutate_location].func.func)
    #println("condition: ", funcs[new_func_index].func == c.interiors[mutate_location].func.func )
    while( funcs[new_func_index].func == c.interiors[mutate_location].func.func )
      new_func_index = rand(1:length(funcs))
      #println("while new_funct_index: ",new_func_index)
    end
    #println("new_func_index: ",new_func_index)
    old_func_arity = c.interiors[mutate_location].func.arity
    old_inputs = c.interiors[mutate_location].inputs
    new_func = funcs[new_func_index]
    new_inputs = Array{Int64}(undef, new_func.arity)
    for i = 1:new_func.arity
      if i <= length(old_inputs)
        new_inputs[i] = old_inputs[i]
      else
        maxindex = mutate_location + c.params.numinputs - 1
        minindex = max(1,mutate_location + c.params.numinputs - c.params.numlevelsback)
        #println("mut_location: ",mutate_location,"  minindex: ",minindex,"  maxindex: ",maxindex)
        new_inputs[i] = rand(minindex:maxindex)
      end
    end
    c.interiors[mutate_location] = InteriorNode(new_func,new_inputs)
    #println("new interior node: ",c.interiors[mutate_location])
    #println("new interior node: ",c[mutate_location+c.params.numinputs])
    #println("func ",rand(1:100))
  elseif mutate_location <= num_funcs_to_mutate + sum(num_inputs_list)  # mutate an interior
    ml = mutate_location
    #println("mutate a interior node ml:",mutate_location)
    interior_mutate_location = mutate_location - num_funcs_to_mutate
    #println("interior mutate_location: ",interior_mutate_location)
    i = 1  # i will be the index of the interior node one of whose inputs will be mutated
    num_inputs_sum = num_inputs_list[1]
    #println("i: ",i,"  num_inputs_sum: ",num_inputs_sum)
    while(num_inputs_sum < interior_mutate_location)
      i += 1
      num_inputs_sum += num_inputs_list[i]
      #println("i: ",i,"  num_inputs_sum: ",num_inputs_sum)
    end
    #println("i: ",i,"  c.interiors[i]: ",c.interiors[i])
    active = c.interiors[i].active
    j  = num_inputs_sum - interior_mutate_location + 1  # j is the index of the input to mutate
    #println("(i,j): ",(i,j),"  num_inputs_sum: ",num_inputs_sum)
    maxindex = i + c.params.numinputs -1
    minindex = max(1, i + c.params.numinputs - c.params.numlevelsback)
    intindex = rand(minindex:maxindex)
    if maxindex > minindex
      while( intindex == c.interiors[i].inputs[j] )
        intindex = rand(minindex:maxindex)
      end
    end
    #println("minindex: ",minindex,"  maxindex: ",maxindex,"  intindex: ",intindex)
    c.interiors[i].inputs[j] = intindex
    #println("(i,j): ",(i,j),"  c.interiors[i].inputs: ",c.interiors[i].inputs)
    #print("ml: ",mutate_location,"  ")
    #print_circuit(c)
    #println("inputs: ",rand(1:100))
  elseif mutate_location > num_funcs_to_mutate + sum(num_inputs_list)   # mutate an output
    # Mutating an output node should never happen because random chromosomes always use that last interior nodes as input,
    #   and num_mutate_locations() returns a value that does not include mutation of output nodes.
    error("mutate_chromosome!() is mutating an output node which should never happen")
    #println("mutate the output: ",mutate_location - num_funcs_to_mutate - sum(num_inputs_list ))
    active = true   # outputs are always active
    minindex = max(1, num_funcs_to_mutate + c.params.numinputs - c.params.numlevelsback + 1 )
    maxindex = num_funcs_to_mutate + c.params.numinputs
    if maxindex == mutate_location
      print_chromosome(c)
      exit()
    end
    #println("minindex: ",minindex,"  maxindex: ",maxindex)
    if maxindex > minindex   # no change if there is only one alternative for the output node index
      outindex = rand(minindex:maxindex)
      #println("outindex: ",outindex)
      #println("num_funcs_to_mutate: ", num_funcs_to_mutate )
      #println("sum(num_inputs_list): ", sum(num_inputs_list))
      while outindex == c.outputs[mutate_location - num_funcs_to_mutate - sum(num_inputs_list)].input
        outindex = rand(minindex:maxindex)
      end
      c.outputs[mutate_location - num_funcs_to_mutate - sum(num_inputs_list)].input = outindex
    end
  end
  #println("active: ",active)
  set_active_to_false(c)
  (c,active)
end

# Mutates c in all possible ways and returns a list of the outputs of the mutated chromosomes
#    or the pair (avg_robustness, evolvability)
# Deterministic if all functions in default_funcs() have the same arity
# If robustness_only==true returns the pair: (avg_robustness, evolvability)
# Otherwise returns vector of either outputs, or chromosomes, or (outputs, chromosomes) pairs
function mutate_all( c::Chromosome, funcs::Vector{Func};
      robustness_only::Bool=false, output_outputs::Bool=true, output_chromosomes::Bool=false )
  #println("mutate_all: numlevelsback: ", c.params.numlevelsback )
  #sav_c = deepcopy(c)
  if robustness_only
    output_outputs = output_chromosomes = false
    robustness_sum = 0.0; robustness_count = 0
    result = Vector{MyInt}[]   # Save outputs to give a measure of evolvability
  elseif output_outputs && !output_chromosomes
    result = Vector{MyInt}[]
  elseif !output_outputs && output_chromosomes
    result = Chromosome[]
  elseif output_outputs && output_chromosomes
    result = Tuple{Vector{MyInt},Chromosome}[]
  end
  context = construct_context(c.params.numinputs)
  orig_output = output_values(c)
  num_mutate_locs = num_mutate_locations( c, funcs )
  interiors_inputs_list = [ c.interiors[i].inputs for i = 1:c.params.numinteriors ]
  num_inputs_list = map(length, interiors_inputs_list)
  num_funcs_to_mutate = length(funcs) > 1 ? c.params.numinteriors : 0
  for mutate_location = 1:num_mutate_locs
    new_c = output_chromosomes ? deepcopy(c) : c
    if mutate_location <= num_funcs_to_mutate   # mutate a func
      #println("mutate a func ml:",mutate_location,"  node: ",c.interiors[mutate_location])
      active = c[mutate_location].active
      for new_func_index = 1:length(funcs)
        # Does not generate all alternative mutations in the very unusual case when the new function has greater arity
        #    than the old function
        #println("new_func: ",funcs[new_func_index].func,"  old_func: ", c.interiors[mutate_location].func.func )
        if funcs[new_func_index].func == c.interiors[mutate_location].func.func
          continue   # skip
        end
        #println("new_funct_index: ",new_func_index,"  funcs[new_func_index].func: ",funcs[new_func_index].func)
        #println("  c.interiors[mutate_location].func: ",c.interiors[mutate_location].func.func)
        #println("condition: ", funcs[new_func_index].func == c.interiors[mutate_location].func.func )
        #println("new_func_index: ",new_func_index)
        old_func_arity = c.interiors[mutate_location].func.arity
        old_inputs = c.interiors[mutate_location].inputs
        new_func = funcs[new_func_index]
        new_inputs = Array{Int64}(undef, new_func.arity)
        for i = 1:new_func.arity
          if i <= length(old_inputs)
            new_inputs[i] = old_inputs[i]
          else
            maxindex = mutate_location + c.params.numinputs - 1
            minindex = max(1,mutate_location + c.params.numinputs - c.params.numlevelsback)
            #println("mut_location: ",mutate_location,"  minindex: ",minindex,"  maxindex: ",maxindex)
            new_inputs[i] = rand(minindex:maxindex)
          end
        end
        new_c = deepcopy(c)  # This is necessary:  test done on 9/24/21
        #new_c = output_chromosomes ? deepcopy(c) : c
        new_c.interiors[mutate_location] = InteriorNode(new_func,new_inputs)
        deactivate_chromosome!(new_c)
        new_output = execute_chromosome(new_c,context)
        if robustness_only
          robustness_sum = (new_output == orig_output) ? robustness_sum + 1 : robustness_sum
          robustness_count+=1
          push!(result,new_output)
        elseif output_outputs && !output_chromosomes
          push!(result,new_output)
        elseif !output_outputs && output_chromosomes
          push!(result,new_c)
        elseif output_outputs && output_chromosomes
          push!(result,(new_output,new_c))
        end
      end
      #println("new interior node: ",new_c.interiors[mutate_location])
    elseif mutate_location <= num_funcs_to_mutate + sum(num_inputs_list)  # mutate an interior
      #println("mutate an interior node ml:",mutate_location, "  node: ",c.interiors[mutate_location-num_funcs_to_mutate])
      interior_mutate_location = mutate_location - num_funcs_to_mutate
      #println("interior mutate_location: ",interior_mutate_location)
      i = 1  # i will be the index of the interior node one of whose inputs will be mutated
      num_inputs_sum = num_inputs_list[1]
      #println("i: ",i,"  num_inputs_sum: ",num_inputs_sum)
      while(num_inputs_sum < interior_mutate_location)
        i += 1
        num_inputs_sum += num_inputs_list[i]
        #println("i: ",i,"  num_inputs_sum: ",num_inputs_sum)
      end
      #println("i: ",i,"  c.interiors[i]: ", c.interiors[i])
      j  = num_inputs_sum - interior_mutate_location + 1  # j is the index of the input to mutate
      maxindex = i + c.params.numinputs -1
      minindex = max(1, i + c.params.numinputs - c.params.numlevelsback)
      #println("(i,j): ",(i,j),"  num_inputs_sum: ",num_inputs_sum,"  minidex: ",minindex,"  maxindex: ",maxindex)
      for intindex = minindex:maxindex
        #println("intindex: ",intindex,"  c.interiors[i].inputs[j]: ",c.interiors[i].inputs[j])
        if intindex == c.interiors[i].inputs[j]
          #println("continue")
          continue
        end
        new_c = deepcopy(c)   # Is this necessary???  Maybe only when the chromosome is returned
        new_c.interiors[i].inputs[j] = intindex
        #println("intindex: ",intindex,"  (i,j): ",(i,j),"  new_c.interiors[i].inputs: ",new_c.interiors[i].inputs)
        deactivate_chromosome!(new_c)
        new_output = execute_chromosome(new_c,context)
        if robustness_only
          robustness_sum = (new_output == orig_output) ? robustness_sum + 1 : robustness_sum
          robustness_count+=1
          push!(result,new_output)
        elseif output_outputs && !output_chromosomes
          push!(result,new_output)
        elseif !output_outputs && output_chromosomes
          push!(result,new_c)
        elseif output_outputs && output_chromosomes
          push!(result,(new_output,new_c))
        end
      end
    elseif mutate_location > num_funcs_to_mutate + sum(num_inputs_list)   # mutate an output
      # Mutating an output node should never happen because random chromosomes always use that last interior nodes as input,
      #   and num_mutate_locations() returns a value that does not include mutation of output nodes.
      error("mutate_all() is mutating an output node which should never happen")
      #println("mutate the output: ",mutate_location - num_funcs_to_mutate - sum(num_inputs_list ))
      #active = true   # outputs are always active
      minindex = max(1, num_funcs_to_mutate + c.params.numinputs - c.params.numlevelsback + 1 )
      maxindex = num_funcs_to_mutate + c.params.numinputs
      if maxindex == mutate_location
        print_chromosome(c)
        exit()
      end
      #println("minindex: ",minindex,"  maxindex: ",maxindex)
      if maxindex > minindex   # no change if there is only one alternative for the output node index
        for outindex = minindex:maxindex
          #outindex = rand(minindex:maxindex)
          if outindex == c.outputs[mutate_location - num_funcs_to_mutate - sum(num_inputs_list)].input
            continue
          end
        end
        println("outindex: ",outindex)
        new_c.outputs[mutate_location - num_funcs_to_mutate - sum(num_inputs_list)].input = outindex
        new_output = execute_chromosome(new_c,context)
        push!(result,new_c)
      end
    end
  end
  if robustness_only
    (robustness_sum/robustness_count, length(unique(result))/length(result)) # pair of avg robustenss and evolvability
  else
    result
  end
end

function num_mutate_locations( c::Chromosome, funcs::Vector{Func} )
  num_funcs_to_mutate = length(funcs) > 1 ? c.params.numinteriors : 0
  interiors_inputs_list = [ c.interiors[i].inputs for i = 1:c.params.numinteriors ]
  outputs_input_list = [ c.outputs[i].input for i = 1:c.params.numoutputs ]
  #println("int_list: ", interiors_inputs_list, "   out_list: ",outputs_input_list)
  num_inputs_list = map(length, interiors_inputs_list)
  #num_inputs = sum(num_inputs_list) + c.params.numoutputs
  num_inputs = sum(num_inputs_list)
  #println("num_inputs_list: ",num_inputs_list,"  num_inputs: ",num_inputs)
  if length(funcs) == 1
    num_mutate_locations = num_inputs
  else
    num_mutate_locations = num_funcs_to_mutate + num_inputs
  end
  num_mutate_locations
end

function print_chromosome(f::IO, c::Chromosome )
  for i = 1:(c.params.numinputs + c.params.numinteriors + c.params.numoutputs)
    println(f,"i: ",i,"  ",c[i])
  end
  check_recursive(c)
end

function print_chromosome( c::Chromosome )
  for i = 1:(c.params.numinputs + c.params.numinteriors + c.params.numoutputs)
    println("i: ",i,"  ",c[i])
  end
  check_recursive(c)
end

function check_recursive( c::Chromosome )
  for i = (c.params.numinputs + 1) : c.params.numinteriors
    for j = 1:length(c[i].inputs)
      #println("i: ",i,"  inputs[",j,"] = ",c[i].inputs[j])
      if i == j
        println("recursive ")
        exit()
      end
    end
  end
end

function number_active_old( c::Chromosome )
  num_active = 0
  for i = 1:c.params.numinputs
    if c.inputs[i].active
      num_active += 1
    end
  end
  for i = 1:c.params.numinteriors
    if c.interiors[i].active
      num_active += 1
    end
  end
  num_active
end

function number_active( c::Chromosome )
  if !c[c.outputs[1].input].active   # if chromosome has not been executed
    context = construct_context(c.params.numinputs)
    execute_chromosome(c,context)
  end
  num_act_in = reduce(+,[c.inputs[i].active for i = 1:length(c.inputs)])
  num_act_int =  reduce(+,[c.interiors[i].active for i = 1:length(c.interiors)])
  num_act_in + num_act_int
end

function number_active_gates( c::Chromosome )
  if !c[c.outputs[1].input].active   # if chromosome has not been executed
    context = construct_context(c.params.numinputs)
    execute_chromosome(c,context)
  end
  num_act_int =  reduce(+,[c.interiors[i].active for i = 1:length(c.interiors)])
end

# Requires that cache values are set in the various versions of evaluat_node()
function node_values( c::Chromosome )
  output_values = [c[c.outputs[i].input].cache for i = 1:length(c.outputs)]
  input_values = [c.inputs[i].cache for i = 1:length(c.inputs)]
  interior_values = [c.interiors[i].cache for i = 1:(length(c.interiors))]
  #output_values = [c.interiors[i].cache for i = (length(c.interiors)-length(c.outputs)+1):length(c.interiors)]
  (input_values,interior_values,output_values)
end

function output_values( c::Chromosome )
  if !c[c.outputs[1].input].active   # if chromosome has not been executed
    context = construct_context(c.params.numinputs)
    return execute_chromosome(c,context)
  else
    return [c[c.outputs[i].input].cache for i = 1:length(c.outputs)]
  end
end

function deactivate_chromosome!( c::Chromosome )
  for in in c.inputs
    in.active = false
  end
  for int in c.interiors
    int.active = false
  end
  c
end

function fraction_active( c::Chromosome )
  number_active(c)/(c.params.numinputs+c.params.numinteriors)
end

# Returns a chromosome with the same output values as c and with all
#  inactive gate nodes removed.
function remove_inactive( c::Chromosome )
  if !c[c.outputs[1].input].active   # if chromosome has not been executed
    context = construct_context(c.params.numinputs)
    execute_chromosome(c,context)
  end
  new_ints = InteriorNode[]
  numinputs = length(c.inputs)
  numints = length(c.interiors)
  int_inds = collect(1:numints)
  # if i refers to a node in c, then map_indices[i] references
  #   the corresponding node in the new chromosome
  map_indices = zeros(Int64, numinputs+numints )
  active_gate_indices = int_inds[ [c.interiors[i].active for i = 1:numints]]
  #map_indices = ttt(numinputs,numints,active_gate_indices)
  map_indices = collect(1:numinputs)
  j = 1
  for i = 1:numints
    if active_gate_indices[j] > i
      push!(map_indices,0)
    else
      push!(map_indices,j+numinputs)
      j+= 1
    end
  end
  #println("map_indices: ",map_indices)
  for j in active_gate_indices
    new_inputs = map(x->map_indices[x],c.interiors[j].inputs)
    #println("j: ",j,"  new_inputs: ",new_inputs)
    new_int = InteriorNode( c.interiors[j].func, new_inputs )
    push!(new_ints,new_int)
  end
  new_outs = [ OutputNode( map_indices[c.outputs[i].input])  for i = 1:length(c.outputs) ]
  p = c.params
  new_p = Parameters( p.numinputs, p.numoutputs, length(new_ints), p.numlevelsback )
  nc = Chromosome( new_p, c.inputs, new_ints, new_outs, c.fitness, c.robustness )
  @assert output_values(nc) == output_values(c)
  nc
end

function ttt(numinputs,numints,active_gate_indices)
  map_indices = collect(1:numinputs)
  j = 1
  for i = 1:numints
    if active_gate_indices[j] > i
      push!(map_indices,0)
    else
      push!(map_indices,j+numinputs)
      j+= 1
    end
  end
  map_indices
end

function getindex(c::Chromosome, index::Integer)
    if index <= c.params.numinputs   # input node
        return c.inputs[index]
    end

    if index > c.params.numinteriors + c.params.numinputs  # output node
        return c.outputs[index - c.params.numinteriors - c.params.numinputs]
    end

    return c.interiors[index-c.params.numinputs]  # interior node
end

# Number of bits where x and y differ.  x and y are interpreted as bit strings
function hamming( x::MyInt, y::MyInt )
  xr = xor( x, y )
  result = 0
  my_one = convert(MyInt,1)
  my_zero = convert(MyInt,0)
  while xr != my_zero
    result +=  xr & my_one
    xr >>= 1
  end
  result
end

# Hamming distance between MyInts which is between 0 and 1.
# If hamming(x,y) >= 2^numinputs/2, then hamming_distance(x,y,numinputs) = 1.0
function hamming_distance( x::MyInt, y::MyInt, numinputs::Int64 )
  result = hamming(x,y)/2^numinputs
end

# Hamming distance between Goals which is between 0 and 1.
function hamming_distance( x::Vector{MyInt}, y::Vector{MyInt}, numinputs::Int64 )
  @assert length( x ) == length( y )
  sum( hamming_distance( x[i], y[i], numinputs ) for i = 1:length(x))/length(x)
end

# the minimum of the hamming distance and 1+hamming_distance( x, bitwise_complement(y))
function ihamming_distance( x::MyInt, y::MyInt, numinputs::Int64 )
  Ones = Main.CGP.construct_ones(numinputs)[numinputs]
  complement_y = xor(y,Ones)
  #@printf("0x0%0x\n",complement_y)
  #println("hd:  ",hamming_distance(x,y,numinputs))
  #println("chd: ",hamming_distance(x,complement_y,numinputs))
  #println("xhd: ",1.0/numinputs+hamming_distance(x,complement_y,numinputs))
  return min(hamming_distance(x,y,numinputs),1.0/2^numinputs+hamming_distance(x,complement_y,numinputs))
end

function ihamming_distance( x::Vector{MyInt}, y::Vector{MyInt}, numinputs::Int64 )
  sum( ihamming_distance( x[i], y[i], numinputs ) for i = 1:length(x))/length(x)
end

# Change the fields of chromosome c to be the fields of chromosom c_to_copy
function copy_chromosome!( c::Chromosome, c_to_copy::Chromosome )
  c.params = c_to_copy.params
  c.inputs = c_to_copy.inputs
  c.interiors = c_to_copy.interiors
  c.outputs = c_to_copy.outputs
  c.interiors = c_to_copy.interiors
end

# Computes the fraction of mutations that do not change the output
# Not really accurate because it counts one mutation per num_mutate_location(), while in fact
#    there may be more than one random choice per num_mutate_location()
# Superceded by calling   mutate_all( c, funcs, robustness_only=true ) which returns the pair (robustness,evolvability)
# If active_only==true, then only mutations that change active nodes are considered
# If active_only==false, then all mutations are considered
# Note that results are partially random since the result of mutations at a specific location can be different.
function mutational_robustness( c::Chromosome, funcs::Vector{Func}; active_only::Bool=false )
  context = construct_context( c.params.numinputs )
  prev_out = execute_chromosome( c, context )
  count_no_change = 0
  count_no_change = 0.0
  num_mut_locs = 0
  for j = 1:num_mutate_locations( c, funcs )
    cc = deepcopy(c)
    (cc,active) = mutate_chromosome!( cc, funcs, j )
    out_c = execute_chromosome( cc, context )
    #println("j: ",j,"  active: ",active)
    #print_chromosome( cc )
    if !active_only || active # consider all mutations if !active_only, only active mutations if active_only
      #out_c = execute_chromosome( cc, context )
      count_no_change += ((out_c == prev_out) ? 1 : 0)
      if active_only && active
        num_mut_locs += 1
      end
    end
    #println("j: ",j,"  active: ",active,"  no_change: ",(out_c == prev_out),"  count_no_change: ",count_no_change)
  end
  #println("num_mut_locs: ",num_mut_locs,"  num_mutate_locations(): ",num_mutate_locations(c,funcs),"  count_no_change: ",count_no_change)
  #@assert num_mut_locs == num_mutate_locations(c,funcs)
  if !active_only
    num_mut_locs = num_mutate_locations( c, funcs )
  end
  count_no_change/num_mut_locs
end

# Implements equation 3.3 of Macia and Sole (2009)
#  The average Hamming deviation of the output from the unperturbed output under perturbation of the output of each node
function fault_tolerance_fitness( c::Chromosome )
  numinputs = c.params.numinputs
  numoutputs = c.params.numoutputs
  numints = c.params.numinteriors
  #println("(numinputs,numoutputs,numints):",(numinputs,numoutputs,numints))
  context = construct_context( c.params.numinputs )
  outputs = output_values( c )
  #println("outputs: ",outputs)
  distances = fill(0.0,numints)
  for i = (1+numinputs):(numinputs+numints)
    out_ft = execute_chromosome_ft(c,context,i)
    #println("i: ",i,"  out_ft: ",out_ft)
    #println("hamming dis: ",hamming_distance(execute_chromosome_ft(c,context,i)[1], outputs[1], numinputs))
    distances[i-numinputs] =
      sum( hamming_distance(execute_chromosome_ft(c,context,i)[k], outputs[k], numinputs) for k = 1:numoutputs)
  end
  #println("distances: ",distances)
  1.0-sum(distances)/numints/numoutputs
end

#Assumes nodearity==2
mutable struct Gate_node
  node_index::Int64
  func::Func
  input1::Int64
  input2::Int64
end

# Creates a Chromosome (circuit) from the concise format.
# Example:
# julia>circuit((1,2,3), ((4,OR,1,2), (5,AND,2,3), (6,XOR,4,5)))
#    creates a circuit with 3 input nodes and 3 gate nodes and 1 implicit output node.
# gate nodes have 4 fields:
#    node_index:  should be the index of the node.  Not used in construcing the circuit, but checked for correctness
#    node_function:  See Func.jl for options
#    node_input1:  an integer in the range from 1 to the index of the node minus 1
#    node_input2:  an integer in the range from 1 to the index of the node minus 1
# Assumes nodearity==2 and numoutputs == 1
function circuit( inputs::Tuple, gates::Tuple;
    levsback::Int64=length(inputs)+length(gates))
  numinputs = length(inputs)
  errors = false
  for i = 1:length(gates)
    if gates[i][1] != i+numinputs
      println("index of gate ",i," not correct: it should be: ",i+numinputs)
      errors = true
    end
  end
  for i = 1:length(gates)
    if !((1 <= gates[i][3])  && (gates[i][3] < numinputs+i))
      error("illegal first gate input for gate: ",i)
    end
  end
  for i = 1:length(gates)
    if !((1 <= gates[i][4])  && (gates[i][4] < numinputs+i))
      error("illegal second gate input for gate: ",i)
    end
  end
  p = Parameters( numinputs=length(inputs), numoutputs=1, numinteriors=length(gates), numlevelsback=levsback )
  in_nodes = [InputNode(i) for i in inputs]
  gate_nodes = [InteriorNode(g[2], [g[3],g[4]] ) for g in gates ]
  out_nodes = [OutputNode( length(inputs)+length(gates ) ) ]
  c = Chromosome( p, in_nodes, gate_nodes, out_nodes, 0.0, 0.0 )
  if errors
    println("correct input to this function should have been: ")
    print_circuit(c)
  end
  c
end

# Outputs the concise format of Chromosome (circuit) c to IO stream f
# print_node_tuple() and gate_tuple() are defined in Node.jl
function print_circuit( f::IO, c::Chromosome; include_fitness::Bool=false, include_robustness::Bool=false,
    include_pheno::Bool=false )
  @assert c.params.nodearity==2
  @assert c.params.numinputs>1
  print(f,"circuit(")
  print_node_tuple(f, c.inputs )
  print(f,",")
  gate_tuple(f, c.interiors, c.params.numinputs )
  if c.params.numoutputs>1
    print_node_tuple(f, c.outputs )
  end
  if include_fitness
    @printf(f,", %5.3f",c.fitness)
  end
  if include_robustness
    if typeof(c.robustness) == PredType
      print(f,", ",c.robustness)
    elseif typeof(c.robustness) == Float64
      @printf(f,", %5.3f",c.robustness)
    end
  end
  if include_pheno
    if c.params.numoutputs == 1
      @printf(f,", 0x%x",output_values(c)[1])
    else
      print(f,", [")
      for i = 1:(c.params.numoutputs-1)
        @printf(f,"0x%x, ",output_values(c)[i])
      end
      @printf(f,"0x%x]",output_values(c)[c.params.numoutputs])
    end
  end
  println(f,")")
end

function print_circuit( c::Chromosome; include_fitness::Bool=false, include_robustness::Bool=false, include_pheno::Bool=false )
  print_circuit( Base.stdout, c, include_fitness=include_fitness, include_robustness=include_robustness,
    include_pheno=include_pheno )
end

# The following struct defintions and function definitions build_chromosome() and print_build_chromosome()
#   have mostly been replaced by the above definitions of circuit() and print_circuit()
mutable struct Int_node
  func::Func
  inputs::Vector{Int64}
end

mutable struct Input_node
  index::Int64
end

mutable struct Output_node
  input::Integer
end

# Example call: build_chromosome( [Input_node(1),Input_node(2)], [Int_node(OR,[1,2]),Int_node(AND,[2,3])],[Output_node(4)])
# Depreciated 12/18/20
function build_chromosome( input_nodes::Vector{Input_node}, interior_nodes::Vector{Int_node}, output_nodes::Vector{Output_node};
    levsback::Int64=length(input_nodes)+length(interior_nodes))
  num_in = length(input_nodes)
  num_ints = length(interior_nodes)
  num_outs = length(output_nodes)
  p = Parameters( numinputs=num_in, numoutputs=num_outs, numinteriors=num_ints, numlevelsback=levsback )
  in_nodes = [InputNode(in_node.index) for in_node in input_nodes]
  int_nodes = [InteriorNode(int_node.func, int_node.inputs) for int_node in interior_nodes]
  out_nodes = [OutputNode(outnode.input) for outnode in output_nodes]
  Chromosome( p, in_nodes, int_nodes, out_nodes, 0.0, 0.0 )
end

# Example calls:  build_chromosome((1,2), ((OR,[1,2]),(AND,[2,3])),(4,))
# Example calls:  build_chromosome((1,2), ((OR,[1,2]),(AND,[2,3])),(4,),0.0)
# Depreciated 12/18/20
function build_chromosome( inputs::Tuple, ints::Tuple, outs::Tuple, fitness::Float64=0.0;
    levsback::Int64=length(inputs)+length(ints))
  num_in = length(inputs)
  num_ints = length(ints)
  num_outs = length(outs)
  p = Parameters( numinputs=num_in, numoutputs=num_outs, numinteriors=num_ints, numlevelsback=levsback )
  in_nodes = [InputNode(in_index) for in_index in inputs]
  int_nodes = [InteriorNode(int_pair[1], int_pair[2]) for int_pair in ints]
  out_nodes = [OutputNode(out_index) for out_index in outs]
  Chromosome( p, in_nodes, int_nodes, out_nodes, fitness, 0.0 )
end

function print_build_chromosome( f::IO, c::Chromosome; include_fitness::Bool=false )
  print(f, "build_chromosome(")
  print_node_tuple(f, c.inputs )
  print(f,",")
  print_node_tuple(f, c.interiors )
  print(f,",")
  print_node_tuple(f, c.outputs )
  if include_fitness
    println(f,", ",c.fitness)
  end
  println(f,")")
  if typeof(f) == IOStream  # Don't close Base.stdout since this kills julia
    close(f)
  end
end

function print_build_chromosome( c::Chromosome )
  print_build_chromosome( Base.stdout, c)
end

# Generates a Int128 integer corresponding to chromosome ch.  The integer is unique for parameters p.
# Does not take into account the levelsback parameter
# Assumes that outputs come from the last numoutputs interior nodes and thus don't affect the number of chromosomes.
# See test/testChromosome.jl for a test----but only where all gates have arity max_arity.
function chromosome_to_int( ch::Chromosome, funcs::Vector{Func}=default_funcs(ch.params.numinputs); maxarity::Int64=2 )
  result = Int128(0)
  for i = 1:length(ch.interiors)
    result += gate_int( i, ch, maxarity, funcs )
    multiplier = i < length(ch.interiors) ? length(funcs)*(ch.params.numinputs+i+1)^maxarity : 1
    result *= multiplier
    #result = i < length(ch.interiors) ? result*length(funcs)*(ch.params.numinputs+i+1)^maxarity : result
    #println("i: ",i,"  gate_int: ",gate_int( i, ch, maxarity, funcs ),"  multiplier: ",multiplier, "  result: ",result)
    #println("ci i: ",i,"  gate_int: ",gate_int( i, ch, maxarity, funcs ),"  result: ",result)
  end
  #println("result: ",result)
  result
end

function gate_int( i::Int64, ch::Chromosome, maxarity::Int64, funcs::Vector{Func} )
  numinputs = ch.params.numinputs
  funcs_int = findfirst(x->x==ch.interiors[i].func,funcs)[1]-1
  gate_inputs = ch.interiors[i].inputs
  il = inputsList( maxarity, numinputs+i )
  ni = length(il)   # multiplier which should be (numinputs+i)^maxarity
  #println("ni: ",ni,"  maxarity: ",maxarity,"  numinputs+i: ",numinputs+i)
  @assert ni == (numinputs+i)^maxarity
  inputs_int = findfirst(x->x==gate_inputs,il)
  funcs_int*ni + inputs_int
end

# Generates a list of all Int64 vectors of length numinputs and values from 1:maxval
function inputsList( numinputs::Int64, maxval::Int64 )
  if numinputs == 1
    return [ [i] for i = 1:maxval ]
  end
  result = inputsList( numinputs-1, maxval )
  new_result = Vector{Int64}[]
  for r in result
    for i = 1:maxval
      dcr = deepcopy(r)
      push!(dcr,i)
      push!(new_result,dcr)
      #println("i: ",i,"  dcr: ",dcr)
    end
  end
  #println("numimnputs: ",numinputs,"  new_result: ",new_result)
  new_result
end

# Produces a list of all circuits corresponding the paramters p --- except that it ignores the levelsback parameter
function enumerate_circuits( p::Parameters, funcs::Vector{Func}=default_funcs(p.numinputs); maxarity::Int64=2 )
  enumerate_circuits( p, p.numinteriors, funcs, maxarity=maxarity )
end

# Recursive helper function
function enumerate_circuits( p::Parameters, numints::Int64, funcs::Vector{Func}; maxarity::Int64=2 )
  if numints == 0
    result = [Chromosome( p, map(i->InputNode(i),collect(1:p.numinputs)), InteriorNode[], [OutputNode(p.numinputs)], 0.0, 0.0 )]
    return result
  end
  prev_result = enumerate_circuits( p, numints-1, funcs )
  result = Chromosome[]
  for prev_ch in prev_result
    for func in funcs
      for inputs in inputsList( maxarity, p.numinputs+numints-1 )
        new_interiors = vcat(prev_ch.interiors,[InteriorNode(func,inputs)])
        new_ch = Chromosome( p, prev_ch.inputs,new_interiors,[OutputNode(p.numinputs+numints)],0.0,0.0)
        push!(result,new_ch)
      end
    end
  end
  result
end

function int_to_chromosome( ch_int::Int128, p::Parameters, funcs::Vector{Func}=default_funcs(p.numinputs); maxarity::Int64=2 )
  inputs = [ InputNode(i) for i = 1:p.numinputs ]
  g_ints = gate_int_list( ch_int, p, maxarity, funcs )
  interiors = InteriorNode[]
  for i = 1:p.numinteriors
    push!(interiors, int_to_gate( g_ints[i], i, p, maxarity, funcs ) )
  end
  outputs = [ OutputNode(i) for i = (p.numinputs + p.numinteriors -p.numoutputs + 1):p.numinputs + p.numinteriors]
  Chromosome( p, inputs, interiors, outputs, 0.0, 0.0 )
end

# list of the gate_ints used by chromsosome_int
function gate_int_list( ch_int::Integer, p::Parameters, maxarity::Int64, funcs::Vector{Func}=default_funcs(p.numinputs) )
  result = zeros(Int64,p.numinteriors)
  multipliers = [ length(funcs)*(p.numinputs+i+1)^maxarity for i = 1:p.numinteriors-1 ]
  #println("multipliers: ",multipliers)
  for i = p.numinteriors:-1:2
    result[i] = ch_int % multipliers[i-1]
    ch_int = div(ch_int,multipliers[i-1])
  end
  result[1] = ch_int
  result
end

function int_to_gate( g_int::Int64, i::Int64, p::Parameters, maxarity::Int64, funcs::Vector{Func}=default_funcs(p.numinputs) )
  il = inputsList( maxarity, p.numinputs+i )
  ni = (p.numinputs+i)^maxarity
  InteriorNode( funcs[div(g_int,ni)+1], il[ g_int % ni ] )
end

#= Outdated:  replaced by the newer version below
# Returns an unique integer for i^{th} interior node (gate) of chromosome ch
# Ignores any limitations due to the levelsback parameter
function gate_int( i::Int64, ch::Chromosome, funcs::Vector{Func} )
  #println("funcs: ",funcs)
  result = (findall(x->x==ch.interiors[i].func,funcs)[1]-1)*(ch.params.numinputs+1)
  #println("gi result: ",result)
  for j = 1:ch.interiors[i].func.arity
    @assert ch.interiors[i].inputs[j] <= ch.params.numinputs+i+1
    result += ch.interiors[i].inputs[j]-1
    result = j < length(ch.interiors[i].inputs) ?  result*(ch.params.numinputs+i+1) : result
    #println("j: ",j,"  result: ",result)
  end
  #println("gate_int result: ",result)
  result
end
=#

# Outdated 9/13/21
# Returns a vector of integers which is a characterization of the chromosome for the parameters c.params
# The length of the result should be p.numinteriors*(1+p.nodearity).
# Not correct in that codes for gate inputs are sometimes too large.
function circuit_code( c::Chromosome )
  result = Int64[]
  funcs = default_funcs(c.params.numinputs)
  if length(c.interiors) == 0
    return Int64[]
  end
  for i = 1:c.params.numinteriors
    # Determine index of gate function
    j = 0
    while j < length(funcs) && funcs[j+1].func != c.interiors[i].func.func
      j += 1
    end
    if j > length(funcs)
      error("illegal function in function circuit_code()")
    end
    #println("i: ",i,"  function code: ",j)
    push!(result,j)
    # Add interior node inputs to result
    index = i + c.params.numinputs  # node index in chromosome
    for k = 1:length(c.interiors[i].inputs)
      code = max(c.interiors[i].inputs[k]-(index-c.params.numlevelsback),0)
      #println("i: ",i,"  k: ",k,"  code: ",code)
      push!(result, code)
    end
  end
  # Note:  assumes that the input fields of output nodes are set by default and thus don't need to be recorded
  result
end

# Outdated 9/13/21 Not correct
# Converts a circuit code to a Chromosome.   Inverse function to circuit_code().
function code_to_circuit( code::Vector{Int64}, p::Parameters )
  funcs = default_funcs( p.numinputs )
  interior_nodes = InteriorNode[]
  for i = 1:p.numinteriors
    j = 3*(i-1)+1   # index of code component
    func = funcs[code[j] + 1]
    index = i + p.numinputs  # node index in Chromosome
    input1 = code[j+1] + max(index-p.numlevelsback,0)
    input2 = code[j+2] + max(index-p.numlevelsback,0)
    #println("i: ",i,"  j: ",j,"  func: ",func,"  input1: ",input1,"  input2: ",input2)
    push!( interior_nodes, InteriorNode(func,[input1,input2]) )
  end
  input_nodes = [ InputNode(j) for j = 1:p.numinputs ]
  output_nodes = [ OutputNode(j) for j = (p.numinputs+p.numinteriors-p.numoutputs+1):(p.numinputs+p.numinteriors) ]
  c = Chromosome(p, input_nodes, interior_nodes, output_nodes, 0.0, 0.0 )
end

# Outdated  9/13/21 by chromosome_to_int()
# An integer that characterizes the chromosome
# Doesn't overflow for 11 gates, 8 numlevelsback
function circuit_int( c::Chromosome )
  circuit_int( circuit_code(c), c.params )
end

# Outdated  9/13/21 by chromosome_to_int()
# Not correct  See diary12_25.txt for example.
function circuit_int( c_code::Vector{Int64}, p::Parameters )
  result = Int128(0)
  multiplier = 1
  funcs = default_funcs( p.numinputs )
  k = 1
  for i = 1:p.numinteriors
    multiplier = length(funcs)
    result = c_code[k] + result*multiplier
    #print("i: ",i,"  k: ",k,"  multiplier: ",multiplier,"  c_code[k]: ",c_code[k])
    #println("  result: ",result)
    k += 1
    multiplier = min(p.numlevelsback,i-1+p.numinputs)
    for j = 1:p.nodearity
      #println("result: ",result,"  multiplier: ",multiplier,"  c_code[k]: ",c_code[k])
      result = c_code[k] + result*multiplier
      #print("i: ",i,"  j: ",j,"  k: ",k,"  multiplier: ",multiplier,"  c_code[k]: ",c_code[k])
      #println("  result: ",result)
      k += 1
    end
  end
  result
end

# Outdated:  replaced in functionality by int_to_chromosome()
function int_to_circuit_code( c_int::Integer, p::Parameters )
  c_int = Int128(c_int)
  c_code = zeros(Int64,3*p.numinteriors)
  k = 3*p.numinteriors
  for i = p.numinteriors:-1:1
    multiplier = min(p.numlevelsback,i-1+p.numinputs)
    println("i: ",i,"  multiplier: ",multiplier)
    for j = p.nodearity:-1:1
      c_int_mod= c_int % multiplier
      #prev_c_int =c_int
      c_int ÷= multiplier
      #c_int_mod = prev_c_int - c_int
      println("i: ",i,"  j: ",j,"  c_int: ",c_int,"  c_int_mod: ",c_int_mod)
      c_code[k] = c_int_mod
      k -= 1
    end
    multiplier = length(funcs)
    println("i: ",i,"  multiplier: ",multiplier)
    c_int_mod= c_int % multiplier
    c_int ÷= multiplier
    println("i: ",i,"  c_int: ",c_int,"  c_int_mod: ",c_int_mod)
    c_code[k] = c_int_mod
    k -= 1
  end
  c_code
end

# Hamming distance between chromosomes normalized to be between 0.0 and 1.0
function circuit_distance( c1::Chromosome, c2::Chromosome )
  code1 = circuit_code(c1)
  code2 = circuit_code(c2)
  @assert length(code1) == length(code2)
  diff_count = 0
  for i = 1:length(code1)
    diff_count += code1[i] == code2[i] ? 0 : 1
  end
  diff_count/length(code1)
end

# Return the number of circuits for parameters p and number of funcs nfuncs if nfuncs>0
# If nfuncs==0, nfuncs is reset to be length(default_funcs(p.numinputs))
function count_circuits( p::Parameters; nfuncs::Int64=0 )
  @assert p.numoutputs == 1   # Not tested for more than 1 output, but probably works in this case.
  nfuncs = nfuncs==0 ? length(default_funcs(p.numinputs)) : nfuncs
  multiplier = Int128(1)
  mij = 0
  for i = 1:p.numinteriors
    mf = nfuncs
    multiplier *= mf
    for j = 1:p.nodearity
      mij = min(p.numlevelsback,i-1+p.numinputs)
      multiplier *= mij
    end
    #print("i: ",i,"  mf: ",mf,"  mij: ",mij,"  log multiplier: ",log10(multiplier))
    exp = trunc(log10(multiplier))
    fract = 10^(log10(multiplier)-exp)
    #println("  exp: ",exp,"  fract: ",fract)
    #@printf("  multiplier: %4.2f",fract)
    #@printf("e+%2i\n",exp)
    #=
    try
      @printf("  multiplier:  %8.2e\n",multiplier)
    catch
    end
    =#
  end
  multiplier
end

# Mutates chromosome c by inserting a gate or deleting a gate
function mutate_num_gates!( c::Chromosome; prob_insert::Float64=0.5 )
  if rand() <= prob_insert
    c = insert_gate!(c)
  else
    c = insert_gate!(c)
  end
  c
end

# Insert a gate into c so that output_values(c) is unchanged.
# The new gate is inserted at position new_gate_index.
# The following gates are shifted right by 1.
# The new gate replaces an input connection to the existing gate a position new_gate_index (before the shift)
# The output of the new gate is the input to the existing gate.
# One input to the new gate is the source of the input to the exisiting gate.
# The other is chosen randomly according to the levelsback constraint.
function insert_gate!( c::Chromosome )
  funcs = default_funcs(c.params.numinputs)
  p = c.params
  # Chose a random interior node as the "new gate".
  # Don't insert a gate to replace an output gate:
  new_gate_index = rand(1:(p.numinteriors-p.numoutputs+1))  # Position of the new gate
  #println("new_gate_index: ",new_gate_index)
  p = c.params = Parameters( p.numinputs, p.numoutputs, p.numinteriors+1, p.numlevelsback+1 )
  new_gate = deepcopy(c.interiors[new_gate_index])  # will be modified below
  input_index = rand(1:2)
  new_gate.func = input_index == 1 ? IN1 : IN2  # IN1 and IN2 are gates that return input1 and input 2 respectively.
  c.interiors = vcat(c.interiors[1:(new_gate_index-1)],[new_gate],c.interiors[new_gate_index:end])
  for i = 1:p.numoutputs
    index = p.numinputs + p.numinteriors + i - p.numoutputs # use the last numoutputs interiors
    c.outputs[i] = OutputNode(index)
  end
  for i = (new_gate_index+1):p.numinteriors
    for j = 1:p.nodearity
      if c.interiors[i].inputs[j] >= p.numinputs + new_gate_index
        c.interiors[i].inputs[j] += 1
      end
    end
  end
  # Modify one of the inputs of interiors[new_gate_index+1] to point to the new gate
  c.interiors[new_gate_index+1].inputs[input_index] = p.numinputs + new_gate_index
  set_active_to_false(c)
  #println("insert_gate!  new interiors: ",c.params.numinteriors)
  c
end

function delete_gate!( c::Chromosome, interior_to_delete::Int64 )
  #println("delete_gate! interior_to_delete: ",interior_to_delete,"  active: ",c.interiors[interior_to_delete].active)
  p = c.params
  @assert interior_to_delete <= p.numinteriors - p.numoutputs
  gate_to_delete = c.interiors[interior_to_delete]
  new_levelsback = (p.numlevelsback > p.numinputs) ? p.numlevelsback-1 : p.numlevelsback  # Don't set numlevelsback to less than p.numinputs.
  p = c.params = Parameters( p.numinputs, p.numoutputs, p.numinteriors-1, new_levelsback )
  c.interiors = vcat(c.interiors[1:(interior_to_delete-1)],c.interiors[(interior_to_delete+1):end])
  for i = 1:p.numoutputs
    index = p.numinputs + p.numinteriors + i - p.numoutputs # use the last numoutputs interiors
    c.outputs[i] = OutputNode(index)
  end
  for i = interior_to_delete:p.numinteriors
    for j = 1:p.nodearity
      if c.interiors[i].inputs[j] >= interior_to_delete + p.numinputs
        c.interiors[i].inputs[j] -= 1
      end
    end
  end
  #println("delete_gate!  new interiors: ",c.params.numinteriors)
  c
end

function delete_gate!( c::Chromosome )
  output_values(c)  # needed to set active gates to active
  if number_active_gates(c) == c.params.numinteriors
    dg = rand(1:(c.params.numinteriors-c.params.numoutputs))
    return delete_gate!( c, dg )
  end
  inactive_interior_list = filter!(i->!c.interiors[i].active, collect(1:c.params.numinteriors))
  dg = rand(inactive_interior_list)
  return delete_gate!( c, dg )
end