text:
# -*- encoding: utf-8 -*-
#
# The MIT License (MIT)
#
# Copyright © 2021 Matteo Foglieni and Riccardo Gervasoni
#

"""
    parse_tonemapping_settings(dict::Dict{String, Any})
        :: (String, String, Float64, Float64, Bool)

Parse a `Dict{String, T} where {T}` for the [`tone_mapping`](@ref) function.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(pfm, png, a, γ, ONLY_FOR_TESTS)` containing:

- `pfm::String = dict["infile"]` : input pfm filename (required)

- `png::String = dict["outfile"]` : output LDR filename (required)

- `a::Float64 = string2positive(dict["normalization"])` : scale factor (default = 0.18); it's converted
  through `string2positive` to a positive floating point number.

- `γ::Float64 = string2positive(dict["gamma"])` : gamma factor (default = 1.0); it's converted
  through `string2positive` to a positive floating point number.

- `lum::Union{Number, Nothing} = string2positive(dict["avg_lum"])` : average luminosity of the image; it's
  converted through `string2positive` to a positive floating point number. If not specified or equal to 0,
  it's calculated through [`avg_lum`](@ref)

- `ONLY_FOR_TESTS::Bool = dict["ONLY_FOR_TESTS"]` : it's a bool variable conceived only to
  test the correct behaviour of the renderer for the input arguments; if set to `true`,
  no rendering is made!

See also:  [`tone_mapping`](@ref), [`string2positive`](@ref)
"""
function parse_tonemapping_settings(dict::Dict{String, T}) where {T}

    keys = ["infile", "outfile", "normalization", "gamma", "avg_lum", "ONLY_FOR_TESTS"]

    for pair in dict
        if (pair[1] in keys) ==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for tonemapping function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    haskey(dict, "infile") ?
        pfm::String = dict["infile"] :
        throw(ArgumentError("need to specify the input pfm file to be tonemapped"))

    haskey(dict, "outfile") ?
        png::String = dict["outfile"] :
        throw(ArgumentError("need to specify the output LDR filename to be saved"))

    a::Float64 = haskey(dict, "normalization") ? string2positive(dict["normalization"]) : 0.18

    γ::Float64 = haskey(dict, "gamma") ? string2positive(dict["gamma"]) : 1.0

    lum::Union{Float64, Nothing} = haskey(dict, "avg_lum") ? string2positive(dict["avg_lum"]) : nothing

    ONLY_FOR_TESTS::Bool = haskey(dict, "ONLY_FOR_TESTS") ? dict["ONLY_FOR_TESTS"] : false

    return (pfm, png, a, γ, lum, ONLY_FOR_TESTS)
end

##########################################################################################92

"""
    parse_onoff_settings(dict::Dict{String, T}) where {T}
        :: (World, RGB{Float32}, RGB{Float32})

Parse a `Dict{String, T} where {T}` for the initialisation of a `OnOffRenderer`.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(World(), background_color, color)` containing the
following variables (the corresponding keys are also showed):

- `World():: World` : is the default constructor of the `World` class, which creates
  an empty world; it will be populated in the function that will use the renderer.

- `background_color::RGB{Float32} = string2color(dict["background_color"])` : set the
  color returned by a light ray which does not hit any object in the scene; the default
  value is `BLACK`, i.e. `RGB{Float32}(0.0, 0.0, 0.0)`.
  The input color value `dict["background_color"]` must be a `String` written in RGB
  components as `"< R , G , B >"`, and it's parsed with the `string2color` function.

- `color::RGB{Float32} = string2color(dict["color"])` : set the color returned by a
  light ray which does hit any object in the scene; the default value is `WHITE`, i.e.
  `RGB{Float32}(1.0, 1.0, 1.0)`.
  The input color value `dict["color"]` must be a `String` written in RGB
  components as `"< R , G , B >"`, and it's parsed with the `string2color` function.

See also:  [`Renderer`](@ref), [`OnOffRenderer`](@ref),
[`World`](@ref), [`string2color`](@ref)
"""
function parse_onoff_settings(dict::Dict{String, T}) where {T}

    keys = [
        "background_color", "color",
    ]

    for pair in dict
        if (pair[1] in keys)==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for onoff-renderer function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    background_color::RGB{Float32} = haskey(dict, "background_color") ?
        string2color(dict["background_color"]) :
        RGB{Float32}(0.0, 0.0, 0.0)

    color::RGB{Float32} = haskey(dict, "color") ?
        string2color(dict["color"]) :
        RGB{Float32}(1.0, 1.0, 1.0)

    return (World(), background_color, color)
end

"""
    parse_flat_settings(dict::Dict{String, T}) where {T}
        :: (World, RGB{Float32})

Parse a `Dict{String, T} where {T}` for the initialisation of a `FlatRenderer`.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(World(), background_color)` containing the following variables
(the corresponding keys are also showed):

- `World():: World` : is the default constructor of the `World` class, which creates
  an empty world; it will be populated in the function that will use the renderer.

- `background_color::RGB{Float32} = string2color(dict["background_color"])` : set the
  color returned by a light ray which does not hit any object in the scene; the default
  value is `BLACK`, i.e. `RGB{Float32}(0.0, 0.0, 0.0)`.
  The input color value `dict["background_color"]` must be a `String` written in RGB
  components as `"< R , G , B >"`, and it's parsed with the `string2color` function.

See also:  [`Renderer`](@ref), [`FlatRenderer`](@ref),
[`World`](@ref), [`string2color`](@ref)
"""
function parse_flat_settings(dict::Dict{String, T}) where {T}

    keys = [
        "background_color",
    ]

    for pair in dict
        if (pair[1] in keys)==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for onoff-renderer function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    background_color::RGB{Float32} =haskey(dict, "background_color") ?
        string2color(dict["background_color"]) :
        RGB{Float32}(0.0, 0.0, 0.0)

    return (World(), background_color,)
end

"""
    parse_pathtracer_settings(dict::Dict{String, T}) where {T}
        :: (World(), RGB{Float32}, PCG, Int64, Int64, Int64)

Parse a `Dict{String, T} where {T}` for the initialisation of a `PathTracer`.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(World(), background_color, PCG(init_state, init_seq), num_of_rays,
max_depth, russian_roulette_limit, ONLY_FOR_TESTS)` containing the following variables
(the corresponding keys are also showed):

- `World():: World` : is the default constructor of the `World` class, which creates
  an empty world; it will be populated in the function that will use the renderer.

- `background_color::RGB{Float32} = string2color(dict["background_color"])` : set the
  color returned by a light ray which does not hit any object in the scene; the default
  value is `BLACK`, i.e. `RGB{Float32}(0.0, 0.0, 0.0)`.
  The input color value `dict["background_color"]` must be a `String` written in RGB
  components as `"< R , G , B >"`, and it's parsed with the `string2color` function.

- `PCG(init_state, init_seq)::PCG` : a mutable struct of the Permuted Congruential
  Generator (PCG), which is a uniform pseudo-random number generator; you can pass as
  input two usigned integer that initialize the generator:
  - `init_state::UInt64 = string2int64(dict["init_state"], true)` : set the initial state
    of the PCG; the input value `dict["init_state"]` is parsed thanks to the `string2int64`
    function.
  - `init_seq::UInt64 = string2int64(dict["init_seq"], true)` : set the initial sequence
    of the PCG; the input value `dict["init_seq"]` is parsed thanks to the `string2int64`
    function.

- `num_of_rays::Int64 = string2int64(dict["num_of_rays"])` : set the number of secondary
  rays that will be fired from each surface point hitted by a light ray; the input value
  `dict["num_of_rays"]` is parsed thanks to the `string2int64` function.

- `max_depth::Int64 = string2int64(dict["max_depth"])` : set the maximum depth number that
  the secondary rays are allowed to have; if that value is exceeded, no more secondary rays
  are generated in the hitten points, and the returned color is `BLACK`; the input value
  `dict["max_depth"]` is parsed thanks to the `string2int64` function.

- `russian_roulette_limit::Int64 = string2int64(dict["russian_roulette_limit"])` : set the
  depth over which a secondary ray is created with the russian roulette algorithm; the input
  value `dict["russian_roulette_limit"]` is parsed thanks to the `string2int64` function.

See also:  [`Renderer`](@ref), [`PathTracer`](@ref),
[`World`](@ref), [`PCG`](@ref), [`string2color`](@ref), [`string2int64`](@ref)
"""
function parse_pathtracer_settings(dict::Dict{String, T}) where {T}

    keys = [
        "init_state", "init_seq",
        "background_color", "num_of_rays", "max_depth",
        "russian_roulette_limit",
    ]

    for pair in dict
        if (pair[1] in keys) ==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for path-renderer function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    init_state::UInt64 = haskey(dict, "init_state") ? string2int64(dict["init_state"], true) : UInt64(54)

    init_seq::UInt64 = haskey(dict, "init_seq") ? string2int64(dict["init_seq"], true) : UInt64(45)

    background_color::RGB{Float32} =haskey(dict, "background_color") ?
        string2color(dict["background_color"]) :
        RGB{Float32}(0.0, 0.0, 0.0)

    num_of_rays::Int64 = haskey(dict, "num_of_rays") ? string2int64(dict["num_of_rays"]) : 10

    max_depth::Int64 = haskey(dict, "max_depth") ? string2int64(dict["max_depth"]) : 2

    russian_roulette_limit::Int64 = haskey(dict, "russian_roulette_limit") ?
        string2int64(dict["russian_roulette_limit"]) : 2

    return (World(),
            background_color,
            PCG(init_state, init_seq),
            num_of_rays, max_depth,
            russian_roulette_limit,
            )
end

"""
    parse_pointlight_settings(dict::Dict{String, T}) where {T}
        :: (World, RGB{Float32}, RGB{Float32})

Parse a `Dict{String, T} where {T}` for the initialisation of a `PointLightRenderer`.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(World(), background_color, color, ONLY_FOR_TESTS)` containing the following variables
(the corresponding keys are also showed):

- `World():: World` : is the default constructor of the `World` class, which creates
  an empty world; it will be populated in the function that will use the renderer.

- `background_color::RGB{Float32} = string2color(dict["background_color"])` : set the
  color returned by a light ray which does not hit any object in the scene;
  the default value is `BLACK`, i.e. `RGB{Float32}(0.0, 0.0, 0.0)`.
  The input color value `dict["background_color"]` must be a `String` written in RGB
  components as `"< R , G , B >"`, and it's parsed with the `string2color` function.

- `ambient_color::RGB{Float32} = string2color(dict["ambient_color"])` : set the minimum
  color returned by a light ray which hits an object on a point, indipendently that is
  or is not directy visible from any of the point-light sources in the scene; the default
  value is `BLACK`, i.e. `RGB{Float32}(0.0, 0.0, 0.0)`.
  The input color value `dict["ambient_color"]` must be a `String` written in RGB
  components as `"< R , G , B >"`, and it's parsed with the `string2color` function.

- `dark_parameter::Float64 = string2positive(dict["dark_parameter"])` : float that defines
  the retuned percentage of the hit point color if it is not visible from any of the point-light
  source of the image; a non-zero value allows to see also the not-directly
  illuminated parts of the image. The default value is 0.05.

See also:  [`Renderer`](@ref), [`PointLightRenderer`](@ref),
[`World`](@ref), [`string2color`](@ref)
"""
function parse_pointlight_settings(dict::Dict{String, T}) where {T}

    keys = [
        "background_color", "ambient_color", "dark_parameter"
    ]

    for pair in dict
        if (pair[1] in keys)==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for onoff-renderer function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    background_color::RGB{Float32} =haskey(dict, "background_color") ?
        string2color(dict["background_color"]) :
        RGB{Float32}(0.0, 0.0, 0.0)

    ambient_color::RGB{Float32} =haskey(dict, "ambient_color") ?
        string2color(dict["ambient_color"]) :
        RGB{Float32}(0.0, 0.0, 0.0)

    dark_parameter::Float64 = haskey(dict, "dark_parameter") ?
        string2positive(dict["dark_parameter"]) : 0.05

    return (World(), background_color, ambient_color, dark_parameter)
end

##########################################################################################92

"""
    parse_demo_settings(dict::Dict{String, T}) where {T}
        :: (
            Renderer, Point, Vec, Float64, Int64, Int64, String, String,
            Int64, String, Bool, Bool, Bool,
            )

Parse a `Dict{String, T} where {T}` for the `demo` function.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(renderer, camera_type, camera_position, α, width, height, pfm, png,
samples_per_pixel, world_type, bool_print, bool_savepfm)` containing the
following variables (the corresponding keys are also showed):

- `renderer::Renderer = haskey(dict, "renderer") ? dict["renderer"] : dict["%COMMAND%"]` :
  it's the renderer to be used (with a default empy `World` that will be populated
  in the `demo` function); the possible keys are two, and must be used differently:
    - the `dict["renderer"]` must contain the renderer itself (i.e. a `Renderer` object);
      if this key exists, it has the priority on the latter key
    - the `dict["%COMMAND%"]` must contain a string that identifies the type of renderer
      to be used, i.e. one of the following strings:
      - `"dict["%COMMAND%"]=>onoff"` -> [`OnOffRenderer`](@ref) algorithm
      - `"dict["%COMMAND%"]=>"flat"` -> [`FlatRenderer`](@ref) algorithm (default value)
      - `"dict["%COMMAND%"]=>"pathtracing"` -> [`PathTracer`](@ref) algorithm
      - `"dict["%COMMAND%"]=>"pointlight"` -> [`PointLightRenderer`](@ref) algorithm
      Moreover, in this second case, you can specify the options for the correspoding
      renderer through another dictionary associated with the kkey of the renderer name;
      that options will be parsed thanks to the corresponding functions.
      We shows in the next lines the key-value syntax described:
      - `"onoff"=>Dict{String, T} where {T}` : parsed with [`parse_onoff_settings`](@ref)
      - `"flat"=>Dict{String, T} where {T}` : parsed with [`parse_flat_settings`](@ref)
      - `"pathtracer"=>Dict{String, T} where {T}` : parsed with [`parse_pathtracer_settings`](@ref)
      - `"pointlight"=>Dict{String, T} where {T}` : parsed with [`parse_pointlight_settings`](@ref)

- `camera_type::String = dict["camera_type"]` : set the perspective projection view;
  it must be one of the following values, and this is checked with the
  `string2stringoneof` function:
  - `"per"` -> set [`PerspectiveCamera`](@ref)  (default value)
  - `"ort"`  -> set [`OrthogonalCamera`](@ref)

- `camera_position::String = typeof(dict["camera_position"]) ∈ [Vec, Point] ? dict["camera_position"] :
  string2vector(dict["camera_position"]) ` : "[X, Y, Z]" coordinates of the
  choosen observation point of view; it can be specified in two ways:
  - if `typeof(dict["camera_position"])` is a `Vec` or a `Point`, it's passed as-is
  - else, it must be a `String` written in the form "[X, Y, Z]" , and it's parsed through
    string2vector` to a `Vec` object

- `α::String = dict["alpha"]` : choosen angle of rotation _*IN RADIANTS*_ respect to vertical (i.e. z)
  axis with a right-handed rule convention (clockwise rotation for entering (x,y,z)-axis
  corresponds to a positive input rotation angle)

- `width::Int64 = string2evenint64(dict["width"])` : number of pixels on the horizontal
  axis to be rendered; it's converted through `string2evenint64` to a even positive integer.

- `height::Int64 = string2evenint64(dict["height"])` : number of pixels on the vertical
  axis to be rendered; it's converted through `string2evenint64` to a even positive integer.

- `a::Float64 = string2positive(dict["normalization"])` : scale factor (default = 0.18); it's converted
  through `string2positive` to a positive floating point number.

- `γ::Float64 = string2positive(dict["gamma"])` : gamma factor (default = 1.0); it's converted
  through `string2positive` to a positive floating point number.

- `lum::Union{Number, Nothing} = string2positive(dict["avg_lum"])` : average luminosity of the image; it's
  converted through `string2positive` to a positive floating point number. If not specified or equal to 0,
  it's calculated through [`avg_lum`](@ref)

- `pfm::String = dict["set_pfm_name"]` : output pfm filename (default `"demo.pfm"`)

- `png::String` = dict["set_png_name"]` : output LDR filename (default `"demo.png"`)

- `samples_per_pixel::Int64  = dict["samples_per_pixel"]` : number of ray to be
  generated for each pixel, implementing the anti-aliasing algorithm; it must be
  a perfect integer square (0,1,4,9,...) and this is checked with the
  `string2rootint64` function; if 0 (default value) is choosen, no anti-aliasing
  occurs, and only one pixel-centered ray is fired for each pixel.

- `world_type::String = dict["world_type"]` : type of the world to be rendered; it
  must be `"A"` or `"B"`, and this is checked with the `string2stringoneof` function

- `bool_print::Bool = dict["bool_print"]` : if `true` (default value), WIP message of
  `demo` function are printed (otherwise no; it's useful for the `demo_animation`
  function)

- `bool_savepfm::Bool = dict["bool_savepfm"]` : if `true` (default value), `demo`
  function saves the pfm file to disk (otherwise no; it's useful for the
  `demo_animation` function)

- `ONLY_FOR_TESTS::Bool = dict["ONLY_FOR_TESTS"]` : it's a bool variable conceived only to
  test the correct behaviour of the renderer for the input arguments; if set to `true`,
  no rendering is made!

See also:  [`demo`](@ref), [`demo_animation`](@ref), [`Renderer`](@ref),
[`Vec`](@ref), [`string2evenint64`](@ref), [`string2stringoneof`](@ref),
[`string2positive`](@ref), [`string2vector`](@ref), [`string2rootint64`](@ref)
"""
function parse_demo_settings(dict::Dict{String, T}) where {T}

    keys = union([
        "%COMMAND%", "renderer",
        "camera_type", "camera_position",
        "alpha", "width", "height",
        "normalization", "gamma", "avg_lum",
        "set_pfm_name", "set_png_name",
        "samples_per_pixel", "world_type",
        "bool_print", "bool_savepfm",
        "ONLY_FOR_TESTS",
    ], RENDERERS)

    for pair in dict
        if (pair[1] in keys) ==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for demo function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    renderer = if haskey(dict, "renderer")
        dict["renderer"]
    elseif haskey(dict, "%COMMAND%")
        if dict["%COMMAND%"] == "onoff"
            options = haskey(dict, "onoff") ? dict["onoff"] : Dict{String, Any}()
            OnOffRenderer(parse_onoff_settings(options)...)
        elseif dict["%COMMAND%"] == "flat"
            options = haskey(dict, "flat") ? dict["flat"] : Dict{String, Any}()
            FlatRenderer(parse_flat_settings(options)...)
        elseif dict["%COMMAND%"] == "pathtracer"
            options = haskey(dict, "pathtracer") ? dict["pathtracer"] : Dict{String, Any}()
            PathTracer(parse_pathtracer_settings(options)...)
        elseif dict["%COMMAND%"] == "pointlight"
            options = haskey(dict, "pointlight") ? dict["pointlight"] : Dict{String, Any}()
            PointLightRenderer(parse_pointlight_settings(options)...)
        end
	else
		FlatRenderer()
	end

    camera_type::String = haskey(dict, "camera_type") ? string2stringoneof(dict["camera_type"], CAMERAS) : "per"

    camera_position::Vec = haskey(dict, "camera_position") ? begin
            typeof(dict["camera_position"]) ∈ [Vec, Point] ?
            dict["camera_position"] :
            string2vector(dict["camera_position"])
        end : Vec(-1.0 , 0. , 0.)

    α::Float64 = haskey(dict, "alpha") ? begin
        typeof(dict["alpha"]) <: Number ?
            dict["alpha"] :
            parse(Float64, dict["alpha"])
        end : 0.

    width::Int64 = haskey(dict, "width") ? string2evenint64(dict["width"]) : 640

    height::Int64 = haskey(dict, "height") ? string2evenint64(dict["height"]) : 480

    a::Float64 = haskey(dict, "normalization") ? string2positive(dict["normalization"]) : 0.18

    γ::Float64 = haskey(dict, "gamma") ? string2positive(dict["gamma"]) : 1.0

    lum::Union{Float64, Nothing} = haskey(dict, "avg_lum") ? string2positive(dict["avg_lum"]) : nothing

    pfm::String = haskey(dict, "set_pfm_name") ? dict["set_pfm_name"] : "demo.pfm"

    png::String = haskey(dict, "set_png_name") ? dict["set_png_name"] : "demo.png"

    world_type::String = haskey(dict, "world_type") ?
        string2stringoneof(dict["world_type"], DEMO_WORLD_TYPES) : "A"

    samples_per_pixel::Int64 = haskey(dict, "samples_per_pixel") ?
        string2rootint64(dict["samples_per_pixel"])^2 : 0

    bool_print::Bool = haskey(dict, "bool_print") ?  dict["bool_print"] : true

    bool_savepfm::Bool = haskey(dict, "bool_savepfm") ? dict["bool_savepfm"] : true

    ONLY_FOR_TESTS::Bool = haskey(dict, "ONLY_FOR_TESTS") ? dict["ONLY_FOR_TESTS"] : false

    return (
            renderer,
            camera_type,
            camera_position,
            α,
            width, height,
            a, γ, lum,
            pfm, png,
            samples_per_pixel,
            world_type,
            bool_print, bool_savepfm,
            ONLY_FOR_TESTS,
        )
end

"""
    parse_demoanimation_settings(dict::Dict{String, T}) where {T}
        :: (
            Renderer, String, Vec, Int64, Int64, String,
            Int64, String, Bool,
            )

Parse a `Dict{String, T} where {T}` for the `demo_animation` function.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(renderer, camera_type, camera_position, width, height, anim,
samples_per_pixel, world_type)` containing the following variables
(the corresponding keys are also showed):

- `renderer::Renderer = haskey(dict, "renderer") ? dict["renderer"] : dict["%COMMAND%"]` :
  it's the renderer to be used (with a default empy `World` that will be populated
  in the `demo` function); the possible keys are two, and must be used differently:
    - the `dict["renderer"]` must contain the renderer itself (i.e. a `Renderer` object);
      if this key exists, it has the priority on the latter key
    - the `dict["%COMMAND%"]` must contain a string that identifies the type of renderer
      to be used, i.e. one of the following strings:
      - `"dict["%COMMAND%"]=>onoff"` -> [`OnOffRenderer`](@ref) algorithm
      - `"dict["%COMMAND%"]=>"flat"` -> [`FlatRenderer`](@ref) algorithm (default value)
      - `"dict["%COMMAND%"]=>"pathtracing"` -> [`PathTracer`](@ref) algorithm
      - `"dict["%COMMAND%"]=>"pointlight"` -> [`PointLightRenderer`](@ref) algorithm
      Moreover, in this second case, you can specify the options for the correspoding
      renderer through another dictionary associated with the kkey of the renderer name;
      that options will be parsed thanks to the corresponding functions.
      We shows in the next lines the key-value syntax described:
      - `"onoff"=>Dict{String, T} where {T}` : parsed with [`parse_onoff_settings`](@ref)
      - `"flat"=>Dict{String, T} where {T}` : parsed with [`parse_flat_settings`](@ref)
      - `"pathtracer"=>Dict{String, T} where {T}` : parsed with [`parse_pathtracer_settings`](@ref)
      - `"pointlight"=>Dict{String, T} where {T}` : parsed with [`parse_pointlight_settings`](@ref)

- `camera_type::String = dict["camera_type"]` : set the perspective projection view;
  it must be one of the following values, and this is checked with the
  `string2stringoneof` function:
  - `"per"` -> set [`PerspectiveCamera`](@ref)  (default value)
  - `"ort"`  -> set [`OrthogonalCamera`](@ref)

- `camera_position::String = typeof(dict["camera_position"]) ∈ [Vec, Point] ? dict["camera_position"] :
  string2vector(dict["camera_position"]) ` : "[X, Y, Z]" coordinates of the
  choosen observation point of view; it can be specified in two ways:
  - if `typeof(dict["camera_position"])` is a `Vec` or a `Point`, it's passed as-is
  - else, it must be a `String` written in the form "[X, Y, Z]" , and it's parsed through
    string2vector` to a `Vec` object

- `width::Int64 = string2evenint64(dict["width"])` : number of pixels on the horizontal
  axis to be rendered; it's converted through `string2evenint64` to a even positive integer.

- `height::Int64 = string2evenint64(dict["height"])` : number of pixels on the vertical
  axis to be rendered; it's converted through `string2evenint64` to a even positive integer.

- `a::Float64 = string2positive(dict["normalization"])` : scale factor (default = 0.18); it's converted
  through `string2positive` to a positive floating point number.

- `γ::Float64 = string2positive(dict["gamma"])` : gamma factor (default = 1.0); it's converted
  through `string2positive` to a positive floating point number.

- `lum::Union{Number, Nothing} = string2positive(dict["avg_lum"])` : average luminosity of the image; it's
  converted through `string2positive` to a positive floating point number. If not specified or equal to 0,
  it's calculated through [`avg_lum`](@ref)

- `anim::String = dict["set_anim_name"]` : output animation filename (default
  `"demo_animation.mp4"`)

- `samples_per_pixel::Int64  = dict["samples_per_pixel"]` : number of ray to be
  generated for each pixel, implementing the anti-aliasing algorithm; it must be
  a perfect integer square (0,1,4,9,...) and this is checked with the
  `string2rootint64` function; if 0 (default value) is choosen, no anti-aliasing
  occurs, and only one pixel-centered ray is fired for each pixel.

- `world_type::String = dict["world_type"]` : type of the world to be rendered; it
  must be `"A"` or `"B"`, and this is checked with the `string2stringoneof` function

- `ONLY_FOR_TESTS::Bool = dict["ONLY_FOR_TESTS"]` : it's a bool variable conceived only to
  test the correct behaviour of the renderer for the input arguments; if set to `true`,
  no rendering is made!

See also:  [`demo`](@ref), [`demo_animation`](@ref), [`Renderer`](@ref),
[`Vec`](@ref), [`string2evenint64`](@ref), [`string2stringoneof`](@ref),
[`string2positive`](@ref), [`string2vector`](@ref), [`string2rootint64`](@ref)
"""
function parse_demoanimation_settings(dict::Dict{String, T}) where {T}

    keys = union([
        "%COMMAND%", "renderer",
        "camera_type", "camera_position",
        "width", "height",
        "normalization", "gamma", "avg_lum", "world_type",
        "set_anim_name", "samples_per_pixel",
        "ONLY_FOR_TESTS",
    ], RENDERERS)

    for pair in dict
        if (pair[1] in keys) ==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for demo_animation function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    renderer = if haskey(dict, "renderer")
        dict["renderer"]
    elseif haskey(dict, "%COMMAND%")
        if dict["%COMMAND%"] == "onoff"
            options = haskey(dict, "onoff") ? dict["onoff"] : Dict{String, Any}()
            OnOffRenderer(parse_onoff_settings(options)...)
        elseif dict["%COMMAND%"] == "flat"
            options = haskey(dict, "flat") ? dict["flat"] : Dict{String, Any}()
            FlatRenderer(parse_flat_settings(options)...)
        elseif dict["%COMMAND%"] == "pathtracer"
            options = haskey(dict, "pathtracer") ? dict["pathtracer"] : Dict{String, Any}()
            PathTracer(parse_pathtracer_settings(options)...)
        elseif dict["%COMMAND%"] == "pointlight"
            options = haskey(dict, "pointlight") ? dict["pointlight"] : Dict{String, Any}()
            PointLightRenderer(parse_pointlight_settings(options)...)
        end
	else
		FlatRenderer()
	end

    camera_type::String = haskey(dict, "camera_type") ? string2stringoneof(dict["camera_type"], CAMERAS) : "per"

    camera_position::Vec = haskey(dict, "camera_position") ? begin
            typeof(dict["camera_position"]) ∈ [Vec, Point] ?
            dict["camera_position"] :
            string2vector(dict["camera_position"])
        end : Vec(-1.0 , 0. , 0.)

    width::Int64 = haskey(dict, "width") ? string2evenint64(dict["width"]) : 640

    height::Int64 = haskey(dict, "height") ? string2evenint64(dict["height"]) : 480

    a::Float64 = haskey(dict, "normalization") ? string2positive(dict["normalization"]) : 0.18

    γ::Float64 = haskey(dict, "gamma") ? string2positive(dict["gamma"]) : 1.0

    lum::Union{Float64, Nothing} = haskey(dict, "avg_lum") ? string2positive(dict["avg_lum"]) : nothing

    anim::String = haskey(dict, "set_anim_name") ? dict["set_anim_name"] : "demo_animation.mp4"

    samples_per_pixel::Int64 = haskey(dict, "samples_per_pixel") ?
        string2rootint64(dict["samples_per_pixel"])^2 : 0

    world_type::String = haskey(dict, "world_type") ?
        string2stringoneof(dict["world_type"], DEMO_WORLD_TYPES) : "A"

    ONLY_FOR_TESTS::Bool = haskey(dict, "ONLY_FOR_TESTS") ? dict["ONLY_FOR_TESTS"] : false

    return (renderer,
            camera_type, camera_position,
            width, height, a, γ, lum, anim,
            samples_per_pixel, world_type,
            ONLY_FOR_TESTS
            )
end

##########################################################################################92

"""
    parse_render_settings(dict::Dict{String, T}) where {T}
        :: (
            String, Renderer, String, Vec, Float64, Int64, Int64, String,
            String, Int64, Bool, Bool, Dict{String, Float64}, Bool,
            )

Parse a `Dict{String, T} where {T}` for the `render` function.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(scenefile, renderer, camera_type, camera_position, α, width, height, pfm, png,
samples_per_pixel, world_type, bool_print, bool_savepfm, declare_float, ONLY_FOR_TESTS)`
containing the following variables (the corresponding keys are also showed):

- `scenefile::String = dict["scenefile"]` : name of the scene file to be rendered;
  it must be written with the correct syntax, see the [`tutorial_basic_sintax.txt`](../examples/tutorial_basic_sintax.txt)
  and the [`demo_world_B.txt`](../examples/demo_world_B.txt) files.

- `renderer::Renderer = haskey(dict, "renderer") ? dict["renderer"] : dict["%COMMAND%"]` :
  it's the renderer to be used (with a default empy `World` that will be populated
  in the `demo` function); the possible keys are two, and must be used differently:
    - the `dict["renderer"]` must contain the renderer itself (i.e. a `Renderer` object);
      if this key exists, it has the priority on the latter key
    - the `dict["%COMMAND%"]` must contain a string that identifies the type of renderer
      to be used, i.e. one of the following strings:
      - `"dict["%COMMAND%"]=>onoff"` -> [`OnOffRenderer`](@ref) algorithm
      - `"dict["%COMMAND%"]=>"flat"` -> [`FlatRenderer`](@ref) algorithm (default value)
      - `"dict["%COMMAND%"]=>"pathtracing"` -> [`PathTracer`](@ref) algorithm
      - `"dict["%COMMAND%"]=>"pointlight"` -> [`PointLightRenderer`](@ref) algorithm
      Moreover, in this second case, you can specify the options for the correspoding
      renderer through another dictionary associated with the kkey of the renderer name;
      that options will be parsed thanks to the corresponding functions.
      We shows in the next lines the key-value syntax described:
      - `"onoff"=>Dict{String, T} where {T}` : parsed with [`parse_onoff_settings`](@ref)
      - `"flat"=>Dict{String, T} where {T}` : parsed with [`parse_flat_settings`](@ref)
      - `"pathtracer"=>Dict{String, T} where {T}` : parsed with [`parse_pathtracer_settings`](@ref)
      - `"pointlight"=>Dict{String, T} where {T}` : parsed with [`parse_pointlight_settings`](@ref)

- `camera_type::String = dict["camera_type"]` : set the perspective projection view;
  it must be one of the following values, and this is checked with the
  `string2stringoneof` function:
  - `"per"` -> set [`PerspectiveCamera`](@ref)  (default value)
  - `"ort"`  -> set [`OrthogonalCamera`](@ref)

- `camera_position::String = typeof(dict["camera_position"]) ∈ [Vec, Point] ? dict["camera_position"] :
  string2vector(dict["camera_position"]) ` : "[X, Y, Z]" coordinates of the
  choosen observation point of view; it can be specified in two ways:
  - if `typeof(dict["camera_position"])` is a `Vec` or a `Point`, it's passed as-is
  - else, it must be a `String` written in the form "[X, Y, Z]" , and it's parsed through
    string2vector` to a `Vec` object

- `α::String = dict["alpha"]` : choosen angle of rotation _*IN RADIANTS*_ respect to vertical (i.e. z)
  axis with a right-handed rule convention (clockwise rotation for entering (x,y,z)-axis
  corresponds to a positive input rotation angle)

- `width::Int64 = string2evenint64(dict["width"])` : number of pixels on the horizontal
  axis to be rendered; it's converted through `string2evenint64` to a even positive integer.

- `height::Int64 = string2evenint64(dict["height"])` : number of pixels on the vertical
  axis to be rendered; it's converted through `string2evenint64` to a even positive integer.

- `a::Float64 = string2positive(dict["normalization"])` : scale factor (default = 0.18); it's converted
  through `string2positive` to a positive floating point number.

- `γ::Float64 = string2positive(dict["gamma"])` : gamma factor (default = 1.0); it's converted
  through `string2positive` to a positive floating point number.

- `lum::Union{Number, Nothing} = string2positive(dict["avg_lum"])` : average luminosity of the image; it's
  converted through `string2positive` to a positive floating point number. If not specified or equal to 0,
  it's calculated through [`avg_lum`](@ref)

- `pfm::String = dict["set_pfm_name"]` : output pfm filename (default `"scene.pfm"`)

- `png::String` = dict["set_png_name"]` : output LDR filename (default `"scene.png"`)

- `samples_per_pixel::Int64  = dict["samples_per_pixel"]` : number of ray to be
  generated for each pixel, implementing the anti-aliasing algorithm; it must be
  a perfect integer square (0,1,4,9,...) and this is checked with the
  `string2rootint64` function; if 0 (default value) is choosen, no anti-aliasing
  occurs, and only one pixel-centered ray is fired for each pixel.

- `bool_print::Bool = dict["bool_print"]` : if `true` (default value), WIP message of
  `demo` function are printed (otherwise no; it's useful for the `demo_animation`
  function)

- `bool_savepfm::Bool = dict["bool_savepfm"]` : if `true` (default value), `demo`
  function saves the pfm file to disk (otherwise no; it's useful for the
  `demo_animation` function)

- `declare_float::Union{Dict{String, Float64}, Nothing} = declare_float2dict(dict["declare_float"])`
  : an option for the command line to manually override the values of the float variables in
  the scene file.
  The input `dict["declare_float"]` must be a `String` written such as `"var1:0.1, var2 : 2.5"`;
  such a string is parsed through the `declare_float2dict` in a `Dict{String, Float64}`
  where each overriden variable name (the key) is associated with its float value
  (`declare_float=>Dict("var1"=>0.1, "var2"=>2.5)`)

- `ONLY_FOR_TESTS::Bool = dict["ONLY_FOR_TESTS"]` : it's a bool variable conceived only to
  test the correct behaviour of the renderer for the input arguments; if set to `true`,
  no rendering is made!

See also:  [`render`](@ref),  [`Renderer`](@ref),
[`Vec`](@ref), [`string2evenint64`](@ref), [`string2stringoneof`](@ref),
[`string2positive`](@ref), [`string2vector`](@ref), [`string2rootint64`](@ref),
[`declare_float2dict`](@ref)
"""
function parse_render_settings(dict::Dict{String, T}) where {T}
    keys = union([
        "scenefile",
        "%COMMAND%", "renderer",
        "camera_type", "camera_position",
        "alpha", "width", "height",
        "normalization", "gamma", "avg_lum",
        "set_pfm_name", "set_png_name",
        "samples_per_pixel",
        "bool_print", "bool_savepfm",
        "declare_float",
        "ONLY_FOR_TESTS",
    ], RENDERERS)

    for pair in dict
        if (pair[1] in keys)==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for demo function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    haskey(dict, "scenefile") ?
        scenefile::String = dict["scenefile"] :
        throw(ArgumentError("need to specify the input scenefile to be rendered"))

    renderer = if haskey(dict, "renderer")
        dict["renderer"]
    elseif haskey(dict, "%COMMAND%")
    if dict["%COMMAND%"] == "onoff"
        options = haskey(dict, "onoff") ? dict["onoff"] : Dict{String, Any}()
        OnOffRenderer(parse_onoff_settings(options)...)
    elseif dict["%COMMAND%"] == "flat"
        options = haskey(dict, "flat") ? dict["flat"] : Dict{String, Any}()
        FlatRenderer(parse_flat_settings(options)...)
    elseif dict["%COMMAND%"] == "pathtracer"
        options = haskey(dict, "pathtracer") ? dict["pathtracer"] : Dict{String, Any}()
        PathTracer(parse_pathtracer_settings(options)...)
    elseif dict["%COMMAND%"] == "pointlight"
        options = haskey(dict, "pointlight") ? dict["pointlight"] : Dict{String, Any}()
        PointLightRenderer(parse_pointlight_settings(options)...)
    end
    else
        FlatRenderer()
    end

    camera_type::Union{String, Nothing} = haskey(dict, "camera_type") && !isnothing(dict["camera_type"])  ?
        string2stringoneof(dict["camera_type"], CAMERAS) :
        nothing

    camera_position::Vec = haskey(dict, "camera_position") ? begin
            typeof(dict["camera_position"]) ∈ [Vec, Point] ?
            dict["camera_position"] :
            string2vector(dict["camera_position"])
        end : Vec(-1.0 , 0. , 0.)

    α::Float64 = haskey(dict, "alpha") ? begin
        typeof(dict["alpha"]) <: Number ?
            dict["alpha"] :
            parse(Float64, dict["alpha"])
        end : 0.

    width::Int64 = haskey(dict, "width") ? string2evenint64(dict["width"]) : 640

    height::Int64 = haskey(dict, "height") ? string2evenint64(dict["height"]) : 480

    a::Float64 = haskey(dict, "normalization") ? string2positive(dict["normalization"]) : 0.18

    γ::Float64 = haskey(dict, "gamma") ? string2positive(dict["gamma"]) : 1.0

    lum::Union{Float64, Nothing} = haskey(dict, "avg_lum") ? string2positive(dict["avg_lum"]) : nothing

    pfm::String = haskey(dict, "set_pfm_name") ? dict["set_pfm_name"] : "scene.pfm"

    png::String = haskey(dict, "set_png_name") ? dict["set_png_name"] : "scene.png"

    bool_print::Bool = haskey(dict, "bool_print") ?  dict["bool_print"] : true

    bool_savepfm::Bool = haskey(dict, "bool_savepfm") ? dict["bool_savepfm"] : true

    samples_per_pixel::Int64 = haskey(dict, "samples_per_pixel") ?
        string2rootint64(dict["samples_per_pixel"])^2 : 0

    declare_float::Union{Dict{String, Float64}, Nothing} = haskey(dict, "declare_float") ?
        (typeof(dict["declare_float"]) == Dict{String, Float64} ?
        dict["declare_float"] : declare_float2dict(dict["declare_float"])
        ) : nothing

    ONLY_FOR_TESTS::Bool = haskey(dict, "ONLY_FOR_TESTS") ? dict["ONLY_FOR_TESTS"] : false

    return (
            scenefile,
            renderer,
            camera_type,
            camera_position,
            α,
            width, height,
            a, γ, lum,
            pfm, png,
            samples_per_pixel,
            bool_print, bool_savepfm,
            declare_float,
            ONLY_FOR_TESTS,
        )
end

##########################################################################################92

"""
    parse_render_animation_settings(dict::Dict{String, T}) where {T}
        :: (
            Function, Vector{String}, Any,
            String, Renderer, String, Vec, Float64, Int64, Int64, String,
            Int64, Bool, Dict{String, Float64}, Bool,
            )

Parse a `Dict{String, T} where {T}` for the `render_animation` function.

## Input

A `dict::Dict{String, T} where {T}`

## Returns

A tuple `(func, vec_variables, iterable, scenefile, renderer, camera_type, camera_position,
α, width, height, anim, samples_per_pixel, bool_print, declare_float, ONLY_FOR_TESTS)`
containing the following variables (the corresponding keys are also showed):

- `scenefile::String = dict["scenefile"]` : name of the scene file to be rendered;
  it must be written with the correct syntax, see the [`tutorial_basic_sintax.txt`](../examples/tutorial_basic_sintax.txt)
  and the [`demo_world_B.txt`](../examples/demo_world_B.txt) files.

- `renderer::Renderer = haskey(dict, "renderer") ? dict["renderer"] : dict["%COMMAND%"]` :
  it's the renderer to be used (with a default empy `World` that will be populated
  in the `demo` function); the possible keys are two, and must be used differently:
    - the `dict["renderer"]` must contain the renderer itself (i.e. a `Renderer` object);
      if this key exists, it has the priority on the latter key
    - the `dict["%COMMAND%"]` must contain a string that identifies the type of renderer
      to be used, i.e. one of the following strings:
      - `"dict["%COMMAND%"]=>onoff"` -> [`OnOffRenderer`](@ref) algorithm
      - `"dict["%COMMAND%"]=>"flat"` -> [`FlatRenderer`](@ref) algorithm (default value)
      - `"dict["%COMMAND%"]=>"pathtracing"` -> [`PathTracer`](@ref) algorithm
      - `"dict["%COMMAND%"]=>"pointlight"` -> [`PointLightRenderer`](@ref) algorithm
      Moreover, in this second case, you can specify the options for the correspoding
      renderer through another dictionary associated with the kkey of the renderer name;
      that options will be parsed thanks to the corresponding functions.
      We shows in the next lines the key-value syntax described:
      - `"onoff"=>Dict{String, T} where {T}` : parsed with [`parse_onoff_settings`](@ref)
      - `"flat"=>Dict{String, T} where {T}` : parsed with [`parse_flat_settings`](@ref)
      - `"pathtracer"=>Dict{String, T} where {T}` : parsed with [`parse_pathtracer_settings`](@ref)
      - `"pointlight"=>Dict{String, T} where {T}` : parsed with [`parse_pointlight_settings`](@ref)

- `camera_type::String = dict["camera_type"]` : set the perspective projection view;
  it must be one of the following values, and this is checked with the
  `string2stringoneof` function:
  - `"per"` -> set [`PerspectiveCamera`](@ref)  (default value)
  - `"ort"`  -> set [`OrthogonalCamera`](@ref)

- `camera_position::String = typeof(dict["camera_position"]) ∈ [Vec, Point] ? dict["camera_position"] :
  string2vector(dict["camera_position"]) ` : "[X, Y, Z]" coordinates of the
  choosen observation point of view; it can be specified in two ways:
  - if `typeof(dict["camera_position"])` is a `Vec` or a `Point`, it's passed as-is
  - else, it must be a `String` written in the form "[X, Y, Z]" , and it's parsed through
    string2vector` to a `Vec` object

- `α::String = dict["alpha"]` : choosen angle of rotation _*IN RADIANTS*_ respect to vertical (i.e. z)
  axis with a right-handed rule convention (clockwise rotation for entering (x,y,z)-axis
  corresponds to a positive input rotation angle)

- `width::Int64 = string2evenint64(dict["width"])` : number of pixels on the horizontal
  axis to be rendered; it's converted through `string2evenint64` to a even positive integer.

- `height::Int64 = string2evenint64(dict["height"])` : number of pixels on the vertical
  axis to be rendered; it's converted through `string2evenint64` to a even positive integer.

- `a::Float64 = string2positive(dict["normalization"])` : scale factor (default = 0.18); it's converted
  through `string2positive` to a positive floating point number.

- `γ::Float64 = string2positive(dict["gamma"])` : gamma factor (default = 1.0); it's converted
  through `string2positive` to a positive floating point number.

- `lum::Union{Number, Nothing} = string2positive(dict["avg_lum"])` : average luminosity of the image; it's
  converted through `string2positive` to a positive floating point number. If not specified or equal to 0,
  it's calculated through [`avg_lum`](@ref)

- `anim::String = dict["set_anim_name"]` : output animation name (default `"animation.mp4"`)

- `samples_per_pixel::Int64  = dict["samples_per_pixel"]` : number of ray to be
  generated for each pixel, implementing the anti-aliasing algorithm; it must be
  a perfect integer square (0,1,4,9,...) and this is checked with the
  `string2rootint64` function; if 0 (default value) is choosen, no anti-aliasing
  occurs, and only one pixel-centered ray is fired for each pixel.

- `bool_print::Bool = dict["bool_print"]` : if `true` (default value), WIP message of
  `demo` function are printed (otherwise no; it's useful for the `demo_animation`
  function)

- `declare_float::Union{Dict{String, Float64}, Nothing} = declare_float2dict(dict["declare_float"])`
  : an option for the command line to manually override the values of the float variables in
  the scene file.
  The input `dict["declare_float"]` must be a `String` written such as `"var1:0.1, var2 : 2.5"`;
  such a string is parsed through the `declare_float2dict` in a `Dict{String, Float64}`
  where each overriden variable name (the key) is associated with its float value
  (`declare_float=>Dict("var1"=>0.1, "var2"=>2.5)`)

- `ONLY_FOR_TESTS::Bool = dict["ONLY_FOR_TESTS"]` : it's a bool variable conceived only to
  test the correct behaviour of the renderer for the input arguments; if set to `true`,
  no rendering is made!

See also:  [`render_animation`](@ref),  [`Renderer`](@ref),
[`Vec`](@ref), [`string2evenint64`](@ref), [`string2stringoneof`](@ref),
[`string2positive`](@ref), [`string2vector`](@ref), [`string2rootint64`](@ref),
[`declare_float2dict`](@ref), [`string2iterable`](@ref), [`string2vec_variables`](@ref)
"""
function parse_render_animation_settings(dict::Dict{String, T}) where {T}

    keys = [
        "function", "vec_variables", "iterable",
        "scenefile",
        "%COMMAND%",
        "onoff", "flat", "pathtracer", "pointlight",
        "camera_type", "camera_position",
        "alpha", "width", "height",
        "normalization", "gamma", "avg_lum",
        "set_anim_name",
        "bool_print", "samples_per_pixel",
        "declare_float",
        "ONLY_FOR_TESTS",
    ]

    for pair in dict
        if (pair[1] in keys)==false
            throw(ArgumentError(
                "invalid key : $(pair[1])\n"*
                "valid keys for demo function are:\n "*
                "$(["$(key)" for key in keys])"
            ))
        end
    end

    haskey(dict, "function") ?
        func::Function = string2function(dict["function"]) :
        throw(ArgumentError("need to specify the function name to be used in the rendering"))

    haskey(dict, "vec_variables") ?
        vec_variables = string2vec_variables(dict["vec_variables"]) :
        throw(ArgumentError("need to specify the variables to be changed between the frames"))

    haskey(dict, "iterable") ?
        iterable = string2iterable(dict["iterable"]) :
        throw(ArgumentError("need to specify the iterable on which I must made the frames"))

    haskey(dict, "scenefile") ?
        scenefile::String = dict["scenefile"] :
        throw(ArgumentError("need to specify the input scenefile to be rendered"))

    renderer = if haskey(dict, "renderer")
        dict["renderer"]
    elseif haskey(dict, "%COMMAND%")
    if dict["%COMMAND%"] == "onoff"
        options = haskey(dict, "onoff") ? dict["onoff"] : Dict{String, Any}()
        OnOffRenderer(parse_onoff_settings(options)...)
    elseif dict["%COMMAND%"] == "flat"
        options = haskey(dict, "flat") ? dict["flat"] : Dict{String, Any}()
        FlatRenderer(parse_flat_settings(options)...)
    elseif dict["%COMMAND%"] == "pathtracer"
        options = haskey(dict, "pathtracer") ? dict["pathtracer"] : Dict{String, Any}()
        PathTracer(parse_pathtracer_settings(options)...)
    elseif dict["%COMMAND%"] == "pointlight"
        options = haskey(dict, "pointlight") ? dict["pointlight"] : Dict{String, Any}()
        PointLightRenderer(parse_pointlight_settings(options)...)
    end
    else
        FlatRenderer()
    end

    camera_type::Union{String, Nothing} = haskey(dict, "camera_type") && !isnothing(dict["camera_type"]) ?
        string2stringoneof(dict["camera_type"], CAMERAS) :
        nothing

    camera_position::Vec = haskey(dict, "camera_position") ? begin
            typeof(dict["camera_position"]) ∈ [Vec, Point] ?
            dict["camera_position"] :
            string2vector(dict["camera_position"])
        end : Vec(-1.0 , 0. , 0.)

    α::Float64 = haskey(dict, "alpha") ? begin
        typeof(dict["alpha"]) <: Number ?
            dict["alpha"] :
            parse(Float64, dict["alpha"])
        end : 0.

    width::Int64 = haskey(dict, "width") ? string2evenint64(dict["width"]) : 640

    height::Int64 = haskey(dict, "height") ? string2evenint64(dict["height"]) : 480

    a::Float64 = haskey(dict, "normalization") ? string2positive(dict["normalization"]) : 0.18

    γ::Float64 = haskey(dict, "gamma") ? string2positive(dict["gamma"]) : 1.0

    lum::Union{Float64, Nothing} = haskey(dict, "avg_lum") ? string2positive(dict["avg_lum"]) : nothing

    anim::String = haskey(dict, "set_anim_name") ? dict["set_anim_name"] : "scene_animation.mp4"

    bool_print::Bool = haskey(dict, "bool_print") ?  dict["bool_print"] : true

    samples_per_pixel::Int64 = haskey(dict, "samples_per_pixel") ?
        string2rootint64(dict["samples_per_pixel"])^2 : 0

    declare_float::Union{Dict{String, Float64}, Nothing} = haskey(dict, "declare_float") ?
        (typeof(dict["declare_float"]) == Dict{String, Float64} ?
        dict["declare_float"] : declare_float2dict(dict["declare_float"])
        ) : nothing

    ONLY_FOR_TESTS::Bool = haskey(dict, "ONLY_FOR_TESTS") ? dict["ONLY_FOR_TESTS"] : false

    return (
            func,
            vec_variables,
            iterable,
            scenefile,
            renderer,
            camera_type,
            camera_position,
            α,
            width, height, a, γ, lum,
            anim,
            samples_per_pixel,
            bool_print,
            declare_float,
            ONLY_FOR_TESTS,
        )
end


text:
ADCME_DIR = joinpath(homedir(), ".julia/adcme")
if !isdir(ADCME_DIR)
    mkpath(ADCME_DIR)
end

if !isdir(joinpath(ADCME_DIR, "bin"))
    mkpath(joinpath(ADCME_DIR, "bin"))
end
if !isdir(joinpath(ADCME_DIR, "lib/Libraries"))
    mkpath(joinpath(ADCME_DIR, "lib/Libraries"))
end
if !isdir(joinpath(ADCME_DIR, "include"))
    mkpath(joinpath(ADCME_DIR, "include"))
end

if !islink("$(joinpath(ADCME_DIR, "bin/unzip"))")
    symlink("/usr/bin/unzip", "$(joinpath(ADCME_DIR, "bin/unzip"))")
end
if !islink("$(joinpath(ADCME_DIR, "bin/make"))")
    symlink("/usr/bin/make", "$(joinpath(ADCME_DIR, "bin/make"))")
end
if !islink("$(joinpath(ADCME_DIR, "bin/cmake"))")
    symlink("/usr/bin/cmake", "$(joinpath(ADCME_DIR, "bin/cmake"))")
end
if !islink("$(joinpath(ADCME_DIR, "bin/ninja"))")
    symlink("/usr/local/bin/ninja", "$(joinpath(ADCME_DIR, "bin/ninja"))")
end
if !islink("$(joinpath(ADCME_DIR, "bin/pip"))")
    symlink("/usr/local/bin/pip", "$(joinpath(ADCME_DIR, "bin/pip"))")
end
if !islink("$(joinpath(ADCME_DIR, "lib/libopenblas.so"))")
    symlink("/usr/lib/x86_64-linux-gnu/libopenblas.so", "$(joinpath(ADCME_DIR, "lib/libopenblas.so"))")
end

deps = """
BINDIR = "$(joinpath(ADCME_DIR, "bin"))"
LIBDIR = "$(joinpath(ADCME_DIR, "lib"))"
TF_INC = "/usr/local/lib/python3.6/dist-packages/tensorflow_core/include"
TF_ABI = "0"
PREFIXDIR = "$(joinpath(ADCME_DIR, "lib/Libraries"))"
CC = "/usr/bin/gcc"
CXX = "/usr/bin/g++"
CMAKE = "/usr/bin/cmake"
MAKE = "/usr/bin/make"
GIT = "LibGit2"
PYTHON = "/usr/local/bin/python"
TF_LIB_FILE = "/usr/local/lib/python3.6/dist-packages/tensorflow_core/libtensorflow_framework.so.1"
LIBCUDA = ""
CUDA_INC = ""
NINJA = "/usr/bin/ninja"
INCDIR = "$(joinpath(ADCME_DIR, "include"))"
__STR__ = join([BINDIR,LIBDIR,TF_INC,TF_ABI,PREFIXDIR,CC,CXX,CMAKE,MAKE,GIT,PYTHON,TF_LIB_FILE,LIBCUDA,CUDA_INC,NINJA,INCDIR], ";")
"""

open("deps.jl", "w") do io
    write(io, deps)
end

ext: jl


text:
"""
Adaptive Stress Testing for the POMDPs.jl ecosystem.
"""
module POMDPStressTesting

include("AST.jl")
include(joinpath("solvers", "drl", "PolicyOptimization.jl"))

using .AST
using .PolicyOptimization
using CrossEntropyMethod
using Distributions
using Markdown
using MCTS
using Parameters
using POMDPs
using ProgressMeter
using Random
using Statistics

# Visualization specific.
using D3Trees
using Requires

function __init__()
    @require PyPlot="d330b81b-6aea-500a-939a-2ce795aea3ee" begin
        @require Seaborn="d2ef9438-c967-53ab-8060-373fdd9e13eb" include(joinpath("visualization", "figures.jl"))
    end
end

export AST,
       ASTMDP,
       ASTParams,
       ASTState,
       ASTAction,
       ASTSeedAction,
       ASTSampleAction,
       actiontype,
       BlackBox,
       GrayBox,

       search!,
       playback,
       online_path,
       logpdf,
       get_top_path,
       hash_uint32,

       visualize,
       full_width_notebook,
       episodic_figures,
       distribution_figures,
       distribution_figures!,
       FailureMetrics,
       print_metrics,
       failure_metrics,
       combine_ast_metrics,
       latex_metrics,
       reset_metrics!,
       highest_loglikelihood_of_failure,
       most_likely_failure,

       MCTSPWSolver,
       CEMSolver,
       CEMPlanner,
       RandomSearchSolver,
       RandomSearchPlanner,
       TRPOSolver,
       TRPOPlanner,
       PPOSolver,
       PPOPlanner,
       get_action,
       sample_action,

       # For MCTS and RandomSearch
       solve,
       action

include(joinpath("utils", "metrics.jl"))
include(joinpath("visualization", "tree_visualization.jl"))

include(joinpath("solvers", "mcts.jl"))
include(joinpath("solvers", "cem.jl"))
include(joinpath("solvers", "random_search.jl"))

end # module POMDPStressTesting


text:
"""
Holidays for Canada
"""
struct CanadaSettlement <: HolidayCalendar end
const Canada = CanadaSettlement

"""
Holidays for Toronto Stock Exchange
"""
struct CanadaTSX <: HolidayCalendar end

function isholiday(::CanadaSettlement, dt::Dates.Date)

    yy = Dates.year(dt)
    mm = Dates.month(dt)
    dd = Dates.day(dt)
    ww = Dates.dayofweek(dt)

    # Bisection
    if mm >= 8
        # Fixed holidays
        if (
                # first Monday of August (Provincial Holiday)
                findweekday(Dates.Monday, yy, 8, 1, true)  == dt
                # first Monday of September (Labor Day)
                || findweekday(Dates.Monday, yy, 9, 1, true)  == dt
                # second Monday of October (Thanksgiving Day)
                || findweekday(Dates.Monday, yy, 10, 2, true) == dt
                # November 11th (possibly moved to Monday)
                || adjustweekendholidayPost(Dates.Date(yy, 11, 11)) == dt
                # Christmas
                || adjustweekendholidayPost( Dates.Date(yy, 12, 25) ) == dt
                # Boxing
                || adjustweekendholidayPost(adjustweekendholidayPost( Dates.Date(yy, 12, 25) ) + Dates.Day(1)) == dt
            )
            return true
        end
    else
        # mm < 8
        # Fixed holidays
        if (
                # New Year's Day
                adjustweekendholidayPost(Dates.Date(yy, 01, 01); adjust_saturdays=false) == dt
                # Family Day (third Monday in February, since 2008)
                || ( yy >= 2008 && findweekday(Dates.Monday, yy, 2, 3, true) == dt )
                # The Monday on or preceding 24 May (Victoria Day)
                || (dd > 17 && dd <= 24 && ww == Dates.Monday && mm == Dates.May)
                # July 1st, possibly moved to Monday (Canada Day)
                || adjustweekendholidayPost(Dates.Date(yy, 07, 01)) == dt
          )
            return true
        end

        # Easter occurs up to April, which is before August (mm < 8). See test/easter-min-max.jl .
        # Holidays based on easter date
        dt_rata::Int = Dates.days(dt)
        e_rata::Int = easter_rata(Dates.Year(yy))

        if (
                # Good Friday
                ( dt_rata == ( e_rata - 2 ) )
            )
            return true
        end
    end

    return false
end

function isholiday(::CanadaTSX, dt::Dates.Date)

    yy = Dates.year(dt)
    mm = Dates.month(dt)
    dd = Dates.day(dt)
    ww = Dates.dayofweek(dt)

    # Bisection
    if mm >= 8
        # Fixed holidays
        if (
                # first Monday of August (Provincial Holiday)
                findweekday(Dates.Monday, yy, 8, 1, true)  == dt
                # first Monday of September (Labor Day)
                || findweekday(Dates.Monday, yy, 9, 1, true)  == dt
                # second Monday of October (Thanksgiving Day)
                || findweekday(Dates.Monday, yy, 10, 2, true) == dt
                # Christmas
                || adjustweekendholidayPost( Dates.Date(yy, 12, 25) ) == dt
                # Boxing
                || adjustweekendholidayPost(adjustweekendholidayPost( Dates.Date(yy, 12, 25) ) + Dates.Day(1)) == dt
            )
            return true
        end
    else
        # mm < 8
        # Fixed holidays
        if (
                # New Year's Day
                adjustweekendholidayPost(Dates.Date(yy, 01, 01); adjust_saturdays=false) == dt
                # Family Day (third Monday in February, since 2008)
                || ( yy >= 2008 && findweekday(Dates.Monday, yy, 2, 3, true) == dt )
                # The Monday on or preceding 24 May (Victoria Day)
                || (dd > 17 && dd <= 24 && ww == Dates.Monday && mm == Dates.May)
                # July 1st, possibly moved to Monday (Canada Day)
                || adjustweekendholidayPost(Dates.Date(yy, 07, 01)) == dt

          )
            return true
        end

        # Easter occurs up to April, which is before August (mm < 8). See test/easter-min-max.jl .
        # Holidays based on easter date
        dt_rata::Int = Dates.days(dt)
        e_rata::Int = easter_rata(Dates.Year(yy))

        if (
                # Good Friday
                ( dt_rata == ( e_rata - 2 ) )
            )
            return true
        end
    end

    return false
end


text:
# Module BLAST
# ============
#
# A module for running command line BLAST and parsing BLAST output files.

module BLAST

export
    blastn,
    blastp,
    readblastXML,
    readblastXML2,
    BLASTResult

using BioAlignments
using EzXML
using LightXML
using BioSequences

include("blastcommandline.jl")

end # module BLAST


text:
# ---
# title: 205. Isomorphic Strings
# id: problem205
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Hash Table
# link: <https://leetcode.com/problems/isomorphic-strings/description/>
# hidden: true
# ---
#
# Given two strings **_s_** and **_t_** , determine if they are isomorphic.
#
# Two strings are isomorphic if the characters in **_s_** can be replaced to get
# **_t_**.
#
# All occurrences of a character must be replaced with another character while
# preserving the order of characters. No two characters may map to the same
# character but a character may map to itself.
#
# **Example 1:**
#
#
#
#     Input: **_s_** = "egg", **_t =_**"add"
#     Output: true
#
#
# **Example 2:**
#
#
#
#     Input: **_s_** = "foo", **_t =_**"bar"
#     Output: false
#
# **Example 3:**
#
#
#
#     Input: **_s_** = "paper", **_t =_**"title"
#     Output: true
#
# **Note:**
# You may assume both **_s  _**and **_t  _**have the same length.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


text:
@generated function vrangeincr(::Val{W}, i::I, ::Val{O}) where {W,I<:Integer,O}
    bytes = I === Int ? min(8, VectorizationBase.prevpow2(VectorizationBase.REGISTER_SIZE ÷ W)) : sizeof(I)
    # bytes = min(8, VectorizationBase.prevpow2(VectorizationBase.REGISTER_SIZE ÷ W))
    bits = 8bytes
    jtypesym = Symbol(:Int, bits)
    iexpr = bytes == sizeof(I) ? :i : Expr(:call, :%, :i, jtypesym)
    typ = "i$(bits)"
    vtyp = "<$W x $typ>"
    rangevec = join(("$typ $(w+O)" for w ∈ 0:W-1), ", ")
    instrs = String[]
    push!(instrs, "%ie = insertelement $vtyp undef, $typ %0, i32 0")
    push!(instrs, "%v = shufflevector $vtyp %ie, $vtyp undef, <$W x i32> zeroinitializer")
    push!(instrs, "%res = add $vtyp %v, <$rangevec>")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta,:inline))
        Base.llvmcall(
            $(join(instrs,"\n")), Vec{$W,$jtypesym}, Tuple{$jtypesym}, $iexpr
        )
    end
end
@generated function vrangeincr(::Val{W}, i::T, ::Val{O}) where {W,T<:FloatingTypes,O}
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    rangevec = join(("$typ $(w+O).0" for w ∈ 0:W-1), ", ")
    instrs = String[]
    push!(instrs, "%ie = insertelement $vtyp undef, $typ %0, i32 0")
    push!(instrs, "%v = shufflevector $vtyp %ie, $vtyp undef, <$W x i32> zeroinitializer")
    push!(instrs, "%res = fadd $vtyp %v, <$rangevec>")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta,:inline))
        Base.llvmcall(
            $(join(instrs,"\n")), Vec{$W,$T}, Tuple{$T}, i
        )
    end
end
@generated function vrangemul(::Val{W}, i::I, ::Val{O}) where {W,I<:Integer,O}
    bytes = I === Int ? min(8, VectorizationBase.prevpow2(VectorizationBase.REGISTER_SIZE ÷ W)) : sizeof(I)
    bits = 8bytes
    jtypesym = Symbol(:Int, bits)
    iexpr = bytes == sizeof(I) ? :i : Expr(:call, :%, :i, jtypesym)
    typ = "i$(bits)"
    vtyp = "<$W x $typ>"
    rangevec = join(("$typ $(w+O)" for w ∈ 0:W-1), ", ")
    instrs = String[]
    push!(instrs, "%ie = insertelement $vtyp undef, $typ %0, i32 0")
    push!(instrs, "%v = shufflevector $vtyp %ie, $vtyp undef, <$W x i32> zeroinitializer")
    push!(instrs, "%res = mul $vtyp %v, <$rangevec>")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta,:inline))
        Base.llvmcall(
            $(join(instrs,"\n")), Vec{$W,$jtypesym}, Tuple{$jtypesym}, $iexpr
        )
    end
end
@generated function vrangemul(::Val{W}, i::T, ::Val{O}) where {W,T<:FloatingTypes,O}
    typ = llvmtype(T)
    vtyp = "<$W x $typ>"
    rangevec = join(("$typ $(w+O).0" for w ∈ 0:W-1), ", ")
    instrs = String[]
    push!(instrs, "%ie = insertelement $vtyp undef, $typ %0, i32 0")
    push!(instrs, "%v = shufflevector $vtyp %ie, $vtyp undef, <$W x i32> zeroinitializer")
    push!(instrs, "%res = fmul fast $vtyp %v, <$rangevec>")
    push!(instrs, "ret $vtyp %res")
    quote
        $(Expr(:meta,:inline))
        Base.llvmcall(
            $(join(instrs,"\n")), Vec{$W,$T}, Tuple{$T}, i
        )
    end
end
@inline svrangeincr(::Val{W}, i, ::Val{O}) where {W,O} = SVec(vrangeincr(Val{W}(), i, Val{O}()))
@inline svrangemul(::Val{W}, i, ::Val{O}) where {W,O} = SVec(vrangemul(Val{W}(), i, Val{O}()))

@inline vrange(i::_MM{W}) where {W} = vrangeincr(Val{W}(), i.i, Val{0}())
@inline svrange(i::_MM{W}) where {W} = SVec(vrangeincr(Val{W}(), i.i, Val{0}()))
@inline Base.:(+)(i::_MM{W}, j::AbstractSIMDVector{W}) where {W} = vadd(vrange(i), j)
@inline Base.:(+)(i::AbstractSIMDVector{W}, j::_MM{W}) where {W} = vadd(i, vrange(j))
@inline Base.:(*)(i::_MM{W}, j::AbstractSIMDVector{W}) where {W} = vmul(vrange(i), j)
@inline Base.:(*)(i::AbstractSIMDVector{W}, j::_MM{W}) where {W} = vmul(i, vrange(j))

@inline vrange(::Val{W}) where {W} = vrange(Val{W}(), Float64)
@inline svrange(::Val{W}) where {W} = svrange(Val{W}(), Float64)

@inline vrange(i::_MM{W}, ::Type{T}) where {W,T} = vrangeincr(Val{W}(), T(i.i), Val{0}())
@inline vrange(i::_MM{W}, ::Type{T}) where {W,T <: Integer} = vrangeincr(Val{W}(), i.i % T, Val{0}())
@inline svrange(i::_MM, ::Type{T}) where {T} = SVec(vrange(i, T))

@inline Base.:(<<)(i::_MM, j::Integer) = svrange(i) << j
@inline Base.:(>>)(i::_MM, j::Integer) = svrange(i) >> j
@inline Base.:(>>>)(i::_MM, j::Integer) = svrange(i) >>> j

@inline Base.:(*)(i::_MM{W}, j::T) where {W,T} = vmul(svrange(i), j)
@inline Base.:(*)(j::T, i::_MM{W}) where {W,T} = vmul(svrange(i), j)
@inline vconvert(::Type{Vec{W,T}}, i::_MM{W}) where {W,T} = vrange(i, T)
@inline vconvert(::Type{SVec{W,T}}, i::_MM{W}) where {W,T} = svrange(i, T)