module Io

import Base.write

using Avro.Common
using Avro.Schemas

export Encoder,
       Decoder,
       BinaryEncoder,
       BinaryDecoder,
       encode_null,
       encode_boolean,
       encode_int,
       encode_long,
       encode_float,
       encode_double,
       encode_byte,
       encode_bytes,
       encode_fixed,
       encode_string,
       decode_null,
       decode_boolean,
       decode_int,
       decode_long,
       decode_float,
       decode_double,
       decode_byte,
       decode_bytes,
       decode_fixed,
       decode_string

abstract type Encoder end;
abstract type Decoder end;

"""
Writes binary data of builtin and primtive types to an output stream.
"""
struct BinaryEncoder <: Encoder
    stream::IO
end

"""
Reads data of simple and primitive types from an input stream.
"""
struct BinaryDecoder <: Decoder
    stream::IO
end

# Encoders

encode_null(::BinaryEncoder, ::Nothing) = 0
encode_boolean(encoder::BinaryEncoder, value::Bool) = write(encoder.stream, value)

encode_int(encoder::BinaryEncoder, value::Int32) = _encode_varint(encoder.stream, _encode_zigzag(value))
encode_long(encoder::BinaryEncoder, value::Int64) = _encode_varint(encoder.stream, _encode_zigzag(value))

function encode_float(encoder::BinaryEncoder, value::Float32)
    isnan(value) ? write(encoder.stream, NaN32) : write(encoder.stream, value)
end

function encode_double(encoder::BinaryEncoder, value::Float64)
    isnan(value) ? write(encoder.stream, NaN64) : write(encoder.stream, value)
end

encode_byte(encoder::BinaryEncoder, value::UInt8) = write(encoder.stream, value)

function encode_bytes(encoder::BinaryEncoder, value::Vector{UInt8})
    encode_long(encoder, length(value))
    write(encoder.stream, value)
end

encode_fixed(encoder::BinaryEncoder, value::Vector{UInt8}) = write(encoder.stream, value)

function encode_string(encoder::BinaryEncoder, value::String)
    encode_long(encoder, sizeof(value)) + write(encoder.stream, value)
end

function _encode_zigzag(n::T) where T <: Integer
	num_bits = sizeof(T) * 8
	(n << 1) ⊻ (n >> (num_bits - 1))
end

function _encode_varint(stream::IO, n::T) where T <: Integer
    max_bytes = sizeof(T) + ceil(Int, sizeof(T) / 8)
    bytes_written = 0
    while n > 0x7f && bytes_written < max_bytes
        bytes_written += write(stream, ((n | 0x80) & 0xff) % UInt8)
        n >>>= 7
    end
    if n > 0x7f
        throw(Exception("Invalid variable-length integer encoding"))
    end
    bytes_written += write(stream, n % UInt8)
end

# Decoders

decode_null(::Decoder) = nothing
decode_boolean(decoder::Decoder) = read(decoder.stream, Bool)

decode_int(decoder::Decoder) = _decode_zigzag(_decode_varint(decoder.stream, Int32))
decode_long(decoder::Decoder) = _decode_zigzag(_decode_varint(decoder.stream, Int64))

decode_float(decoder::Decoder) = read(decoder.stream, Float32)
decode_double(decoder::Decoder) = read(decoder.stream, Float64)

decode_byte(decoder::Decoder) = read(decoder.stream, UInt8)

function decode_bytes(decoder::Decoder)
    nb = decode_long(decoder)
    read(decoder.stream, nb)
end

decode_fixed(decoder::Decoder, nb::Integer) = read(decoder.stream, nb)

function decode_string(decoder::Decoder)
    nb = decode_long(decoder)
    String(decode_fixed(decoder, nb))
end

_decode_zigzag(n::Integer) = (n >>> 1) ⊻ -(n & 1)

function _decode_varint(stream::IO, ::Type{T}) where T <: Integer
    max_bytes = sizeof(T) + ceil(Int, sizeof(T) / 8)
    b = read(stream, UInt8) % T
    n = b & 0x7f
    bytes_read = 1
    while b > 0x7f && bytes_read < max_bytes
        b = read(stream, UInt8) % T
        n ⊻= (b & 0x7f) << (7 * bytes_read)
        bytes_read += 1
    end
    if b > 0x7f
        throw(Exception("Invalid variable-length integer encoding"))
    end
    n
end

end


using CMPFit, GFit, Gnuplot
using QSFit, DataStructures, Statistics, Dierckx

import QSFit: known_spectral_lines, add_qso_continuum!, LineComponent

export T2Recipe

abstract type T2Recipe <: DefaultRecipe end

function default_options(::Type{T}) where T <: T2Recipe
    out = OrderedDict{Symbol, Any}()
    out[:host_template] = "Ell5"
    out[:use_host_template] = true
    out[:use_balmer] = false
    out[:use_ironuv] = false
    out[:use_ironopt] = false
    out[:use_lorentzian_profiles] = false
    out[:n_unk] = 2
    out[:unk_avoid] = [4863 .+ [-1,1] .* 50,
                       6565 .+ [-1,1] .* 150,
                       5008 .+ [-1,1] .* 25]
    return out
end

function known_spectral_lines(source::QSO{T}) where T <: T2Recipe
    list = [
        NarrowLine(                      :Lyb                       ),
        NarrowLine(                      :Lya                       ),
        NarrowLine(                      :NV_1241                   ),
        NarrowLine(                      :CIV_1549                  ),
        NarrowLine(                      :CIII_1909                 ),
        NarrowLine(                      :MgII_2798                 ),
        NarrowLine(                      :NeV_3426                  ),
        NarrowLine(                      :OII_3727                  ),
        NarrowLine(                      :NeIII_3869                ),
        NarrowLine(                      :Hg                        ),
        NarrowLine(                      :Hb                        ),
        NarrowLine(                      :OIII_4959                 ),
        NarrowLine(                      :OIII_5007                 ),
        AsymmTailLine(                   :OIII_5007      ,     :blue),
        NarrowLine(                      :OI_6300                   ),
        NarrowLine(                      :OI_6364                   ),
        NarrowLine(                      :NII_6549                  ),
        NarrowLine(                      :Ha                        ),
        NarrowLine(                      :NII_6583                  ),
        NarrowLine(                      :SII_6716                  ),
        NarrowLine(                      :SII_6731                  )]
    return list
end

function add_qso_continuum(source::QSO{T}, pspec::PreparedSpectrum, model::Model) where T <: T2Recipe
    λ = domain(model)[:]

    comp = QSFit.powerlaw(median(λ))
    comp.alpha.val = -1.8

    model[:qso_cont] = comp
    push!(model[:Continuum].list, :qso_cont)
    evaluate!(model)
end

function LineComponent(source::QSO{T}, line::NarrowLine, multicomp::Bool) where T <: T2Recipe
    lc = LineComponent(parent_recipe(source), line, multicomp) # invoke parent recipe
    lc.comp.fwhm.low  = 10
    lc.comp.fwhm.high = 1000
    lc.comp.voff.high = 500
    @info line.tid lc.comp.fwhm.val
    return lc
end

function LineComponent(source::QSO{T}, line::AsymmTailLine, multicomp::Bool) where T <: T2Recipe
    comp = LineComponent(source, GenericLine(line.tid), multicomp).comp
    comp.fwhm.val  = 500
    comp.fwhm.high = 1e3
    comp.voff.low  = line.side == :blue ?    0  :  720
    comp.voff.high = line.side == :blue ?  720  :    0
    return LineComponent(line, comp, multicomp)
end

function default_unk_line(source::QSO{T}) where T <: T2Recipe
    comp = SpecLineGauss(5e3)
    comp.norm.val = 0.
    comp.center.fixed = false
    comp.center.low = 0
    comp.center.high = Inf
    comp.fwhm.val  = 500
    comp.fwhm.low  = 10
    comp.fwhm.high = 2e3
    comp.voff.fixed = true
    return comp
end

function add_patch_functs!(source::QSO{T}, pspec::PreparedSpectrum, model::Model) where T <: T2Recipe
    # Patch parameters
    @try_patch! begin
        # model[:OIII_4959].norm = model[:OIII_5007].norm / 3
        model[:OIII_4959].voff = model[:OIII_5007].voff
    end
    @try_patch! begin
        model[:OIII_5007_bw].voff += model[:OIII_5007].voff
        model[:OIII_5007_bw].fwhm += model[:OIII_5007].fwhm
        model[:OIII_5007_bw].norm += model[:OIII_5007].norm # is this correct way round?
    end
    @try_patch! begin
        # model[:OI_6300].norm = model[:OI_6364].norm / 3
        model[:OI_6300].voff = model[:OI_6364].voff
    end
    @try_patch! begin
        # model[:NII_6549].norm = model[:NII_6583].norm / 3
        model[:NII_6549].voff = model[:NII_6583].voff
    end
    @try_patch! begin
        # model[:SII_6716].norm = model[:SII_6731].norm / 1.5
        model[:SII_6716].voff = model[:SII_6731].voff
    end
end


struct Fσ end
struct FGσ end

# type for doing a SE expt
mutable struct ParamExpt{T}
#	paTD::Data.P_misfit #  to measure data misfit
#	acqgeom::Acquisition.Geom # mainly positions of receivers are used from here, for the source see P
	σ::Matrix{T} # see Niels et al.
	Q::Matrix{T} # Q*k/η (see Niels et al.)
	P::Array{T,3} # pressure snapshots (B*P) from seismic modeling
	LP::Array{T,3} # ∇⋅([Q*k/η](x,z)∇B*P)
	data::Array{T,2}
	data_obs::Array{T,2}
	data_misfit::Array{T,1}
#	datP::Data.TD
#	datLP::Data.TD
	ψ::Vector{T} # state variable, we are going to record this state data=ACQ*ψ
	ψ0::Vector{T} # background state variable, only used for born modelling
	adjsrc::Vector{T}
	adjψ::Vector{T} # adjoint state variable
	adjψ2::Vector{T} # some storage of size adjψ
	paQ::Param{T} # Param for each snapshot
	paσ::Param{T} # Param for each snapshot
	paσ0::Param{T} # Param for each snapshot, only used for Born modelling
	tgrid::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}} # tgrid of snapshots
	mgrid::Vector{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}} # model grid of snapshots
	ACQ::SparseMatrixCSC{T,Int64}
	g::Matrix{T}
	gtemp::Vector{T}
end

"""
some dummy ACQ, use `GeoPhyInv.Acquisition.ACQmat` later
"""
function testACQ(nz,nx,nr)

	ACQ=spzeros(nr,nz*nx)
	for i in 1:2:nr*nz*nx
		ACQ[i]=randn()
	end
	return ACQ
end

# allocate and forward modelling in true model
function ParamExpt(snaps, tgrid, mgrid,  Qv, k, η, σ, ACQmat=nothing; σobs=nothing, Qobs=nothing)
	nz=length(mgrid[1])
	nx=length(mgrid[2])
	nt=length(tgrid)
	(size(snaps)≠(nz,nx,nt)) && error("tgrid, mgrid and snaps inconsistent")
	(size(Qv)≠(nz,nx)) && error("tgrid, mgrid and Qv inconsistent")
	(size(k)≠(nz,nx)) && error("tgrid, mgrid and k inconsistent")
	(size(η)≠(nz,nx)) && error("tgrid, mgrid and η inconsistent")
	(size(σ)≠(nz,nx)) && error("tgrid, mgrid and σ inconsistent")

	LP=zero(snaps);
	ψ=zeros(nz*nx);
	ψ0=zeros(nz*nx);

	adjsrc=zeros(nz*nx);
	adjψ=zeros(nz*nx+1);
	adjψ2=zeros(nz*nx+1);

	Q= k .* Qv ./ η # combine all these

#	dobs=Data.TD_zeros([:P],tgrid,acqgeom)
	#Random.randn!(dobs) # dummy dobs, update later
#	paTD=Data.P_misfit(Data.TD_zeros([:P],tgrid,acqgeom),dobs);

	paQ=Param(mgrid, Q)
	paσ=Param(mgrid, σ)
	paσ0=Param(mgrid, σ)

	if(ACQmat===nothing)
		ACQ=testACQ(nz,nx,5)
	else
		ACQ=ACQmat
	end
	data=zeros(nt,size(ACQ,1))
	data_obs=randn(nt,size(ACQ,1))
	data_misfit=randn(size(ACQ,1))

	pa=ParamExpt(#paTD,
	      #acqgeom,
	      σ, Q, snaps,  LP, #
	      data,
	      data_obs,
	      data_misfit,
	      ψ,ψ0,
	      adjsrc,
	      adjψ,
	      adjψ2,
	     # ssnaps,
	     # data,
	     #datP, datLP,
	     #ψ,
	     paQ, paσ, paσ0,
	     tgrid, mgrid,
	     ACQ,
	    zero(σ),zeros(length(σ)))

	# record P and LP
#	record!(pa.datP, pa.P, pa.mgrid)
#	record!(pa.datLP, pa.LP, pa.mgrid)

	#
	if(!(σobs===nothing) && !(Qobs===nothing))
		updateLP!(pa, Qobs)
		update_observed_data!(pa, σobs)
	end
	return pa
end

#=
# given snapshots, record a data obj TD
function record!(data::Data.TD, snaps, mgrid)
	ifield=1;
	iss=1;
	nr=data.acqgeom.nr
	dat=data.d[iss,ifield]
	for ir = 1:nr[iss]
		din=view(dat,:,ir)
		irx=argmin(abs.(mgrid[2].-data.acqgeom.rx[iss][ir]))
		irz=argmin(abs.(mgrid[1].-data.acqgeom.rz[iss][ir]))
		for it in 1:length(data.tgrid)
			din[it]=snaps[irz,irx,it]
		end
	end
end
=#

# generate LP from P, need to optimize this routine, has a loop over snapshots
function updateLP!(pa::ParamExpt, Q=pa.Q)
	updateA!(pa.paQ, Q)
	@showprogress 10 "time loop LP\t" for it in 1:length(pa.tgrid)
		snap_in=view(pa.P,:,:,it)
		snap_out=view(pa.LP,:,:,it)
		applyA!(snap_out, snap_in, pa.paQ; A=pa.paQ.A)
	end
	return nothing
end

function mod!(pa::ParamExpt)
	updateLP!(pa)
	mod!(pa, pa.σ, Fσ())
end

# forward modeling to generate ψ
# update psi using LP, has a loop over snapshots
# most expensive routine
function mod!(pa::ParamExpt, σ, mode)
	updateA!(pa.paσ, σ)
	qrA=factorize(pa.paσ.A)
	qrAt=factorize(pa.paσ.At)
	fill!(pa.g, 0.0)
	@showprogress 10 "time loop ψ\t" for it in 1:length(pa.tgrid)
		snap_in=view(pa.LP,:,:,it)
		forwψ!(pa.ψ,snap_in,pa.paσ,qrA)

		# record
		dat_slice=view(pa.data,it,:)
		mul!(dat_slice,pa.ACQ,pa.ψ) # record

		# wanna do adjoint modeling? depends on mode
		dat_slice_obs=view(pa.data_obs,it,:)
		adjψ!(pa.adjψ, pa.adjψ2, pa.ψ,
		      pa.adjsrc, pa.g, pa.gtemp,
		      pa.ACQ, pa.data_misfit, dat_slice_obs, pa.paσ, qrAt, mode)
	end
	return nothing
end

function forwψ!(ψ, src, pa::Param,qrA)
	applyinvA!(ψ, src, pa; A=qrA)
end
function adjψ!(adjψ, adjψ2, ψ, adjsrc, g, gtemp, ACQ, data_misfit, data_obs, pa, qrAt,   ::Fσ)
end
function adjψ!(adjψ, adjψ2, ψ, adjsrc, g, gtemp, ACQ, data_misfit, data_obs, pa, qrAt,  ::FGσ)

	mul!(data_misfit,ACQ,ψ)
	for i in eachindex(data_misfit)
		data_misfit[i] -= data_obs[i]
	end
	rmul!(data_misfit,-2.0)

	adjψ_core!(adjψ, adjψ2, ψ, adjsrc, g, gtemp, ACQ, data_misfit, pa, qrAt)

end
function adjψ_core!(adjψ, adjψ2, ψ, adjsrc, g, gtemp, ACQ, data_misfit, pa, qrAt)

	mul!(adjsrc, transpose(ACQ), data_misfit)

	applyinvAt!(adjψ, adjsrc, pa, At=qrAt)

	abT_T_dX!(gtemp, ψ, adjψ, adjψ2, pa.dAdx)
	#= requires a lot of memory (use only for testing)
	mul!(ψψ, adjψ, transpose(ψ))
	copyto!(ψψvec,ψψ)
	mul!(gtemp, pa.dAdx', ψψvec)
	=#

	for i in eachindex(gtemp)
		g[i]+=gtemp[i]
	end

end

# imaging condition
function abT_T_dX!(g, ψ, adjψ, adjψ2, A)
	n=length(ψ)
	fill!(g, 0.0)
	for i in 1:n
		AA=A[i]
		mul!(adjψ2,AA,ψ)
		g[i] = dot(adjψ,adjψ2)
		# do everything by hand, was slow...
		#for k in 1:n
		#	for j in 1:n+1
		#		g[i] += ψ[k]*adjψ[j]*A[j+(k-1)*(n+1),i]
		#	end
		#end
	end
end

function func(x,pa::ParamExpt)
	mod!(pa, x, Fσ())
	f=sum(abs2,pa.data.-pa.data_obs)
	return f
end

function grad(x,pa::ParamExpt)
	mod!(pa, x, FGσ())
	return paE.g
end

# do forward modeling to generate observed data in paTD
function update_observed_data!(pa::ParamExpt, σobs)
	# save sigma
	σsave=copy(pa.σ)
	mod!(pa, σobs, Fσ())
	copyto!(pa.data_obs, pa.data)
	fill!(pa.data, 0.0) # clear data
	# put sigma back
	copyto!(pa.σ, σsave)
	# dummy modeling, just to remove traces of σobs
	mod!(pa, σobs, Fσ())
end


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md

#using PyPlot
#using JuliaFEM
#using JuliaFEM.MaterialModels: stiffnessTensor, calculate_stress, State
#using JuliaFEM.MaterialModels: stiffnessTensorPlaneStress
using Test

#=
function test_von_mises_3D_basic()

    steps = 1000
    strain_max = 0.003
    num_cycles = 3
    E = 200.0e3
    nu =  0.3
    ν = 0.3
    nu = 0.3
    C = E/((1.0+nu)*(1.0-2.0*nu)) * [
        1.0-nu nu nu 0.0 0.0 0.0
        nu 1.0-nu nu 0.0 0.0 0.0
        nu nu 1.0-nu 0.0 0.0 0.0
        0.0 0.0 0.0 0.5-nu 0.0 0.0
        0.0 0.0 0.0 0.0 0.5-nu 0.0
        0.0 0.0 0.0 0.0 0.0 0.5-nu]

    strain_tot = zeros(Float64, (steps, 6))
    strain_tot2 = zeros(Float64, (steps, 6))
    strain_tot3 = zeros(Float64, (steps, 6))

    # Adding only strain in x-axis and counting for the poisson effect
    strain_tot[:, 1] = strain_max * sin(2 * pi * linspace(0, num_cycles, steps))
    strain_tot[:, 2] = strain_max * sin(2 * pi * linspace(0, num_cycles, steps)).*-ν
    strain_tot[:, 3] = strain_max * sin(2 * pi * linspace(0, num_cycles, steps)).*-ν
    strain_tot[:, 4] = strain_max / 10 * sin(2 * pi * linspace(0, num_cycles, steps))

    strain_last = zeros(Float64, (6))
    strain_p = zeros(Float64, (6))
    stress = zeros(Float64, (6, 1))
    stress_y =  200.0
    ss = Float64[]
    ee = Float64[]

    eig_stress = zeros(Float64, (3, 3))
    eig_vals = zeros(Float64, (steps, 3))

    function fill_tensor(a, b)
        a[1, 1] = b[1]
        a[2, 2] = b[2]
        a[3, 3] = b[3]

        a[1, 2] = b[6]
        a[1, 3] = b[5]
        a[2, 3] = b[4]

        a[2, 1] = b[6]
        a[3, 1] = b[5]
        a[3, 2] = b[4]
    end

    @info("Starting calculation")
    tic()
    params = Dict("yield_stress" => stress_y)
    stress_new = zeros(Float64, 6)
    stress_last = zeros(Float64, 6)
    strain = zeros(Float64, 6)
    Dtan = zeros(6,6)
    #for i=1:steps
    #    strain_new = reshape(strain_tot[i, :, :], (6, 1))
    #    dstrain = strain_new - strain
    #    JuliaFEM.plastic_von_mises!(stress_new, stress_last, dstrain, C, params, Dtan, Val{:type_3d})
    #    strain[:] = vec(strain_new)[:]
    #    push!(ss, stress[1])
    #    push!(ee, strain[1])
    #    fill_tensor(eig_stress, stress_new)
    #    eig_vals[i, :] = sort(eigvals(eig_stress))
    #    stress_last[:] = stress_new[:]
    #end

    toc()
    # ================ Plotting =================== #
    n(θ, ϕ) = [sin(θ)*cos(ϕ)
               sin(θ)*sin(ϕ)
               cos(θ)]
    m(θ, ϕ, χ) = [-sin(ϕ)*cos(χ)-cos(θ)*cos(ϕ)*sin(χ)
                   cos(ϕ)*cos(χ)-cos(θ)*sin(ϕ)*sin(χ)
                   sin(θ)*sin(χ)]

    w = [sqrt(2/3) * 200 * m(54.735 * pi / 180, 45 * pi/180, x) for x=0:0.15:(2*pi+0.1)]
    base_vec =  [1 1 1] / sqrt(3)

    for i=-5:5
        tt = [w[x] + vec(base_vec) + 50 * i for x=1:length(w)]
        x = map(x->tt[x][1], collect(1:length(w)))
        y = map(x->tt[x][2], collect(1:length(w)))
        z = map(x->tt[x][3], collect(1:length(w)))
        plot3D(x, y, z, color="blue")
    end

    tt = [w[x] + vec(base_vec) + 50 * -5 for x=1:length(w)]
    x_start = map(x->tt[x][1], collect(1:length(w)))[1:5:end]
    y_start = map(x->tt[x][2], collect(1:length(w)))[1:5:end]
    z_start = map(x->tt[x][3], collect(1:length(w)))[1:5:end]

    tt = [w[x] + vec(base_vec) + 50 * 5 for x=1:length(w)]
    x_end = map(x->tt[x][1], collect(1:length(w)))[1:5:end]
    y_end = map(x->tt[x][2], collect(1:length(w)))[1:5:end]
    z_end = map(x->tt[x][3], collect(1:length(w)))[1:5:end]

    for i=1:length(x_start)
        x = [x_start[i], x_end[i]]
        y = [y_start[i], y_end[i]]
        z = [z_start[i], z_end[i]]
        plot3D(x, y, z, color="blue")
    end

    @info("Calculation finished")
    # plot3D(ee, ss)

    # plot the surface
    xx = zeros(10, 10)
    yy = zeros(10, 10)

    for i=1:10
        for j=1:10
            xx[i, j] = (i - 5) * 100
            yy[i, j] = (j - 5) * 100
        end
    end

    # calculate corresponding z
    z = zeros(10, 10)
    for i=1:10
        for j=1:10
            z[i, j] = 1
        end
    end

    # ==================================================================
    # plot the surface
    plot_surface(xx, yy, z, color="blue")

    stress_y =  200.0

    function vm_upper(a, c)
        vals = f(a[1], a[2], c)
        vm(vals[1], vals[2], 200)
    end
    vm(a,b) = sqrt(a^2 - a*b + b^2) - stress_y
    f(m,c) = [600*cos(c) 600*sin(c)].*m
    x_vals = []
    max_iter = 100
    y_vals = []
    for i=0:0.1:(2*pi+0.3)
        wf(x) = f(x, i)
        t = 0.01
        step = 2
        merkki = -1
        s11, s22 = wf(t)
        ii = 0
        while (abs(vm(s11, s22)) > 1e-7) && ii < max_iter
            val = vm(s11, s22)
            if sign(val) != merkki
                merkki *= -1
                step *= -0.5
            end
            t += step
            s11, s22 = wf(t)
            ii += 1
        end
        push!(x_vals, s11)
        push!(y_vals, s22)
    end
    plot(x_vals, y_vals, zeros(length(y_vals)), color="yellow")
    axis("equal")
    # ==================================================================

    plot3D(eig_vals[:, 1], eig_vals[:, 2], eig_vals[:, 3], color="red")
    PyPlot.title("Stress path and von Mises yield surface")
    PyPlot.xlabel("Eig Stress 1")
    PyPlot.ylabel("Eig Stress 2")
    PyPlot.zlabel("Eig Stress 3")
    PyPlot.grid()
    PyPlot.show()

end

function test_von_mises_planestress_basic()

    steps = 1000
    strain_max = 0.004
    num_cycles = 1.
    E = 200000.
    nu = 0.3
    ν = 0.3
    C = E/((1+nu)*(1-2*nu)) .* [
        1-nu   nu 0
        nu   1-nu 0
        0    0    (1-2*nu)/2]

    strain_tot = zeros(Float64, (steps, 3))

    # Adding only strain in x-axis and counting for the poisson effect
    strain_tot[:, 1] = strain_max * sin(2 * pi * linspace(0, num_cycles, steps))
    strain_tot[:, 2] = strain_max * sin(2 * pi * linspace(0, num_cycles, steps)).*-ν
    strain_tot[:, 3] = strain_max * sin(2 * pi * linspace(0, num_cycles, steps)).*-ν

    strain_last = zeros(Float64, (3))
    strain_p = zeros(Float64, (3))
    stress = zeros(Float64, (3, 1))
    stress_y =  400
    ss = Float64[]
    ee = Float64[]

    ss2 = Float64[]
    ee2 = Float64[]

    eig_stress = zeros(Float64, (3, 3))
    eig_vals = zeros(Float64, (steps, 3))

    @info("Starting calculation")
    tic()

    stress_new = zeros(Float64, 3)
    stress_last = zeros(Float64, 3)
    strain = zeros(Float64, 3)
    strain_last = zeros(Float64, 3)
    params = Dict("yield_stress" => stress_y)
    #Dtan = C
    Dtan = zeros(3,3)
    for i=1:steps
        println("last stress: ", round(stress_last, 2))
        strain_new = vec(strain_tot[i, :, :])
        dstrain = strain_new - strain
        println("analytical stress: ", round((C * strain_new)', 2))

        JuliaFEM.plastic_von_mises!(stress_new, stress_last, dstrain, C, params, Dtan, Val{:type_2d})
        strain[:] = vec(strain_new)[:]
        s1, s2, t12  = stress_new
        se1 = (s1 + s2)/2 + sqrt(((s1 - s2)/2)^2 + t12^2)
        se2 = (s1 + s2)/2 - sqrt(((s1 - s2)/2)^2 + t12^2)
        push!(ss, se1)
        push!(ee, se2)
        stress_last[:] = stress_new[:]
    end
    toc()

    function vm_upper(a, c)
        vals = f(a[1], a[2], c)
        vm(vals[1], vals[2], 200)
    end
    vm(a,b) = sqrt(a^2 - a*b + b^2) - stress_y
    f(m,c) = [600*cos(c) 600*sin(c)].*m
    x_vals = []
    max_iter = 100
    y_vals = []
    for i=0:0.1:(2*pi+0.3)
        wf(x) = f(x, i)
        t = 0.01
        step = 2
        merkki = -1
        s11, s22 = wf(t)
        ii = 0
        while (abs(vm(s11, s22)) > 1e-7) && ii < max_iter
            val = vm(s11, s22)
            if sign(val) != merkki
                merkki *= -1
                step *= -0.5
            end
            t += step
            s11, s22 = wf(t)
            ii += 1
        end
        push!(x_vals, s11)
        push!(y_vals, s22)
    end

    plot(x_vals, y_vals)
    plot(ee, ss)
    show()
end

test_von_mises_3D_basic()

# test_von_mises_planestress_basic()

=#


# Rejection SSA Method (RSSA), implementation following:
# Marchetti, Priami and Thanh - Simulation Algorithms for Computational Systems Biology
# Note, this implementation **assumes** jump rate functions are monotone
# functions of the current population sizes (i.e. u)
# requires vartojumps_map and fluct_rates as JumpProblem keywords

mutable struct RSSAJumpAggregation{T,T2,S,F1,F2,RNG,VJMAP,JVMAP,BD,T2V} <: AbstractSSAJumpAggregator
    next_jump::Int
    next_jump_time::T
    end_time::T
    cur_rate_low::Vector{T}
    cur_rate_high::Vector{T}
    sum_rate::T
    cur_u_bnds::Matrix{T2}
    ma_jumps::S
    rates::F1
    affects!::F2
    save_positions::Tuple{Bool,Bool}
    rng::RNG
    vartojumps_map::VJMAP
    jumptovars_map::JVMAP
    bracket_data::BD
    ulow::T2V
    uhigh::T2V
  end

  function RSSAJumpAggregation(nj::Int, njt::T, et::T, crs::Vector{T}, sr::T,
                                        maj::S, rs::F1, affs!::F2, sps::Tuple{Bool,Bool},
                                        rng::RNG; u::U, vartojumps_map=nothing, jumptovars_map=nothing,
                                        bracket_data=nothing, kwargs...) where {T,S,F1,F2,RNG,U}
    # a dependency graph is needed and must be provided if there are constant rate jumps
    if vartojumps_map === nothing
        error("To use the RSSA algorithm a map from variables to depedent jumps must be supplied.")
    else
        vtoj_map = vartojumps_map
    end

    if jumptovars_map === nothing
        error("To use the RSSA algorithm a map from jumps to dependent variables must be supplied.")
    else
        jtov_map = jumptovars_map
    end

    # vectors to store bracketing intervals for jump rates
    crl_bnds = similar(crs)
    crh_bnds = similar(crs)

    # a bracket data structure is needed for updating species populations
    bd = (bracket_data === nothing) ? BracketData{T,eltype(U)}() : bracket_data

    # matrix to store bracketing interval for species and the relative interval width
    # first row is Xlow, second is Xhigh
    cs_bnds = Matrix{eltype(U)}(undef, 2, length(u))
    ulow    = @view cs_bnds[1,:]
    uhigh   = @view cs_bnds[2,:]

    RSSAJumpAggregation{T,eltype(U),S,F1,F2,RNG,typeof(vtoj_map),typeof(jtov_map),typeof(bd),typeof(ulow)}(
                        nj, njt, et, crl_bnds, crh_bnds, sr, cs_bnds, maj, rs,
                        affs!, sps, rng, vtoj_map, jtov_map, bd, ulow, uhigh)
end

########### The following routines should be templates for all SSAs ###########

# condition for jump to occur
@inline function (p::RSSAJumpAggregation)(u, t, integrator)
    p.next_jump_time == t
end

# executing jump at the next jump time
function (p::RSSAJumpAggregation)(integrator)
    execute_jumps!(p, integrator, integrator.u, integrator.p, integrator.t)
    generate_jumps!(p, integrator, integrator.u, integrator.p, integrator.t)
    register_next_jump_time!(integrator, p, integrator.t)
    nothing
end

# setting up a new simulation
function (p::RSSAJumpAggregation)(dj, u, t, integrator) # initialize
    initialize!(p, integrator, u, integrator.p, t)
    register_next_jump_time!(integrator, p, t)
    nothing
end

############################# Required Functions ##############################

# creating the JumpAggregation structure (function wrapper-based constant jumps)
function aggregate(aggregator::RSSA, u, p, t, end_time, constant_jumps,
                   ma_jumps, save_positions, rng; kwargs...)

    # handle constant jumps using function wrappers
    rates, affects! = get_jump_info_fwrappers(u, p, t, constant_jumps)

    build_jump_aggregation(RSSAJumpAggregation, u, p, t, end_time, ma_jumps,
                           rates, affects!, save_positions, rng; u=u,
                           kwargs...)
end

# set up a new simulation and calculate the first jump / jump time
function initialize!(p::RSSAJumpAggregation, integrator, u, params, t)
    set_bracketing!(p, u, params, t)
    generate_jumps!(p, integrator, u, params, t)
    nothing
end

# execute one jump, changing the system state
function execute_jumps!(p::RSSAJumpAggregation, integrator, u, params, t)
    # execute jump
    u = update_state!(p, integrator, u)
    update_rates!(p, u, params, t)
    nothing
end

# calculate the next jump / jump time
@fastmath function generate_jumps!(p::RSSAJumpAggregation, integrator, u, params, t)
    sum_rate = p.sum_rate
    # if no more events possible there is nothing to do
    if nomorejumps!(p, sum_rate)
        return nothing
    end
    # next jump type
    @unpack ma_jumps, rates, cur_rate_high, cur_rate_low, rng = p
    #rerl        = one(sum_rate)
    rerl        = zero(sum_rate)

    r      = rand(rng) * sum_rate
    jidx   = linear_search(cur_rate_high, r)
    rerl  += randexp(rng)
    @inbounds while rejectrx(ma_jumps, rates, cur_rate_high, cur_rate_low, rng, u, jidx, params, t)
        # sample candidate reaction
        r      = rand(rng) * sum_rate
        jidx   = linear_search(cur_rate_high, r)
        #rerl *= rand(p.rng)
        rerl += randexp(rng)
    end
    p.next_jump = jidx

    #p.next_jump_time = t + (-one(sum_rate) / sum_rate) * log(rerl)
    p.next_jump_time = t + rerl / sum_rate

    nothing
end

######################## SSA specific helper routines #########################

"Update rates"
@inline function update_rates!(p::RSSAJumpAggregation, u, params, t)
    # update bracketing intervals
    ubnds       = p.cur_u_bnds
    sum_rate    = p.sum_rate
    crhigh      = p.cur_rate_high

    @inbounds for uidx in p.jumptovars_map[p.next_jump]
        uval = u[uidx]

        # if new u value is outside the bracketing interval
        if uval == zero(uval) || uval < ubnds[1,uidx] || uval > ubnds[2,uidx]
            # update u bracketing interval
            ubnds[1,uidx], ubnds[2,uidx] = get_spec_brackets(p.bracket_data, uidx, uval)

            # for each dependent jump, update jump rate brackets
            for jidx in p.vartojumps_map[uidx]
                sum_rate -= crhigh[jidx]
                p.cur_rate_low[jidx], crhigh[jidx] = get_jump_brackets(jidx, p, params, t)
                sum_rate += crhigh[jidx]
            end
        end
    end
    p.sum_rate = sum_rate
end


#################################
"""
    fgmax = loadfgmax(loaddir::AbstractString, fg::VisClaw.FixedGrid; nval_save::Integer=fg.nval)

Function: fgmaxXXXX.txt reader
"""
function loadfgmax(loaddir::AbstractString, fg::VisClaw.FixedGrid; nval_save::Integer=fg.nval)
    ## check nval_save
    nval_save > fg.nval && (nval_save=fg.nval)

    ## fgmaxXXXX.txt
    filename = "fgmax"*@sprintf("%04d",fg.id)*".txt"
    ## load
    dat = readdlm(joinpath(loaddir, filename), Float64)
    dat[dat.<-1e10] .= NaN

    ncol = 4 + 2(fg.nval) + 1
    ## assign
    if fg.style == 0 || fg.style == 1
        topo = dat[:,4]
        D = dat[:,5]
        tD = dat[:,5+fg.nval]
        tarrival = dat[:,end]
        if fg.nval >= 2
            v = dat[:,6]
            tv = dat[:,6+fg.nval]
        end
        if fg.nval >= 5
            M = dat[:,7]
            tM = dat[:,7+fg.nval]
            Mflux = dat[:,8]
            tMflux = dat[:,8+fg.nval]
            hmin = dat[:,9]
            thmin = dat[:,9+fg.nval]
        end
    elseif fg.style == 2 || fg.style == 3
        valall = permutedims(reshape(dat, (fg.nx, fg.ny, ncol)), [2 1 3])
        topo = valall[:,:,4]
        D = valall[:,:,5]
        tD = valall[:,:,5+fg.nval]
        tarrival = valall[:,:,end]
        if fg.nval >= 2
            v = valall[:,:,6]
            tv = valall[:,:,6+fg.nval]
        end
        if fg.nval >= 5
            M = valall[:,:,7]
            tM = valall[:,:,7+fg.nval]
            Mflux = valall[:,:,8]
            tMflux = valall[:,:,8+fg.nval]
            hmin = valall[:,:,9]
            thmin = valall[:,:,9+fg.nval]
        end
    elseif fg.style == 4
        indc = [[fg.flag[i][1] fg.flag[i][2]] for i=1:fg.npts]
        indc = vcat(indc...)
        ind = sortperm(indc[:,1])

        topo = dat[ind,4]
        D = dat[ind,5]
        tD = dat[ind,5+fg.nval]
        tarrival = dat[ind,end]
        if fg.nval >= 2
            v = dat[ind,6]
            tv = dat[ind,6+fg.nval]
        end
        if fg.nval >= 5
            M = dat[ind,7]
            tM = dat[ind,7+fg.nval]
            Mflux = dat[ind,8]
            tMflux = dat[ind,8+fg.nval]
            hmin = dat[ind,9]
            thmin = dat[ind,9+fg.nval]
        end
    end

    if nval_save == 1;     fgmax = VisClaw.FGmax(topo,D,tD,tarrival)
    elseif nval_save == 2; fgmax = VisClaw.FGmax(topo,D,v,tD,tv,tarrival)
    elseif nval_save == 5; fgmax = VisClaw.FGmax(topo,D,v,M,Mflux,hmin,tD,tv,tM,tMflux,thmin,tarrival)
    end

    # return
    return fgmax

end
#################################


#posit_cgen_helpers.jl.  A bunch of helper functions that make the cgen
#process smoother.

#symbols for each number
const sym = Dict(1 => :a, 2 => :b, 3 => :c, 4 => :d)

float_for_posit_size(n) = (n < 32) ? 32 : 64
c_ftype(n) = Dict(32 => "float", 64 => "double")[n]

#C internal use conversion functions
to_f(n, es, v) = "p$(n)e$(es)_to_f($v)"
to_p(n, es, v) = "f_to_p$(n)e$(es)($v)"

#C datatype names
p(n,es) = "p$(n)e$(es)_t"
#C function names
p(n,es,op) = "p$(n)e$(es)_$(op)"
#C++  class names
c(n,es) = "P$(n)e$(es)"

function c_literal(n::Unsigned)
  if isa(n, UInt8)
    "((uint8_t) 0x$(hex(n,2)))"
  elseif isa(n, UInt16)
    "((uint16_t) 0x$(hex(n,4)))"
  elseif isa(n, UInt32)
    "0x$(hex(n,8))L"
  elseif isa(n, UInt64)
    "0x$(hex(n,16))LL"
  end
end

const bits_to_UInt = Dict(8=>UInt8, 16=>UInt16, 32=>UInt32, 64=>UInt64)

top_bits(n) = one(bits_to_UInt[n]) << (n - 1)
zero_bits(n) = zero(bits_to_UInt[n])


include("./simulate.jl")

# Compile
params = Params(nsteps=10)
simulate(params)

# 100k simulation
params = Params(
    nsteps=200,
    x0 = getcoor("./ne100k_initial.pdb"),
    cutoff = 12.,
    box = Box([ 99.88, 99.88, 99.88 ], 12., lcell=2)
)
@time simulate(params)


@static if isdefined(Turing, :CmdStan)
  function DualAveraging(spl::Sampler{<:AdaptiveHamiltonian}, adapt_conf::CmdStan.Adapt, ϵ::Real)
      # Hyper parameters for dual averaging
      γ = adapt_conf.gamma
      t_0 = adapt_conf.t0
      κ = adapt_conf.kappa
      δ = adapt_conf.delta
      return DualAveraging(γ, t_0, κ, δ, DAState(ϵ))
  end
end

@static if isdefined(Turing, :CmdStan)
    function get_threephase_params(adapt_conf::CmdStan.Adapt)
        init_buffer = adapt_conf.init_buffer
        term_buffer = adapt_conf.term_buffer
        window_size = adapt_conf.window
        next_window = init_buffer + window_size - 1
        return init_buffer, term_buffer, window_size, next_window
    end
end


# using Dates

@testset "Are.jl" begin
    filename = "data\\test.are"
    dc = DDR2import.Are.read(filename)
    @test dc["AZ"].nb_point == 48
    @test dc["AZ"].bottom_fl == 0.0
    @test dc["AZ"].top_fl == 0.0
    @test dc["AZ"].surface == 0.0
    @test dc["AZ"].sector_num == 0.0
    @test dc["AZ"].points[5,1] == 45.0
    @test dc["AZ"].points[4,2] == -40.0
    @test dc["AZ"].box[1,1] ≈ 45.00775771902942 atol = 0.00001
    #LAST TEST
    @test dc["EB"].nb_point == 550
    @test dc["EB"].points[550,1] == 51.5
end


abstract type AbstractPeriodicOrbitProblem end

# Periodic orbit computations by finite differences
abstract type AbstractPOFDProblem <: AbstractPeriodicOrbitProblem end

abstract type AbstractShootingProblem <: AbstractPeriodicOrbitProblem end

# get the number of time slices / sections
@inline getM(pb::AbstractPeriodicOrbitProblem) = pb.M

# update a problem with arguments
function update(prob::AbstractPeriodicOrbitProblem, F, dF, hopfpt, ζr, M, orbitguess_a, period) end

####################################################################################################
# if we use the same code as for newton (see below) in continuation, it is difficult to tell the eigensolver not to use the jacobian but instead the monodromy matrix. So we have to use a dedicated composite type for the jacobian to handle this case.

struct ShootingJacobian{Tpb <: AbstractShootingProblem, Torbitguess, Tp}
	pb::Tpb
	x::Torbitguess
	par::Tp
end

# evaluation of the jacobian
(shjac::ShootingJacobian)(dx) = shjac.pb(shjac.x, shjac.par, dx)

####################################################################################################
# newton wrapper
"""
$(SIGNATURES)

This is the Newton-Krylov Solver for computing a periodic orbit using (Standard / Poincaré) Shooting method.
Note that the linear solver has to be apropriately set up.

# Arguments

Similar as [`newton`](@ref) except that `prob` is either a [`ShootingProblem`](@ref) or a [`PoincareShootingProblem`](@ref). These two problems have specific options to be tuned, we refer to their link for more information and to the tutorials.

# Output:
- solution
- history of residuals
- flag of convergence
- number of iterations
"""
function newton(prob::AbstractShootingProblem, orbitguess, par, options::NewtonPar; kwargs...)
	return newton(prob,
			(x, p) -> (dx -> prob(x, p, dx)),
			orbitguess, par, options; kwargs...)
end

"""
$(SIGNATURES)

This is the deflated Newton-Krylov Solver for computing a periodic orbit using a (Standard / Poincaré) Shooting method.

# Arguments

Similar as [`newton`](@ref) except that `prob` is either a [`ShootingProblem`](@ref) or a [`PoincareShootingProblem`](@ref).

# Output:
- solution
- history of residuals
- flag of convergence
- number of iterations
"""
function newton(prob::AbstractShootingProblem, orbitguess, options::NewtonPar, defOp::DeflationOperator; kwargs...)
	return newton(prob,
			(x, p) -> (dx -> prob(x, p, dx)),
			orbitguess, par,
			options, defOp; kwargs...)
end

####################################################################################################
# Continuation for shooting problems

"""
$(SIGNATURES)

This is the continuation method for computing a periodic orbit using a (Standard / Poincaré) Shooting method.

# Arguments

Similar as [`continuation`](@ref) except that `prob` is either a [`ShootingProblem`](@ref) or a [`PoincareShootingProblem`](@ref).

- `printPeriod` boolean to print the period of the solution. This is useful for `prob::PoincareShootingProblem` as this information is not easily available.
"""

function continuation(prob::AbstractShootingProblem, orbitguess, par, lens::Lens, _contParams::ContinuationPar, linearAlgo::AbstractBorderedLinearSolver; printPeriod = true, kwargs...)

	contParams = check(_contParams)

	options = contParams.newtonOptions

	if contParams.computeEigenValues
		contParams = @set contParams.newtonOptions.eigsolver = FloquetQaDShooting(contParams.newtonOptions.eigsolver)
	end

	if (prob isa PoincareShootingProblem)

		if printPeriod
			printSolutionPS = (x, p) -> getPeriod(prob, x, set(par, lens, p))
			return continuation(
				prob,
				(x, p) -> ShootingJacobian(prob, x, p),
				orbitguess, par, lens,
				contParams, linearAlgo;
				printSolution = printSolutionPS,
				kwargs...)
		end
	end

	return continuation(
		prob,
		(x, p) -> ShootingJacobian(prob, x, p),
		orbitguess, par, lens,
		contParams, linearAlgo;
		kwargs...)
end

"""
$(SIGNATURES)

This is the continuation routine for computing a periodic orbit using a (Standard / Poincaré) Shooting method.

# Arguments

Similar as [`continuation`](@ref) except that `prob` is either a [`ShootingProblem`](@ref) or a [`PoincareShootingProblem`](@ref).

- `printPeriod` boolean to print the period of the solution. This is useful for `prob::PoincareShootingProblem` as this information is not easily available.
"""
function continuation(prob::AbstractShootingProblem, orbitguess, par, lens::Lens, contParams::ContinuationPar; linearAlgo = BorderingBLS(), printPeriod = true, kwargs...)
	_linearAlgo = @set linearAlgo.solver = contParams.newtonOptions.linsolver
	return continuation(prob, orbitguess, par, lens, contParams, _linearAlgo; printPeriod = printPeriod, kwargs...)
end

####################################################################################################
"""
$(SIGNATURES)

Perform automatic branch switching from a Hopf bifurcation point labelled `ind_bif` in the list of the bifurcated points on a previously computed branch `br::ContResult`. It first compute a Hopf normal form.

# Arguments

- `F, dF, d2F, d3F`: function `(x,p) -> F(x,p)` and its differentials `(x,p,dx) -> d1F(x,p,dx)`, `(x,p,dx1,dx2) -> d2F(x,p,dx1,dx2)`... These are used to compute the Hopf normal form.
- `br` branch result from a call to `continuation`
- `ind_hopf` index of the bifurcation point in `br`
- `contParams` parameters for the call to `continuation`
- `prob` problem used to specify the way the periodic orbit is computed. It can be [`PeriodicOrbitTrapProblem`](@ref), [`ShootingProblem`](@ref) or [`PoincareShootingProblem`](@ref) .

# Optional arguments

- `linearPO` linear algorithm used for the computation of periodic orbits when `prob` is [`PeriodicOrbitTrapProblem`](@ref))
- `Jt` is the jacobian adjoint, used for computation of the eigen-elements of the jacobian adjoint, needed to compute the spectral projector for the Hopf normal form!!!!!! COMME NEWTON FOLD
- `δ = 1e-8` used for finite differences
- `δp` used to specify a particular guess for the parameter on the bifurcated branch which is otherwise determined by `contParams.ds`. This allows to use a step larger than `contParams.dsmax`.
- `ampfactor = 1` factor which alter the amplitude of the bifurcated solution. Useful to magnify the bifurcated solution when the bifurcated branch is very steep.
- `usedeflation = true` whether to use nonlinear deflation (see [Deflated problems](@ref)) to help finding the guess on the bifurcated branch

!!! note "Linear solver"
    You have to be carefull about the options `contParams.newtonOptions.linsolver`. In the case of Matrix-Free solver, you have to pass the right number of unknowns `N * M + 1`. Note that the options for the preconditioner are not accessible yet.

!!! tip "Jacobian tranpose"
    The adjoint of the jacobian `J` is computed internally when `Jt = nothing` by using `tranpose(J)` which works fine when `J` is an `AbstractArray`. In this case, do not pass the jacobian adjoint like `Jt = (x, p) -> transpose(d_xF(x, p))` otherwise the jacobian would be computed twice!

!!! warning "Hessian"
    The hessian of `F`, when `d2F` is not `nothing`, is computed with Finite differences.
"""
function continuation(F, dF, d2F, d3F, br::ContResult, ind_bif::Int, _contParams::ContinuationPar, prob::AbstractPeriodicOrbitProblem ; Jt = nothing, δ = 1e-8, δp = nothing, ampfactor = 1, usedeflation = false, kwargs...)
	# compute the normal form of the branch point
	verbose = get(kwargs, :verbosity, 0) > 1 ? true : false
	cb = get(kwargs, :callbackN, (args...;kwargs...)->true)

	hopfpt = hopfNormalForm(F, dF, d2F, d3F, br, ind_bif ; Jt = Jt, δ = δ, nev = _contParams.nev, verbose = verbose)

	# compute predictor for point on new branch
	ds = isnothing(δp) ? _contParams.ds : δp
	Ty = typeof(ds)
	pred = predictor(hopfpt, ds; verbose = verbose, ampfactor = Ty(ampfactor))

	verbose && printstyled(color = :green, "#"^51*
			"\n--> Start Hopf branch switching.
			\n--> Bifurcation type = ", hopfpt.type,
			"\n----> newp = ", pred.p, ", δp = ", pred.p - br.bifpoint[ind_bif].param,
			"\n----> amplitude = ", pred.amp,
			"\n----> period = ", abs(2pi/pred.ω), "\n")

	# we compute a phase so that the constraint equation
	# < u(0) − u_hopf, ψ > is satisfied, i.e. equal to zero.
	ζr = real.(hopfpt.ζ); ζi = imag.(hopfpt.ζ)
	# this phase is for POTrap problem constraint to be satisfied
	ϕ = atan(dot(ζr, ζr), dot(ζi, ζr))
	verbose && printstyled(color = :green,"----> phase ϕ = ", ϕ/pi, "⋅π\n")

	M = getM(prob)
	orbitguess_a = [pred.orbit(t - ϕ) for t in LinRange(0, 2pi ,M+1)[1:M]]

	# build the variable to hold the functional for computing PO based on finite differences
	probPO, orbitguess = update(prob, F, dF, hopfpt, ζr, M, orbitguess_a, abs(2pi/pred.ω))

	if _contParams.newtonOptions.linsolver isa GMRESIterativeSolvers
		_contParams = @set _contParams.newtonOptions.linsolver.N = length(orbitguess)
	end

	# perform continuation
	branch, u, tau = continuation(probPO, orbitguess, set(br.params, br.param_lens, pred.p), br.param_lens, _contParams; kwargs...)

	return setproperties(branch; type = :PeriodicOrbit, functional = probPO), u, tau

end


"""
    AbstractZero <: AbstractDifferential

This is zero-like differential types.
If a AD system encounter a propagator taking as input only subtypes of `AbstractZero` then
it can stop performing any AD operations, as all propagator are linear functions, and thus
the final result will be zero.

All `AbstractZero` subtypes are singleton types.
There are two of them [`Zero()`](@ref) and [`DoesNotExist()`](@ref).
"""
abstract type AbstractZero <: AbstractDifferential end
Base.iszero(::AbstractZero) = true

Base.iterate(x::AbstractZero) = (x, nothing)
Base.iterate(::AbstractZero, ::Any) = nothing

Base.Broadcast.broadcastable(x::AbstractZero) = Ref(x)
Base.Broadcast.broadcasted(::Type{T}) where T<:AbstractZero = T()

# Linear operators
Base.adjoint(z::AbstractZero) = z
Base.transpose(z::AbstractZero) = z
Base.:/(z::AbstractZero, ::Any) = z

"""
    Zero() <: AbstractZero

The additive identity for differentials.
This is basically the same as `0`.
A derivative of `Zero()`. does not propagate through the primal function.
"""
struct Zero <: AbstractZero end

extern(x::Zero) = false  # false is a strong 0. E.g. `false * NaN = 0.0`

Base.eltype(::Type{Zero}) = Zero

Base.zero(::AbstractDifferential) = Zero()
Base.zero(::Type{<:AbstractDifferential}) = Zero()

"""
    DoesNotExist() <: AbstractZero

This differential indicates that the derivative does not exist.
It is the differential for a Primal type that is not differentiable.
Such an Integer, or Boolean (when not being used as a represention of a value that normally
would be a floating point.)
The only valid way to pertube such a values is to not change it at all.
As such, `DoesNotExist` is functionally identical to `Zero()`,
but provides additional semantic information.

If you are adding this differential to a primal then something is wrong.
A optimization package making use of this might like to check for such a case.

!!! note:
    This does not indicate that the derivative it is not implemented,
    but rather that mathematically it is not defined.

This mostly shows up as the deriviative with respect to dimension, index, or size
arguments.
```
    function rrule(fill, x, len::Int)
        y = fill(x, len)
        fill_pullback(ȳ) = (NO_FIELDS, @thunk(sum(Ȳ)), DoesNotExist())
        return y, fill_pullback
    end
```
"""
struct DoesNotExist <: AbstractZero end

function extern(x::DoesNotExist)
    throw(ArgumentError("Derivative does not exit. Cannot be converted to an external type."))
end


@testset "Floats" begin

res = Parsers.xparse(Float64, "1x")

testcases = [
    (str="", x=0.0, code=(INVALID | EOF), len=0, tot=0),
    (str="-", x=0.0, code=(INVALID | EOF), len=1, tot=1),
    (str=".", x=0.0, code=(INVALID | EOF), len=1, tot=1),
    (str="1", x=1.0, code=(OK | EOF), len=1, tot=1),
    (str="+1", x=1.0, code=(OK | EOF), len=2, tot=2),
    (str="1.1", x=1.1, code=(OK | EOF), len=3, tot=3),
    (str="1.", x=1.0, code=(OK | EOF), len=2, tot=2),
    (str="-1.", x=-1.0, code=(OK | EOF), len=3, tot=3),
    (str="-1. ", x=-1.0, code=(OK | EOF), len=4, tot=4),
    (str="1.a", x=1.0, code=(OK | EOF | INVALID_DELIMITER), len=2, tot=2),
    (str="1.1.", x=1.1, code=(OK | EOF | INVALID_DELIMITER), len=3, tot=3),
    (str="1e23", x=1e23, code=(OK | EOF), len=4, tot=4),
    (str="1E23", x=1e23, code=(OK | EOF), len=4, tot=4),
    (str="1f23", x=1e23, code=(OK | EOF), len=4, tot=4),
    (str="1F23", x=1e23, code=(OK | EOF), len=4, tot=4),
    (str="428.E+03", x=428000.0, code=(OK | EOF), len=8, tot=8),
    (str="1.0e", x=0.0, code=(INVALID | EOF), len=4, tot=4),
    (str="1.0ea", x=0.0, code=(INVALID | EOF | INVALID_DELIMITER), len=5, tot=5),
    (str="100000000000000000000000", x=1e23, code=(OK | EOF), len=24, tot=24),
    (str="1e-100", x=1e-100, code=(OK | EOF), len=6, tot=6),
    (str="123456700", x=1.234567e+08, code=(OK | EOF), len=9, tot=9),
    (str="99999999999999974834176", x=9.999999999999997e+22, code=(OK | EOF), len=23, tot=23),
    (str="100000000000000000000001", x=1.0000000000000001e+23, code=(OK | EOF), len=24, tot=24),
    (str="100000000000000008388608", x=1.0000000000000001e+23, code=(OK | EOF), len=24, tot=24),
    (str="100000000000000016777215", x=1.0000000000000001e+23, code=(OK | EOF), len=24, tot=24),
    (str="100000000000000016777216", x=1.0000000000000003e+23, code=(OK | EOF), len=0, tot=0),

    (str="-1", x=-1.0, code=(OK | EOF), len=0, tot=0),
    (str="-0.1", x=-0.1, code=(OK | EOF), len=0, tot=0),
    (str="-0", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="1e-20", x=1e-20, code=(OK | EOF), len=0, tot=0),
    (str="625e-3", x=0.625, code=(OK | EOF), len=0, tot=0),

    # zeros
    (str="0", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="0e0", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0e0", x=-0.0, code=(OK | EOF), len=0, tot=0),
    (str="+0e0", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="0e-0", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0e-0", x=-0.0, code=(OK | EOF), len=0, tot=0),
    (str="+0e-0", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="0e+0", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0e+0", x=-0.0, code=(OK | EOF), len=0, tot=0),
    (str="+0e+0", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="0e+01234567890123456789", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="0.00e-01234567890123456789", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0e+01234567890123456789", x=-0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0.00e-01234567890123456789", x=-0.0, code=(OK | EOF), len=0, tot=0),
    (str="0e291", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="0e292", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="0e347", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="0e348", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0e291", x=-0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0e292", x=-0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0e347", x=-0.0, code=(OK | EOF), len=0, tot=0),
    (str="-0e348", x=-0.0, code=(OK | EOF), len=0, tot=0),

    # Infs/invalid NaNs
    (str="n", x=0.0, code=(INVALID | EOF), len=1, tot=1),
    (str="na", x=0.0, code=(INVALID | EOF), len=2, tot=2),
    (str="n", x=0.0, code=(INVALID | EOF), len=1, tot=1),
    (str="inf", x=Inf, code=(OK | EOF), len=0, tot=0),
    (str="infinity", x=Inf, code=(OK | EOF), len=0, tot=0),
    (str="-inf", x=-Inf, code=(OK | EOF), len=0, tot=0),
    (str="-Inf", x=-Inf, code=(OK | EOF), len=0, tot=0),
    (str="-infinity", x=-Inf, code=(OK | EOF), len=0, tot=0),
    (str="-INFINITY", x=-Inf, code=(OK | EOF), len=0, tot=0),
    (str="+inf", x=Inf, code=(OK | EOF), len=0, tot=0),
    (str="i", x=0.0, code=(INVALID | EOF), len=1, tot=1),
    (str="in", x=0.0, code=(INVALID | EOF), len=2, tot=2),
    (str="infi", x=Inf, code=(OK | EOF), len=1, tot=1),
    (str="infin", x=Inf, code=(OK | EOF), len=1, tot=1),
    (str="infini", x=Inf, code=(OK | EOF), len=1, tot=1),
    (str="infinit", x=Inf, code=(OK | EOF), len=1, tot=1),
    (str="i,", x=0.0, code=(INVALID | DELIMITED | INVALID_DELIMITER), len=1, tot=1),
    (str="in,", x=0.0, code=(INVALID | DELIMITED | INVALID_DELIMITER), len=2, tot=2),
    (str="infi,", x=Inf, code=(OK | DELIMITED), len=1, tot=1),
    (str="infin,", x=Inf, code=(OK | DELIMITED), len=1, tot=1),
    (str="infini,", x=Inf, code=(OK | DELIMITED), len=1, tot=1),
    (str="infinit,", x=Inf, code=(OK | DELIMITED), len=1, tot=1),

    # largest float64
    (str="1.7976931348623157e308", x=1.7976931348623157e+308, code=(OK | EOF), len=0, tot=0),
    (str="-1.7976931348623157e308", x=-1.7976931348623157e+308, code=(OK | EOF), len=0, tot=0),
    # next float64 - too large
    (str="1.7976931348623159e308", x=+Inf, code=(OK | EOF), len=0, tot=0),
    (str="-1.7976931348623159e308", x=-Inf, code=(OK | EOF), len=0, tot=0),
    # the border is ...158079
    # borderline - okay
    (str="1.7976931348623158e308", x=1.7976931348623157e+308, code=(OK | EOF), len=0, tot=0),
    (str="-1.7976931348623158e308", x=-1.7976931348623157e+308, code=(OK | EOF), len=0, tot=0),
    # borderline - too large
    (str="1.797693134862315808e308", x=+Inf, code=(OK | EOF), len=0, tot=0),
    (str="-1.797693134862315808e308", x=-Inf, code=(OK | EOF), len=0, tot=0),

    # a little too large
    (str="1e308", x=1e+308, code=(OK | EOF), len=0, tot=0),
    (str="2e308", x=+Inf, code=(OK | EOF), len=0, tot=0),
    (str="1e309", x=+Inf, code=(OK | EOF), len=0, tot=0),

    # way too large
    (str="1e310", x=+Inf, code=(OK | EOF), len=0, tot=0),
    (str="-1e310", x=-Inf, code=(OK | EOF), len=0, tot=0),
    (str="1e400", x=+Inf, code=(OK | EOF), len=0, tot=0),
    (str="-1e400", x=-Inf, code=(OK | EOF), len=0, tot=0),
    (str="1e400000", x=+Inf, code=(OK | EOF), len=0, tot=0),
    (str="-1e400000", x=-Inf, code=(OK | EOF), len=0, tot=0),

    # denormalized
    (str="1e-305", x=1e-305, code=(OK | EOF), len=0, tot=0),
    (str="1e-306", x=1e-306, code=(OK | EOF), len=0, tot=0),
    (str="1e-307", x=1e-307, code=(OK | EOF), len=0, tot=0),
    (str="1e-308", x=1e-308, code=(OK | EOF), len=0, tot=0),
    (str="1e-309", x=1e-309, code=(OK | EOF), len=0, tot=0),
    (str="1e-310", x=1e-310, code=(OK | EOF), len=0, tot=0),
    (str="1e-322", x=1e-322, code=(OK | EOF), len=0, tot=0),
    # smallest denormal
    (str="5e-324", x=5e-324, code=(OK | EOF), len=0, tot=0),
    (str="4e-324", x=5e-324, code=(OK | EOF), len=0, tot=0),
    (str="3e-324", x=5e-324, code=(OK | EOF), len=0, tot=0),
    # too small
    (str="2e-324", x=0.0, code=(OK | EOF), len=0, tot=0),
    # way too small
    (str="1e-350", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="1e-400000", x=0.0, code=(OK | EOF), len=0, tot=0),

    # try to overflow exponent
    (str="1e-4294967296", x=0.0, code=(OK | EOF), len=0, tot=0),
    (str="1e+4294967296", x=+Inf, code=(OK | EOF), len=0, tot=0),
    (str="1e-18446744073709551616", x=0.0, code=(OK | EOF), len=0, tot=0),

    (str="1e+18446744073709551616", x=+Inf, code=(OK | EOF), len=0, tot=0),

    # Parse errors
    # (str="1e", x=missing, code=(INVALID | EOF), len=0, tot=0),
    # (str="1e-", x=missing, code=(INVALID | EOF), len=0, tot=0),
    # (str=".e-1", x=missing, code=(INVALID | EOF), len=0, tot=0),
    # (str="1\x00.2", x=1.0, code=(OK), len=0, tot=0),

    # http:#www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/
    (str="2.2250738585072012e-308", x=2.2250738585072014e-308, code=(OK | EOF), len=0, tot=0),
    # http://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/
    (str="2.2250738585072011e-308", x=2.225073858507201e-308, code=(OK | EOF), len=0, tot=0),

    # A very large number (initially wrongly parsed by the fast algorithm).
    (str="4.630813248087435e+307", x=4.630813248087435e+307, code=(OK | EOF), len=0, tot=0),

    # A different kind of very large number.
    (str="22.222222222222222", x=22.22222222222222, code=(OK | EOF), len=0, tot=0),

    # Exactly halfway between 1 and math.Nextafter(1, 2).
    # Round to even (down).
    (str="1.00000000000000011102230246251565404236316680908203125", x=1.0, code=(OK | EOF), len=0, tot=0),
    # # Slightly lower; still round down.
    (str="1.00000000000000011102230246251565404236316680908203124", x=1.0, code=(OK | EOF), len=0, tot=0),
    # # Slightly higher; round up.
    (str="1.00000000000000011102230246251565404236316680908203126", x=1.0000000000000002, code=(OK | EOF), len=0, tot=0),

    (str="-5.871153289887625082e-01", x=-5.871153289887625082e-01, code=(OK | EOF), len=0, tot=0),
    (str="8.095032986136727615e-01", x=8.095032986136727615e-01, code=(OK | EOF), len=0, tot=0),
    (str="9.900000000000006573e-01", x=9.900000000000006573e-01, code=(OK | EOF), len=0, tot=0),
    (str="9.900000000000006573e-01", x=9.900000000000006573e-01, code=(OK | EOF), len=0, tot=0),
    (str="-9.866066418838319585e-01", x=-9.866066418838319585e-01, code=(OK | EOF), len=0, tot=0),
    (str="-3.138907529596844714e+00", x=-3.138907529596844714e+00, code=(OK | EOF), len=0, tot=0),
    (str="-5.129218106887371675e+00", x=-5.129218106887371675e+00, code=(OK | EOF), len=0, tot=0),
    (str="-4.803915800699462224e+00", x=-4.803915800699462224e+00, code=(OK | EOF), len=0, tot=0),

    # issue #18
    (str=".24409E+03", x=244.09, code=(OK | EOF), len=0, tot=0),
    (str=".24409E+03 ", x=244.09, code=(OK | EOF), len=0, tot=0),
    (str="-.2", x=-0.2, code=(OK | EOF), len=0, tot=0),
    (str=".2", x=0.2, code=(OK | EOF), len=0, tot=0),

    # from https://www.icir.org/vern/papers/testbase-report.pdf
    (str=string(5.0 * exp10(+125)), x=(5.0 * exp10(+125)), code=(OK | EOF), len=0, tot=0),
    (str=string(69.0 * exp10(+267)), x=(69.0 * exp10(+267)), code=(OK | EOF), len=0, tot=0),
    (str=string(999.0 * exp10(-26)), x=(999.0 * exp10(-26)), code=(OK | EOF), len=0, tot=0),
    (str=string(7861.0 * exp10(-34)), x=(7861.0 * exp10(-34)), code=(OK | EOF), len=0, tot=0),
    (str=string(75569.0 * exp10(-254)), x=(75569.0 * exp10(-254)), code=(OK | EOF), len=0, tot=0),
    (str=string(928609.0 * exp10(-261)), x=(928609.0 * exp10(-261)), code=(OK | EOF), len=0, tot=0),
    (str=string(9210917.0 * exp10(+80)), x=(9210917.0 * exp10(+80)), code=(OK | EOF), len=0, tot=0),
    (str=string(84863171.0 * exp10(+114)), x=(84863171.0 * exp10(+114)), code=(OK | EOF), len=0, tot=0),
    (str=string(653777767.0 * exp10(+273)), x=(653777767.0 * exp10(+273)), code=(OK | EOF), len=0, tot=0),
    (str=string(5232604057.0 * exp10(-298)), x=(5232604057.0 * exp10(-298)), code=(OK | EOF), len=0, tot=0),
    (str=string(27235667517.0 * exp10(-109)), x=(27235667517.0 * exp10(-109)), code=(OK | EOF), len=0, tot=0),
    (str=string(653532977297.0 * exp10(-123)), x=(653532977297.0 * exp10(-123)), code=(OK | EOF), len=0, tot=0),
    (str=string(3142213164987.0 * exp10(-294)), x=(3142213164987.0 * exp10(-294)), code=(OK | EOF), len=0, tot=0),
    (str=string(46202199371337.0 * exp10(-72)), x=(46202199371337.0 * exp10(-72)), code=(OK | EOF), len=0, tot=0),
    (str=string(231010996856685.0 * exp10(-73)), x=(231010996856685.0 * exp10(-73)), code=(OK | EOF), len=0, tot=0),
    (str=string(9324754620109615.0 * exp10(+212)), x=(9324754620109615.0 * exp10(+212)), code=(OK | EOF), len=0, tot=0),
    (str=string(78459735791271921.0 * exp10(+49)), x=(78459735791271921.0 * exp10(+49)), code=(OK | EOF), len=0, tot=0),
    (str=string(272104041512242479.0 * exp10(+200)), x=(272104041512242479.0 * exp10(+200)), code=(OK | EOF), len=0, tot=0),
    (str=string(6802601037806061975.0 * exp10(+198)), x=(6802601037806061975.0 * exp10(+198)), code=(OK | EOF), len=0, tot=0),
    (str=string(20505426358836677347.0 * exp10(-221)), x=(20505426358836677347.0 * exp10(-221)), code=(OK | EOF), len=0, tot=0),
    (str=string(836168422905420598437.0 * exp10(-234)), x=(836168422905420598437.0 * exp10(-234)), code=(OK | EOF), len=0, tot=0),
    (str=string(4891559871276714924261.0 * exp10(+222)), x=(4891559871276714924261.0 * exp10(+222)), code=(OK | EOF), len=0, tot=0),

    (str=string(9.0 * exp10(-265)), x=(9.0 * exp10(-265)), code=(OK | EOF), len=0, tot=0),
    (str=string(85.0 * exp10(-037)), x=(85.0 * exp10(-037)), code=(OK | EOF), len=0, tot=0),
    (str=string(623.0 * exp10(+100)), x=(623.0 * exp10(+100)), code=(OK | EOF), len=0, tot=0),
    (str=string(3571.0 * exp10(+263)), x=(3571.0 * exp10(+263)), code=(OK | EOF), len=0, tot=0),
    (str=string(81661.0 * exp10(+153)), x=(81661.0 * exp10(+153)), code=(OK | EOF), len=0, tot=0),
    (str=string(920657.0 * exp10(-023)), x=(920657.0 * exp10(-023)), code=(OK | EOF), len=0, tot=0),
    (str=string(4603285.0 * exp10(-024)), x=(4603285.0 * exp10(-024)), code=(OK | EOF), len=0, tot=0),
    (str=string(87575437.0 * exp10(-309)), x=(87575437.0 * exp10(-309)), code=(OK | EOF), len=0, tot=0),
    (str=string(245540327.0 * exp10(+122)), x=(245540327.0 * exp10(+122)), code=(OK | EOF), len=0, tot=0),
    (str=string(6138508175.0 * exp10(+120)), x=(6138508175.0 * exp10(+120)), code=(OK | EOF), len=0, tot=0),
    (str=string(83356057653.0 * exp10(+193)), x=(83356057653.0 * exp10(+193)), code=(OK | EOF), len=0, tot=0),
    (str=string(619534293513.0 * exp10(+124)), x=(619534293513.0 * exp10(+124)), code=(OK | EOF), len=0, tot=0),
    (str=string(2335141086879.0 * exp10(+218)), x=(2335141086879.0 * exp10(+218)), code=(OK | EOF), len=0, tot=0),
    (str=string(36167929443327.0 * exp10(-159)), x=(36167929443327.0 * exp10(-159)), code=(OK | EOF), len=0, tot=0),
    (str=string(609610927149051.0 * exp10(-255)), x=(609610927149051.0 * exp10(-255)), code=(OK | EOF), len=0, tot=0),
    (str=string(3743626360493413.0 * exp10(-165)), x=(3743626360493413.0 * exp10(-165)), code=(OK | EOF), len=0, tot=0),
    (str=string(94080055902682397.0 * exp10(-242)), x=(94080055902682397.0 * exp10(-242)), code=(OK | EOF), len=0, tot=0),
    (str=string(899810892172646163.0 * exp10(+283)), x=(899810892172646163.0 * exp10(+283)), code=(OK | EOF), len=0, tot=0),
    (str=string(7120190517612959703.0 * exp10(+120)), x=(7120190517612959703.0 * exp10(+120)), code=(OK | EOF), len=0, tot=0),
    (str=string(25188282901709339043.0 * exp10(-252)), x=(25188282901709339043.0 * exp10(-252)), code=(OK | EOF), len=0, tot=0),
    (str=string(308984926168550152811.0 * exp10(-052)), x=(308984926168550152811.0 * exp10(-052)), code=(OK | EOF), len=0, tot=0),
    (str=string(6372891218502368041059.0 * exp10(+064)), x=(6372891218502368041059.0 * exp10(+064)), code=(OK | EOF), len=0, tot=0),

    (str=string(8511030020275656.0 * exp2(-0342)), x=(8511030020275656.0 * exp2(-0342)), code=(OK | EOF), len=0, tot=0),
    (str=string(5201988407066741.0 * exp2(-0824)), x=(5201988407066741.0 * exp2(-0824)), code=(OK | EOF), len=0, tot=0),
    (str=string(6406892948269899.0 * exp2(+0237)), x=(6406892948269899.0 * exp2(+0237)), code=(OK | EOF), len=0, tot=0),
    (str=string(8431154198732492.0 * exp2(+0072)), x=(8431154198732492.0 * exp2(+0072)), code=(OK | EOF), len=0, tot=0),
    (str=string(6475049196144587.0 * exp2(+0099)), x=(6475049196144587.0 * exp2(+0099)), code=(OK | EOF), len=0, tot=0),
    (str=string(8274307542972842.0 * exp2(+0726)), x=(8274307542972842.0 * exp2(+0726)), code=(OK | EOF), len=0, tot=0),
    (str=string(5381065484265332.0 * exp2(-0456)), x=(5381065484265332.0 * exp2(-0456)), code=(OK | EOF), len=0, tot=0),
    (str=string(6761728585499734.0 * exp2(-1057)), x=(6761728585499734.0 * exp2(-1057)), code=(OK | EOF), len=0, tot=0),
    (str=string(7976538478610756.0 * exp2(+0376)), x=(7976538478610756.0 * exp2(+0376)), code=(OK | EOF), len=0, tot=0),
    (str=string(5982403858958067.0 * exp2(+0377)), x=(5982403858958067.0 * exp2(+0377)), code=(OK | EOF), len=0, tot=0),
    (str=string(5536995190630837.0 * exp2(+0093)), x=(5536995190630837.0 * exp2(+0093)), code=(OK | EOF), len=0, tot=0),
    (str=string(7225450889282194.0 * exp2(+0710)), x=(7225450889282194.0 * exp2(+0710)), code=(OK | EOF), len=0, tot=0),
    (str=string(7225450889282194.0 * exp2(+0709)), x=(7225450889282194.0 * exp2(+0709)), code=(OK | EOF), len=0, tot=0),
    (str=string(8703372741147379.0 * exp2(+0117)), x=(8703372741147379.0 * exp2(+0117)), code=(OK | EOF), len=0, tot=0),
    (str=string(8944262675275217.0 * exp2(-1001)), x=(8944262675275217.0 * exp2(-1001)), code=(OK | EOF), len=0, tot=0),
    (str=string(7459803696087692.0 * exp2(-0707)), x=(7459803696087692.0 * exp2(-0707)), code=(OK | EOF), len=0, tot=0),
    (str=string(6080469016670379.0 * exp2(-0381)), x=(6080469016670379.0 * exp2(-0381)), code=(OK | EOF), len=0, tot=0),
    (str=string(8385515147034757.0 * exp2(+0721)), x=(8385515147034757.0 * exp2(+0721)), code=(OK | EOF), len=0, tot=0),
    (str=string(7514216811389786.0 * exp2(-0828)), x=(7514216811389786.0 * exp2(-0828)), code=(OK | EOF), len=0, tot=0),
    (str=string(8397297803260511.0 * exp2(-0345)), x=(8397297803260511.0 * exp2(-0345)), code=(OK | EOF), len=0, tot=0),
    (str=string(6733459239310543.0 * exp2(+0202)), x=(6733459239310543.0 * exp2(+0202)), code=(OK | EOF), len=0, tot=0),
    (str=string(8091450587292794.0 * exp2(-0473)), x=(8091450587292794.0 * exp2(-0473)), code=(OK | EOF), len=0, tot=0),

    (str=string(6567258882077402.0 * exp2(+952)), x=(6567258882077402.0 * exp2(+952)), code=(OK | EOF), len=0, tot=0),
    (str=string(6712731423444934.0 * exp2(+535)), x=(6712731423444934.0 * exp2(+535)), code=(OK | EOF), len=0, tot=0),
    (str=string(6712731423444934.0 * exp2(+534)), x=(6712731423444934.0 * exp2(+534)), code=(OK | EOF), len=0, tot=0),
    (str=string(5298405411573037.0 * exp2(-957)), x=(5298405411573037.0 * exp2(-957)), code=(OK | EOF), len=0, tot=0),
    (str=string(5137311167659507.0 * exp2(-144)), x=(5137311167659507.0 * exp2(-144)), code=(OK | EOF), len=0, tot=0),
    (str=string(6722280709661868.0 * exp2(+363)), x=(6722280709661868.0 * exp2(+363)), code=(OK | EOF), len=0, tot=0),
    (str=string(5344436398034927.0 * exp2(-169)), x=(5344436398034927.0 * exp2(-169)), code=(OK | EOF), len=0, tot=0),
    (str=string(8369123604277281.0 * exp2(-853)), x=(8369123604277281.0 * exp2(-853)), code=(OK | EOF), len=0, tot=0),
    (str=string(8995822108487663.0 * exp2(-780)), x=(8995822108487663.0 * exp2(-780)), code=(OK | EOF), len=0, tot=0),
    (str=string(8942832835564782.0 * exp2(-383)), x=(8942832835564782.0 * exp2(-383)), code=(OK | EOF), len=0, tot=0),
    (str=string(8942832835564782.0 * exp2(-384)), x=(8942832835564782.0 * exp2(-384)), code=(OK | EOF), len=0, tot=0),
    (str=string(8942832835564782.0 * exp2(-385)), x=(8942832835564782.0 * exp2(-385)), code=(OK | EOF), len=0, tot=0),
    (str=string(6965949469487146.0 * exp2(-249)), x=(6965949469487146.0 * exp2(-249)), code=(OK | EOF), len=0, tot=0),
    (str=string(6965949469487146.0 * exp2(-250)), x=(6965949469487146.0 * exp2(-250)), code=(OK | EOF), len=0, tot=0),
    (str=string(6965949469487146.0 * exp2(-251)), x=(6965949469487146.0 * exp2(-251)), code=(OK | EOF), len=0, tot=0),
    (str=string(7487252720986826.0 * exp2(+548)), x=(7487252720986826.0 * exp2(+548)), code=(OK | EOF), len=0, tot=0),
    (str=string(5592117679628511.0 * exp2(+164)), x=(5592117679628511.0 * exp2(+164)), code=(OK | EOF), len=0, tot=0),
    (str=string(8887055249355788.0 * exp2(+665)), x=(8887055249355788.0 * exp2(+665)), code=(OK | EOF), len=0, tot=0),
    (str=string(6994187472632449.0 * exp2(+690)), x=(6994187472632449.0 * exp2(+690)), code=(OK | EOF), len=0, tot=0),
    (str=string(8797576579012143.0 * exp2(+588)), x=(8797576579012143.0 * exp2(+588)), code=(OK | EOF), len=0, tot=0),
    (str=string(7363326733505337.0 * exp2(+272)), x=(7363326733505337.0 * exp2(+272)), code=(OK | EOF), len=0, tot=0),
    (str=string(8549497411294502.0 * exp2(-448)), x=(8549497411294502.0 * exp2(-448)), code=(OK | EOF), len=0, tot=0),

    # from lemire's test suite
    (str="7.3177701707893310e+15", x=7.3177701707893310e+15, code=(OK | EOF), len=0, tot=0),
    (str="9007199254740995", x=9.007199254740996e15, code=(OK | EOF), len=0, tot=0),
    (str="7e23", x=7e23, code=(OK | EOF), len=0, tot=0),
    (str="-65.613616999999977", x=-65.613616999999977, code=(OK | EOF), len=0, tot=0),
    (str="7.2057594037927933e+16", x=7.2057594037927933e+16, code=(OK | EOF), len=0, tot=0),
    (str="0.1e-308", x=0.1e-308, code=(OK | EOF), len=0, tot=0),
    (str="0.01e-307", x=0.01e-307, code=(OK | EOF), len=0, tot=0),
    (str="1.79769e+308", x=1.79769e+308, code=(OK | EOF), len=0, tot=0),
    (str="2.22507e-308", x=2.22507e-308, code=(OK | EOF), len=0, tot=0),
    (str="-1.79769e+308", x=-1.79769e+308, code=(OK | EOF), len=0, tot=0),
    (str="-2.22507e-308", x=-2.22507e-308, code=(OK | EOF), len=0, tot=0),
];

for useio in (false, true)
    for (i, case) in enumerate(testcases)
        # println("testing case = $i, str = $(case.str)")
        res = Parsers.xparse(Float64, useio ? IOBuffer(case.str) : case.str)
        x, code, tlen = res.val, res.code, res.tlen
        if !Parsers.invalid(code)
            @test x == case.x
        end
        @test code == case.code
    end
end

# NaNs
case = (str="nan", x=NaN, code=(OK | EOF), len=3, tot=3)
res = Parsers.xparse(Float64, case.str)
x, code, tlen = res.val, res.code, res.tlen
@test isnan(x)
@test code == case.code
@test tlen == 3
case = (str="NaN", x=NaN, code=(OK | EOF), len=3, tot=3)
res = Parsers.xparse(Float64, case.str)
x, code, tlen = res.val, res.code, res.tlen
@test isnan(x)
@test code == case.code
@test tlen == 3
case = (str="NAN", x=NaN, code=(OK | EOF), len=3, tot=3)
res = Parsers.xparse(Float64, case.str)
x, code, tlen = res.val, res.code, res.tlen
@test isnan(x)
@test code == case.code
@test tlen == 3
case = (str="nAN", x=NaN, code=(OK | EOF), len=3, tot=3)
res = Parsers.xparse(Float64, case.str)
x, code, tlen = res.val, res.code, res.tlen
@test isnan(x)
@test code == case.code
@test tlen == 3
case = (str="nAN,", x=NaN, code=(OK | DELIMITED), len=4, tot=4)
res = Parsers.xparse(Float64, case.str)
x, code, tlen = res.val, res.code, res.tlen
@test isnan(x)
@test code == case.code
@test tlen == 4

# #25
case = (str="74810199.033988851037472901827191090834", x=7.481019903398885e7, code=(OK | EOF))
res = Parsers.xparse(Float64, case.str)
x, code, tlen = res.val, res.code, res.tlen
@test x == case.x
@test code == case.code
@test tlen == 39

# https://github.com/quinnj/JSON3.jl/issues/57
@test_throws Parsers.Error Parsers.parse(Float64,join(rand(1:9, 2000), ""))
@test Parsers.parse(BigFloat, "3.14") == BigFloat("3.14")
@test Parsers.parse(BigFloat, "3.14 ") == BigFloat("3.14")

# https://github.com/JuliaData/Parsers.jl/issues/53
bytes = codeunits("a,b,c\n.,1,3")
res = Parsers.xparse(Float64, bytes, 7, 11)
x, code, tlen = res.val, res.code, res.tlen
@test code == (INVALID | DELIMITED)

# https://github.com/JuliaData/CSV.jl/issues/710
# problematic really small floats on various threshold boundaries
@test Parsers.parse(Float64, "9.88e-324") === 1.0e-323
@test Parsers.parse(Float64, "4.94e-324") === 5.0e-324
@test Parsers.parse(Float64, "8.40e-323") === 8.4e-323
@test Parsers.parse(Float64, "3091.") === 3091.0

# https://github.com/JuliaData/CSV.jl/issues/769
@test Parsers.parse(Float64, "9223372036854775808") === 9.223372036854776e18

# random sampling of Float16 testing
for _ = 1:1000
    x = rand(Float16)
    str = string(x)
    @test Base.parse(Float16, str) === Parsers.parse(Float16, str)
end

# discovered from JSON tests
@test Parsers.tryparse(Float64, "0e+") === nothing

# https://github.com/JuliaData/CSV.jl/issues/916
@test  Parsers.parse(Float64, "0.44311945001372019574271437679879349172") === 0.4431194500137202
@test Parsers.parse(BigFloat, "0.44311945001372019574271437679879349172") == BigFloat("0.44311945001372019574271437679879349172")

# https://github.com/JuliaData/Parsers.jl/issues/83
@test Parsers.parse(Float64,"9.3494547075363499E-311") === 9.3494547075363e-311
@test Parsers.parse(Float64,"9.349454707536349999E-311") === 9.3494547075363e-311

# https://github.com/JuliaData/CSV.jl/issues/938
v1 = Parsers.parse(BigFloat, "0.994322841995507271075540969506")
v2 = Parsers.parse(BigFloat, "0.794322841995507271075540969506")
@test v1 !== v2

# parsing larger than Int64 with dangling non-digits
@test_throws Parsers.Error Parsers.parse(Float64, "3130307457683247493156627A")

end # @testset


#  Copyright 2016, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
#  This Source Code Form is subject to the terms of the Mozilla Public
#  License, v. 2.0. If a copy of the MPL was not distributed with this
#  file, You can obtain one at http://mozilla.org/MPL/2.0/.

function random_aff_expr(N, vars::Vector)
    ex = Expr(:call, :+)
    for _ in 1:N
        vl, vr = Any[], Any[]
        for i in randperm(length(vars))
            v = vars[i]
            if rand(Bool)
                push!(vl, :($(rand()-0.5)))
            end
            if rand(Bool)
                push!(vr, :($(rand()-0.5)))
            end
            if rand(Bool)
                push!(vl, :($(rand()-0.5) * $v))
            end
            if rand(Bool)
                push!(vr, :($(rand()-0.5) * $v))
            end
            if rand(Bool)
                push!(vl, :($v * $(rand()-0.5)))
            end
            if rand(Bool)
                push!(vl, :($v * $(rand()-0.5)))
            end
        end
        if rand(Bool)
            push!(vl, :($(rand()-0.5)))
        end
        if rand(Bool)
            push!(vr, :($(rand()-0.5)))
        end
        if !isempty(vl) || !isempty(vr)
            if isempty(vl)
                tmp = Expr(:call, :+, vr...)
            elseif isempty(vr)
                tmp = Expr(:call, :+, vl...)
            else
                tmp = Expr(:call, :*,
                        Expr(:call, :+, vl...),
                        Expr(:call, :+, vr...))
            end
            push!(ex.args, tmp)
        end
    end
    return ex
end

m = Model()
@variable(m, x)
@variable(m, y)
@variable(m, z)
@variable(m, w)
@variable(m, v)

N = 5
vars = [:x, :y, :z, :w, :v, :(identity(x)), :(identity(y)), :(identity(z))]

const ε = 10eps()
nvars = length(vars)

function test_approx_equal_exprs(ex1, ex2)
    res = true
    # test constant term
    abs(ex1.aff.constant - ex2.aff.constant) < ε || (res = false)

    # test aff terms
    vals = zeros(nvars)
    for i in 1:length(ex1.aff.vars)
        vals[ex1.aff.vars[i].col] += ex1.aff.coeffs[i]
    end
    for i in 1:length(ex2.aff.vars)
        vals[ex2.aff.vars[i].col] -= ex2.aff.coeffs[i]
    end
    for v in vals
        abs(v) < ε || (res = false)
    end

    # test quad terms
    qvals = zeros(nvars,nvars)
    for i in 1:length(ex1.qcoeffs)
        j,k = sort([ex1.qvars1[i].col, ex1.qvars2[i].col])
        qvals[j,k] += ex1.qcoeffs[i]
    end
    for i in 1:length(ex2.qcoeffs)
        j,k = sort([ex2.qvars1[i].col, ex2.qvars2[i].col])
        qvals[j,k] -= ex2.qcoeffs[i]
    end
    for v in vals
        abs(v) < ε || (res = false)
    end
    if !res
        str = "The following expression did not pass the fuzzer:\n    ex1 = QuadExpr($(ex1.qvars1),$(ex1.qvars2),$(ex1.qcoeffs),AffExpr($(ex1.aff.vars),$(ex1.aff.coeffs),$(ex1.aff.constant)))\n    ex2 = QuadExpr($(ex2.qvars1),$(ex2.qvars2),$(ex2.qcoeffs),AffExpr($(ex2.aff.vars),$(ex2.aff.coeffs),$(ex2.aff.constant)))"
        warn(str)
    end
    return res
end

println("[fuzzer] Check macros for expression construction")

for _ in 1:100
    raff = random_aff_expr(N, vars)
    ex = @eval @JuMP.Expression($raff)
    @fact test_approx_equal_exprs(ex, eval(raff)) --> true
end


@enum ContourEnum full_contour=1 keldysh_contour=2 imaginary_contour=3

struct Contour
  domain::ContourEnum
  branches::Vector{Branch}
  branch_indices::NTuple{3, Int} # branches[branch_indices[Int(BranchEnum)]].domain == BranchEnum

  function Contour(branches::AbstractVector{Branch})
    contour_enum = get_contour_enum(branches)
    branch_indices::Vector{Int} = [-1, -1, -1]
    for (i, b) in enumerate(branches)
      branch_indices[Int(b.domain)] = i
    end
    new(contour_enum, branches, Tuple(branch_indices))
  end

end

function Contour(d::ContourEnum; tmax=0.0, β=0.0)
  if d == full_contour
    branches = [Branch(forward_branch, tmax), Branch(backward_branch, tmax), Branch(imaginary_branch, β)]
  elseif d == keldysh_contour
    branches = [Branch(forward_branch, tmax), Branch(backward_branch, tmax)]
  else
    branches = [Branch(imaginary_branch, β)]
  end
  return Contour(branches)
end

function nbranches(c::ContourEnum)
  if c == full_contour
    return 3
  elseif c == keldysh_contour
    return 2
  else
    return 1
  end
end

function nbranches(c::Contour)
  return nbranches(c.domain)
end

function branch_set(c::ContourEnum)
  if c == full_contour
    return Set([forward_branch, backward_branch, imaginary_branch])
  elseif c == keldysh_contour
    return Set([forward_branch, backward_branch])
  else
    return Set([imaginary_branch])
  end
end

function get_contour_enum(branches::Set{BranchEnum})
  for c in instances(ContourEnum)
    branch_set(c) == branches && return c
  end
  @assert false "invalid branch set"
end

function get_contour_enum(branches::AbstractVector{Branch})
  branch_enums = Set([b.domain for b in branches])
  @assert length(branch_enums) == length(branches) "branches must be unique"
  return get_contour_enum(branch_enums)
end

function twist(c::Contour)
  return Contour(circshift(c.branches, -1))
end

function get_branch(c::Contour, d::BranchEnum)
  idx = c.branch_indices[Int(d)]
  return idx == -1 ? nothing : c.branches[idx]
end

"""
θ(t1, t2) = t1 >= t2 ? 1.0 : 0.0
"""
function heaviside(c::Contour, t1::BranchPoint, t2::BranchPoint)
  t1.domain == t2.domain ? t1.ref >= t2.ref : c.branch_indices[t1.domain] > c.branch_indices[t2.domain]
end

"""
Heaviside function on standard keldysh contour
"""
function heaviside(t1::BranchPoint, t2::BranchPoint)
  t1.domain == t2.domain ?  t1.ref >= t2.ref : t1.domain > t2.domain
end

const θ = heaviside

function in(b::BranchEnum, c::ContourEnum)
  if c == keldysh_contour
    return b == imaginary_branch ? false : true
  elseif c == imaginary_branch
    return b == imaginary ? true : false
  else
    return true
  end
end

function in(b::BranchEnum, c::Contour)
  return in(b, c.domain)
end


partial = false
include("lorenz.jl")
include("../extra/makie.jl")
using Makie, GLVisualize
using GeometryTypes, Colors
using Bridge: _viridis
skippoints = 1;
scene = Scene(resolution = (800, 800))
eyeposition = Float32[-37.2262, 65.8173, 37.4411]
sphere = Sphere(Point3f0(0,0,0), 1.0f0)
circle = Sphere(Point2f0(0,0), 1.0f0)
#RGB(Bridge._viridis[20]...)
maxviri = 200
viri = _viridis[round.(Int,range(1, stop=maxviri, length=length(Xtrue)))]
lines(( X2.yy), linewidth = 2.0, color = map(x->RGBA(Float32.(x)..., 0.9f0), viri) )
#scatter([X2.yy[1].+ SVector(-0.2,-0.2,1)], marker = '↘',  markersize = 0.8, color=:black)

ma = Float32[10,10,10]
axis(map(x -> UnitRange(0, round.(Int,x)), ma)..., showaxis = (true,true,true), showgrid = (false,false,false))
ma2 = ma .+ 2
for (x, c) in zip(ℝ{3}[(ma2[1], 0, 0), (0, ma2[2], 0),(0, 0, ma2[3]), (0,0,0)], ['x', 'y', 'z', '•'])
    scatter([x], marker = c, markersize = 1., color = :black)
end

scatter(collect(Models.foci(P)), marker=circle, markersize = .5,  strokewidth = 0.1, strokecolor = :white, color = :black)

scatter([Models.foci(P)[1] + SVector(-0.2,-0.2,1.0)], marker= 'a', markersize = .8, color = :black)
scatter([Models.foci(P)[2] + SVector(-0.2,-0.2,1.0)], marker='b', markersize = .8, color = :black)

center!(scene)
println(Makie.getscreen(scene).cameras[:perspective].view.value)

push!(Makie.getscreen(scene).cameras[:perspective].view,
    @SArray Float32[0.433338 0.901231 -3.7998f-7 1.95197; -0.237208 0.114057 0.96474 -20.4296; 0.869454 -0.418059 0.263205 -90.2712; 0.0 0.0 0.0 1.0]
)

Makie.save("output/lorenz1.png", scene);


################################################################################
#
#	CONCRETE TYPE
#
#   ReciprocalUnitcell{P,B} <: AbstractReciprocalUnitcell{P,B}
#   --> P is the reciprocal site type (<: AbstractReciprocalPoint{D})
#       --> D is the dimension of embedding space
#   --> B is the bond type (<: AbstractBond{L,N})
#       --> N is the dimension of the Bravais lattice that the bond is located in
#       --> L is the label type of bonds
#
#   FILE CONTAINS
#       - abstract type definition
#       - interface definition
#       - TODO interface testing
#
################################################################################

################################################################################
#
#   ABSTRACT TYPE DEFINITION
#
################################################################################
mutable struct ReciprocalUnitcell{P,B} <: AbstractReciprocalUnitcell{P,B}

    # basis vectors of the Bravais lattice (of the reciprocal unitcell)
    lattice_vectors	:: Vector{Vector{Float64}}

    # the gamma point (only site within the reciprocal unitcell)
    gamma_point     :: P

    # list of bonds
    bonds			:: Vector{B}

end

# export the type
export ReciprocalUnitcell

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

# constructor
function newReciprocalUnitcell(
            ::Type{ReciprocalUnitcell{P,B}},
            lattice_vectors :: Vector{<:Vector{<:Real}},
            bonds           :: Vector{B}
        ) :: ReciprocalUnitcell{P,B} where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N}}

    # call the constructor
    return ReciprocalUnitcell{P,B}(
        lattice_vectors,
        newReciprocalPoint(P, zeros(D), "Gamma", "\\Gamma"),
        bonds
    )
end

# export the constructor
export newReciprocalUnitcell

# accessing a list of lattice vectors
function latticeVectors(
            unitcell :: ReciprocalUnitcell{P,B}
        ) :: Vector{Vector{Float64}} where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N}}

    # return the lattice vectors
    return unitcell.lattice_vectors
end
# setting a list of lattice vectors
function latticeVectors!(
            unitcell        :: ReciprocalUnitcell{P,B},
            lattice_vectors :: Vector{<:Vector{<:Real}}
        ) where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N}}

    # set the lattice vectors
    unitcell.lattice_vectors = lattice_vectors
end

# export the latticeVectors interface
export latticeVectors, latticeVectors!

# accessing the gamma point
function gammaPoint(
            unitcell :: ReciprocalUnitcell{P,B}
        ) :: P where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N}}

    # return the gamma point
    return unitcell.gamma_point
end

# setting the gamma point
function gammaPoint!(
            unitcell :: ReciprocalUnitcell{P,B},
            site     :: P
        ) where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N}}

    # set the gamma point
    unitcell.gamma_point = site
end

# export gamma point interface
export gammaPoint, gammaPoint!

# accessing a list of bonds
function bonds(
            unitcell :: ReciprocalUnitcell{P,B}
        ) :: Vector{B} where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N}}

    # return the internal list of bonds
    return unitcell.bonds
end
# setting a list of bonds
function bonds!(
            unitcell :: ReciprocalUnitcell{P,B},
            bonds    :: Vector{B}
        ) where {D,N,L,P<:AbstractReciprocalPoint{D},B<:AbstractBond{L,N}}

    # set the internal list of bonds
    unitcell.bonds = bonds
end

# export the bonds interface
export bonds, bonds!


module ModelWrappers

############################################################################################
#Import External packages
import Base: Base, length, fill, fill!
import StatsBase: StatsBase, sample, sample!
import BaytesCore: BaytesCore, subset, update
using BaytesCore:
    AbstractModelWrapper,
    AbstractObjective,
    AbstractResult,
    BaytesCore,
    Tuple_to_Namedtuple,
    UpdateBool,
    UpdateTrue,
    UpdateFalse

using DocStringExtensions:
    DocStringExtensions, TYPEDEF, TYPEDFIELDS, FIELDS, SIGNATURES, FUNCTIONNAME
using ArgCheck: ArgCheck, @argcheck
using UnPack: UnPack, @unpack, @pack!
using Random: Random, AbstractRNG, GLOBAL_RNG

#!NOTE: These libraries are relevant for transform part
using LinearAlgebra: LinearAlgebra, Diagonal, LowerTriangular, tril!, diag, issymmetric
using Distributions: Distributions, Distribution, logpdf
using Bijectors:
    Bijectors, Bijector, logpdf_with_trans,
    TruncatedBijector, SimplexBijector, CorrBijector, PDBijector
#!NOTE: These libraries are only relevant for 'Differentiation' files - open to make separate library later on but for now easier to test changes with AD libraries included in a single library.
using ChainRulesCore, DistributionsAD, DiffResults
using ForwardDiff, ReverseDiff, Zygote

############################################################################################
# A bunch of constants used throughout the package
"Maximum value after transformation before tagged as non-finite."
const max_val = 1e+100
"Smallest decrease allowed in the log objective results before tagged as divergent."
const min_Δ = -1e+3

############################################################################################
#Import
include("Core/Core.jl")
include("Models/Models.jl")
include("Differentiation/Differentiation.jl")

############################################################################################
#export
export
    UpdateBool,
    UpdateTrue,
    UpdateFalse

end


#  Copyright 2022 California Institute of Technology
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
# Author: Philip G. Brodrick, philip.g.brodrick@jpl.nasa.gov
module SpectralUnmixing

using ArchGDAL
using EllipsisNotation
using DelimitedFiles
using Logging
using Statistics
using Distributed
using Printf
using LinearAlgebra
using Combinatorics
using Random

include("Datasets.jl")
include("EndmemberLibrary.jl")
include("Solvers.jl")
include("Plotting.jl")

# Endmember Library Functions
export SpectralLibrary, load_data!, filter_by_class!, read_envi_wavelengths, interpolate_library_to_new_wavelengths!, remove_wavelength_region_inplace!, scale_library!
export reduce_endmembers_nmf!, reduce_endmembers_kmeans!, reduce_endmembers_pca!, brightness_normalize!, split_library

# Plotting Functions
export plot_mean_endmembers, plot_endmembers, plot_endmembers_individually

# Dataset functions
export initiate_output_datasets, set_band_names, write_results

# Unmixing and simulation functions
export unmix_line, unmix_pixel, simulate_pixel

function wl_index(wavelengths::Vector{Float64}, target)
    argmin(abs.(wavelengths .- target))
end

function scale_data(refl::Matrix{Float64}, wavelengths::Vector{Float64}, criteria::String)

    if criteria == "none"
        return refl
    elseif criteria == "brightness"
        bad_regions_wl = [[1300,1500],[1800,2000]]
        good_bands = convert(Vector{Bool}, ones(length(wavelengths)))
        for br in bad_regions_wl
            good_bands[wl_index(wavelengths, br[1]):wl_index(wavelengths, br[2])] .= false
        end
        if length(size(refl)) == 2
            norm = sqrt.(mean(refl[:,good_bands].^2, dims=2))
        else
            norm = sqrt.(mean(refl[good_bands].^2))
        end
    else
        try
            target_wl = parse(Float64,criteria)
            if length(size(refl)) == 2
                norm = refl[:,wl_index(wavelengths, target_wl)] ./ 0.5
            else
                norm = refl[wl_index(wavelengths, target_wl)] ./ 0.5
            end
        catch e
            throw(ArgumentError(string("normalization must be [none, brightness, or a specific wavelength].  Provided:", criteria)))
        end
    end

    return refl ./ norm
end

function get_sma_permutation(class_idx, num_endmembers::Vector{Int64}, combination_type::String, library_length::Int64)
    if num_endmembers[1] != -1
        if combination_type == "class-even"

            perm_class_idx = []
            for class_subset in class_idx
                push!(perm_class_idx, Random.shuffle(class_subset))
            end

            perm = []
            selector = 1
            while selector <= num_endmembers[1]
                _p = mod(selector, length(perm_class_idx)) + 1
                push!(perm, perm_class_idx[_p][1])
                deleteat!(perm_class_idx[_p],1)

                if length(perm_class_idx[_p]) == 0
                    deleteat!(perm_class_idx,_p)
                end
                selector += 1
            end

        else
            perm = randperm(library_length)[1:num_endmembers[1]]
        end
    else
        perm = convert(Vector{Int64},1:library_length)
    end

    return perm
end

function results_from_mc(results::Matrix{Float64}, cost::Vector{Float64}, mode::String)

    if size(results)[1] == 1
        output_var = nothing
    else
        output_var = std(results, dims=1)[1,:]
    end

    if occursin("best", mode)
        best_idx = argmin(cost)
        output = results[best_idx,:]
    else
        output = mean(results, dims=1)[1,:]
    end

    return output, output_var

end

function unmix_pixel(library::SpectralLibrary, img_dat_input::Array{Float64}, unc_dat, class_idx, options, mode::String, n_mc::Int64, num_endmembers::Vector{Int64}, normalization::String, optimization::String, max_combinations::Int64, combination_type::String)

    if length(size(img_dat_input)) == 1
        img_dat = reshape(img_dat_input, 1, length(img_dat_input))
    else
        img_dat = img_dat_input
    end

    mc_comp_frac = zeros(n_mc, size(library.spectra)[1]+1)
    scores = zeros(n_mc)
    for mc in 1:n_mc #monte carlo loop
        Random.seed!(mc)

        d = img_dat
        if isnothing(unc_dat) == false
            d += (rand(size(d)) .* 2 .- 1) .* unc_dat
        end

        if occursin("sma", mode)
            perm = get_sma_permutation(class_idx, num_endmembers, combination_type, size(library.spectra)[1])
            G = library.spectra[perm, library.good_bands]

            G = scale_data(G, library.wavelengths[library.good_bands], normalization)'

            if occursin("pinv", optimization)
                inverse_method = "pinv"
            elseif occursin("qr", optimization)
                inverse_method = "qr"
            else
                inverse_method = "default"
            end
            x0 = dolsq(G, d', method=inverse_method)

            x0 = x0[:]
            res = nothing
            if occursin("bvls", optimization)
                res, cost = bvls(G, d[:], x0, zeros(size(x0)), ones(size(x0)), 1e-3, 100, 1, inverse_method)
            elseif occursin("ldsqp", optimization)
                res, cost = opt_solve(G, d[:], x0, zeros(length(x0)), ones(length(x0)) )
            elseif occursin("inverse", optimization)
                res = x0
                r = G * x0 - d[:]
                cost = dot(r,r)
            end
            mc_comp_frac[mc, perm] = res
            scores[mc] = cost

        elseif occursin("mesma", mode)
            solutions = []
            costs = zeros(size(options)[1]).+1e12

            if max_combinations != -1 && length(options) > max_combinations
                perm = randperm(length(options))[1:max_combinations]
            else
                perm = convert(Vector{Int64},1:length(options))
            end

            for (_comb, comb) in enumerate(options[perm])
                comb = [c for c in comb]
                #G = hcat(library.spectra[comb,:], ones(size(library.spectra[comb,:])[1],1))
                G = scale_data(library.spectra[comb, library.good_bands], library.wavelengths[library.good_bands], normalization)'

                x0 = dolsq(G, d')
                ls = nothing
                if optimization == "bvls"
                    ls, lc = bvls(G, d[:], x0, zeros(size(x0)), ones(size(x0)), 1e-3, 10, 1)
                    costs[_comb] = lc
                elseif optimization == "ldsqp"
                    ls, lc = opt_solve(G, d[:], x0, 0, 1)
                    costs[_comb] = lc
                elseif optimization == "inverse"
                    ls = x0
                    r = G * x0 - d[:]
                    costs[_comb] = dot(r,r)
                end
                push!(solutions,ls)

            end
            best = argmin(costs)
            scores[mc] = best

            mc_comp_frac[mc, [ind for ind in options[perm][best]]] = solutions[best]
        else
            error("Invalid mode provided")
        end
    end

    # Calculate the sum of values (inverse of shade), and then normalize
    mc_comp_frac[mc_comp_frac .< 0] .= 0
    mc_comp_frac[:,end] = sum(mc_comp_frac,dims=2)
    mc_comp_frac[:,1:end-1] = mc_comp_frac[:,1:end-1] ./ mc_comp_frac[:,end]

    # Aggregate results from per-library to per-unique-class
    mixture_results = zeros(size(mc_comp_frac)[1], length(library.class_valid_keys) + 1)
    for _i in 1:size(mc_comp_frac)[1]
        for (_class, cl) in enumerate(library.class_valid_keys)
            mixture_results[_i, _class] = sum(mc_comp_frac[_i,1:end-1][cl .== library.classes])
        end
        mixture_results[_i, end] = mc_comp_frac[_i,end]
    end

    output_mixture, output_mixture_var = results_from_mc(mixture_results, scores, mode)
    output_comp_frac, output_comp_frac_var = results_from_mc(mc_comp_frac, scores, mode)

    return output_mixture, output_mixture_var, output_comp_frac, output_comp_frac_var

end

function unmix_line(line::Int64, reflectance_file::String, mode::String, refl_nodata::Float64,
                    refl_scale::Float64, normalization::String, library::SpectralLibrary,
                    reflectance_uncertainty_file::String = "", n_mc::Int64 = 1,
                    combination_type::String = "all", num_endmembers::Vector{Int64} = [2,3],
                    max_combinations::Int64 = -1, optimization="bvls")

    Random.seed!(13)
    println(line)

    img_dat, unc_dat, good_data = load_line(reflectance_file, reflectance_uncertainty_file, line, library.good_bands, refl_nodata)
    mixture_results = fill(-9999.0, sum(good_data), size(library.class_valid_keys)[1] + 1)
    complete_fractions = zeros(size(img_dat)[1], size(library.spectra)[1] + 1)

    if n_mc > 1
        mixture_results_std = fill(-9999.0, sum(good_data), size(library.class_valid_keys)[1] + 1)
        complete_fractions_std = zeros(size(img_dat)[1], size(library.spectra)[1] + 1)
    else
        mixture_results_std = nothing
        complete_fractions_std = nothing
    end

    if isnothing(img_dat)
        return line, nothing, good_data, nothing, nothing
    end
    scale_data(img_dat, library.wavelengths[library.good_bands], normalization)
    img_dat = img_dat ./ refl_scale

    class_idx = []
    if combination_type == "class-even"
        for uc in library.class_valid_keys
            push!(class_idx, (1:size(library.classes)[1])[library.classes .== uc])
        end
    end

    # Prepare combinations if relevant
    options = []
    if mode == "mesma"
        if combination_type == "class-even"
            options = collect(Iterators.product(class_idx...))[:]
        elseif combination_type == "all"
            for num in num_endmembers
                combo = [c for c in combinations(1:length(library.classes), num)]
                push!(options,combo...)
            end
        else
            error("Invalid combiation string")
        end
    end

    # Solve for each pixel
    for _i in 1:size(img_dat)[1] # Pixel loop

        lid = img_dat[_i:_i,:]
        if isnothing(unc_dat)
            lud = nothing
        else
            lud = unc_dat[_i:_i,:]
        end

        loc_mixture_res, loc_mixture_var, loc_cf_res, loc_cf_var  = unmix_pixel(library, lid,
            lud, class_idx, options, mode, n_mc, num_endmembers, normalization, optimization,
            max_combinations, combination_type)

        complete_fractions[_i,:] = loc_cf_res
        mixture_results[_i,:] = loc_mixture_res

        if n_mc > 1
            complete_fractions_std[_i,:] = loc_cf_var
            mixture_results_std[_i,:] = loc_mixture_var
        end

    end

    return line, mixture_results, good_data, mixture_results_std, complete_fractions

end

function class_assign_fractions(complete_fractions, library::SpectralLibrary)
    # Aggregate results from per-library to per-unique-class
    if length(size(complete_fractions)) == 1
        cf = reshape(complete_fractions, (1, length(complete_fractions)))
    else
        cf = complete_fractions
    end
    mixture_results = zeros(size(cf)[1], length(library.class_valid_keys))

    for _i in 1:size(cf)[1]
        for (_class, cl) in enumerate(library.class_valid_keys)
            mixture_results[_i, _class] = sum(complete_fractions[_i,1:end-1][cl .== library.classes])
        end
    end

    if length(size(complete_fractions)) == 1
        return mixture_results[1,:]
    else
        return mixture_results
    end

end

function simulate_pixel(library::SpectralLibrary, max_components::Int64, combination_type::String, seed::Int64)

    Random.seed!(seed)

    output_mixture = zeros(size(library.spectra)[2])
    output_mixture[:] .= NaN

    class_idx = []
    if combination_type == "class-even"
        for uc in library.class_valid_keys
            push!(class_idx, (1:size(library.classes)[1])[library.classes .== uc])
        end
    end
    perm = get_sma_permutation(class_idx, [max_components], combination_type, size(library.spectra)[1])

    G = library.spectra[perm,:]

    distribution = rand(max_components)
    distribution = distribution ./ sum(distribution)

    output_distribution = zeros(size(library.spectra)[1])
    output_distribution[perm] = distribution

    output_distribution_classes = zeros(size(library.class_valid_keys))
    for (_class, cl) in enumerate(library.class_valid_keys)
        output_distribution_classes[_class] = sum(output_distribution[cl .== library.classes])
    end

    simulated_rfl = G' * distribution
    return simulated_rfl, output_distribution, output_distribution_classes

end

end


include("$(pwd())/startup.jl")

#fp = "./ExampleFiles/STOFDATA/" # All files in this path will be processed

fp = "/media/wiebke/Extreme SSD/PSM_vs_PTR3/Data/apiTOFdata/CLOUD10/run1734_02/"

filefilterRegexp = r"\.h5$"
#rf = "./ExampleFiles/STOFDATA/2017-05-24_12h50m39_NH4.h5"  # The mass scale from this file defines the mass scale of all

rf = "$(fp)APi4_Data_2015.10.20-13h34m44s.h5"

#masslist = MasslistFunctions.loadMasslist("./ExampleFiles/MASSLISTS/exampleMasslistSTOF.csv")

masslist = MasslistFunctions.loadMasslist("$(fp)MassList_NO3-_AP_03-01-20.csv")

cr = [37 137]

# alternatively: use an auto generated masslist
# masslistMasses, masslistElements, masslistElementsMasses, masslistCompositions = createMassList(C=0:20, O=0:20, N=0:1, allowRadicals=false) #
(masslistMasses, masslistElements, masslistElementsMasses, masslistCompositions) = masslist
s = (masslistMasses.>0) .& ( masslistMasses.<600)
masslistMasses = masslistMasses[s]
masslistCompositions = masslistCompositions[s,:]

####################### END OF SETTINGS ###############################################################

####################### Processing sequence ###########################################################

correctMassScaleAndExtractSumSpec(
    fp,
    masslistMasses,
    masslistElements,
    masslistElementsMasses,
    masslistCompositions,
    rf,
    cr,
    filefilterRegexp=filefilterRegexp,
    onlyUseAverages = true,
    plotControlMass = true,
    recalibInterval = 300,
    resolution = 1500,
    firstNFiles=0,
    lastNFiles = 0
    )

baselineAndPeakshape(
    fp,
    peakshapeRegions=4,
    peakshapeRegionStretch=1,
    peakshapeQuantileValue = 0.2,
    peakfindingNoiseThresholdValue = 2,
    peakfindingSignalLimit=0.01
    )

mtrx = deconvolute(
    fp,
    calcTransposed = false
    )