text:
# Используем более привычные английские названия сторон света вместо немецких
""" Север """
North = Nord
""" Юг """
South = Sud
#""" Запад """ - уже определён
#West = West
""" Восток """
East = Ost

"""
mutable struct Robot_for_labyrinth

robot::Robot - Обычный робот
move_side::HorizonSide - Напрвление, в котором будет двигаться робота
count_of_external_corners::Int - Количество встреченных "внешних" углов. (Углы, по 270 градусов относительно робота)
count_of_internal_corners::Int - Количество встреченных "внутренних" углов. (Углы, по 90 градусов относительно робота)
start_point_border_position::HorizonSide - Информация о том, с какой стороны от робота на старте находилась граница либиринта. Нужна, чтобы понять, что робот обошёл лабиринт
previos_state::HorizonSide - Информация о том, где на прошлом шаге от робота находилась граница. Нужна для обхода лабиринта.
MyRobot(r::Robot) = new(r, North, 0, 0 North, North) - конструктор структуры, в который надо передать обычного робота. Остальные поля инициализируются дефолтными значениями

"""
mutable struct Robot_for_labyrinth
    robot::Robot
    move_side::HorizonSide
    count_of_external_corners::Int
    count_of_internal_corners::Int
    start_point_border_position::HorizonSide
    previos_state::HorizonSide
    Robot_for_labyrinth(r::Robot) = new(r, North, 0, 0, North, North)

end

"""
get_start_point_border_position(r::Robot_for_labyrinth) :: HorizonSide

Функция, определяющая, с какой стороны от робота имеется граница.
Возвращает первое из направлений, считая с Юга против часовой стрелки.
"""
function get_border_position(r::Robot_for_labyrinth) :: HorizonSide

    for i in (South , East, North, West)
        if (isborder(r.robot, i))
            return i
        end
    end
    return North
end

"""
get_move_side(r::Robot_for_labyrinth) :: HorizonSide

Определяет, в какую сторону* при старте надо двигаться роботу.
*В этом направлении может быть граница, это будет нормально, тогда робот "повернёт"
"""
function get_move_side(r::Robot_for_labyrinth) :: HorizonSide
    return next_side(get_border_position(r))
end

"""
show_ans(r::Robot_for_labyrinth)

Печатает ответ, на основе данных из робота.
"""
function show_ans(r::Robot_for_labyrinth)
    if (r.count_of_external_corners > r.count_of_internal_corners)
        println("Робот снаружи лабиринта")
    elseif (r.count_of_external_corners < r.count_of_internal_corners)
        println("Робот внутри лабиринта")
    else
        println("Похоже, что лабиринт вырожденный")
    end
end

"""
update_corners_count!(r::Robot_for_labyrinth)

Если текущая клетка является углом, увеличивает соответственню переменную Robot_for_labyrinth на 1
"""
function update_corners_count!(r::Robot_for_labyrinth)
    if get_type_of_corner(r) == 1
        r.count_of_external_corners += 1
        println("внешний угол")
    elseif get_type_of_corner(r) == 2
        r.count_of_internal_corners += 1
        println("внутрениий угол")
    end
end

"""
get_type_of_corner(r::Robot_for_labyrinth) :: Int

Вовзращает 0, если робот не находится в углу,
возвращает 1, если робот во внешнем углу,
возвращает 2, если робот во внутреннем углу.
"""
function get_type_of_corner(r::Robot_for_labyrinth) :: Int
    ans = 0
    for i in (North, East, South, West)
        if (isborder(r.robot, i) && isborder(r.robot, HorizonSide(mod(Int(i)-1,4))))
            ans =  2
        end
    end

    if (isborder(r.robot, r.previos_state) == false )
        ans = 1
    end
    return ans
end

"""
one_step_move_around_labyrinth!(r::Robot_for_labyrinth)

Перемещает робота на 1 шаг вдоль лабиринта, меняет направление движения в зависимости от типа текущего угла.

"""
function one_step_move_around_labyrinth!(r::Robot_for_labyrinth)
    if !(isborder(r.robot, r.move_side))
        move!(r.robot, r.move_side)
    end
    r.previos_state = prev_side(r.move_side)

    if get_type_of_corner(r) == 1
        change_move_side_to_prev_side!(r)
    elseif get_type_of_corner(r) == 2
        change_move_side_to_next_side!(r)
    end

end

"""
change_move_side_to_next_side!(r::Robot_for_labyrinth)

Изменяет сторону движения на следующую
"""
function change_move_side_to_next_side!(r::Robot_for_labyrinth)
    r.move_side = next_side(r.move_side)
end

"""
change_move_side_to_prev_side!(r::Robot_for_labyrinth)

Изменяет сторону движения на предыдущую
"""
function change_move_side_to_prev_side!(r::Robot_for_labyrinth)
    r.move_side = prev_side(r.move_side)
end

"""
Возвращает следующую сторону
"""
next_side(side::HorizonSide)::HorizonSide = HorizonSide(mod(Int(side)+1,4))

"""
Возвращает предыдущую сторону
"""
prev_side(side::HorizonSide)::HorizonSide= HorizonSide(mod(Int(side)-1,4))


text:
using Test
using TransformPlayground

@testset "TransformPlayground" begin
    include("test_1d_fft.jl")
    include("test_1d_dct.jl")
    include("test_2d_dct.jl")
    include("test_fft_y.jl")
end

ext: jl


text:
############################################################################################
"""
$(SIGNATURES)
Change trace.val to 3d Array that is consistent with MCMCCHains dimensons. First dimension is iterations, second number of parameter, third number of chains.

# Examples
```julia
```

"""
function trace_to_3DArray(
    trace::Trace,
    model::ModelWrapper,
    tagged::Tagged,
    burnin::Integer,
    thinning::Integer
)
    ## Get trace information
    Nparams = length(tagged)
    @unpack Nchains, iterations = trace.info.sampling
    effective_iterations = (burnin+1):thinning:iterations
    ## Preallocate array
    mcmcchain = zeros(length(effective_iterations), Nparams, Nchains)
    ## Flatten corresponding parameter
    Threads.@threads for chain in Base.OneTo(Nchains)
        for (iter, index) in enumerate(effective_iterations)
            mcmcchain[iter, :, chain] .= flatten(tagged.info.reconstruct, subset(trace.val[chain][index], tagged.parameter))
        end
    end
    ## Return MCMCChain
    return mcmcchain
end
############################################################################################
"""
$(SIGNATURES)
Change trace.val to 3d Array and return Posterior mean as NamedTuple and as Vector

# Examples
```julia
```

"""
function trace_to_posteriormean(
    mod_array::AbstractArray,
    model::ModelWrapper,
    tagged::Tagged,
    burnin::Integer,
    thinning::Integer
)
    mod_array_mean = map(iter -> mean(view(mod_array, :, iter, :)), Base.OneTo(size(mod_array, 2)))
    mod_nt_mean = ModelWrappers.unflatten(model, tagged, mod_array_mean)
    return mod_array_mean, mod_nt_mean
end
function trace_to_posteriormean(
    trace::Trace,
    model::ModelWrapper,
    tagged::Tagged,
    burnin::Integer,
    thinning::Integer
)
    return trace_to_posteriormean(
        trace_to_3DArray(trace, model, tagged, burnin, thinning),
        model, tagged, burnin, thinning
    )
end

############################################################################################
#export
export trace_to_3DArray, trace_to_posteriormean


text:
using Revise

Revise.revise()

using Term

install_stacktrace()
import Base: ExceptionStack

import Term: highlight_syntax

# TODO mostly working but error not rendering correctly

# define nested functions
b(x::String) = x * "a"

"""
    This is a function!
"""
yet_another_function(z) = b(z)

# is this going to work?
outer(y) = yet_another_function(y)

# println(z)
1 = "a"

# outer(2)

# TODO load code and highlight https://juliadocs.github.io/Highlights.jl/stable/
# TODO try getting error hints again


text:
using Documenter, Recommendation

makedocs(
    format = :html,
    modules = [Recommendation],
    sitename = "Recommendation.jl",
    authors = "Takuya Kitazawa",
    linkcheck = !("skiplinks" in ARGS),
    pages = [
        "Home" => "index.md",
        "Getting Started" => "getting_started.md",
        "References" => [
            "notation.md",
            "baseline.md",
            "collaborative_filtering.md",
            "content_based_filtering.md",
            "evaluation.md",
        ],
    ],
)

deploydocs(
    repo = "github.com/takuti/Recommendation.jl.git",
    target = "build",
)


text:
using Random

print("Set the range of random numbers : ")

randrange = parse(Int64,readline())

range = collect(1:randrange)

print(Random.rand(range))

max_stars_repo_name: cashewcodes/Basic-Julia-Projects

max_issues_repo_name: cashewcodes/Basic-Julia-Projects

max_forks_repo_name: cashewcodes/Basic-Julia-Projects


text:
getquadrature(d::ContinuousUnivariateDistribution, numquadnodes::Vector{Int}) =
	qnwdist(d, numquadnodes[1])

getquadrature(d::Normal, numquadnodes::Vector{Int}) =
    qnwnorm(numquadnodes[1], mean(d), std(d)^2)

getquadrature(d::Uniform, numquadnodes::Vector{Int}) =
    qnwunif(numquadnodes[1], params(d)...)

getquadrature(d::LogNormal, numquadnodes::Vector{Int}) =
    qnwlogn(numquadnodes[1], meanlogx(d), varlogx(d))

getquadrature(d::MvNormal, numquadnodes::Vector{Int}) =
	qnwnorm(numquadnodes, Vector(d.μ), Matrix(d.Σ))

transitionmatrix(p::DDP;
    numquadnodes::Vector{Int} = 5*ones(Int64, length(p.shockdist.μ))) =
    _transitionmatrix(p, p.intdim, numquadnodes)

"""Generate a nExogStates x nExogStates transition matrix."""
_transitionmatrix(p::DDP, method::Type{Separable_ExogStates}, numquadnodes::Vector{Int}) =
    _transitionmatrix(p.transfunc,
	getnonchoicevars(p),
	getnonchoicevars(p),
    p.shockdist, numquadnodes)

"""Generate a nStates x nExogStates transition matrix."""
_transitionmatrix(p::DDP, method::Type{Separable_States}, numquadnodes::Vector{Int}) =
    _transitionmatrix(p.transfunc,
    p.tStateVectors,
	getnonchoicevars(p),
   	p.shockdist, numquadnodes)

"""Generate a (nChoices*nStates) x nExogStates transition matrix."""
_transitionmatrix(p::DDP, method::Type{Separable}, numquadnodes::Vector{Int}) =
    _transitionmatrix(p.transfunc,
	    p.tStateVectors, getchoicevars(p),
		getnonchoicevars(p),
	    p.shockdist, numquadnodes)

"""Generate a (nChoices*nStates) x nStates transition matrix."""
_transitionmatrix(p::DDP, method::Type{All}, numquadnodes::Vector{Int}) =
	_transitionmatrix(p.transfunc,
		p.tStateVectors, p.tChoiceVectors,
		p.tStateVectors,
		p.shockdist, numquadnodes)

# for choices and states as input
function _transitionmatrix(transfunc,
    tInputVectorsStates::NTuple{dimStates, Vector{T1}},
	tInputVectorsChoices::NTuple{dimChoices, Vector{T2}},
	tOutputVectors,
    shockdist, numquadnodes) where {dimStates, dimChoices, T1, T2}

    Shocks, vWeights = getquadrature(shockdist, numquadnodes)
	dimshocks = length(shockdist)

    nOutputStates = prod(length.(tOutputVectors))
    dimOutputStates = length(tOutputVectors)

    nInputStates = prod(length.(tInputVectorsStates)) * prod(length.(tInputVectorsChoices))

    g = zeros(dimOutputStates, nInputStates)
    mG = spzeros(nInputStates, nOutputStates)
    basisOutputStates = Basis(SplineParams.(tOutputVectors,0,1))
    PhiTemp = spzeros(size(mG)...)

	iterator_choices = getiterator(tInputVectorsChoices)

	# choices change faster than states
	iterator = Iterators.product(iterator_choices, Iterators.product(tInputVectorsStates...))

	lowerbounds = minimum.(tOutputVectors)
	upperbounds = maximum.(tOutputVectors)

	# loop over all shock combinations
    for i = 1 : length(vWeights)
        # loop over all inputstates
        for (j, (choices, states)) in enumerate(iterator)
			if dimshocks > 1
            	# g[:,j] .= inbounds.(transfunc(states, choices,  Shocks[:,i]),
				# 	lowerbounds, upperbounds)
            	g[:,j] .= transfunc(states, choices,  Shocks[:,i])
			else
				# g[:,j] .= inbounds.(transfunc(states, choices,  Shocks[i]),
				# 	lowerbounds, upperbounds)
				g[:,j] .= transfunc(states, choices,  Shocks[i])
			end
        end
        PhiTemp .= BasisMatrix(basisOutputStates, Expanded(), g', 0).vals[1]
        mG .= mG + vWeights[i] * PhiTemp
    end
    # is G fully sparse?
    # G[G < 1E-10] = 0
    # dropzeros!(G)

    # VERY IMPORTANT TO SPECIFY TYPE HERE, BECAUSE BASISMATRICES DOES NOT GIVE FIXED OUTPUT
    #  if gettransitionmatrix turns out to be a bottleneck, then I need to dig deeper into this
    return mG::SparseMatrixCSC{Float64,Int64}
end #getG

# for states as input
function _transitionmatrix(transfunc,
    tInputVectors::NTuple{N, Vector{T}}, tOutputVectors,
    shockdist, numquadnodes) where {N,T}

	Shocks, vWeights = getquadrature(shockdist, numquadnodes)
	dimshocks = length(shockdist)

	nInputStates = prod(length.(tInputVectors))
    dimOutputStates = length(tOutputVectors)
    nOutputStates = prod(length.(tOutputVectors))

    g = zeros(dimOutputStates, nInputStates)
    mG = spzeros(nInputStates, nOutputStates)
    basisOutputStates = Basis(SplineParams.(tOutputVectors,0,1))
    PhiTemp = spzeros(size(mG)...)

	iterator = getiterator(tInputVectors)

	lowerbounds = minimum.(tOutputVectors)
	upperbounds = maximum.(tOutputVectors)

    # loop over all shock combinations
    for i = 1 : length(vWeights)
        # loop over all inputstates
        for (j, states) in enumerate(iterator)
			if dimshocks > 1
            	# g[:,j] .= inbounds.(transfunc(states, Shocks[:,i]),
				# 	lowerbounds, upperbounds)
            	g[:,j] .= transfunc(states, Shocks[:,i])
        	else
				# g[:,j] .= inbounds.(transfunc(states, Shocks[i]),
				# 	lowerbounds, upperbounds)
				g[:,j] .= transfunc(states, Shocks[i])
			end
        end
        PhiTemp .= BasisMatrix(basisOutputStates, Expanded(), g', 0).vals[1]
        mG .= mG + vWeights[i] * PhiTemp
    end
    return mG::SparseMatrixCSC{Float64,Int64}

end


text:
"""
`@filter(d, x)`

Filter rows according to the expression `x` row by row in `d`.
Symbols refer to fields of the row.
In this context, `_` refers to the whole row. To use actual symbols, escape them with `^`, as in `^(:a)`.
Use `cols(c)` to refer to field `c` where `c` is a variable that evaluates to a symbol. `c` must be available in
the scope where the macro is called.

## Examples

```jldoctest filter
julia> t = table((a = [1,2,3], b=[2,3,4]));

julia> @filter t :a < 3
Table with 2 rows, 2 columns:
a  b
────
1  2
2  3

julia> @filter t 2*:a > :b
Table with 2 rows, 2 columns:
a  b
────
2  3
3  4
```
"""
macro filter(args...)
    esc(filter_helper(args...))
end

function filter_helper(d, x)
    anon_func, syms = extract_anonymous_function(x, replace_field)
    if !isempty(syms) && !(:(_) in syms)
        fields = Expr(:call, :(JuliaDBMeta.All), syms...)
        :(filter($anon_func, (JuliaDBMeta._table)($d), select = $fields))
    else
        :(filter($anon_func, (JuliaDBMeta._table)($d)))
    end
end

function filter_helper(x)
    i = gensym()
    Expr(:(->), i, filter_helper(i, x))
end


text:
using LinearAlgebra: cross

mutable struct Scanner
    position::Union{Vector{Int}, Nothing}
    beacons::Vector{Vector{Int}}
    Scanner() = new(nothing, Vector{Int}[])
    Scanner(position, beacons) = new(position, beacons)
end

copy(scanner::Scanner) = Scanner(scanner.position, scanner.beacons)

islocated(scanner::Scanner) = !isnothing(scanner.position)

function locate!(scanners::Vector{Scanner})
    if !any(islocated.(scanners))
        scanners[1].position = [0, 0, 0]
    end
    located_scanners = filter(islocated, scanners)
    while length(located_scanners) < length(scanners)
        for located_scanner in located_scanners
            for scanner in scanners
                islocated(scanner) && continue
                orient!(located_scanner, scanner) && push!(located_scanners, scanner)
            end
        end
    end
end

function orient!(origin::Scanner, scanner::Scanner)
    # using LinearAlgebra: cross
    # basis = [
    #     [ 1,  0,  0],
    #     [ 0,  1,  0],
    #     [ 0,  0,  1],
    #     [-1,  0,  0],
    #     [ 0, -1,  0],
    #     [ 0,  0, -1]
    # ]
    # rotations = Matrix{Int}[]
    # for b1 in basis, b2 in basis
    #     b3 = cross(b1, b2)
    #     b3 == [0, 0, 0] && continue
    #     push!(rotations, hcat(b1, b2, b3))
    # end
    rotations = [
        [[ 1  0  0;  0  1  0;  0  0  1]]
        [[ 1  0  0;  0  0 -1;  0  1  0]]
        [[ 1  0  0;  0 -1  0;  0  0 -1]]
        [[ 1  0  0;  0  0  1;  0 -1  0]]
        [[ 0  1  0;  1  0  0;  0  0 -1]]
        [[ 0  0  1;  1  0  0;  0  1  0]]
        [[ 0 -1  0;  1  0  0;  0  0  1]]
        [[ 0  0 -1;  1  0  0;  0 -1  0]]
        [[ 0  1  0;  0  0  1;  1  0  0]]
        [[ 0  0 -1;  0  1  0;  1  0  0]]
        [[ 0 -1  0;  0  0 -1;  1  0  0]]
        [[ 0  0  1;  0 -1  0;  1  0  0]]
        [[-1  0  0;  0  1  0;  0  0 -1]]
        [[-1  0  0;  0  0  1;  0  1  0]]
        [[-1  0  0;  0 -1  0;  0  0  1]]
        [[-1  0  0;  0  0 -1;  0 -1  0]]
        [[ 0  1  0; -1  0  0;  0  0  1]]
        [[ 0  0 -1; -1  0  0;  0  1  0]]
        [[ 0 -1  0; -1  0  0;  0  0 -1]]
        [[ 0  0  1; -1  0  0;  0 -1  0]]
        [[ 0  1  0;  0  0 -1; -1  0  0]]
        [[ 0  0  1;  0  1  0; -1  0  0]]
        [[ 0 -1  0;  0  0  1; -1  0  0]]
        [[ 0  0 -1;  0 -1  0; -1  0  0]]
    ]
    for rotation in rotations
        rotated_scanner = rotate(scanner, rotation)
        position = findposition(origin, rotated_scanner)
        if !isnothing(position)
            scanner.position = position
            scanner.beacons = [position + beacon for beacon in rotated_scanner.beacons]
            return true
        end
    end
    false
end

rotate(scanner::Scanner, rotation::Matrix{Int}) = rotate!(copy(scanner), rotation)

function rotate!(scanner::Scanner, rotation::Matrix{Int})
    scanner.beacons = [rotation * beacon for beacon in scanner.beacons]
    scanner
end

function findposition(origin::Scanner, scanner::Scanner)
    overlaps = Dict{Vector{Int}, Int}()
    for origin_beacon in origin.beacons
        for scanner_beacon in scanner.beacons
            position = origin_beacon - scanner_beacon
            overlaps[position] = get(overlaps, position, 0) + 1
        end
    end
    max_overlaps, position = findmax(overlaps)
    max_overlaps >= 12 && return position
    nothing
end

function Base.parse(::Type{Vector{Scanner}}, lines::Vector{String})
    scanners = Scanner[]
    for line in lines
        if occursin("scanner", line)
            push!(scanners, Scanner())
            continue
        end
        isempty(line) && continue
        scanner = last(scanners)
        push!(scanner.beacons, parse.(Int, split(line, ",")))
    end
    scanners
end

function beacons(scanners::Vector{Scanner})
    beacon_set = Set{Vector{Int}}()
    for scanner in scanners
        for beacon in scanner.beacons
            push!(beacon_set, beacon)
        end
    end
    beacon_set
end

scanners = parse(Vector{Scanner}, readlines("day19/scanners.txt"))

locate!(scanners)

p1 = length(beacons(scanners))

@show p1

p2 = maximum(sum(abs.(s1.position - s2.position)) for s1 in scanners for s2 in scanners)

@show p2


text:
export AbstractNorm, InfNorm, WeightedNorm, EuclideanNorm, distance, norm, init!, update!

using Base: @propagate_inbounds
import LinearAlgebra: norm

"""
    AbstractNorm

An `AbstractNorm` represents any norm of a vector space.
All norms are callable. `norm(x)` computes the norm of `x` and `norm(x,y)` computes the distance
`norm(x - y).`
"""
abstract type AbstractNorm end

"""
    distance(u, v, norm::AbstractNorm)

Compute the distance ||u-v|| with respect to the given norm `norm`.
"""
distance(u, v, norm::AbstractNorm) = MethodError(distance, (u, v, norm))

"""
    norm(u, norm::AbstractNorm)

Compute the norm ||u|| with respect to the given norm `norm`.
"""
LinearAlgebra.norm(u, norm::AbstractNorm) = MethodError(LinearAlgebra.norm, (u, norm))

(N::AbstractNorm)(x) = norm(x, N)
(N::AbstractNorm)(x, y) = distance(x, y, N)

##################
## WeightedNorm ##
##################

Base.@kwdef struct WeightedNormOptions
    scale_min::Float64 = 1e-4
    scale_abs_min::Float64 = 1e-6
    scale_max::Float64 = exp2(div(1023, 2))
end

Base.show(io::IO, opts::WeightedNormOptions) = print_fieldnames(io, opts)
Base.show(io::IO, ::MIME"application/prs.juno.inline", opts::WeightedNormOptions) = opts

"""
    WeightedNorm(d::AbstractVector, norm::AbstractNorm; options...)
    WeightedNorm(norm::AbstractNorm, n::Integer; options...)
    WeightedNorm(norm::AbstractNorm, x::AbstractVector; options...)

A `WeightedNorm` represents a weighted variant of norm `norm` of a `n`-dimensional vector space.`
A norm ``||x||`` is weighted by introducing a vector of additional weights `d` such that
the new norm is ``||D⁻¹x||`` where ``D`` is the diagonal matrix with diagonal ``d`.
The `WeightedNorm` is desigened to change the weights dynamically by using [`init!(::WeightedNorm, x)`](@ref)
and [`update!(::WeightedNorm, x)`](@ref). The weights are there constructed such that
``||D⁻¹x|| ≈ 1.0``.
The weights can be accessed and changed by indexing.

### Options

* `scale_min = sqrt(eps())`: The minimal size of `dᵢ` is `scale_min` time the (weighted) norm of `x`.
* `scale_abs_min = min(scale_min^2, 200 * sqrt(eps()))`: The absolute minimal size of `dᵢ`.
* `scale_max = 1.0 / eps() / sqrt(2)`: The absolute maximal size of `dᵢ`

"""
struct WeightedNorm{N<:AbstractNorm} <: AbstractNorm
    weights::Vector{Float64}
    norm::N
    options::WeightedNormOptions
end
function WeightedNorm(
    weights::Vector{Float64},
    norm::AbstractNorm,
    opts = WeightedNormOptions(),
)
    WeightedNorm(weights, norm, opts)
end
WeightedNorm(norm::AbstractNorm, x::AbstractVector) = WeightedNorm(norm, length(x))
WeightedNorm(norm::AbstractNorm, n::Integer) = WeightedNorm(ones(n), norm)

"""
    weights(WN::WeightedNorm)

Returns the weights of the weighted norm.
"""
weights(WN::WeightedNorm) = WN.weights

Base.length(WN::WeightedNorm) = length(WN.weights)
Base.size(WN::WeightedNorm) = (length(WN),)
Base.ndims(::Type{<:WeightedNorm}) = 1
Base.copyto!(WN::WeightedNorm, x) = copyto!(WN.weights, x)
@propagate_inbounds Base.getindex(WN::WeightedNorm, i::Integer) = getindex(WN.weights, i)
@propagate_inbounds Base.setindex!(WN::WeightedNorm, x, i::Integer) =
    setindex!(WN.weights, x, i)

"""
    init!(w::WeightedNorm, x::AbstractVector)

Setup the weighted norm `w` for `x`.
"""
function init!(w::WeightedNorm, x)
    point_norm = w.norm(x)
    for i = 1:length(w)
        wᵢ = fast_abs(x[i])
        if wᵢ < w.options.scale_min * point_norm
            wᵢ = w.options.scale_min * point_norm
        elseif wᵢ > w.options.scale_max * point_norm
            wᵢ = w.options.scale_max * point_norm
        end
        w[i] = max(wᵢ, w.options.scale_abs_min)
    end
    w
end
init!(n::AbstractNorm, ::Any) = n

"""
    update!(w::WeightedNorm, x)

Update the weighted norm `w` for `x`, this will interpolate between the previous weights
and the norm of `x`.
"""
function update!(w::WeightedNorm, x)
    norm_x = w(x)
    for i = 1:length(x)
        wᵢ = (fast_abs(x[i]) + w[i]) / 2
        if wᵢ < w.options.scale_min * norm_x
            wᵢ = w.options.scale_min * norm_x
        elseif wᵢ > w.options.scale_max * norm_x
            wᵢ = w.options.scale_max * norm_x
        end
        if isfinite(wᵢ)
            w[i] = max(wᵢ, w.options.scale_abs_min)
        end
    end
    w
end
update!(n::AbstractNorm, ::Any) = n

####################
## Specific norms ##
####################
"""
    InfNorm <: AbstractNorm

The infinity or [maximum norm](https://en.wikipedia.org/wiki/Norm_(mathematics)#Maximum_norm).
"""
struct InfNorm <: AbstractNorm end

function distance(x, y, ::InfNorm)
    n = length(x)
    @boundscheck n == length(y)
    @inbounds dmax = abs2(x[1] - y[1])
    for i = 2:n
        @inbounds dᵢ = abs2(x[i] - y[i])
        dmax = Base.FastMath.max_fast(dmax, dᵢ)
    end
    d = sqrt(dmax)
    if isinf(d)
        @inbounds dmax = abs(x[1] - y[1])
        for i = 2:n
            @inbounds dᵢ = abs2(x[i] - y[i])
            dmax = max(dmax, dᵢ)
        end
        return dmax
    else
        return d
    end
end
function distance(x, y, w::WeightedNorm{InfNorm})
    inf_distance(x, y, weights(w))
end
function inf_distance(x, y, w)
    n = length(x)
    @boundscheck n == length(w)
    @inbounds dmax = abs2((x[1] - y[1]) / w[1])
    for i = 2:n
        @inbounds dᵢ = abs2((x[i] - y[i]) / w[i])
        dmax = Base.FastMath.max_fast(dmax, dᵢ)
    end
    d = sqrt(dmax)
    if isinf(d)
        @inbounds dmax = abs((x[1] - y[1]) / w[1])
        for i = 2:n
            @inbounds dᵢ = abs((x[i] - y[i]) / w[i])
            dmax = max(dmax, dᵢ)
        end
        return dmax
    else
        return d
    end
end

function norm(x, ::InfNorm)
    n = length(x)
    @inbounds dmax = abs2(x[1])
    for i = 2:n
        @inbounds dᵢ = abs2(x[i])
        dmax = Base.FastMath.max_fast(dmax, dᵢ)
    end
    d = sqrt(dmax)

    if isinf(d)
        @inbounds dmax = abs(x[1])
        for i = 2:n
            @inbounds dᵢ = abs(x[i])
            dmax = max(dmax, dᵢ)
        end
        return dmax
    else
        return d
    end
end
function norm(x, w::WeightedNorm{InfNorm})
    n = length(x)
    @boundscheck n == length(w)
    @inbounds dmax = abs2(x[1] / w[1])
    for i = 2:n
        @inbounds dᵢ = abs2(x[i] / w[i])
        dmax = Base.FastMath.max_fast(dmax, dᵢ)
    end
    d = sqrt(dmax)

    if isinf(d)
        @inbounds dmax = abs(x[1] / w[1])
        for i = 2:n
            @inbounds dᵢ = abs(x[i] / w[i])
            dmax = max(dmax, dᵢ)
        end
        return dmax
    else
        return d
    end
end

"""
    EuclideanNorm <: AbstractNorm

The Euclidean resp. 2-norm.
"""
struct EuclideanNorm <: AbstractNorm end

@inline function distance(x, y, ::EuclideanNorm)
    n = length(x)
    @boundscheck n == length(y)
    @inbounds d = abs2(x[1] - y[1])
    for i = 2:n
        @inbounds d += abs2(x[i] - y[i])
    end
    sqrt(d)
end
@inline function norm(x, ::EuclideanNorm)
    n = length(x)
    @inbounds d = abs2(x[1])
    for i = 2:n
        @inbounds d += abs2(x[i])
    end
    sqrt(d)
end


text:
#==============================================================================#
# AWSS3.jl
#
# S3 API. See http://docs.aws.amazon.com/AmazonS3/latest/API/APIRest.html
#
# Copyright OC Technology Pty Ltd 2014 - All rights reserved
#==============================================================================#

__precompile__()

module AWSS3

export S3Path, s3_arn, s3_put, s3_get, s3_get_file, s3_exists, s3_delete, s3_copy,
       s3_create_bucket,
       s3_put_cors,
       s3_enable_versioning, s3_delete_bucket, s3_list_buckets,
       s3_list_objects, s3_list_objects_v2, s3_list_keys, s3_list_versions,
       s3_get_meta, s3_purge_versions,
       s3_sign_url, s3_begin_multipart_upload, s3_upload_part,
       s3_complete_multipart_upload, s3_multipart_upload,
       s3_get_tags, s3_put_tags, s3_delete_tags

using AWSCore
using FilePathsBase
using FilePathsBase: /, join
using HTTP
using OrderedCollections: OrderedDict
using SymDict
using Retry
using XMLDict
using EzXML
using Dates
using Base64
using UUIDs

const SSDict = Dict{String,String}

__init__() = FilePathsBase.register(S3Path)

"""
    s3_arn(resource)
    s3_arn(bucket,path)

[Amazon Resource Name](http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)
for S3 `resource` or `bucket` and `path`.
"""
s3_arn(resource) = "arn:aws:s3:::$resource"
s3_arn(bucket, path) = s3_arn("$bucket/$path")

# S3 REST API request.

function s3(aws::AWSConfig,
            verb,
            bucket="";
            headers=SSDict(),
            path="",
            query=SSDict(),
            version="",
            content="",
            return_stream=false,
            return_raw=false,
            return_headers=false)

    # Build query string...
    if version != ""
        query["versionId"] = version
    end
    query_str = HTTP.escapeuri(query)

    resource = string("/", HTTP.escapepath(path),
                      query_str == "" ? "" : "?$query_str")

    # Build Request...
    request = @SymDict(service = "s3",
                       verb,
                       resource,
                       headers,
                       content,
                       return_stream,
                       return_raw,
                       aws...)

    @repeat 3 try

        # Check bucket region cache...
        if haskey(aws, :bucket_region) &&
           haskey(aws[:bucket_region], bucket)
            request[:region] = aws[:bucket_region][bucket]
        end

        # Build URL...
        if haskey(aws, :endpoint)
            if bucket == ""
                url = string(aws[:endpoint], resource)
            else
                url = string(aws[:endpoint], "/", bucket, resource)
            end
        else
            region = get(request, :region, "")
            url = string(get(aws, :protocol, "https"), "://",
                         bucket, bucket == "" ? "" : ".",
                         "s3",
                         region == "" ? "" : ".", region,
                         ".amazonaws.com",
                         resource)
        end
        request[:url] = url

        if return_headers
            response, headers = AWSCore.do_request(request; return_headers=return_headers)
        else
            response = AWSCore.do_request(request; return_headers=return_headers)
        end

        # Handle 301 Moved Permanently with missing Location header.
        # https://github.com/samoconnor/AWSS3.jl/issues/25
        if response isa XMLDict.XMLDictElement &&
           get(response, "Code", "") == "PermanentRedirect" &&
           haskey(response, "Endpoint")

            if AWSCore.debug_level > 0
                println("S3 endpoint redirect $bucket -> $(response["Endpoint"])")
            end
            request[:url] = string(get(aws, :protocol, "https"), "://",
                                   response["Endpoint"], resource)
            return AWSCore.do_request(request; return_headers=return_headers)
        end

        return (return_headers ? (response, headers) : response)

    catch e

        # Update bucket region cache if needed...
        @retry if ecode(e) == "AuthorizationHeaderMalformed" &&
                  haskey(e.info, "Region")

            if AWSCore.debug_level > 0
                println("S3 region redirect $bucket -> $(e.info["Region"])")
            end
            if !haskey(aws, :bucket_region)
                aws[:bucket_region] = SSDict()
            end
            aws[:bucket_region][bucket] = e.info["Region"]
        end
    end

    @assert false # Unreachable.
end

"""
    s3_get([::AWSConfig], bucket, path; <keyword arguments>)

[Get Object](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html)
from `path` in `bucket`.

# Optional Arguments
- `version=`: version of object to get.
- `retry=true`: try again on "NoSuchBucket", "NoSuchKey"
                (common if object was recently created).
- `raw=false`:  return response as `Vector{UInt8}`
                (by default return type depends on `Content-Type` header).
"""
function s3_get(aws::AWSConfig, bucket, path; version="",
                                              retry=true,
                                              raw=false)

    @repeat 4 try

        return s3(aws, "GET", bucket; path = path,
                                      version = version,
                                      return_raw = raw)

    catch e
        @delay_retry if retry && ecode(e) in ["NoSuchBucket", "NoSuchKey"] end
    end
end

s3_get(a...; b...) = s3_get(default_aws_config(), a...; b...)

"""
    s3_get_file([::AWSConfig], bucket, path, filename; [version=])

Like `s3_get` but streams result directly to `filename`.
"""
function s3_get_file(aws::AWSConfig, bucket, path, filename; version="")

    stream = s3(aws, "GET", bucket; path = path,
                                    version = version,
                                    return_stream = true)

    open(filename, "w") do file
        while !eof(stream)
            write(file, readavailable(stream))
        end
    end
end

s3_get_file(a...; b...) = s3_get_file(default_aws_config(), a...; b...)

function s3_get_file(aws::AWSConfig, buckets::Vector, path, filename; version="")

    i = start(buckets)

    @repeat length(buckets) try

        bucket, i = next(buckets, i)
        s3_get_file(aws, bucket, path, filename; version=version);

    catch e
        @retry if ecode(e) in ["NoSuchKey", "AccessDenied"] end
    end
end

"""
   s3_get_meta([::AWSConfig], bucket, path; [version=])

[HEAD Object](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html)

Retrieves metadata from an object without returning the object itself.
"""
function s3_get_meta(aws::AWSConfig, bucket, path; version="")

    s3(aws, "HEAD", bucket; path = path, version = version)
end

s3_get_meta(a...; b...) = s3_get_meta(default_aws_config(), a...; b...)

"""
    s3_exists([::AWSConfig], bucket, path [version=])

Is there an object in `bucket` at `path`?
"""
function s3_exists(aws::AWSConfig, bucket, path; version="")

    @repeat 2 try

        s3_get_meta(aws, bucket, path; version = version)

        return true

    catch e

        @delay_retry if ecode(e) in ["NoSuchBucket", "404",
                                   "NoSuchKey", "AccessDenied"]
        end
        @ignore if ecode(e) in ["404", "NoSuchKey", "AccessDenied"]
            return false
        end
    end
end

s3_exists(a...; b...) = s3_exists(default_aws_config(), a...; b...)

"""
    s3_delete([::AWSConfig], bucket, path; [version=]

[DELETE Object](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html)
"""
function s3_delete(aws::AWSConfig, bucket, path; version="")

    s3(aws, "DELETE", bucket; path = path, version = version)
end

s3_delete(a...; b...) = s3_delete(default_aws_config(), a...; b...)

"""
    s3_copy([::AWSConfig], bucket, path; to_bucket=bucket, to_path=path)

[PUT Object - Copy](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html)

# Optional Arguments
- `metadata::Dict=`; optional `x-amz-meta-` headers.
"""
function s3_copy(aws::AWSConfig, bucket, path;
                 acl::AbstractString="",
                 to_bucket=bucket, to_path=path, metadata::SSDict = SSDict())

    headers = SSDict("x-amz-copy-source" => "/$bucket/$path",
                     "x-amz-metadata-directive" => "REPLACE",
                     Pair["x-amz-meta-$k" => v for (k, v) in metadata]...)

    if !isempty(acl)
        headers["x-amz-acl"] = acl
    end

    s3(aws, "PUT", to_bucket; path = to_path, headers = headers)
end

s3_copy(a...; b...) = s3_copy(default_aws_config(), a...; b...)

"""
    s3_create_bucket([:AWSConfig], bucket)

[PUT Bucket](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUT.html)
"""
function s3_create_bucket(aws::AWSConfig, bucket)
    if AWSCore.debug_level > 0
        println("""Creating Bucket "$bucket"...""")
    end

    @protected try

        if aws[:region] == "us-east-1"

            s3(aws, "PUT", bucket)

        else

            s3(aws, "PUT", bucket;
                headers = SSDict("Content-Type" => "text/plain"),
                content = """
                <CreateBucketConfiguration
                             xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                    <LocationConstraint>$(aws[:region])</LocationConstraint>
                </CreateBucketConfiguration>""")
        end

    catch e
        @ignore if ecode(e) == "BucketAlreadyOwnedByYou" end
    end
end

s3_create_bucket(a) = s3_create_bucket(default_aws_config(), a)

"""
    s3_put_cors([::AWSConfig], bucket, cors_config)

[PUT Bucket cors](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTcors.html)

```
s3_put_cors("my_bucket", \"\"\"
    <?xml version="1.0" encoding="UTF-8"?>
    <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
        <CORSRule>
            <AllowedOrigin>http://my.domain.com</AllowedOrigin>
            <AllowedOrigin>http://my.other.domain.com</AllowedOrigin>
            <AllowedMethod>GET</AllowedMethod>
            <AllowedMethod>HEAD</AllowedMethod>
            <AllowedHeader>*</AllowedHeader>
            <ExposeHeader>Content-Range</ExposeHeader>
        </CORSRule>
    </CORSConfiguration>
\"\"\"
```
"""
function s3_put_cors(aws::AWSConfig, bucket, cors_config)
    s3(aws, "PUT", bucket, path = "?cors", content = cors_config)
end

s3_put_cors(a...) = s3_put_cors(default_aws_config(), a...)

"""
    s3_enable_versioning([::AWSConfig], bucket)

[Enable versioning for `bucket`](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTVersioningStatus.html).
"""
function s3_enable_versioning(aws::AWSConfig, bucket)

    s3(aws, "PUT", bucket;
       query = SSDict("versioning" => ""),
       content = """
       <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
           <Status>Enabled</Status>
       </VersioningConfiguration>""")
end

s3_enable_versioning(a) = s3_enable_versioning(default_aws_config(), a)

"""
    s3_put_tags([::AWSConfig], bucket, [path,] tags::Dict)

PUT `tags` on
[`bucket`](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTtagging.html)
or
[object (`path`)](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUTtagging.html).

See also `tags=` option on [`s3_put`](@ref).
"""
function s3_put_tags(aws::AWSConfig, bucket, tags::SSDict)
    s3_put_tags(aws, bucket, "", tags)
end

function s3_put_tags(aws::AWSConfig, bucket, path, tags::SSDict)

    tags = Dict("Tagging" =>
           Dict("TagSet" =>
           Dict("Tag" =>
           [Dict("Key" => k, "Value" => v) for (k,v) in tags])))

    s3(aws, "PUT", bucket;
       path = path,
       query = SSDict("tagging" => ""),
       content = XMLDict.node_xml(tags))
end

s3_put_tags(a...) = s3_put_tags(default_aws_config(), a...)

"""
    s3_get_tags([::AWSConfig], bucket, [path])

Get tags from
[`bucket`](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETtagging.html)
or
[object (`path`)](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGETtagging.html).
"""
function s3_get_tags(aws::AWSConfig, bucket, path="")

    @protected try

        tags = s3(aws, "GET", bucket; path = path, query = SSDict("tagging" => ""))
        if isempty(tags["TagSet"])
            return SSDict()
        end
        tags = tags["TagSet"]
        tags = isa(tags["Tag"], Vector) ? tags["Tag"] : [tags["Tag"]]

        return SSDict(x["Key"] => x["Value"] for x in tags)

    catch e
        @ignore if ecode(e) == "NoSuchTagSet"
            return SSDict()
        end
    end
end

s3_get_tags(a...) = s3_get_tags(default_aws_config(), a...)

"""
    s3_delete_tags([::AWSConfig], bucket, [path])

Delete tags from
[`bucket`](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketDELETEtagging.html)
or
[object (`path`)](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETEtagging.html).
"""
function s3_delete_tags(aws::AWSConfig, bucket, path="")
    s3(aws, "DELETE", bucket; path = path, query = SSDict("tagging" => ""))
end

s3_delete_tags(a...) = s3_delete_tags(default_aws_config(), a...)

"""
    s3_delete_bucket([::AWSConfig], "bucket")

[DELETE Bucket](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketDELETE.html).
"""
s3_delete_bucket(aws::AWSConfig, bucket) = s3(aws, "DELETE", bucket)

s3_delete_bucket(a) = s3_delete_bucket(default_aws_config(), a)

"""
    s3_list_buckets([::AWSConfig])

[List of all buckets owned by the sender of the request](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTServiceGET.html).
"""
function s3_list_buckets(aws::AWSConfig = default_aws_config())

    r = s3(aws,"GET", headers=SSDict("Content-Type" => "application/json"))
    buckets = r["Buckets"]
    if isempty(buckets)
        return []
    end
    buckets = buckets["Bucket"]
    [b["Name"] for b in (isa(buckets, Vector) ? buckets : [buckets])]
end

"""
    s3_list_objects([::AWSConfig], bucket, [path_prefix]; delimiter="/", max_items=1000)

[List Objects](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html)
in `bucket` with optional `path_prefix`.

Returns an iterator of `Dict`s with keys `Key`, `LastModified`, `ETag`, `Size`,
`Owner`, `StorageClass`.
"""
function s3_list_objects(aws::AWSConfig, bucket, path_prefix=""; delimiter="/", max_items=nothing)
    return Channel() do chnl
        more = true
        num_objects = 0
        next_token = ""

        while more
            q = Dict{String, String}()
            q["list-type"] = "2"

            if path_prefix != ""
                q["prefix"] = path_prefix
            end
            if delimiter != ""
                q["delimiter"] = delimiter
            end
            if max_items !== nothing
                # Note: AWS only returns up to 1000 items
                q["max-keys"] = string(max_items - num_objects)
            end

            if next_token != ""
                q["continuation-token"] = next_token
            end

            @repeat 4 try
                # Request objects
                r = s3(aws, "GET", bucket; query = q)

                # Add each object from the response and update our object count
                if haskey(r, "Contents")
                    l = isa(r["Contents"], Vector) ? r["Contents"] : [r["Contents"]]
                    for object in l
                        put!(chnl, xml_dict(object))
                        num_objects += 1
                    end
                end

                # It's possible that the response has a "CommonPrefixes",
                # in which case we add each prefix from the response and update our count.
                if haskey(r, "CommonPrefixes")
                    l = isa(r["CommonPrefixes"], Vector) ? r["CommonPrefixes"] : [r["CommonPrefixes"]]
                    for prefix in l
                        put!(chnl, xml_dict(prefix))
                        num_objects +=1
                    end
                end

                # Continue looping if the results were truncated and we haven't exceeded out max_items (if specified)
                if r["IsTruncated"] == "true" && (max_items === nothing || num_objects < max_items)
                    more = true
                    next_token = get(r, "NextContinuationToken", "")
                else
                    more = false
                end

            catch e
                @delay_retry if ecode(e) in ["NoSuchBucket"] end
            end
        end
    end
end

s3_list_objects(a...; kwargs...) = s3_list_objects(default_aws_config(), a...; kwargs...)

"""
    s3_list_objects_v2([::AWSConfig], bucket, [path_prefix]; delimiter="/", start_after="", max_items=1000)

[List Objects](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html)
in `bucket` with optional `path_prefix`.

Returns an iterator of `Dict`s with keys `Key`, `LastModified`, `ETag`, `Size`,
`Owner`, `StorageClass`.

This uses the `ListObjectV2` function call.
"""
function s3_list_objects_v2(aws::AWSS3.AWSConfig, bucket, path_prefix=""; delimiter="/", start_after="", max_items=nothing)
    return Channel() do chnl
        more = true
        num_objects = 0
        contoken = ""

        while more
            q = Dict{String, String}("list-type"=>"2")
            if path_prefix != ""
                q["prefix"] = path_prefix
            end
            if delimiter != ""
                q["delimiter"] = delimiter
            end
            if contoken != ""
                q["continuation-token"] = contoken
            end
            if start_after != ""
                q["start-after"] = start_after
            end
            if max_items !== nothing
                q["max-keys"] = string(max_items - num_objects)
            end

            @repeat 4 try
                # Request objects
                r = s3(aws, "GET", bucket; query = q)

                # Add each object from the response and update our object count / marker
                if haskey(r, "Contents")
                    l = isa(r["Contents"], Vector) ? r["Contents"] : [r["Contents"]]
                    for object in l
                        put!(chnl, xml_dict(object))
                        num_objects += 1
                    end
                # It's possible that the response doesn't have "Contents" and just has a prefix,
                # in which case we should just save the next marker and iterate.
                elseif haskey(r, "Prefix")
                    put!(chnl, Dict("Key" => r["Prefix"]))
                    num_objects += 1
                end

                contoken = get(r, "NextContinuationToken", "")

                # Continue looping if the results were truncated and we haven't exceeded out max_items (if specified)
                more = r["IsTruncated"] == "true" && (max_items === nothing || num_objects < max_items)
            catch e
                @delay_retry if ecode(e) in ["NoSuchBucket"] end
            end
        end
    end
end

s3_list_objects_v2(a...; kwargs...) = s3_list_objects_v2(default_aws_config(), a...; kwargs...)

"""
    s3_list_keys([::AWSConfig], bucket, [path_prefix]; delimiter="/", start_after="", max_items=1000)

Like [`s3_list_objects`](@ref) but returns object keys as `Vector{String}`.
"""
function s3_list_keys(aws::AWSConfig, a...; kwargs...)

    [coalesce(get(x, "Key", missing), get(x, "Prefix", missing))
     for x in s3_list_objects_v2(aws,  a...; kwargs...)]

end

s3_list_keys(a...) = s3_list_keys(default_aws_config(), a...; kwargs...)

"""
    s3_list_versions([::AWSConfig], bucket, [path_prefix])

[List object versions](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETVersion.html) in `bucket` with optional `path_prefix`.
"""
function s3_list_versions(aws::AWSConfig, bucket, path_prefix="")

    more = true
    versions = []
    marker = ""

    while more

        query = SSDict("versions" => "", "prefix" => path_prefix)
        if marker != ""
            query["key-marker"] = marker
        end

        r = s3(aws, "GET", bucket; query = query)
        more = r["IsTruncated"] == "true"
        for e in eachelement(EzXML.root(r.x))
            if nodename(e) in ["Version", "DeleteMarker"]
                version = xml_dict(e)
                version["state"] = nodename(e)
                push!(versions, version)
                marker = version["Key"]
            end
        end
    end
    return versions
end

s3_list_versions(a...) = s3_list_versions(default_aws_config(), a...)

"""
    s3_purge_versions([::AWSConfig], bucket, [path [, pattern]])

[DELETE](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html)
all object versions except for the latest version.
"""
function s3_purge_versions(aws::AWSConfig, bucket, path="", pattern="")

    for v in s3_list_versions(aws, bucket, path)
        if pattern == "" || occursin(pattern, v["Key"])
            if v["IsLatest"] != "true"
                s3_delete(aws, bucket, v["Key"]; version = v["VersionId"])
            end
        end
    end
end

s3_purge_versions(a...) = s3_purge_versions(default_aws_config(), a...)

"""
    s3_put([::AWSConfig], bucket, path, data; <keyword arguments>

[PUT Object](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html)
`data` at `path` in `bucket`.

# Optional Arguments
- `data_type=`; `Content-Type` header.
- `encoding=`; `Content-Encoding` header.
- `acl=`; 'x-amz-acl' header for setting access permissions with canned config.
    See [here](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl).
- `metadata::Dict=`; `x-amz-meta-` headers.
- `tags::Dict=`; `x-amz-tagging-` headers
                 (see also [`s3_put_tags`](@ref) and [`s3_get_tags`](@ref)).
"""
function s3_put(aws::AWSConfig,
                bucket, path, data::Union{String,Vector{UInt8}},
                data_type="", encoding="";
                acl::AbstractString="",
                metadata::SSDict = SSDict(),
                tags::SSDict = SSDict())

    if data_type == ""
        data_type = "application/octet-stream"
        ext = splitext(path)[2]
        for (e, t) in [
            (".html", "text/html"),
            (".js",   "application/javascript"),
            (".pdf",  "application/pdf"),
            (".csv",  "text/csv"),
            (".txt",  "text/plain"),
            (".log",  "text/plain"),
            (".dat",  "application/octet-stream"),
            (".gz",   "application/octet-stream"),
            (".bz2",  "application/octet-stream"),
        ]
            if ext == e
                data_type = t
                break
            end
        end
    end

    headers = SSDict("Content-Type" => data_type,
                     Pair["x-amz-meta-$k" => v for (k, v) in metadata]...)

    if !isempty(tags)
        headers["x-amz-tagging"] = HTTP.escapeuri(tags)
    end

    if !isempty(acl)
        headers["x-amz-acl"] = acl
    end

    if encoding != ""
        headers["Content-Encoding"] = encoding
    end

    s3(aws, "PUT", bucket;
       path = path,
       headers = headers,
       content = data)
end

s3_put(a...; b...) = s3_put(default_aws_config(), a...; b...)

function s3_begin_multipart_upload(aws::AWSConfig,
                                   bucket, path,
                                   data_type = "application/octet-stream")

    s3(aws, "POST", bucket; path=path, query = SSDict("uploads"=>""))
end

function s3_upload_part(aws::AWSConfig, upload, part_number, part_data)

    _, headers = s3(aws, "PUT", upload["Bucket"];
                  path = upload["Key"],
                  query = Dict("partNumber" => part_number,
                               "uploadId" => upload["UploadId"]),
                  content = part_data,
                  return_headers = true)

    return Dict(headers)["ETag"]
end

function s3_complete_multipart_upload(aws::AWSConfig,
                                      upload, parts::Vector{String})
    doc = XMLDocument()
    rootnode = setroot!(doc, ElementNode("CompleteMultipartUpload"))

    for (i, etag) in enumerate(parts)
        part = addelement!(rootnode, "Part")
        addelement!(part, "PartNumber", string(i))
        addelement!(part, "ETag", etag)
    end

    response = s3(aws, "POST", upload["Bucket"];
                  path = upload["Key"],
                  query = Dict("uploadId" => upload["UploadId"]),
                  content = string(doc))

    return response
end

function s3_multipart_upload(aws::AWSConfig, bucket, path, io::IOStream,
                             part_size_mb = 50)

    part_size = part_size_mb * 1024 * 1024

    upload = s3_begin_multipart_upload(aws, bucket, path)

    tags = Vector{String}()
    buf = Vector{UInt8}(undef, part_size)

    i = 0
    while (n = readbytes!(io, buf, part_size)) > 0
        if n < part_size
            resize!(buf, n)
        end
        push!(tags, s3_upload_part(aws, upload, (i += 1), buf))
    end

    s3_complete_multipart_upload(aws, upload, tags)
end

using MbedTLS

function _s3_sign_url_v2(
    aws::AWSConfig, bucket, path, seconds=3600;
    verb="GET", content_type="application/octet-stream", protocol="http",
)

    path = HTTP.escapepath(path)

    expires = round(Int, Dates.datetime2unix(now(Dates.UTC)) + seconds)

    query = SSDict("AWSAccessKeyId" =>  aws[:creds].access_key_id,
                   "x-amz-security-token" => aws[:creds].token,
                   "Expires" => string(expires),
                   "response-content-disposition" => "attachment")

    if verb != "PUT"
        content_type = ""
    end

    to_sign = "$verb\n\n$content_type\n$(query["Expires"])\n" *
              "x-amz-security-token:$(query["x-amz-security-token"])\n" *
              "/$bucket/$path?" *
              "response-content-disposition=attachment"

    key = aws[:creds].secret_key
    query["Signature"] = digest(MD_SHA1, to_sign, key) |> base64encode |> strip

    endpoint=string(protocol, "://",
                    bucket, ".s3.", aws[:region], ".amazonaws.com")
    return "$endpoint/$path?$(HTTP.escapeuri(query))"
end

function _s3_sign_url_v4(
    aws::AWSConfig, bucket, path, seconds=3600;
    verb="GET", content_type="application/octet-stream", protocol="http",
)

    path = HTTP.escapepath("/$bucket/$path")

    now_datetime = now(Dates.UTC)
    datetime_stamp = Dates.format(now_datetime, "YYYYmmddTHHMMSSZ")
    date_stamp = Dates.format(now_datetime, "YYYYmmdd")

    service = "s3"
    scheme = "AWS4"
    algorithm = "HMAC-SHA256"
    terminator = "aws4_request"

    scope = "$date_stamp/$(aws[:region])/$service/$terminator"
    host = if aws[:region] == "us-east-1"
        "s3.amazonaws.com"
    else
        "s3-$(aws[:region]).amazonaws.com"
    end

    headers = OrderedDict{String, String}("Host" => host)
    sort!(headers; by = name -> lowercase(name))
    canonical_header_names = join(map(name -> lowercase(name), headers |> keys |> collect), ";")

    query = OrderedDict{String, String}(
        "X-Amz-Expires" => string(seconds),
        "X-Amz-Algorithm" => "$scheme-$algorithm",
        "X-Amz-Credential" => "$(aws[:creds].access_key_id)/$scope",
        "X-Amz-Date" => datetime_stamp,
        "X-Amz-Security-Token" => aws[:creds].token,
        "X-Amz-SignedHeaders" => canonical_header_names
    )

    if !isempty(aws[:creds].token)
        query["X-Amz-Security-Token"] = aws[:creds].token
    end

    sort!(query; by = name -> lowercase(name))

    canonical_headers = join(map(header -> "$(lowercase(header.first)):$(lowercase(header.second))\n", collect(headers)))

    canonical_request = string(
        "$verb\n",
        "$path\n",
        "$(HTTP.escapeuri(query))\n",
        "$canonical_headers\n",
        "$canonical_header_names\n",
        "UNSIGNED-PAYLOAD"
    )

    string_to_sign = string(
        "$scheme-$algorithm\n",
        "$datetime_stamp\n",
        "$scope\n",
        digest(MD_SHA256, canonical_request) |> bytes2hex
    )

    key_secret = string(scheme, aws[:creds].secret_key)
    key_date = digest(MD_SHA256, date_stamp, key_secret)
    key_region = digest(MD_SHA256, aws[:region], key_date)
    key_service = digest(MD_SHA256, service, key_region)
    key_signing = digest(MD_SHA256, terminator, key_service)
    signature = digest(MD_SHA256, string_to_sign, key_signing)

    query["X-Amz-Signature"] = signature |> bytes2hex

    return string(protocol, "://", host, path, "?", HTTP.escapeuri(query))
end

"""
    s3_sign_url([::AWSConfig], bucket, path, [seconds=3600];
                [verb="GET"], [content_type="application/octet-stream"],
                [protocol="http"], [signature_version="v4"])

Create a
[pre-signed url](http://docs.aws.amazon.com/AmazonS3/latest/dev/ShareObjectPreSignedURL.html) for `bucket` and `path` (expires after for `seconds`).

To create an upload URL use `verb="PUT"` and set `content_type` to match
the type used in the `Content-Type` header of the PUT request.

For compatibility, the signature version 2 signing process can be used by setting
`signature_version="v2"` but it is [recommended](https://docs.aws.amazon.com/general/latest/gr/signature-version-2.html) that the default version 4 is used.

```
url = s3_sign_url("my_bucket", "my_file.txt"; verb="PUT")
Requests.put(URI(url), "Hello!")
```
```
url = s3_sign_url("my_bucket", "my_file.txt";
                  verb="PUT", content_type="text/plain")

Requests.put(URI(url), "Hello!";
             headers=Dict("Content-Type" => "text/plain"))
```
"""
function s3_sign_url(
    aws::AWSConfig, bucket, path, seconds=3600;
    verb="GET", content_type="application/octet-stream", protocol="http",
    signature_version="v4",
)

    if signature_version == "v2"
        _s3_sign_url_v2(
            aws, bucket, path, seconds;
            verb=verb, content_type=content_type, protocol=protocol,
        )
    elseif signature_version == "v4"
        _s3_sign_url_v4(
            aws, bucket, path, seconds;
            verb=verb, content_type=content_type, protocol=protocol,
        )
    else
        throw(ArgumentError("Unknown signature version $signature_version"))
    end
end

s3_sign_url(a...;b...) = s3_sign_url(default_aws_config(), a...;b...)

include("s3path.jl")

end #module AWSS3

#==============================================================================#
# End of file.
#==============================================================================#


text:
# This file is a part of RRRMC.jl. License is MIT: http://github.com/carlobaldassi/RRRMC.jl/LICENCE.md

module ThreeSpin

using ExtractMacro
using ..Interface

export GraphThreeSpin

import ..Interface: energy, delta_energy, neighbors, allΔE, getN

struct GraphThreeSpin <: DiscrGraph{Int}
end

@doc """
    GraphThreeSpin() <: DiscrGraph{Int}

A trivial `DiscrGraph` type with 3 spins, ferromagnetic interactions
(\$J=1\$), no fields, and periodic boundary conditions.

Only useful for testing/debugging purposes.
""" -> GraphThreeSpin()

getN(::GraphThreeSpin) = 3

function energy(X::GraphThreeSpin, C::Config)
    @extract C : N s
    @assert N == 3
    return -(2s[1] - 1) * (2s[2] - 1) - (2s[2] - 1) * (2s[3] - 1) - (2s[3] - 1) * (2s[1] - 1)
end

function delta_energy(X::GraphThreeSpin, C::Config, move::Int)
    @extract C : N s
    @assert 1 ≤ move ≤ N
    @assert N == 3

    ΔE = 0
    1 ≤ move ≤ 2 && (ΔE += 2 * (2s[1] - 1) * (2s[2] - 1))
    2 ≤ move ≤ 3 && (ΔE += 2 * (2s[2] - 1) * (2s[3] - 1))
    (move == 1 || move == 3) && (ΔE += 2 * (2s[3] - 1) * (2s[1] - 1))

    return ΔE
end

#neighbors(X::GraphThreeSpin, i::Int) = return i == 1 ? (2,) : i == 2 ? (1, 3) : (2,)
neighbors(X::GraphThreeSpin, i::Int) = return (mod1(i-1, 3), mod1(i+1,3))
allΔE(::Type{GraphThreeSpin}) = (0, 4)

end


text:
@testset "1189.maximum-number-of-balloons.jl" begin
    @test max_num_of_ballons("nlaebolko") == 1
    @test max_num_of_ballons("loonbalxballpoon") == 2
    @test max_num_of_ballons("leetcode") == 0
    @test max_num_of_ballons("balloonnnbaloo") == 1
end


text:
export @set, @lens, @set!
using MacroTools

"""
    @set assignment

Return a modified copy of deeply nested objects.

# Example
```jldoctest
julia> using Setfield

julia> struct T;a;b end

julia> t = T(1,2)
T(1, 2)

julia> @set t.a = 5
T(5, 2)

julia> t
T(1, 2)

julia> t = @set t.a = T(2,2)
T(T(2, 2), 2)

julia> @set t.a.b = 3
T(T(2, 3), 2)
```
"""
macro set(ex)
    setmacro(identity, ex, overwrite=false)
end

"""
    @set! assignment

Shortcut for `obj = @set obj...`.

julia> t = (a=1,)
(a = 1,)

julia> @set! t.a=2
(a = 2,)

julia> t
(a = 2,)
"""
macro set!(ex)
    setmacro(identity, ex, overwrite=true)
end

is_interpolation(x) = x isa Expr && x.head == :$

foldtree(op, init, x) = op(init, x)
foldtree(op, init, ex::Expr) =
    op(foldl((acc, x) -> foldtree(op, acc, x), ex.args; init=init), ex)

need_dynamic_lens(ex) =
    foldtree(false, ex) do yes, x
        yes || x === :end || x === :_
    end

replace_underscore(ex, to) = postwalk(x -> x === :_ ? to : x, ex)

function lower_index(collection::Symbol, index, dim)
    if isexpr(index, :call)
        return Expr(:call, lower_index.(collection, index.args, dim)...)
    elseif index === :end
        if dim === nothing
            return :($(Base.lastindex)($collection))
        else
            return :($(Base.lastindex)($collection, $dim))
        end
    end
    return index
end

function parse_obj_lenses(ex)
    if @capture(ex, front_[indices__])
        obj, frontlens = parse_obj_lenses(front)
        if any(is_interpolation, indices)
            if !all(is_interpolation, indices)
                throw(ArgumentError(string(
                    "Constant and non-constant indexing (i.e., indices",
                    " with and without \$) cannot be mixed.")))
            end
            index = esc(Expr(:tuple, [x.args[1] for x in indices]...))
            lens = :($ConstIndexLens{$index}())
        elseif any(need_dynamic_lens, indices)
            @gensym collection
            indices = replace_underscore.(indices, collection)
            dims = length(indices) == 1 ? nothing : 1:length(indices)
            lindices = esc.(lower_index.(collection, indices, dims))
            lens = :($DynamicIndexLens($(esc(collection)) -> ($(lindices...),)))
        else
            index = esc(Expr(:tuple, indices...))
            lens = :($IndexLens($index))
        end
    elseif @capture(ex, front_.property_)
        property isa Union{Symbol,String} || throw(ArgumentError(
            string("Error while parsing :($ex). Second argument to `getproperty` can only be",
                   "a `Symbol` or `String` literal, received `$property` instead.")
        ))
        obj, frontlens = parse_obj_lenses(front)
        lens = :($PropertyLens{$(QuoteNode(property))}())
    elseif @capture(ex, f_(front_))
        obj, frontlens = parse_obj_lenses(front)
        lens = :($FunctionLens($(esc(f))))
    else
        obj = esc(ex)
        return obj, ()
    end
    obj, tuple(frontlens..., lens)
end

function parse_obj_lens(ex)
    obj, lenses = parse_obj_lenses(ex)
    lens = Expr(:call, compose, lenses...)
    obj, lens
end

function get_update_op(sym::Symbol)
    s = String(sym)
    if !endswith(s, '=') || isdefined(Base, sym)
        # 'x +=' etc. is actually 'x = x +', and so '+=' isn't defined in Base.
        # '>=' however is a function, and not an assignment operator.
        msg = "Operation $sym doesn't look like an assignment"
        throw(ArgumentError(msg))
    end
    Symbol(s[1:end-1])
end

struct _UpdateOp{OP,V}
    op::OP
    val::V
end
(u::_UpdateOp)(x) = u.op(x, u.val)

"""
    setmacro(lenstransform, ex::Expr; overwrite::Bool=false)

This function can be used to create a customized variant of [`@set`](@ref).
It works by applying `lenstransform` to the lens that is used in the customized `@set` macro
at runtime.
```julia
function mytransform(lens::Lens)::Lens
    ...
end
macro myset(ex)
    setmacro(mytransform, ex)
end
```
See also [`lensmacro`](@ref).
"""
function setmacro(lenstransform, ex::Expr; overwrite::Bool=false)
    @assert ex.head isa Symbol
    @assert length(ex.args) == 2
    ref, val = ex.args
    obj, lens = parse_obj_lens(ref)
    dst = overwrite ? obj : gensym("_")
    val = esc(val)
    ret = if ex.head == :(=)
        quote
            lens = ($lenstransform)($lens)
            $dst = $set($obj, lens, $val)
        end
    else
        op = get_update_op(ex.head)
        f = :($_UpdateOp($op,$val))
        quote
            lens = ($lenstransform)($lens)
            $dst = $modify($f, $obj, lens)
        end
    end
    ret
end

"""
    @lens

Construct a lens from a field access.

# Example

```jldoctest
julia> using Setfield

julia> struct T;a;b;end

julia> t = T("A1", T(T("A3", "B3"), "B2"))
T("A1", T(T("A3", "B3"), "B2"))

julia> l = @lens _.b.a.b
(@lens _.b.a.b)

julia> get(t, l)
"B3"

julia> set(t, l, 100)
T("A1", T(T("A3", 100), "B2"))

julia> t = ("one", "two")
("one", "two")

julia> set(t, (@lens _[1]), "1")
("1", "two")
```

"""
macro lens(ex)
    lensmacro(identity, ex)
end

"""
    lensmacro(lenstransform, ex::Expr)

This function can be used to create a customized variant of [`@lens`](@ref).
It works by applying `lenstransform` to the created lens at runtime.
```julia
function mytransform(lens::Lens)::Lens
    ...
end
macro mylens(ex)
    lensmacro(mytransform, ex)
end
```
See also [`setmacro`](@ref).
"""
function lensmacro(lenstransform, ex)
    obj, lens = parse_obj_lens(ex)
    if obj != esc(:_)
        msg = """Cannot parse lens $ex. Lens expressions must start with _, got $obj instead."""
        throw(ArgumentError(msg))
    end
    :($(lenstransform)($lens))
end

has_atlens_support(l::Lens) = has_atlens_support(typeof(l))
has_atlens_support(::Type{<:Lens}) = false
has_atlens_support(::Type{<:Union{PropertyLens, IndexLens, ConstIndexLens, FunctionLens, IdentityLens}}) =
    true
has_atlens_support(::Type{ComposedLens{LO, LI}}) where {LO, LI} =
    has_atlens_support(LO) && has_atlens_support(LI)

print_application(io::IO, l::PropertyLens{field}) where {field} = print(io, ".", field)
print_application(io::IO, l::IndexLens) = print(io, "[", join(repr.(l.indices), ", "), "]")
print_application(io::IO, ::ConstIndexLens{I}) where I =
    print(io, "[", join(string.("\$", I), ", "), "]")
print_application(io::IO, l::IdentityLens) = print(io, "")

function print_application(io::IO, l::ComposedLens)
    print_application(io, l.outer)
    print_application(io, l.inner)
end

function print_application(printer, io, ::FunctionLens{f}) where f
    print(io, f, '(')
    printer(io)
    print(io, ')')
end

function print_application(printer, io, l)
    @assert has_atlens_support(l)
    printer(io)
    print_application(io, l)
end

function print_application(printer, io, l::ComposedLens)
    print_application(io, l.inner) do io
        print_application(printer, io, l.outer)
    end
end

# Since `show` of `ComposedLens` needs to call `show` of other lenses,
# we explicitly define text/plain `show` for `ComposedLens` to propagate
# the "context" (2-arg or 3-arg `show`) with which `show` has to be called.
# See: https://github.com/jw3126/Setfield.jl/pull/86
Base.show(io::IO, ::MIME"text/plain", l::ComposedLens) =
    _show(io, MIME("text/plain"), l)

function _show(io::IO, mime, l::Lens)
    if has_atlens_support(l)
        print_in_atlens(io, l)
    elseif mime === nothing
        show(io, l)
    else
        show(io, mime, l)
    end
end

function _show(io::IO, mime, l::ComposedLens)
    if has_atlens_support(l)
        print_in_atlens(io, l)
    else
        _show(io, mime, l.outer)
        print(io, " ∘ ")
        _show(io, mime, l.inner)
    end
end

function print_in_atlens(io, l)
    print(io, "(@lens ")
    print_application(io, l) do io
        print(io, '_')
    end
    print(io, ')')
end

function Base.show(io::IO, T::Type{<:FunctionLens{S}}) where S
   print(io, "typeof")
   show(io, T())
end


text:
fs()
write(joinpath("_layout", "head.html"), "")
write(joinpath("_layout", "foot.html"), "")
write(joinpath("_layout", "page_foot.html"), "")
write("config.md", "")

@testset "fill2" begin
    write("index.md", """
        @def var = 5
        {{fill var page2}}
        """)
    write("page2.md", """
        @def var = 7
        {{fill var index}}
        """)
    F.serve(single=true, clear=true, cleanup=false)
    index = joinpath("__site", "index.html")
    pg2   = joinpath("__site", "page2", "index.html")
    @test isapproxstr(read(index, String), """

7

        """)
    @test isapproxstr(read(pg2, String), """

5

        """)
end


text:
using Mimi

# STEP 1 DEFINE THE COMPONENTS

# gross economy component
@defcomp grosseconomy begin
    Y = Variable(index = [time]) # Gross output
    K = Variable(index = [time]) # Capital
    L = Parameter(index = [time]) # Labor
    TFP = Parameter(index = [time]) # Total factor productivity
    s = Parameter(index = [time]) # Savings rate
    δ = Parameter() # Depreciation rate on capital
    k0 = Parameter() # Initial level of capital
    β = Parameter() # Capital share

    function run_timestep(p, v, d, t)
        # Define an equation for K
        if is_first(t)
            # Note the use of v. and p. to distinguish between variables
            # and parameters
            v.K[t] = p.k0
        else
            v.K[t] = (1 - p.δ) * v.K[t-1] + v.Y[t-1] * p.s[t-1]
        end

        # Define an equation for YGROSS
        v.Y[t] = p.TFP[t] * v.K[t]^p.β * p.L[t]^(1-p.β)
    end
end

# component for greenhouse gas emissions
@defcomp emissions begin
    E = Variable(index = [time]) # Total greenhouse gas emissions
    M = Variable(index = [time]) # Energy consumption

    ω = Parameter(index = [time]) # CO2 intensity of energy mix
    ϵ = Parameter(index = [time]) # energy intensity of output
    Y = Parameter(index = [time]) # Gross output - now a Parameter

    function run_timestep(p, v, d, t)
        # equation for M
        v.M[t] = p.ϵ[t] * p.Y[t] # note the p. in front of gross
        # Define an equation for E
        v.E[t] = p.ω[t] * v.M[t]
    end
end

# component for climate
@defcomp climate begin
    CO2_AT = Variable(index = [time]) # Atmospheric CO2 concentration
    CO2_UP = Variable(index = [time]) # Upper ocean CO2 concentration
    CO2_LO = Variable(index = [time]) # Lower ocean CO2 concentration
    F = Variable(index = [time]) # Radiative forcing
    FEX = Variable(index = [time]) # Radiative forcing due to non CO2
    T_AT = Variable(index = [time]) # Atmospheric temperature over pre-industrial levels
    T_LO = Variable(index = [time]) # Lower ocean tempature over pre-industrial levels

    CO2_AT_0 = Parameter() # Initial value for CO2_AT
    CO2_UP_0 = Parameter() # Initial value for CO2_UP
    CO2_LO_0 = Parameter() # Initial value for CO2_LO
    F_0 = Parameter() # Initial value for F
    FEX_0 = Parameter() # Initial value for FEX
    T_AT_0 = Parameter() # Initial value for T_AT
    T_LO_0 = Parameter() # Initial value for T_LO

    CO2_AT_PRE = Parameter() # Pre-industrial CO2 concentration in atmosphere
    CO2_LO_PRE = Parameter() # Pre-industrial CO2 concentration in lower ocean
    CO2_UP_PRE = Parameter() # Pre-industrial CO2 concentration in upper ocean
    F2CO2 = Parameter() # Increase in radiative forcing (since the pre-industrial period) due to doubling of CO2
    fex = Parameter() # Annual increase in radiative forcing (since the pre-industrial level)
    ϕ_11 = Parameter() # Transfer coefficient for carbon from the atmosphere to the atmosphere
    ϕ_12 = Parameter() # Transfer coefficient for carbon from the atmosphere to the upper ocean/biosphere
    ϕ_21 = Parameter() # Transfer coefficient for carbon from the upper ocean/biosphere to the atmosphere
    ϕ_22 = Parameter() # Transfer coefficient for carbon from the upper ocean/biosphere to the upper ocean/biosphere
    ϕ_23 = Parameter() # Transfer coefficient for carbon from the upper ocean/biosphere to the lower ocean
    ϕ_32 = Parameter() # Transfer coefficient for carbon from the lower ocean to the upper ocean/biosphere
    ϕ_33 = Parameter() # Transfer coefficient for carbon from the lower ocean to the lower ocean
    t_1 = Parameter() # Speed of adjustment parameter in the atmospheric temperature equation
    t_2 = Parameter() # Coefficient of heat loss from the atmosphere to the lower ocean (atmospheric temperature equation)
    t_3 = Parameter() # Coefficient of heat loss from the atmosphere to the lower ocean (lower ocean temperature equation)
    S = Parameter() # Equilibrium climate sensitivity

    E = Parameter(index = [time]) # Total greenhouse gas emissions

    function run_timestep(p, v, d, t)
        if is_first(t)
            v.CO2_AT[t] = p.CO2_AT_0
            v.CO2_UP[t] = p.CO2_UP_0
            v.CO2_LO[t] = p.CO2_LO_0
            v.FEX[t] = p.FEX_0
            v.F[t] = p.F_0
            v.T_AT[t] = p.T_AT_0
            v.T_LO[t] = p.T_LO_0
        else
            # Equation for CO2_AT
            v.CO2_AT[t] = p.E[t] + p.ϕ_11 * v.CO2_AT[t-1] + p.ϕ_21 * v.CO2_UP[t-1]
            # Equation for CO2_UP
            v.CO2_UP[t] = p.ϕ_12 * v.CO2_AT[t-1] + p.ϕ_22 * v.CO2_UP[t-1] + p.ϕ_32 * v.CO2_LO[t-1]
            # Equation for CO2_LO
            v.CO2_LO[t] = p.ϕ_23 * v.CO2_UP[t-1] + p.ϕ_22 * v.CO2_LO[t-1]
            # radiative forcing other
            v.FEX[t] = v.FEX[t-1] + p.fex
            # Radiative forcing
            v.F[t] = p.F2CO2 * log2(v.CO2_AT[t]/p.CO2_AT_PRE) + v.FEX[t]
            # Atmospheric temperature
            v.T_AT[t] = v.T_AT[t-1] + p.t_1 * (v.F[t] - p.F2CO2 / p.S * v.T_AT[t-1] - p.t_2 * (v.T_AT[t-1] - v.T_LO[t-1]))
            # Lower ocean temperature
            v.T_LO[t] = v.T_LO[t-1] + p.t_3 * (v.T_AT[t-1] - v.T_LO[t-1])
        end
    end
end

# component for damages
@defcomp damages begin
    D = Variable(index = [time]) # Damages (in % of gross output)
    Ω = Variable(index  = [time]) # Damages in USD

    η₁ = Parameter() # Parameter of damage function
    η₂ = Parameter() # Parameter of damage function
    η₃ = Parameter() # Parameter of damage function

    T_AT = Parameter(index = [time]) # Atmospheric temperature increase
    Y = Parameter(index = [time]) # Gross output

    function run_timestep(p, v, d, t)

        v.D[t] = 1 - 1 / (1 + p.η₁ * p.T_AT[t] + p.η₂ * p.T_AT[t]^2 + p.η₃ * p.T_AT[t]^6.754)
        v.Ω[t] = v.D[t] * p.Y[t]

    end

end

# STEP 2: CONSTRUCT A MODEL BY BINDING BOTH COMPONENTS

# if anay variables of one component are parameters for another, connect_param! is
# used to couple the two

function construct_model(;marginal = false)
    m = Model()

    set_dimension!(m, :time, collect(2015:1:2100))

    # Order matters here, if the emissions component defined first, error
    add_comp!(m, grosseconomy)
    add_comp!(m, emissions)
    add_comp!(m, climate)
    add_comp!(m, damages)

    """
    update_param! used to assign values each component parameter
    with an external connection to an unshared model param
    """
    # Update parameters for the grosseconomy component
    update_param!(m, :grosseconomy, :L, [(1. + 0.003)^t * 6.404 for t in 1:86])
    update_param!(m, :grosseconomy, :TFP, [(1 + 0.01)^t * 3.57 for t in 1:86])
    update_param!(m, :grosseconomy, :s, ones(86) .* 0.22)
    update_param!(m, :grosseconomy, :δ, 0.1)
    update_param!(m, :grosseconomy, :k0, 130.)
    update_param!(m, :grosseconomy, :β, 0.3)

    # update parameters for the emissions component
    update_param!(m, :emissions, :ω, [(1. - 0.002)^t * 0.07 for t in 1:86])

    if marginal
        pulse = 1/1e10 # units of emissions are Gt then we convert 1 ton of pulse in Gt
        update_param!(m, :emissions, :ϵ, [(1. - 0.002)^t * (7.92 + pulse) for t in 1:86])
    else
        update_param!(m, :emissions, :ϵ, [(1. - 0.002)^t * 7.92 for t in 1:86])
    end

    # update parameters for the climate component
    update_param!(m, :climate, :CO2_AT_0, 3120)
    update_param!(m, :climate, :CO2_UP_0, 5628.8)
    update_param!(m, :climate, :CO2_LO_0, 36706.7)
    update_param!(m, :climate, :F_0, 2.30)
    update_param!(m, :climate, :FEX_0, 0.28)
    update_param!(m, :climate, :T_AT_0, 1.0)
    update_param!(m, :climate, :T_LO_0, 0.0068)

    update_param!(m, :climate, :CO2_AT_PRE, 2156.2)
    update_param!(m, :climate, :CO2_LO_PRE, 36670.0)
    update_param!(m, :climate, :CO2_UP_PRE, 4950.5)
    update_param!(m, :climate, :F2CO2, 3.8)
    update_param!(m, :climate, :fex, 0.005)
    update_param!(m, :climate, :ϕ_11, 0.9817)
    update_param!(m, :climate, :ϕ_12, 0.0183)
    update_param!(m, :climate, :ϕ_21, 0.0080)
    update_param!(m, :climate, :ϕ_22, 0.9915)
    update_param!(m, :climate, :ϕ_23, 0.0005)
    update_param!(m, :climate, :ϕ_32, 0.0001)
    update_param!(m, :climate, :ϕ_33, 0.9999)
    update_param!(m, :climate, :t_1, 0.027)
    update_param!(m, :climate, :t_2, 0.018)
    update_param!(m, :climate, :t_3, 0.005)
    update_param!(m, :climate, :S, 3)

    # update parameters for damages function

    update_param!(m, :damages, :η₁, 0)
    update_param!(m, :damages, :η₂, 0.00284)
    update_param!(m, :damages, :η₃, 0.000005)

    # connect parameters for the emissions component
    connect_param!(m, :emissions, :Y, :grosseconomy, :Y)

    # connect parameters for the climate component
    connect_param!(m, :climate, :E, :emissions, :E)

    # connect parameters for the damages component
    connect_param!(m, :damages, :T_AT, :climate, :T_AT)
    connect_param!(m, :damages, :Y, :grosseconomy, :Y)

    return m
end

# STEP 3 RUN THE MODEL

# run the model

m = construct_model()
run(m)

# check model results
getdataframe(m, :climate, :T_AT)

# STEP 4 VISUALIZE
# plot model results
Mimi.plot(m, :grosseconomy, :Y)
Mimi.plot(m, :emissions, :E)
Mimi.plot(m, :climate, :T_AT)
Mimi.plot(m, :damages, :D)

# observe all model result graphs in UI
explore(m)

# STEP 5 PRESENT VALUE OF DAMAGES IN BASELINE SCENARIO

discount_rate = 0.035
discount_factors = [(1/(1 + discount_rate))^((t-1)) for t in 1:86]
damages_usd = getdataframe(m, :damages, :Ω)
present_value_damages = sum(discount_factors .* damages_usd[:,:Ω])

# STEP 6 RUN THE MARGINAL MODEL

m2 = construct_model(marginal = true)
run(m2)

damages_usd_m2 = getdataframe(m2, :damages, :Ω)
present_value_damages_m2 = sum(discount_factors .* damages_usd_m2[:,:Ω])

scc = (present_value_damages_m2 - present_value_damages) * 10^12

max_stars_repo_name: tlorans/SustainableFinance

max_issues_repo_name: tlorans/SustainableFinance

max_forks_repo_name: tlorans/SustainableFinance


text:
Base.@kwdef mutable struct AlgorithmDesc
    name::String
    params = []
    method = nothing
end

function algorithm_gui(algorithm_desc)
    function ()
        algorithms = ["NSGA-II", "MOEA/D"]
        @cstatic item_current = "MOEA/D" begin
            if CImGui.BeginCombo("Algorithm", item_current)
                for n = 0:length(algorithms)-1
                    is_selected = item_current == algorithms[n+1]
                    if CImGui.Selectable(algorithms[n+1], is_selected)
                        item_current = algorithms[n+1]
                        update_algorithm_desc(algorithm_desc, algorithms[n+1])
                    end
                    # set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch)
                    is_selected && CImGui.SetItemDefaultFocus()
                end
                CImGui.EndCombo()
            end
        end
        for param_desc in algorithm_desc.params
            eval(param_gui(algorithm_desc, param_desc))
        end
    end
end

function get_algorithm_desc(algorithm_name)
    if algorithm_name == "MOEA/D"
        AlgorithmDesc(name = algorithm_name,
                      params = [ParamDesc(name="N", type="Int",
                                          value=100, default_value=100,
                                          desc="Population size",
                                          min=2, max=10000),
                                ParamDesc(name="T", type="Int",
                                          value=10, default_value=10,
                                          desc="Number of solutions",
                                          min=2, max=100)])
    elseif algorithm_name == "NSGA-II"
        AlgorithmDesc(name = algorithm_name,
                      params = [ParamDesc(name = "N", type = "Int",
                                          value = 100, default_value = 100,
                                          desc = "Population size",
                                          min = 2, max = 100)])
    end
end

function update_algorithm_desc(algorithm_desc, algorithm_name, param_name=nothing, param_value=nothing)
    should_update = algorithm_desc.method === nothing ? true : false
    if algorithm_name !== nothing && algorithm_desc.name != algorithm_name
        new_algorithm_desc = get_algorithm_desc(algorithm_name)
        algorithm_desc.name = new_algorithm_desc.name
        algorithm_desc.params = new_algorithm_desc.params
        should_update = true
    else
        for param in algorithm_desc.params
            if param.name == param_name
                if param.max !== nothing && param_value > param.max
                        param_value = param.max
                    end
                    if param.min !== nothing && param_value < param.min
                        param_value = param.min
                    end
                if param.value != param_value
                    param.value = param_value
                    should_update = true
                end
            end
        end
    end
    if should_update
        if algorithm_desc.name == "MOEA/D"
            N = get_algorithm_param(algorithm_desc, "N")
            T = get_algorithm_param(algorithm_desc, "T")
            algorithm_desc.method = MOEAD(N=N, T=T)
        elseif algorithm_desc.name == "NSGA-II"
            N = get_algorithm_param(algorithm_desc, "N")
            algorithm_desc.method = NSGAII(populationSize=N)
        end
    end
end

function get_algorithm_param(algorithm_desc, param_name)
    for param in algorithm_desc.params
        if param.name == param_name
            return param.value === nothing ? param.default_value : param.value
        end
    end
end