text:
"""
    MenuAdventures

A module for creating text adventures based on menus.

```jldoctest
julia> using MenuAdventures

julia> using MenuAdventures.Testing

julia> import MenuAdventures: ever_possible, is_transparent, is_vehicle

julia> @universe not_a_struct
ERROR: LoadError: ArgumentError: Cannot parse user struct definition
[...]

julia> @universe struct Universe <: AbstractUniverse
        end;

julia> @noun struct Room <: AbstractRoom
            already_lit::Bool = true
        end;

julia> @noun struct Person <: Noun
        end;

julia> @noun struct Key <: Noun
        end;

julia> ever_possible(::Take, ::Reachable, ::Key) = true;

julia> ever_possible(::UnlockOrLock, ::Inventory, ::Key) = true;

julia> @noun mutable struct LockableDoor <: AbstractDoor
            key::Noun
            closed::Bool = true
            locked::Bool = true
        end;

julia> ever_possible(::OpenOrClose, ::Reachable, ::LockableDoor) = true;

julia> ever_possible(::UnlockOrLock, ::Reachable, ::LockableDoor) = true;

julia> @noun mutable struct Chest <: Noun
            key::Noun
            closed::Bool = true
            locked::Bool = true
        end;

julia> ever_possible(::OpenOrClose, ::Reachable, ::Chest) = true;

julia> ever_possible(::UnlockOrLock, ::Reachable, ::Chest) = true;

julia> ever_possible(::PutInto, ::Reachable, ::Chest) = true;

julia> @noun struct Car <: Noun
        end;

julia> ever_possible(::GoInto, ::Immediate, ::Car) = true;

julia> is_transparent(::Car) = true;

julia> is_vehicle(::Car) = true;

julia> cd(joinpath(pkgdir(MenuAdventures), "test")) do
            check_choices() do interface
                you = Person(
                    "Brandon",
                    description = (universe, self) -> "What a dork!",
                    grammatical_person = second_person,
                    indefinite_article = "",
                )
                entrance = Room(
                    "the entrance",
                    description = (universe, self) -> "The entrance to the castle",
                    indefinite_article = ""
                )
                small_key = Key("small key")
                large_key = Key("large key")
                chest = Chest("chest", small_key)
                universe = Universe(
                    you,
                    introduction = "Welcome!",
                    interface = interface
                )
                universe[entrance, Room("the castle", indefinite_article = "")] = LockableDoor("door", large_key), West()
                universe[entrance, you] = Containing()
                universe[entrance, small_key] = Containing()
                universe[entrance, chest] = Containing()
                universe[chest, large_key] = Containing()
                universe[entrance, Car("car")] = Containing()
                universe
            end
        end
true
```
"""
module MenuAdventures

using Base: disable_text_style, text_colors
import Base: setindex!, show
using FunctionWrappers: FunctionWrapper
using OrderedCollections: OrderedDict
using InteractiveUtils: subtypes
using LightGraphs: DiGraph, inneighbors, outneighbors
using MacroTools: @capture
using MetaGraphsNext: code_for, label_for, MetaGraph
using REPL.Terminals: TTYTerminal
using REPL.TerminalMenus: RadioMenu, request, TerminalMenus, terminal
using TextWrap: println_wrapped

"""
    abstract type Domain end

A domain refers to a search space for a specific argument to an [`Action`](@ref).

Domains serve both as a way of distinguishing different arguments to an action, and also, categorizing the environment around the player.
For example, you are only able to look at things in the [`Visible`](@ref) domain.
To create a new domain, you must add a method for:

- [`MenuAdventures.find_in_domain`](@ref)
- [`MenuAdventures.indefinite`](@ref)
- [`MenuAdventures.interrogative`](@ref)
"""
abstract type Domain end

export Domain

"""
    ExitDirections()

Directions that a player, or the vehicle a player is in, might exit in.

A [`Domain`](@ref).
"""
struct ExitDirections <: Domain end

export ExitDirections

function indefinite(::ExitDirections)
    "some way"
end

function interrogative(::ExitDirections)
    "which way"
end

function blocking_thing_and_relationship(universe, ::ExitDirections)
    get_parent_relationship(universe, get_mover(universe))
end

function find_in_domain(universe, sentence, domain::ExitDirections; lit = true)
    blocking_thing, _ = blocking_thing_and_relationship(universe, domain)
    answers = Answer[]
    for (_, direction) in
        get_exit_directions(universe, blocking_thing)
        if possible_now(universe, sentence, domain, direction)
            push!(answers, make_answer(universe, direction))
        end
    end
    answers
end

"""
    Inventory()

Things the player is carrying.

A [`Domain`](@ref).
"""
struct Inventory <: Domain end

export Inventory

function find_in_domain(universe, sentence, domain::Inventory; lit = true)
    answers = Answer[]
    for (thing, relationship) in
        get_children_relationships(universe, universe.player)
        if relationship isa Carrying && possible_now(universe, sentence, domain, thing)
            push!(answers, make_answer(universe, thing))
        end
    end
    answers
end

"""
    Immediate()

Thing that are in/on the same place the player could move from.

A [`Domain`](@ref).
"""
struct Immediate <: Domain end

export Immediate

function find_in_domain(universe, sentence, domain::Immediate; lit = true)
    answers = Answer[]
    blocking_thing, blocked_relationship =
        blocking_thing_and_relationship(universe, ExitDirections())
    if lit
        for (thing, relationship) in
            get_children_relationships(universe, blocking_thing)
            if relationship === blocked_relationship &&
                possible_now(universe, sentence, domain, thing)
                push!(answers, make_answer(universe, thing))
            end
        end
    end
    answers
end

"""
    Reachable()

Anything the player can reach.

A [`Domain`](@ref). Players can't reach through closed containers by default.
"""
struct Reachable <: Domain end

export Reachable

function blocking(::Reachable, parent_thing, relationship, _)
    relationship isa Containing && is_closable_and_closed(parent_thing)
end

function blocking_thing_and_relationship(universe, ::Reachable)
    # you can't reach outside of the thing that you are in/on/etc.
    get_parent_relationship(universe, universe.player)
end

function find_in_domain(universe, sentence, domain::Reachable; lit = true)
    answers = Answer[]
    if lit
        add_siblings_and_doors!(answers, universe, sentence, domain, blocking_thing_and_relationship(universe, domain)...)
    else
        # you can only reach the things on your person
        for (thing, _) in
            get_children_relationships(universe, universe.player)
            add_thing_and_relations!(answers, universe, sentence, domain, thing)
        end
    end
    answers
end

"""
    Visible()

Anything the player can see.

A [`Domain`](@ref). By default, players can't see into closed, opaque containers.
"""
struct Visible <: Domain end

export Visible

function blocking(::Visible, parent_thing, relationship, thing)
    blocking(Reachable(), parent_thing, relationship, thing) &&
        !(is_transparent(parent_thing))
end

function blocking_thing_and_relationship(universe, domain::Visible)
    thing = universe.player
    blocking_thing, blocked_relationship = get_parent_relationship(universe, thing)
    # locations don't have a parent...
    while !(blocking_thing isa Location) &&
        !(blocking(domain, blocking_thing, blocked_relationship, thing))
        thing = blocking_thing
        blocking_thing, blocked_relationship = get_parent_relationship(universe, thing)
    end
    blocking_thing, blocked_relationship
end

function find_in_domain(universe, sentence, domain::Visible; lit = true)
    answers = Answer[]
    if lit
        blocking_thing, blocked_relationship = blocking_thing_and_relationship(universe, domain)
        # you can look around at the thing you are in/on
        if possible_now(universe, sentence, domain, blocking_thing)
            push!(answers, make_answer(universe, blocking_thing))
        end
        add_siblings_and_doors!(answers, universe, sentence, domain, blocking_thing, blocked_relationship)
    end
    answers
end

"""
    MenuAdventures.find_in_domain(universe, sentence, domain; lit = true)

Return a vector of [`Answer`](@ref)s for all things around the player in a certain [`Domain`](@ref).
"""
find_in_domain

"""
    MenuAdventures.indefinite(domain) = "something"

Placeholder for an object from the [`Domain`](@ref).

For example, the indefinite for [`ExitDirections`](@ref) is "some way".
"""
function indefinite(_)
    "something"
end

"""
    MenuAdventures.interrogative(domain) = "what"

Ask for an object from the [`Domain`](@ref).

For example, the interrogative for [`ExitDirections`](@ref) is "which way".
"""
function interrogative(_)
    "what"
end

"""
    abstract type Relationship end

Relationships show the relationships between [`Noun`](@ref)s.

For example, something can be [`Containing`](@ref) something else.
"""
abstract type Relationship end

export Relationship

"""
    Carrying()

A is `carrying` B means B is carried by A.

A [`Relationship`](@ref).
"""
struct Carrying <: Relationship end

export Carrying

function string_relationship_to(thing_answer, ::Carrying, parent_thing)
    buffer = IOBuffer()
    print(buffer, thing_answer.text)
    print(buffer, " that ")
    show(buffer, parent_thing)
    print(buffer, ' ')
    print(buffer, subject_to_verb(parent_thing, BE))
    print(buffer, " carrying")
    String(take!(buffer))
end

verb_for(::Carrying) = Verb("carry"; third_person_singular_present = "carries")

"""
    Containing()

A is `containing` B means B is in A.

A [`Relationship`](@ref).
"""
struct Containing <: Relationship end

export Containing

function string_relationship_to(thing_answer, ::Containing, parent_thing)
    buffer = IOBuffer()
    print(buffer, thing_answer.text)
    print(buffer, ' ')
    print(buffer, "in ")
    show(IOContext(buffer, :is_subject => false, :subject => get_object(thing_answer)), parent_thing)
    String(take!(buffer))
end

verb_for(::Containing) = Verb("contain")

"""
    abstract type Direction end

`Direction`s show the relationships between [`Location`](@ref)s.

For example, a place can be [`North`](@ref) of another place.
To create a new `Direction`, you must add a method for

- `Base.show`
- the [`MenuAdventures.opposite`](@ref) of the direction.
"""
abstract type Direction end

export Direction

"""
    North()

A [`Direction`](@ref).
"""
struct North <: Direction end

export North

function show(io::IO, ::North)
    print(io, "north")
end

opposite(::North) = South()

"""
    West()

A [`Direction`](@ref).
"""
struct West <: Direction end

export West

function show(io::IO, ::West)
    print(io, "west")
end

opposite(::West) = East()

"""
    South()

A [`Direction`](@ref).
"""
struct South <: Direction end

export South

function show(io::IO, ::South)
    print(io, "south")
end

opposite(::South) = North()

"""
    East()

A [`Direction`](@ref).
"""
struct East <: Direction end

export East

function show(io::IO, ::East)
    print(io, "east")
end

opposite(::East) = West()

"""
    MenuAdventures.opposite(direction::Direction)

The opposite of a [`Direction`](@ref).
"""
opposite

"""
    abstract type Noun end

You must make your own custom `Noun` subtypes for almost everything in your game.

See [`@noun`](@ref) for information about required fields.
Nouns are additionally characterized by the following traits and methods:

  - [`MenuAdventures.ever_possible`](@ref)
  - [`MenuAdventures.is_shining`](@ref)
  - [`MenuAdventures.is_transparent`](@ref)
  - [`MenuAdventures.is_vehicle`](@ref)

The following `IOContext` components will be respected when showing nouns:

- `:capitalize::Bool => false`
- `:known::Bool => true`, set to `false` to include the `indefinite article` if it exists.
- `:is_subject => true`, whether the noun is the subject of a clause. If this is set to `false`, you must also include
- `:subject::Noun`, the subject of the clause.
"""
abstract type Noun end

"""
    abstract type Location <: Noun end

A location (room or door)
"""
abstract type Location <: Noun end

export Location

"""
    abstract type AbstractRoom <: Location end

An abstract room.

In addition to the required fields for [`@noun`](@ref), you must also include an `already_lit::Bool` field for an `AbstractRoom`.
"""
abstract type AbstractRoom <: Location end

export AbstractRoom

"""
    abstract type AbstractDoor <: Location end

An abstract door
"""
abstract type AbstractDoor <: Location end

export AbstractDoor

"""
    abstract type Action end

An `Action` the player can take.

To create a new action, you will need to add methods for

- [`MenuAdventures.ever_possible`](@ref) for abstract possibilities
- [`MenuAdventures.possible_now`](@ref) for concrete possibilities
- [`MenuAdventures.argument_domains`](@ref) to specify the domain of the arguments
- [`MenuAdventures.print_sentence`](@ref) for printing the sentence
- [`MenuAdventures.mention_status`](@ref) for mentioning the status of a thing.

Note that the order arguments are printed in need not match the order they are listed.
However, the order of arguments for [`MenuAdventures.argument_domains`](@ref) must match the order of arguments for [`MenuAdventures.print_sentence`](@ref).

Most importantly, define:

```
function (::MyNewAction)(universe, arguments...) -> Bool
```

which will conduct the action based on user choices.
Return `true` to end the game, or `false` to continue onto the next turn.
You can overload `Action` calls for a [`Noun`](@ref) subtype.
Use `Core.invoke` to avoid replicating the `Action` machinery.
"""
abstract type Action end

export Action

"""
    Answer(text::String, object::Any)

An answer has two fields: `text`, which will be how the option is displayed in a menu, and `object`.

`object` might be a noun, direction, trigger, or even a question.
"""
struct Answer{Object}
    text::String
    # could be an noun, a direction, or another question
    object::Object
end

export Answer

struct Sentence{Action}
    action::Action
    argument_answers::Vector{Answer}
end

export Sentence

"""
    Sentence(action::Action; argument_answers = Answer[])

A sentence has two fields: `action`, the [`Action`](@ref) to be taken, and `argument_answers`, the arguments to the action.

Arguments will be returned as [`Answer`](@ref)s. The subject is implicitly `universe.player`.
"""
function Sentence(action::Action; argument_answers = Answer[])
    Sentence(action, argument_answers)
end

"""
    Drop()

`Drop` something in your [`Inventory`](@ref).
"""
struct Drop <: Action end

export Drop

function argument_domains(::Drop)
    (Inventory(),)
end

function ever_possible(::Drop, ::Inventory, _)
    true
end

function (::Drop)(universe, thing)
    PutInto()(universe, thing, get_parent(universe, universe.player))
end

function print_sentence(io, ::Drop, thing_answer)
    print(io, "Drop ")
    print(io, thing_answer.text)
end

"""
    Go()

`Go` in one of [`ExitDirections`](@ref).

An [`Action`](@ref).
"""
struct Go <: Action end

export Go

function possible_now(universe, ::Sentence{Go}, ::ExitDirections, direction)
    !(is_closable_and_closed(get_first_destination(universe, direction)))
end

function (::Go)(universe, direction)
    GoInto()(universe, get_final_destination(universe, direction))
    return false
end

function print_sentence(io, ::Go, direction_answer)
    print(io, "Go ")
    print(io, direction_answer.text)
end

function argument_domains(::Go)
    (ExitDirections(),)
end

"""
    GoInto()

Go into something [`Immediate`](@ref).

An [`Action`](@ref).
"""
struct GoInto <: Action end

export GoInto

function argument_domains(::GoInto)
    (Immediate(),)
end

function possible_now(universe, sentence::Sentence{GoInto}, domain::Immediate, thing)
    ever_possible(sentence.action, domain, thing) &&
    !(is_closable_and_closed(thing)) &&
    # you can't go into where you already are
    get_parent(universe, universe.player) !== thing
end

# add relations to player
function (::GoInto)(universe, place)
    PutInto()(universe, get_mover(universe), place)
    return false
end

function print_sentence(io, ::GoInto, place_answer)
    print(io, "Go into ")
    print(io, place_answer.text)
end

"""
    Leave()

Leave whatever the player is in/on.

An [`Action`](@ref).
"""
struct Leave <: Action end

function (::Leave)(universe)
    player = universe.player
    parent_thing = get_parent(universe, player)
    grandparent_thing, parent_relationship = get_parent_relationship(universe, parent_thing)
    universe[grandparent_thing, player] = parent_relationship
    return false
end

function argument_domains(::Leave)
    ()
end

function possible_now(universe, ::Leave)
    !(get_parent(universe, universe.player) isa Location)
end

function print_sentence(io, ::Leave)
    print(io, "Leave")
end

export Leave

"""
    ListInventory()

List the player's inventory.

An [`Action`](@ref).
"""
struct ListInventory <: Action end

export ListInventory

function (::ListInventory)(universe)
    player = universe.player
    interface = universe.interface
    relations = OrderedDict{Relationship, Vector{Answer}}()
    for (thing, relationship) in
        get_children_relationships(universe, player)
        # we automatically mention everything that is visible
        push!(get!(relations, relationship, Answer[]), make_answer(universe, thing))
    end
    println(interface)
    show(IOContext(interface, :known => false, :is_subject => true, :capitalize => true), player)
    print(interface, ':')
    println(interface)
    print_relations(universe, 0, Visible(), player, relations)
    return false
end

function print_sentence(io, ::ListInventory)
    print(io, "List inventory")
end

function argument_domains(::ListInventory)
    ()
end

function possible_now(universe, ::ListInventory)
    !isempty(get_children_relationships(universe, universe.player))
end

"""
    LookAt()

Look at something [`Visible`](@ref).

An [`Action`](@ref).
"""
struct LookAt <: Action end

export LookAt

function argument_domains(::LookAt)
    (Visible(),)
end

function possible_now(universe, ::Sentence{LookAt}, ::Visible, thing)
    get_description(universe, thing) != ""
end

function (::LookAt)(universe, thing)
    println_wrapped(
        universe.interface,
        get_description(universe, thing);
        replace_whitespace = false,
    )
    return false
end

function print_sentence(io, ::LookAt, thing_answer)
    print(io, "Look at ")
    print(io, thing_answer.text)
end

"""
    OpenOrClose()

Open or close something [`Reachable`](@ref).

An [`Action`](@ref).
"""
struct OpenOrClose <: Action end

export OpenOrClose

function is_closable_and_closed(thing)
    ever_possible(OpenOrClose(), Reachable(), thing) && thing.closed
end

function possible_now(_, ::Sentence{OpenOrClose}, ::Reachable, thing)
    ever_possible(OpenOrClose(), Reachable(), thing) && !(is_lockable_and_locked(thing))
end

function (::OpenOrClose)(_, thing)
    thing.closed = !(thing.closed)
    return false
end

function print_sentence(io, ::OpenOrClose, thing_answer)
    object = thing_answer.object
    if object isa Noun
        if object.closed
            print(io, "Open ")
        else
            print(io, "Close ")
        end
    else
        print(io, "Open or close ")
    end
    print(io, thing_answer.text)
end

function argument_domains(::OpenOrClose)
    (Reachable(),)
end

"""
    PutInto()

Put something from your [`Inventory`](@ref) into something [`Reachable`](@ref).

An [`Action`](@ref). By default,

    ever_possible(::PutInto, ::Inventory, anything) = true

that is, it is always possible to put something from your inventory into a container, and

    ever_possible(::PutInto, ::Reachable, ::AbstractRoom) = true

that is, all rooms act like containers.
"""
struct PutInto <: Action end

export PutInto

function argument_domains(::PutInto)
    Inventory(), Reachable()
end

ever_possible(::PutInto, ::Inventory, _) = true
ever_possible(::PutInto, ::Reachable, ::AbstractRoom) = true

function possible_now(_, sentence::Sentence{PutInto}, domain::Reachable, thing)
    ever_possible(sentence.action, domain, thing) &&
        !(is_closable_and_closed(thing)) &&
        # can't put something into itself
        thing !== sentence.argument_answers[1].object
end

function (::PutInto)(universe, thing, parent_thing)
    universe[parent_thing, thing] = Containing()
    return false
end

function print_sentence(io, ::PutInto, thing_answer, parent_thing_answer)
    print(io, "Put ")
    print(io, thing_answer.text)
    print(io, " into ")
    print(io, parent_thing_answer.text)
end

"""
    Take()

Take something [`Reachable`](@ref).

An [`Action`](@ref).
"""
struct Take <: Action end

export Take

function argument_domains(::Take)
    (Reachable(),)
end

function possible_now(universe, sentence::Sentence{Take}, domain::Reachable, thing)
    ever_possible(sentence.action, domain, thing) &&
    !(thing isa Location) &&
    begin
        parent_thing, relationship = get_parent_relationship(universe, thing)
        !(parent_thing === universe.player && relationship isa Carrying)
    end
end

function (::Take)(universe, thing)
    ExtraActions.Give()(universe, thing, universe.player)
end

function print_sentence(io, ::Take, thing_answer)
    print(io, "Take ")
    print(io, thing_answer.text)
end

"""
    Quit()

Quit

An [`Action`](@ref).
"""
struct Quit <: Action end

export Quit

function argument_domains(::Quit)
    ()
end

function (::Quit)(_)
    return true
end

function print_sentence(io, ::Quit)
    print(io, "Quit")
end

"""
    UnlockOrLock()

Unlock or lock something [`Reachable`](@ref) with something from your [`Inventory`](@ref).

An [`Action`](@ref).
"""
struct UnlockOrLock <: Action end

export UnlockOrLock

function is_lockable_and_locked(thing)
    ever_possible(UnlockOrLock(), Reachable(), thing) && thing.locked
end

function argument_domains(::UnlockOrLock)
    Reachable(), Inventory()
end

function possible_now(_, sentence::Sentence{UnlockOrLock}, domain::Reachable, thing)
    ever_possible(sentence.action, domain, thing) && is_closable_and_closed(thing)
end

function (::UnlockOrLock)(universe, door, key)
    if door.key === key
        door.locked = !(door.locked)
    else
        println_wrapped(
            universe.interface,
            string_in_color(
                :red,
                uppercasefirst(string(key)),
                ' ',
                subject_to_verb(key, DO),
                "n't fit!",
            ),
        )
    end
    return false
end

function print_sentence(io, ::UnlockOrLock, door_answer, key_answer)
    door_object = door_answer.object
    if door_object isa Noun
        if door_object.locked
            print(io, "Unlock ")
        else
            print(io, "Lock ")
        end
    else
        print(io, "Unlock or lock ")
    end
    print(io, door_answer.text)
    print(io, " with ")
    print(io, key_answer.text)
end

"""
    MenuAdventures.print_sentence(io, action::Action, argument_answers::Answer...)

Print a sentence to `io`.

This allows for inserting connectives like `with`. Arguments will be passed as [`Answer`](@ref)s.
"""
print_sentence

@enum GrammaticalPerson first_person second_person third_person

"""
    first_person

First person (e.g. I).

In games, the narrator is typically the first person.
"""
first_person

export first_person

"""
    second_person

Second person (e.g. you).

In games, the player is typically the second person.
"""
second_person

export second_person

"""
    third_person

Third person (e.g. he, she, it).

In games, everything is typically third person except for the player and narrator.
"""
third_person

export third_person

export Noun

get_description(universe, thing::Noun) = thing.description(universe, thing)

"""
    MenuAdventures.is_transparent(thing::Noun) = false

Whether you can see through a `thing` into its contents.
"""
is_transparent(::Noun) = false

"""
    MenuAdventures.is_vehicle(::Noun) = false

Whether something is a vehicle.
"""
is_vehicle(::Noun) = false

"""
    is_shining(::Noun) = false

Whether something provides light.
"""
is_shining(::Noun) = false

"""
    MenuAdventures.ever_possible(action::Action, domain::Domain, noun::Noun)

Whether it is abstractly possible to apply an [`Action`](@ref) to a [`Noun`](@ref) from a particular [`Domain`](@ref).

For whether it is concretely possible for the player in at a certain moment, see [`possible_now`](@ref).
Most possibilities default to `false`, with some exceptions, documented in specific actions.

Certain possibilities come with required fields:

- `ever_possible(::Open, ::Reachable, noun` requires that `noun` has a mutable `closed::Bool` field.
- `ever_possible(::Unlock, ::Reachable, noun)` requires that `noun` has a `key::Noun` field and a mutable `locked::Bool` field.
"""
ever_possible(_, __, ___) = false

struct Verb
    base::String
    third_person_singular_present::String
end

"""
    Verb(base; third_person_singular_present = string(base, "s"))

Create an English verb.

Use [`subject_to_verb`](@ref) to get the form of a verb to agree with a subject.
Unexported verbs include `MenuAdventures.DO` and `MenuAdventures.BE`.
"""
function Verb(base; third_person_singular_present = string(base, "s"))
    Verb(base, third_person_singular_present)
end

export Verb

const DO = Verb("do"; third_person_singular_present = "does")
const BE = Verb("are"; third_person_singular_present = "is")

"""
    subject_to_verb(subject, verb)

Find the [`Verb`](@ref) form to agree with a subject.
"""
function subject_to_verb(subject, verb)
    if subject.grammatical_person === third_person && !(subject.plural)
        verb.third_person_singular_present
    else
        verb.base
    end
end

export subject_to_verb

"""
    abstract type AbstractUniverse end

Contains all the information about the game universe.

You will need to create your own `AbstractUniverse` subtype. See [`@universe`](@ref) for an easy way to do this.

The universe is organized as an interlinking web of [`Location`](@ref)s connected by [`Direction`](@ref)s.
Each location is the root of a tree of [`Noun`](@ref)s connected by [`Relationship`](@ref)s.

You can add a new thing to the `universe`, or change the location of something, by specifying its relation to another thing:

    universe[parent_thing, thing] = relationship

You can add a connection between locations too, optionally interspersed by a door:

    universe[origin, destination, one_way = false] = direction
    universe[origin, destination, one_way = false] = door, direction

By default, this will create a way back in the [`MenuAdventures.opposite`](@ref) direction. To suppress this, set `one_way = true`
"""
abstract type AbstractUniverse end

export AbstractUniverse

show(io::IO, ::AbstractUniverse) = print(io, "A universe")

function get_parent(universe, thing)
    relationships_graph = universe.relationships_graph
    label_for(
        relationships_graph,
        only(inneighbors(relationships_graph, code_for(relationships_graph, thing))),
    )
end

"""
    get_parent_relationship(universe, thing)

Get the parent of a `thing` in the universe, and the parent's [`Relationship`](@ref) to it.
"""
function get_parent_relationship(universe, thing)
    relationships_graph = universe.relationships_graph
    parent = get_parent(universe, thing)
    parent, relationships_graph[parent, thing]
end

export get_parent_relationship

function over_out_neighbor_codes(a_function, meta_graph, parent_thing)
    Iterators.map(a_function,
        if haskey(meta_graph, parent_thing)
            outneighbors(meta_graph, code_for(meta_graph, parent_thing))
        else
            eltype(meta_graph)[]
        end
    )
end

function out_neighbors_relationships(meta_graph, parent_thing)
    over_out_neighbor_codes(
        function (code)
            thing = label_for(meta_graph, code)
            thing, meta_graph[parent_thing, thing]
        end,
        meta_graph,
        parent_thing,
    )
end

"""
    get_children_relationships(universe, parent_thing)

Get the children of `parent_thing` in the universe, and the [`Relationship`](@ref)s of `parent_thing` to them.
"""
function get_children_relationships(universe, parent_thing)
    out_neighbors_relationships(universe.relationships_graph, parent_thing)
end

export get_children_relationships

"""
    get_exit_directions(universe, location)

Get the exits from a location, and the direction of those exits (if exits exist)
"""
function get_exit_directions(universe, location)
    out_neighbors_relationships(universe.directions_graph, location)
end

export get_exit_directions

function get_mover(universe)
    player = universe.player
    mover = get_parent(universe, player)
    if is_vehicle(mover)
        mover
    else
        player
    end
end

function get_first_destination(universe, blocking_thing, direction)
    only(Iterators.filter(
        function ((location, possible_direction),)
            possible_direction === direction
        end,
        get_exit_directions(universe, blocking_thing),
    ))[1]
end

function get_first_destination(universe, direction)
    blocking_thing, _ = blocking_thing_and_relationship(universe, ExitDirections())
    get_first_destination(universe, blocking_thing, direction)
end

function get_final_destination(universe, direction)
    blocking_thing, _ = blocking_thing_and_relationship(universe, ExitDirections())
    maybe_door = get_first_destination(universe, blocking_thing, direction)
    if maybe_door isa AbstractDoor
        get_first_destination(universe, maybe_door, direction)
    else
        maybe_door
    end
end

function setindex!(
    universe::AbstractUniverse,
    relationship::Relationship,
    parent_thing::Noun,
    thing::Noun;
)
    relationships_graph = universe.relationships_graph
    relationships_graph[parent_thing] = nothing
    if !(thing isa Location) && haskey(relationships_graph, thing)
        old_parent = get_parent(universe, thing)
        if haskey(relationships_graph, old_parent, thing)
            delete!(relationships_graph, old_parent, thing)
        end
    else
        relationships_graph[thing] = nothing
    end
    relationships_graph[parent_thing, thing] = relationship
    nothing
end

function one_way!(
    universe::AbstractUniverse,
    blocking_thing::Location,
    direction::Direction,
    destination::Location,
)
    relationships_graph = universe.relationships_graph
    relationships_graph[blocking_thing] = nothing
    relationships_graph[destination] = nothing
    directions_graph = universe.directions_graph
    directions_graph[blocking_thing] = nothing
    directions_graph[destination] = nothing
    directions_graph[blocking_thing, destination] = direction
    nothing
end

function one_way!(
    universe::AbstractUniverse,
    blocking_thing::Location,
    door::AbstractDoor,
    direction::Direction,
    destination::Location,
)
    one_way!(universe, blocking_thing, direction, door)
    one_way!(universe, door, direction, destination)
    nothing
end

function setindex!(
    universe::AbstractUniverse,
    direction::Direction,
    blocking_thing::Location,
    destination::Location;
    one_way = false,
)
    one_way!(universe, blocking_thing, direction, destination)
    if !one_way
        one_way!(universe, destination, opposite(direction), blocking_thing)
    end
end
function setindex!(
    universe::AbstractUniverse,
    (door, direction)::Tuple{AbstractDoor, Direction},
    blocking_thing::Location,
    destination::Location;
    one_way = false,
)
    one_way!(universe, blocking_thing, door, direction, destination)
    if !one_way
        one_way!(universe, destination, door, opposite(direction), blocking_thing)
    end
end

function maybe_capitalize(capitalize, thing)
    if capitalize
        uppercasefirst(thing)
    else
        thing
    end
end

function show(io::IO, thing::Noun)
    capitalize = get(io, :capitalize, false)
    grammatical_person = thing.grammatical_person
    if grammatical_person === third_person
        indefinite_article = thing.indefinite_article
        if isempty(indefinite_article)
            print(io, maybe_capitalize(capitalize, thing.name))
        else
            if get(io, :known, true)
                print(io, maybe_capitalize(capitalize, "the "))
                print(io, thing.name)
            else
                print(io, maybe_capitalize(capitalize, thing.indefinite_article))
                print(io, ' ')
                print(io, thing.name)
            end
        end
    else
        is_subject = get(io, :is_subject, true)
        print(
            io,
            if grammatical_person === second_person
                if is_subject
                    maybe_capitalize(capitalize, "you")
                else
                    subject = get(io, :subject, nothing)
                    if subject === thing
                        # since the subject is the second person, use the reflexive pronoun instead
                        maybe_capitalize(capitalize, "yourself")
                    else
                        maybe_capitalize(capitalize, "you")
                    end
                end
            else
                error("Unsupported grammatical person")
            end
        )
    end
end

"""
    string_in_color(color::Symbol, arguments...)

Use ASCII escape codes to add a `color` to the `arguments` collected as a string.
"""
function string_in_color(color::Symbol, arguments...)
    string(text_colors[color], arguments..., text_colors[:default])
end

export string_in_color

function make_answer(universe, thing)
    buffer = IOBuffer()
    # things will have already been mentioned in the room descrption
    show(IOContext(buffer, :is_subject => false, :subject => universe.player), thing)
    Answer(String(take!(buffer)), thing)
end

"""
    mention_status(io, action, thing)

Print the status of `thing` corresponding to `action` into `io`.

For example, for the [`OpenOrClose`](@ref) action, will display whether `thing` is open or closed.
Called for all action subtypes when mentioning a thing.
Defaults to doing `nothing`.
"""
function mention_status(_, __, ___)
    nothing
end

function mention_status(io, action::OpenOrClose, thing)
    if ever_possible(action, Reachable(), thing)
        if thing.closed
            print(io, " (closed)")
        else
            print(io, " (open)")
        end
    end
end

function mention_status(io, action::UnlockOrLock, thing)
    if ever_possible(action, Reachable(), thing)
        if thing.locked
            print(io, " (locked)")
        else
            print(io, " (unlocked)")
        end
    end
end

function make_blurb(thing)
    buffer = IOBuffer()
    # things will have already been mentioned in the room descrption
    show(IOContext(buffer, :known => false, :is_subject => true), thing)
    for action_type in subtypes(Action)
        mention_status(buffer, action_type(), thing)
    end
    Answer(String(take!(buffer)), thing)
end

struct Question
    text::String
    answers::Vector{Answer}
end

function get_object(thing::Answer)
    thing.object
end
function get_object(::Question)
    nothing
end

function print_relationship_as_verb(io, parent_thing, relationship::Relationship)
    print(io, subject_to_verb(parent_thing, verb_for(relationship)))
end

function print_relationship_as_verb(io, _, direction::Direction)
    show(io, direction)
end

"""
    MenuAdventures.blocking(domain, parent_thing, relationship, thing)

`parent_thing` is blocked from accessing `thing` via the `relationship`.

By default, [`Reachable`](@ref) `parent_thing`s block `thing`s they are [`Containing`](@ref) if they are closed.
By default, [`Visible`](@ref) `parent_thing`s block `thing`s they are [`Containing`](@ref) if they are closed and not [`MenuAdventures.is_transparent`](@ref).
"""
blocking

"""
    MenuAdventures.possible_now(universe, sentence, domain, thing)

Whether it is currently possible to apply `sentence.action` to a `thing` in a `domain`.

See [`ever_possible`](@ref) for a more abstract possibilities.
`sentence` will contain already chosen arguments, should you wish to access them.
"""
function possible_now(_, sentence, domain, thing)
    ever_possible(sentence.action, domain, thing)
end

function append_parent_relationship_to(_, noun::Noun, __, ___)
    noun
end

function append_parent_relationship_to(universe, answer::Answer, relationship, parent_thing)
    Answer(
        string_relationship_to(answer, relationship, parent_thing),
        append_parent_relationship_to(universe, answer.object, relationship, parent_thing),
    )
end

function append_parent_relationship_to(universe, question::Question, relationship, parent_thing)
    Question(
        string_relationship_to(question, relationship, parent_thing),
        map(
            function (answer)
                append_parent_relationship_to(universe, answer, relationship, parent_thing)
            end,
            question.answers,
        ),
    )
end

function add_thing_and_relations!(answers, universe, sentence, domain, parent_thing)
    if possible_now(universe, sentence, domain, parent_thing)
        push!(answers, make_answer(universe, parent_thing))
    end
    sub_relations = OrderedDict{Relationship, Vector{Answer}}()
    for (thing, relationship) in
        get_children_relationships(universe, parent_thing)
        if !(blocking(domain, parent_thing, relationship, thing))
            add_thing_and_relations!(
                get!(sub_relations, relationship, Answer[]),
                universe,
                sentence,
                domain,
                thing,
            )
            # we automatically mention everything that is visible
            # recur
        end
    end
    for (sub_relationship, sub_answers) in sub_relations
        if !isempty(sub_answers)
            push!(
                answers,
                append_parent_relationship_to(
                    universe,
                    if length(sub_answers) == 1
                        only(sub_answers)
                    else
                        Answer(
                            indefinite(domain),
                            Question(interrogative(domain), sub_answers),
                        )
                    end,
                    sub_relationship,
                    parent_thing,
                ),
            )
        end
    end
end

function add_siblings_and_doors!(answers, universe, sentence, domain, blocking_thing, blocked_relationship)
    for (thing, relationship) in
        get_children_relationships(universe, blocking_thing)
        if relationship === blocked_relationship
            add_thing_and_relations!(answers, universe, sentence, domain, thing)
        end
    end
    for (location, _) in
        get_exit_directions(universe, blocking_thing)
        if location isa AbstractDoor
            add_thing_and_relations!(answers, universe, sentence, domain, location)
        end
    end
end

function print_relations(universe, indent, domain, parent_thing, relations)
    interface = universe.interface
    relationship_indent = indent + 2
    sub_indent = relationship_indent + 2
    for (relationship, answers) in relations
        print(interface, ' '^relationship_indent)
        print_relationship_as_verb(interface, parent_thing, relationship)
        print(interface, ':')
        println(interface)
        for answer in answers
            # don't need the answer text, just the object itself
            thing = answer.object
            sub_relations = OrderedDict{Relationship, Vector{Answer}}()
            # we always stop at the player; inventory must be explicitly asked for
            if !(thing === universe.player)
                for (sub_thing, sub_relationship) in
                    get_children_relationships(universe, thing)
                    if !(blocking(domain, thing, sub_relationship, sub_thing))
                        # we automatically mention everything that is visible
                        push!(
                            get!(sub_relations, sub_relationship, Answer[]),
                            make_blurb(sub_thing),
                        )
                    end
                end
            end
            print(interface, ' '^sub_indent)
            print(interface, make_blurb(thing).text)
            if !isempty(sub_relations)
                print(interface, ':')
            end
            println(interface)
            print_relations(universe, sub_indent, domain, thing, sub_relations)
        end
    end
end

"""
    argument_domains(action::Action)

Return a tuple of the [`Domain`](@ref)s for each argument of an [`Action`](@ref).
"""
argument_domains

"""
    possible_now(universe, action)

Whether it is possible to conduct an action.
Defaults to `true`; you can set to `false` for some actions without arguments.
"""
function possible_now(_, __)
    true
end

function choose(universe, sentence, index, answer)
    interface = universe.interface
    object = answer.object
    if object isa Question
        answers = object.answers
        sort!(answers, by = answer -> answer.text)
        println(interface)
        # add an exta line to add space before the question
        # we need take it first to see if its empty or not
        choice = request(
            interface,
            sprint(
                show,
                replace_argument(sentence, index, Answer(object.text, nothing));
                context = :suffix => "?"
            ),
            RadioMenu(map(
                function (sub_answer)
                    sprint(show, replace_argument(sentence, index, sub_answer), context = :color => :green)
                end,
                answers
            ); charset = :ascii),
        )
        push!(universe.choices_log, choice)
        choose(universe, sentence, index, answers[choice])
    else
        answer
    end
end

function show(io::IO, sentence::Sentence)
    color = get(io, :color, :default)
    print(io, text_colors[color])
    print_sentence(
        io,
        sentence.action,
        sentence.argument_answers...
    )
    print(io, get(io, :suffix, ""))
    print(io, text_colors[:default])
end

function replace_argument(sentence, blank_index, replacement)
    argument_answers_copy = copy(sentence.argument_answers)
    argument_answers_copy[blank_index] = replacement
    Sentence(sentence.action, argument_answers_copy)
end

function is_player_lit(universe; domain = Visible())
    blocking_thing, blocked_relationship = blocking_thing_and_relationship(universe, domain)
    if blocking_thing isa AbstractRoom && blocking_thing.already_lit
        true
    else
        for (thing, relationship) in
            get_children_relationships(universe, blocking_thing)
            if relationship === blocked_relationship &&
                is_lit(universe, thing; domain = domain)
                return true
            end
        end
        false
    end
end

function is_lit(universe, parent_thing; domain = Visible())
    if is_shining(parent_thing)
        true
    else
        for (thing, relationship) in
            get_children_relationships(universe, parent_thing)
            if !(blocking(domain, parent_thing, relationship, thing))
                if is_lit(universe, thing; domain = domain)
                    return true
                end
            end
        end
        false
    end
end

"""
    MenuAdventures.look_around(universe, domain, blocking_thing, blocked_relationship)

Look around a lit location.

You can overload `look_around` for a [`Noun`](@ref) subtype.
Use `Core.invoke` to avoid replicating the `look_around` machinery.
"""
function look_around(universe, domain, blocking_thing, blocked_relationship)
    interface = universe.interface
    println(interface)
    indent = 0
    relations = OrderedDict{Union{Relationship, Direction}, Vector{Answer}}()
    for (thing, relationship) in
        get_children_relationships(universe, blocking_thing)
        if relationship === blocked_relationship
            # we automatically mention everything that is visible
            push!(get!(relations, relationship, Answer[]), make_blurb(thing))
        end
    end
    for (location, direction) in
        get_exit_directions(universe, blocking_thing)
        if location isa AbstractDoor
            push!(get!(relations, direction, Answer[]), make_blurb(location))
        end
    end
    print(interface, ' '^indent)
    show(IOContext(interface, :capitalize => true, :known => false, :is_subject => true), blocking_thing)
    if !isempty(relations)
        print(interface, ':')
    end
    println(interface)
    print_relations(universe, indent, domain, blocking_thing, relations)
end

"""
    turn!(universe; introduce = true)

Start a turn in the [`AbstractUniverse`](@ref), and keep going until an [`Action`](@ref) returns `true`.

You can overload `turn!` for an [`AbstractUniverse`](@ref).
Use `Core.invoke` to avoid replicating the `turn!` machinery.
"""
function turn!(universe; introduce = true)
    interface = universe.interface

    if introduce
        introduction = universe.introduction
        if introduction != ""
            println_wrapped(interface, introduction; replace_whitespace = false)
        end
    end

    # reintroduce the player to their surroundings if the end the turn in a new loaction
    lit = is_player_lit(universe)

    if !lit
        println_wrapped(interface, "In darkness")
    else
        visible = Visible()
        look_around(universe, visible, blocking_thing_and_relationship(universe, visible)...)
    end
    sentences = Sentence[]
    for verb_type in subtypes(Action)
        action = verb_type()
        sentence = Sentence(action)
        dead_end = false
        for domain in argument_domains(action)
            answers = find_in_domain(universe, sentence, domain; lit = lit)
            if isempty(answers)
                dead_end = true
                break
            else
                push!(
                    sentence.argument_answers,
                    if length(answers) == 1
                        only(answers)
                    else
                        Answer(
                            indefinite(domain),
                            Question(interrogative(domain), answers),
                        )
                    end,
                )
            end
        end
        if possible_now(universe, action) && !dead_end
            push!(sentences, sentence)
        end
    end
    choice =
        if length(sentences) > 1
            sort!(sentences, by = repr)
            request(interface, "", RadioMenu(map(
                function (sentence)
                    sprint(show, sentence, context = :color => :green)
                end,
                sentences
            ); charset = :ascii))
        else
            println(interface)
            print(interface, " > ")
            show(IOContext(interface, :color => :green), sentences[1])
            1
        end
    push!(universe.choices_log, choice)

    sentence = sentences[choice]
    argument_answers = sentence.argument_answers

    for (index, argument_answer) in enumerate(argument_answers)
        argument_answers[index] = choose(universe, sentence, index, argument_answer)
    end
    end_game = sentence.action(universe, Iterators.map(function (answer)
        answer.object
    end, argument_answers)...)
    if !end_game
        turn!(universe; introduce = false)
    end
    nothing
end

export turn!

function parse_lines!(positional_arguments, keyword_arguments, lines)
    for line in lines
        if @capture line fieldname_::fieldtype_
            push!(positional_arguments, (field_name = fieldname, field_type = fieldtype))
        elseif @capture line fieldname_::fieldtype_ = default_
            push!(keyword_arguments, (field_name = fieldname, field_type = fieldtype, default = default))
        else
            error("Cannot parse line")
        end
    end
end

function add_line!(struct_lines, constructor_call_arguments, argument, location)
    field_name = argument.field_name
    push!(struct_lines, location)
    push!(struct_lines, Expr(:(::), field_name, argument.field_type))
    push!(constructor_call_arguments, field_name)
end

function add_defaults(user_definition, defaults, location)
    if @capture user_definition (
        struct nounname_ <: thesupertype_
            userfields__
        end
    )
        is_mutable = false
    elseif @capture user_definition (
        mutable struct nounname_ <: thesupertype_
            userfields__
        end
    )
        is_mutable = true
    else
        throw(ArgumentError("Cannot parse user struct definition"))
    end
    positional_arguments = []
    keyword_arguments = []
    parse_lines!(positional_arguments, keyword_arguments, defaults)
    parse_lines!(positional_arguments, keyword_arguments, userfields)
    sort!(keyword_arguments, by = argument -> argument.field_name)
    struct_lines = []
    constructor_positionals = []
    constructor_keywords = []
    constructor_call_arguments = []

    for argument in positional_arguments
        add_line!(struct_lines, constructor_call_arguments, argument, location)
        push!(constructor_positionals, argument.field_name)
    end
    for argument in keyword_arguments
        add_line!(struct_lines, constructor_call_arguments, argument, location)
        push!(constructor_keywords, Expr(:kw, argument.field_name, argument.default))
    end
    Expr(:block,
        location,
        Expr(:struct,
            is_mutable,
            Expr(:(<:), nounname, thesupertype),
            Expr(:block, struct_lines...)
        ),
        location,
        Expr(:function,
            Expr(:call, nounname, Expr(:parameters, constructor_keywords...), constructor_positionals...),
            Expr(:block, location, Expr(:call, nounname, constructor_call_arguments...))
        )
    )
end

"""
    const NOUN_FIELDS = [
        :(name::String),
        :(grammatical_person::GrammaticalPerson = third_person),
        :(indefinite_article::String = "a"),
        :(plural::Bool = false),
        :(description = (_, __) -> "")
    ]

A list of expressions, the fields added by the [`@noun`](@ref) macro.

You can add new noun fields by `push!`ing to this list.
Because the expressions will be escaped, you might want to interpolate in everything except for the field name.
Be careful; obviously changing `NOUN_FIELDS` will change the behavior of the [`@noun`](@ref) macro.
"""
const NOUN_FIELDS = [
    :(name::$String),
    :(grammatical_person::$GrammaticalPerson = $third_person),
    :(indefinite_article::$String = "a"),
    :(plural::$Bool = false),
    :(description::$FunctionWrapper{$String, $Tuple{$AbstractUniverse, $Noun}} = (_, __) -> "")
]

"""
    @noun user_definition

Automatically add [`MenuAdventures.NOUN_FIELDS`](@ref) to a [`Noun`](@ref) struct definition, including sane defaults.

Adds the following fields and defaults:

- `name::String`
- `plural::Bool = false`
- `grammatical_person::GrammaticalPerson = third_person`, see [`third_person`](@ref)
- `indefinite_article::String = "a"`
- `description = (universe, self) -> ""`

Set `indefinite_article` to `""` for proper nouns.
`description` should be a function which takes two arguments, the `universe` and the `thing` itself, and returns a description.
"""
macro noun(user_definition)
    esc(add_defaults(user_definition, NOUN_FIELDS, __source__))
end

export @noun

const UNIVERSE_FIELDS = [
    :(player::$Noun),
    :(interface::$TTYTerminal = ($TerminalMenus).terminal),
    :(introduction::$String = ""),
    :(relationships_graph::$(typeof(MetaGraph(DiGraph(), Label = Noun, EdgeMeta = Relationship))) =
        $MetaGraph($DiGraph(), Label = $Noun, EdgeMeta = $Relationship)),
    :(directions_graph::$(typeof(MetaGraph(DiGraph(), Label = Location, EdgeMeta = Direction))) =
        $MetaGraph($DiGraph(), Label = $Location, EdgeMeta = $Direction)),
    :(choices_log::$(Vector{Int}) = $Int[])
]

"""
    @universe user_definition

Automatically add the required fields to an [`AbstractUniverse`](@ref) struct definition, including sane defaults.

Adds the following fields and defaults:

- `player::Noun`. The player will typically be in [`second_person`](@ref).
- `interface::TTYTerminal = terminal`
- `introduction::String = ""`
- `relationships_graph = MetaGraph(DiGraph(), Label = Noun, EdgeMeta = Relationship))`, the [`Relationship`](@ref)s between [`Noun`](@ref)s.
- `directions_graph = MetaGraph(DiGraph(), Label = Location, EdgeMeta = Direction))`, the [`Direction`](@ref)s between [`Location`](@ref)s.
- `choices_log::Vector{Int} = Int[]` saves all choices the user makes

See [`AbstractUniverse`](@ref) for more information.
"""
macro universe(user_definition)
    esc(add_defaults(user_definition, UNIVERSE_FIELDS, __source__))
end

export @universe

include("ExtraDirections.jl")
include("ExtraActions.jl")
include("Onto.jl")
include("Outfits.jl")
include("Parts.jl")
include("Talking.jl")
include("Testing.jl")

end


text:
mutable struct MemoryBuffer{L,T} <: DenseVector{T}
    data::NTuple{L,T}
    @inline function MemoryBuffer{L,T}(::UndefInitializer) where {L,T}
        @assert isbitstype(T) "Memory buffers must point to bits types, but `isbitstype($T) == false`."
        new{L,T}()
    end
end
@inline Base.unsafe_convert(::Type{Ptr{T}}, d::MemoryBuffer) where {T} = Base.unsafe_convert(Ptr{T}, Base.pointer_from_objref(d))
@inline MemoryBuffer{T}(::UndefInitializer, ::StaticInt{L}) where {L,T} = MemoryBuffer{L,T}(undef)
Base.size(::MemoryBuffer{L}) where L = (L,)
@inline Base.similar(::MemoryBuffer{L,T}) where {L,T} = MemoryBuffer{L,T}(undef)
# Base.IndexStyle(::Type{<:MemoryBuffer}) = Base.IndexLinear()
@inline function Base.getindex(m::MemoryBuffer{L,T}, i::Int) where {L,T}
    @boundscheck checkbounds(m, i)
    GC.@preserve m x = vload(pointer(m), VectorizationBase.lazymul(VectorizationBase.static_sizeof(T), i - one(i)))
    x
end
@inline function Base.setindex!(m::MemoryBuffer{L,T}, x, i::Int) where {L,T}
    @boundscheck checkbounds(m, i)
    GC.@preserve m vstore!(pointer(m), convert(T, x), lazymul(static_sizeof(T), i - one(i)))
end
function core_cache_size(::Type{T}, ::Val{N}) where {T,N}
    CS = VectorizationBase.CACHE_SIZE[N]
    if CS === nothing
        nothing
    else
        StaticInt{CS}() ÷ static_sizeof(T)
        # CC = StaticInt{VectorizationBase.CACHE_COUNT[N]}()
        # NC = StaticInt{VectorizationBase.NUM_CORES}()
        # (StaticInt{CS}() * CC) ÷ (static_sizeof(T) * NC)
    end
end
function cache_size(::Type{T}, ::Val{N}) where {T,N}
    CS = VectorizationBase.CACHE_SIZE[N]
    if CS === nothing
        nothing
    else
        CC = StaticInt{VectorizationBase.CACHE_COUNT[N]}()
        (StaticInt{CS}() * CC) ÷ (static_sizeof(T))
    end
end
@inline function cache_buffer(::Type{T}, ::Val{N}) where {T,N}
    CS = VectorizationBase.CACHE_SIZE[N]
    if CS === nothing
        nothing
    else
        MemoryBuffer{T}(undef, StaticInt{CS}() ÷ static_sizeof(T))# + (StaticInt{4096}() ÷ static_sizeof(T)))
    end
end
@inline function core_cache_buffer(::Type{T}, ::Val{N}) where {T,N}
    L = core_cache_size(T, Val{N}())
    L === nothing && return nothing
    MemoryBuffer{T}(undef, L)# + (StaticInt{4096}() ÷ static_sizeof(T)))
#     ptr = Base.unsafe_convert(Ptr{T}, ACACHE) + (Threads.threadid()-1) * L * static_sizeof(T)
#     ptrarray0(ptr, (L,))
end

@inline undef_memory_buffer(::Type{T}, ::StaticInt{L}) where {T,L} = MemoryBuffer{L,T}(undef)
@inline undef_memory_buffer(::Type{T}, L) where {T} = Vector{T}(undef, L)

# @inline L₁buffer(::Type{T}) where {T} = cache_buffer(T, Val{1}())
# @inline L₂buffer(::Type{T}) where {T} = cache_buffer(T, Val{2}())
# @inline L₃buffer(::Type{T}) where {T} = cache_buffer(T, Val{3}())
# @inline L₄buffer(::Type{T}) where {T} = cache_buffer(T, Val{4}())

abstract type AbstractStrideArray{S,D,T<:VectorizationBase.NativeTypes,N,C,B,R,X,O} <: DenseArray{T,N} end
abstract type AbstractPtrStrideArray{S,D,T,N,C,B,R,X,O} <: AbstractStrideArray{S,D,T,N,C,B,R,X,O} end

# abstract type AbstractConstantStrideArray{S,T,N,C,B,R,X,O,D} <: AbstractArray{T,N} end
# const AbstractStrideArray{S,T,N,C,B,R,X,O,D} = Union{AbstractStrideArray{S,T,N,C,B,R,X,O,D}, ConstantStrideArray{S,T,N,C,B,R,X,O,D}}

# const ALIGN_ALL_FS_ARRAYS = true

struct PtrArray{S,D,T,N,C,B,R,X,O} <: AbstractPtrStrideArray{S,D,T,N,C,B,R,X,O}
    ptr::StridedPointer{T,N,C,B,R,X,O}
    size::S
    # @inline function PtrArray{S,T,N,X,SN,XN,V}(ptr::Ptr{T}, size::NTuple{SN,Int}, stride::NTuple{XN,Int}) where {S,T,N,X,SN,XN,V}
    #     check_N(Val{N}(), S, X)
    #     new(ptr, size, stride)
    # end
end
@inline function PtrArray(ptr::StridedPointer{T,N,C,B,R,X,O}, size::S, ::DenseDims{D}) where {S,D,T,N,C,B,R,X,O}
    PtrArray{S,D,T,N,C,B,R,X,O}(ptr, size)
end

struct StrideArray{S,D,T,N,C,B,R,X,O,A} <: AbstractStrideArray{S,D,T,N,C,B,R,X,O}
    ptr::PtrArray{S,D,T,N,C,B,R,X,O}
    data::A
end

const StrideVector{S,D,T,C,B,R,X,O,A} = StrideArray{S,D,T,1,C,B,R,X,O,A}
const StrideMatrix{S,D,T,C,B,R,X,O,A} = StrideArray{S,D,T,2,C,B,R,X,O,A}
const AbstractStrideVector{S,D,T,C,B,R,X,O} = AbstractStrideArray{S,D,T,1,C,B,R,X,O}
const AbstractStrideMatrix{S,D,T,C,B,R,X,O} = AbstractStrideArray{S,D,T,2,C,B,R,X,O}

@inline StrideArray(A::AbstractArray) = StrideArray(PtrArray(A), A)

@inline LoopVectorization.preserve_buffer(A::MemoryBuffer) = A
@inline LoopVectorization.preserve_buffer(A::StrideArray) = LoopVectorization.preserve_buffer(A.data)
@inline LoopVectorization.preserve_buffer(A::PtrArray) = nothing

@inline maybe_ptr_array(A) = A
@inline maybe_ptr_array(A::AbstractArray) = maybe_ptr_array(ArrayInterface.device(A), A)
@inline maybe_ptr_array(::ArrayInterface.CPUPointer, A::AbstractArray) = PtrArray(A)
@inline maybe_ptr_array(_, A::AbstractArray) = A

"""
  @gc_preserve foo(A, B, C)

Apply to a single, non-nested, function call. It will `GC.@preserve` all the arguments, and substitute suitable arrays with `PtrArray`s.
This has the benefit of potentially allowing statically sized mutable arrays to be both stack allocated, and passed through a non-inlined function boundary.
"""
macro gc_preserve(ex)
    @assert ex.head === :call
    q = Expr(:block)
    call = Expr(:call, esc(ex.args[1]))
    gcp = Expr(:gc_preserve, call)
    for i ∈ 2:length(ex.args)
        arg = ex.args[i]
        A = gensym(:A); buffer = gensym(:buffer);
        if arg isa Expr && arg.head === :kw
            push!(call.args, Expr(:kw, arg.args[1], Expr(:call, :maybe_ptr_array, A)))
            arg = arg.args[2]
        else
            push!(call.args, Expr(:call, :maybe_ptr_array, A))
        end
        push!(q.args, :($A = $(esc(arg))))
        push!(q.args, Expr(:(=), buffer, Expr(:call, :preserve_buffer, A)))
        push!(gcp.args, buffer)
    end
    push!(q.args, gcp)
    q
end

# const FVector{L,T} = StrideVector{Tuple{StaticInt{L}},(true,),T,1,0,(1,)}

# @inline function StrideArray{S,T,N,C,B,R,X,O,D}(ptr, sz, sx, data) where {S,T,N,C,B,R,X,O,D}
    # StrideArray{S,T,N,C,B,R,X,O,D}(PtrArray{S,T,N,X,SN,XN,V}(ptr, sz, sx), data)
# end
# struct FixedSizeArray{S,D,T,N,C,B,R,X,O} <: AbstractStrideArray{S,D,T,N,C,B,R,X,O}
#     ptr::PtrArray{S,D,T,N,C,B,R,X,O}
#     data::MemoryBuffer{L,T}
# end

# struct ConstantArray{S,T,N,X,L} <: AbstractStrideArray{S,T,N,X,0,0,false}
#     data::NTuple{L,Core.VecElement{T}}
# end
# @generated function check_N(::Val{N}, ::Type{S}, ::Type{X}) where {N,S,X}
#     if N == length(S.parameters) == length(X.parameters)
#         nothing
#     else
#         throw("Dimensions declared: $N; Size: $S; Strides: $X")
#     end
# end
# const LazyArray{F,S,T,N,X,SN,XN,V,L} = VectorizationBase.LazyMap{F,A,A<:AbstractStrideArray{S,T,N,X,SN,XN,V,L}}
# struct LazyMap{F,S,T,N,X,SN,XN,V,A<:AbstractStrideArray{S,T,N,X,SN,XN,V}} <: AbstractStrideArray{S,T,N,X,SN,XN,V}
#     f::F
#     ptr::A
# end

# @inline size_tuple(A::PtrArray) = A.size
# @inline stride_tuple(A::PtrArray) = A.stride
# @inline size_tuple(A::AbstractStrideArray) = A.ptr.size
# @inline stride_tuple(A::AbstractStrideArray) = A.ptr.stride

# const AbstractStrideVector{M,T,X1,SN,XN,V} = AbstractStrideArray{Tuple{M},T,1,Tuple{X1},SN,XN,V}
# const AbstractStrideMatrix{M,N,T,X1,X2,SN,XN,V} = AbstractStrideArray{Tuple{M,N},T,2,Tuple{X1,X2},SN,XN,V}
# const StrideVector{M,T,X1,SN,XN,V} = StrideArray{Tuple{M},T,1,Tuple{X1},SN,XN,V}
# const StrideMatrix{M,N,T,X1,X2,SN,XN,V} = StrideArray{Tuple{M,N},T,2,Tuple{X1,X2},SN,XN,V}
# const FixedSizeVector{M,T,X1,L} = FixedSizeArray{Tuple{M},T,1,Tuple{X1},L}
# const FixedSizeMatrix{M,N,T,X1,X2,L} = FixedSizeArray{Tuple{M,N},T,2,Tuple{X1,X2},L}
# const ConstantVector{M,T,X1,L} = ConstantArray{Tuple{M},T,1,Tuple{X1},L}
# const ConstantMatrix{M,N,T,X1,X2,L} = ConstantArray{Tuple{M,N},T,2,Tuple{X1,X2},L}
# const PtrVector{M,T,X1,SN,XN,V} = PtrArray{Tuple{M},T,1,Tuple{X1},SN,XN,V}
# const PtrMatrix{M,N,T,X1,X2,SN,XN,V} = PtrArray{Tuple{M,N},T,2,Tuple{X1,X2},SN,XN,V}
# const AbstractFixedSizeArray{S,T,N,X,V} = AbstractStrideArray{S,T,N,X,0,0,V}
# const AbstractFixedSizeVector{S,T,X,V} = AbstractStrideArray{Tuple{S},T,1,Tuple{X},0,0,V}
# const AbstractFixedSizeMatrix{M,N,T,X1,X2,V} = AbstractStrideArray{Tuple{M,N},T,2,Tuple{X1,X2},0,0,V}
# const AbstractMutableFixedSizeArray{S,T,N,X,V} = AbstractMutableStrideArray{S,T,N,X,0,0,V}
# const AbstractMutableFixedSizeVector{S,T,X,V} = AbstractMutableStrideArray{Tuple{S},T,1,Tuple{X},0,0,V}
# const AbstractMutableFixedSizeMatrix{M,N,T,X1,X2,V} = AbstractMutableStrideArray{Tuple{M,N},T,2,Tuple{X1,X2},0,0,V}

@inline Base.pointer(A::StrideArray) = A.ptr.ptr.p
# @inline Base.pointer(A::FixedSizeArray) = A.ptr.ptr
@inline Base.pointer(A::PtrArray) = A.ptr.p

@inline Base.unsafe_convert(::Type{Ptr{T}}, A::StrideArray{S,D,T}) where {S,D,T} = A.ptr.ptr.p
# @inline Base.unsafe_convert(::Type{Ptr{T}}, A::FixedSizeArray{S,T}) where {S,T} = A.ptr.ptr.p
@inline Base.unsafe_convert(::Type{Ptr{T}}, A::PtrArray{S,D,T}) where {S,D,T} = A.ptr.p

@inline Base.elsize(::AbstractStrideArray{<:Any,<:Any,T}) where {T} = sizeof(T)

# @inline Base.pointer(A::FixedSizeArray{S,T,N,X,L,Ptr{T}}) where {S,T,N,X,L} = A.ptr
# @inline Base.unsafe_convert(::Type{Ptr{T}}, A::FixedSizeArray{S,T,N,X,L,Ptr{T}}) where {S,T,N,X,L} = A.ptr
# @inline Base.pointer(A::FixedSizeArray{S,T,N,X,L,Nothing}) where {S,T,N,X,L} = Base.unsafe_convert(Ptr{T}, Base.pointer_from_objref(A))
# @inline Base.unsafe_convert(::Type{Ptr{T}}, A::FixedSizeArray{S,T,N,X,L,Nothing}) where {S,T,N,X,L} = Base.unsafe_convert(Ptr{T}, Base.pointer_from_objref(A))
# @inline Base.pointer(A::LazyMap) = pointer(A.ptr)
# @inline Base.unsafe_convert(::Type{Ptr{T}}, A::LazyMap{S,T}) where {S,T} = pointer(A.ptr)

ext: jl


text:
module SSLTestCoarsen
using SimpleSDMLayers
using Test
using Statistics

M = convert(Matrix, reshape(1:36, (6,6)))
S = SimpleSDMPredictor(M, 0.0, 1.0, 0.0, 1.0)

min22 = coarsen(S, minimum, (2,2))
@test min22.grid == [1 13 25; 3 15 27; 5 17 29]

max33 = coarsen(S, maximum, (3,3))
@test max33.grid == [15 33; 18 36]

# Coarsen should play nicely with the non-float types
M = SimpleSDMResponse(["a" nothing "b" "c"; "d" "e" "f" "g"; "d" "e" nothing "g"; nothing "x" "y" "z"], 0.0, 1.0, 0.0, 1.0)
@test coarsen(M, x -> reduce(*, x), (2,2)).grid == ["ade" "bfcg"; "dex" "ygz"]

# Should work on a real-world example for a series of functions
temperature = SimpleSDMPredictor(WorldClim, BioClim, 1)
for f in [minimum, maximum, mean, median]
    t_coarse = coarsen(temperature, f, (10, 10))
    @test typeof(t_coarse) <: SimpleSDMPredictor
end

end


text:
using Test
using Logging

using LDrawParser
using CoordinateTransformations, Rotations
using GeometryBasics

# Set logging level
# global_logger(SimpleLogger(stderr, Logging.Debug))
global_logger(SimpleLogger(stderr, Logging.Info))

# Fix randomness during tests
# Random.seed!(0)

@inline function array_isapprox(x::AbstractArray{F},
                  y::AbstractArray{F};
                  rtol::F=sqrt(eps(F)),
                  atol::F=zero(F)) where {F<:AbstractFloat}

    # Easy check on matching size
    if length(x) != length(y)
        return false
    end

    for (a,b) in zip(x,y)
        if !isapprox(a,b, rtol=rtol, atol=atol)
            return false
        end
    end
    return true
end

# Check if array equals a single value
@inline function array_isapprox(x::AbstractArray{F},
                  y::F;
                  rtol::F=sqrt(eps(F)),
                  atol::F=zero(F)) where {F<:AbstractFloat}

    for a in x
        if !isapprox(a, y, rtol=rtol, atol=atol)
            return false
        end
    end
    return true
end

# Define package tests
@time @testset "LDrawParser Package Tests" begin
    testdir = joinpath(dirname(@__DIR__), "test")
    @time @testset "LDrawParser.Transformations" begin
        include(joinpath(testdir, "test_transformations.jl"))
    end
    @time @testset "LDrawParser.Loading" begin
        include(joinpath(testdir, "test_loading.jl"))
    end
end


text:
# CheckAlpha.jl
#   - check units in Frohlich / Feynman alpha, by testing against lit. values

push!(LOAD_PATH,"../src/") # load module from local directory
using PolaronMobility

" Copy and pasted out of a Jupyter notebook; this calculates 'alpha' parameters
for various materials, as a comparison to the literature used when figuring out
the oft-quoted units. "
function checkalpha()
    println(" Alpha-parameter, Cross check 'frohlichalpha()' fn vs. literature values.\n")
    println("NaCl Frohlich paper α=",frohlichalpha(2.3, 5.6, (4.9E13/(2*pi)), 1.0))
    println(" \t should be ~about 5 (Feynman1955)")
    println("CdTe  α=",frohlichalpha(7.1,   10.4,  5.08E12, 0.095))
    println("\t Stone 0.39 / Devreese 0.29 ")
    println("GaAs  α=",frohlichalpha(10.89, 12.9,  8.46E12, 0.063))
    println("\t Devreese 0.068 ")

    println()
    println("Values which were once upon a time of interest, back in 2014 before we had good phonons. Still interesting for sensitivity analysis / scaling.")
    println("Guess at PCBM: 4.0, 6.0 ; α=",frohlichalpha(4.0,6.0, 1E12, 50))
    println("MAPI:")
    println("MAPI  4.5, 24.1, 9THz ; α=",frohlichalpha(4.5,   24.1,  9.0E12,    0.12))
    println("MAPI  4.5, 24.1, 2.25THz - 75 cm^-1 ; α=",frohlichalpha(4.5,   24.1,  2.25E12,    0.12))
    println("MAPI  6.0, 25.7, 9THz ; α=",frohlichalpha(6.0,   25.7,  9.0E12,    0.12))
    println("MAPI  6.0, 36.0, 9THz ; α=",frohlichalpha(6.0,   36,  9.0E12,    0.12))
    println("MAPI  6.0, 36.0, 1THz ; α=",frohlichalpha(6.0,   36,  1.0E12,    0.12))
end
checkalpha()


text:
#!/usr/bin/env julia

const prgm = basename(Base.source_path())

if length(ARGS) < 2
    println("usage: ", prgm, " <file> [line]...")
    exit(1)
end

file = ARGS[1]

const numbers = map(x -> begin
    try
        parse(Uint, x)
    catch
        println(prgm, ": ", x, ": not a number")
        exit(1)
    end
end, ARGS[2:end])

f = open(file)
lines = readlines(f)
close(f)

if maximum(numbers) > length(lines)
    println(prgm, ": detected extraneous line number")
    exit(1)
end

deleteat!(lines, sort(unique(numbers)))
f = open(file, "w")
write(f, join(lines))
close(f)

max_stars_repo_name: ethansaxenian/RosettaDecode

max_issues_repo_name: ethansaxenian/RosettaDecode

max_forks_repo_name: ethansaxenian/RosettaDecode


text:
module GMMInterface

export gmmOptim!, gmmOptim1D!, initOptim, gmmOptimMulti!
using TypeDef, Optim, Helper, Estim, UserFileIO, Gmm #Ipopt
using Printf, Random, DelimitedFiles, Distributed
export setupGmmOptim # for debug

function gmmOptimMulti!(
  fName  ::fileNameType, # Names of output files
  model  ::Int64,      # ID for demand model to estimate
  stage  ::Int64,      # Estimation stage (0 if continuous updating)
  iv     ::String,     # IV approach (opt,blp,diff-quad,diff-local)
  ver    ::String,     # Version of estimation data (824 for final version)
  impF   ::String,     # Type of import files
  simEmp ::Bool=false; # Simulation draws from empirical distribution
  update ::Bool=true,  # Continuous updating of optimal instruments
  seed   ::Int64=20,   # Number of Monte Carlo estimation runs
  nPerson::Int64=200,  # Number of simulation draws for random coefficients
  nIter  ::Int64=1000, # Number of iterations for optimization
  mcid   ::Int64=0)    # ID for estimate to import from previous stage (optional)

  θ̂₂,θ₂,θ₁,seθ,fval,exitflag,θ₂seed,dim,lab,indx,fJLD,fCSV,fINT,fPAR =
    setupGmmOptim(fName,model,stage,iv,ver,impF,update,seed,nPerson,nIter,mcid)

  f =
  let
    n->runOptimMulti(n,θ₂seed[:,n],model,stage,iv,update,nPerson,nIter,ver,
                     impF,simEmp,fJLD,fCSV,fINT,mcid,θ̂₂)
  end

  out = pmap(f, 1:seed)
  for r=1:seed
    param = out[r][1]
    flag  = out[r][2]
    timeR = out[r][3]
    csvTable!(fCSV,param.θ₂,param.θ₁,param.Jstat,flag,r,dim)
    saveResult!(fINT,r,param.θ₂,param.Jstat,flag,dim,timeR)
    saveEstim!(fPAR,param,model,timeR,dim,lab,true)
  end
end

function gmmOptim!(
  fName  ::fileNameType,
  model  ::Int64,
  stage  ::Int64,
  iv     ::String,
  ver    ::String,
  impF   ::String,
  simEmp ::Bool=false;
  update ::Bool=true,
  seed   ::Int64=20,
  nPerson::Int64=200,
  nIter  ::Int64=1000,
  mcid   ::Int64=0)

  θ̂₂,θ₂,θ₁,seθ,fval,exitflag,θ₂seed,dim,lab,indx,fJLD,fCSV,fINT,fPAR =
    setupGmmOptim(fName,model,stage,iv,ver,impF,update,seed,nPerson,nIter,mcid)

  tInit::Float64 = time()
  for r=1:seed
    # Reload fresh data
    param,flag = runOptim(r,θ₂seed[:,r],model,stage,iv,update,nPerson,nIter,
                          ver,impF,simEmp,fJLD,fCSV,fINT,mcid,θ̂₂)
    fval     = [fval; param.Jstat]
    θ₂       = [θ₂ param.θ₂]
    θ₁       = [θ₁ param.θ₁]
    seθ      = [seθ param.seθ]
    exitflag = [exitflag; flag]
    param₀ = collectResult(θ₂,θ₁,seθ,fval,exitflag)
    saveEstim!(fPAR,param₀,model,tInit,dim,lab)
  end
  println("-----------------------------------------------------------------")
  @printf("\nStage %d estimation complete in %.2f sec.\n",stage,time()-tInit)
end

function gmmOptim1D!(
  fName  ::fileNameType,
  model  ::Int64,
  stage  ::Int64,
  iv     ::String,
  ver    ::String,
  impF   ::String,
  simEmp ::Bool=false;
  update ::Bool=true,
  seed   ::Int64=1,
  nPerson::Int64=200,
  nIter  ::Int64=1000,
  mcid   ::Int64=0)

  θ̂₂,θ₂,θ₁,seθ,fval,exitflag,θ₂seed,dim,lab,indx,fJLD,fCSV,fINT,fPAR =
    setupGmmOptim(fName,model,stage,iv,ver,impF,update,seed,nPerson,nIter,mcid)

  tInit::Float64 = time()
  for r=1:seed
    # Reload fresh data
    param,flag = runOptim(r,model,stage,iv,update,nPerson,nIter,ver,impF,simEmp,
                          fJLD,fCSV,fINT,mcid,θ̂₂)
    fval     = [fval; param.Jstat]
    θ₂       = [θ₂ param.θ₂]
    θ₁       = [θ₁ param.θ₁]
    seθ      = [seθ param.seθ]
    exitflag = [exitflag; flag]
    param₀ = collectResult(θ₂,θ₁,seθ,fval,exitflag)
    saveEstim!(fPAR,param₀,model,tInit,dim,lab)
  end
  println("-----------------------------------------------------------------")
  @printf("\nStage %d estimation completed in %.2f seconds.\n",stage,
          time()-tInit)
end

function initOptim(
  rngSeed::Int64,
  dim::dimType,
  seed::Int64,
  alg::Int64,
  indx::indxType;
  θ̂::Vector{Float64}=zeros(0,))

  θ₂       = Array{Float64,2}(undef,dim.θ₂,0)
  θ₁       = Array{Float64,2}(undef,dim.θ₁,0)
  seθ      = Array{Float64,2}(undef,dim.θ,0)
  fval     = Vector{Float64}(undef,0)
  exitflag = BitArray(undef,0)

  x_L = -5*ones(Float64,dim.θ₂,)
  x_U =  5*ones(Float64,dim.θ₂,)
  for k in indx.diagL
    x_L[k] = 0.0
  end
  # Limit upper bound for price random coefficient
  # if modelType==1
  #   x_U[1] = 5
  # end

  if alg==1 #Ipopt
    m::Int64 = 0
    g_L = Vector{Float64}(undef,0)
    g_U = Vector{Float64}(undef,0)
    nele_jac::Int64 = 0                     # Number of non-zeros in Jacobian
    nele_hess::Int64 = dim.θ₂*(dim.θ₂+1)/2  # Number of non-zeros in Hessian
  elseif alg==2 #Optim
    nothing
  else
    error("Invalid alg input in initOptim")
  end
  Random.seed!(rngSeed)
  θ₂seed = Matrix{Float64}(undef,dim.θ₂,seed)
  u = rand(dim.θ₂,seed)
  for r=1:seed
    for k=1:dim.θ₂
      if length(θ̂)==0
        θ₂seed[k,r] = (x_U[k]-x_L[k])*u[k,r]+x_L[k]
      else
        theta = θ̂[k]+20*(u[k,r]-0.5)
        θ₂seed[k,r] =  min(max(theta, x_L[k]), x_U[k])
      end
    end
  end

  if alg==1
    return θ₂,θ₁,seθ,fval,exitflag,θ₂seed,x_L,x_U,m,g_L,g_U,nele_jac,nele_hess
  elseif alg==2
    return θ₂,θ₁,seθ,fval,exitflag,θ₂seed
  end
end

function runOptim( # multivariate θ
  r      ::Int64,
  θ₂init ::Vector{Float64},
  model  ::Int64,
  stage  ::Int64,
  iv     ::String,
  update ::Bool,
  nPerson::Int64,
  nIter  ::Int64,
  ver    ::String,
  impF   ::String,
  simEmp ::Bool,
  fJLD   ::String,
  fCSV   ::String,
  fINT   ::String,
  mcid   ::Int64,
  θ̂₂     ::Vector{Float64}) # 1st stage estimate

  dat,dim,fxp,lab,mat,set,indx,pred,diags =
    initialize(model,iv,nPerson,ver,impF,simEmp)
  if (update==false) & (stage==2)
    if iv=="opt" # 2-stage optimal IV
      println("1st stage estim: ", round.(θ̂₂,digits=4))
      setupMu!(fxp.eμ,pred.eμ₀,pred.adj, θ̂₂,mat.Xᵢ,mat.Dᵢ,mat.νᵢ,dim,indx)
      fixPoint!(fxp.eδ,fxp.∂δ∂θ, fxp.eμ,dat,dim,mat,indx,pred,diags,θ̂₂)
      optimIV!(mat,θ̂₂,dat,dim,fxp,indx,pred,diags)
    else
      updateGMMweight!(mat,θ̂₂,dat,dim,fxp,set,indx,pred,diags)
    end
  end
  f(x) = gmmObjOnly(x, dat,dim,fxp,mat,indx,pred,diags,update)

  timeStart = time()
  println("-----------------------------------------------------------------")
  println("Starting Iter ",r," at θ₂: ",round.(θ₂init,digits=4))
  println("\nf(θ₂init): ",round(f(θ₂init),digits=4))

  result = optimize(f,θ₂init,
                    method=NelderMead(),
                    show_trace=false,
                    g_tol=diags.gtolSimplex,
                    iterations=nIter,
                    callback=earlyStop)
  θ₂      = Optim.minimizer(result)
  flag    = Optim.converged(result)
  Jstat   = Optim.minimum(result)
  timeRun = time()-timeStart
  seθ,θ₁,covθ = stdError(θ₂,model,stage,iv,nPerson,impF,update,
                         dat,dim,fxp,mat,set,indx,pred,diags,mcid)

  # csvTable!(fCSV,θ₂,θ₁,Jstat,flag,r,dim)
  printResult!(r,Jstat,θ₂,flag,timeRun)
  # saveResult!(fINT,r,θ₂,Jstat,flag,dim,timeRun)

  param = paramType(θ₂,θ₁,seθ,Jstat)
  return param, flag
end

function runOptim(
  r      ::Int64,
  model  ::Int64,
  stage  ::Int64,
  iv     ::String,
  update ::Bool,
  nPerson::Int64,
  nIter  ::Int64,
  ver    ::String,
  impF   ::String,
  simEmp ::Bool,
  fJLD   ::String,
  fCSV   ::String,
  fINT   ::String,
  mcid   ::Int64,
  θ̂₂     ::Vector{Float64})

  dat,dim,fxp,lab,mat,set,indx,pred,diags =
    initialize(model,iv,nPerson,ver,impF,simEmp)

  if (update==false) & (stage==2)
    if iv=="opt" # 2-stage optimal IV
      println("1st stage estim: ", round.(θ̂₂,digits=4))
      setupMu!(fxp.eμ,pred.eμ₀,pred.adj, θ̂₂,mat.Xᵢ,mat.Dᵢ,mat.νᵢ,dim,indx)
      fixPoint!(fxp.eδ,fxp.∂δ∂θ, fxp.eμ,dat,dim,mat,indx,pred,diags,θ̂₂)
      optimIV!(mat,θ̂₂,dat,dim,fxp,indx,pred,diags)
    else
      updateGMMweight!(mat,θ̂₂,dat,dim,fxp,set,indx,pred,diags)
    end
  end
  f(x) = gmmObjOnly(x, dat,dim,fxp,mat,indx,pred,diags,update)

  timeStart = time()
  println("-----------------------------------------------------------------")
  result = optimize(f,1e-6,10.0,
                    method=Brent(),
                    show_trace=true,
                    iterations=nIter,
                    callback=earlyStop)
  θ₂      = Optim.minimizer(result)
  flag    = Optim.converged(result)
  Jstat   = Optim.minimum(result)
  timeRun = time()-timeStart
  seθ,θ₁,covθ = stdError([θ₂],model,stage,iv,nPerson,impF,update,
                         dat,dim,fxp,mat,set,indx,pred,diags,mcid)

  csvTable!(fCSV,[θ₂],θ₁,Jstat,flag,r,dim)
  printResult!(r,Jstat,[θ₂],flag,timeRun)
  saveResult!(fINT,r,[θ₂],Jstat,flag,dim,timeRun)

  param = paramType([θ₂],θ₁,seθ,Jstat)
  return param, flag
end

function earlyStop(x::OptimizationState)
  if isnan(x.value) | isinf(x.value)
    println("!!!! GMM objective NaN: ", x.value)
    println("Aborting optimization.\n")
    return true
  end
  return false
end

function runOptimMulti(
  r      ::Int64,
  θ₂init ::Vector{Float64},
  model  ::Int64,
  stage  ::Int64,
  iv     ::String,
  update ::Bool,
  nPerson::Int64,
  nIter  ::Int64,
  ver    ::String,
  impF   ::String,
  simEmp ::Bool,
  fJLD   ::String,
  fCSV   ::String,
  fINT   ::String,
  mcid   ::Int64,
  θ̂₂     ::Vector{Float64})

  tInit = time()
  param,flag = runOptim(r,θ₂init,model,stage,iv,update,nPerson,nIter,
                        ver,impF,simEmp,fJLD,fCSV,fINT,mcid,θ̂₂)
  timeRun = time()-tInit
  @printf("\nStage %d estimation complete in %.2f sec.\n",stage,timeRun)
  return param, flag, timeRun
end

function setupGmmOptim(
  fName  ::fileNameType,
  model  ::Int64,
  stage  ::Int64,
  iv     ::String,
  ver    ::String,
  impF   ::String,
  update ::Bool,
  seed   ::Int64,
  nPerson::Int64,
  nIter  ::Int64,
  mcid   ::Int64)

  println("Initializing Model ",model," with ",nPerson," random draws.")
  dim,lab,indx = importData(model,iv,nPerson,ver,impF;output=0)
  println("Estimation sample size: ",dim.obs)
  fJLD,fCSV,fINT,fPAR = assignFilename(fName,model,stage,iv,nPerson,impF)
  initFile!(fCSV,fINT,model,stage,iv,dim,lab)
  fid = open(fPAR,"w")
  close(fid)
  θ₂,θ₁,seθ,fval,exitflag,θ₂seed = initOptim(10+stage,dim,seed,2,indx)
  θ̂₂ = importθ̂₂(model,stage,mcid,iv,nPerson,impF,dim,update)

  return θ̂₂,θ₂,θ₁,seθ,fval,exitflag,θ₂seed,dim,lab,indx,fJLD,fCSV,fINT,fPAR

end

end

max_stars_repo_name: yutec/FightBrand

max_issues_repo_name: yutec/FightBrand

max_forks_repo_name: yutec/FightBrand


text:
# ____________________________________________________________________
# hostgalaxy
#
function list_hostgalaxy_templates()
    out = Vector{Dict}()
    for file in readdir(joinpath(qsfit_data(), "swire"))
        m = match(r"(.*)_template_norm.sed", file)
        isnothing(m)  &&  continue
        d = Dict(:library=>"swire", :template=>string(m.captures[1]))
        push!(out, d)
    end
    for file in readdir(joinpath(qsfit_data(), "ILBERT2009"))
        m = match(r"(.*).sed$", file)
        isnothing(m)  &&  continue
        d = Dict(:library=>"ILBERT2009", :template=>string(m.captures[1]))
        push!(out, d)
    end
    return out
end

mutable struct hostgalaxy <: AbstractComponent
    norm::Parameter
    library::String
    template::String
    filename::String
    base::Vector{Float64}

    hostgalaxy(d::Dict) = hostgalaxy(d[:template], library=d[:library])

    function hostgalaxy(template::String; library="swire")
        if library == "swire"
            filename = template * "_template_norm.sed"
        end
        if library == "ILBERT2009"
            filename = template * ".sed"
        end
        filename = joinpath(qsfit_data(), library, filename)
        @assert isfile(filename) "File $filename do not exists"
        out = new(Parameter(1), library, template, filename, Vector{Float64}())
        out.norm.val = 1
        out.norm.low = 0
        return out
    end
end

function prepare!(comp::hostgalaxy, domain::Domain{1})
    # Read template
    d = readdlm(comp.filename, comments=true)
    @assert typeof(d) == Matrix{Float64}
    x = d[:,1]
    y = d[:,2]
    # Ensure wavelength values are unique
    jj = sortmerge(x, x)
    i = findall(countmatch(jj, 1) .== 1)
    x = x[i]
    y = y[i]
    # Ensure wavelength values are increasing monotonically
    i = sortperm(x)
    x = x[i]
    y = y[i]
    itp = Spline1D(x, y, k=1, bc="error")
    comp.base = itp(domain[:])
    comp.base ./= itp(5500.)
    return fill(NaN, length(domain))
end

function evaluate!(buffer, comp::hostgalaxy, domain::Domain{1},
                   norm)
    buffer .= norm .* comp.base
end


text:
##### Beginning of file

module Hosts # Begin submodule MirrorUpdater.Hosts

__precompile__(true)

import ..Types
import ..Utils

include(joinpath("BitbucketHost", "BitbucketHost.jl"))
include(joinpath("GitHubHost", "GitHubHost.jl"))
include(joinpath("GitLabHost", "GitLabHost.jl"))

end # End submodule MirrorUpdater.Hosts

##### End of file


text:
# EXCLUDE FROM TESTING
using VectorEngine

function kernel(dummy::Clong, v::Clong, w::Float64)
    @veprintf("dummy = 0x%lx\n", dummy)
    @veprintf("v = %ld 0x%lx\n", v, v)
    @veprintf("w = %f  0x%lx\n", w, w)
    return nothing
end

# Compile the kernel
vefunc = VectorEngine.vefunction(kernel, Tuple{Clong, Clong, Float64})

# Launch kernel
vefunc(Clong(123), Float64(2.34))
vefunc(0, Clong(123), Float64(2.34))

synchronize()


text:
include("Queue.jl")

abstract type ConnectionPool end
function close!(conn) end
function Base.open(f::Function, r::Connection)
    conn = get_connection!(r)
    try
        f(conn)
    finally
        release_connection!(r, conn)
    end
end

get_connection(r::ConnectionPool) = throw(NotImplementedError())
release_connection(r::ConnectionPool, opened_connection) = throw(NotImplementedError())

struct SimplePool <: ConnectionPool
    creator::Function
    dbtype::DataType
    function SimplePool(creator::Function)
        conn = Base.invokelatest(creator)
        dbtype = typeof(conn)
        close!(conn)
        return new(creator, dbtype)
    end
end
get_connection(r::SimplePool) = Base.invokelatest(r.creator)
release_connection(r::SimplePool, conn) =  close!(conn)

"""A :class:`.Pool` that imposes a limit on the number of open connections.
    :class:`.QueuePool` is the default pooling implementation used for
    all :class:`.Engine` objects, unless the SQLite dialect is in use.
    """

struct QueuePool <: ConnectionPool
    creator::Function
    _pool::Queue
    overflow::Integer
    max_overflow::Integer
    timeout::Integer
    uselifo::Bool
    _overflow_lock::ReentrantLock
    recycle::Bool
    reset_on_return::Bool

end
"""

Arguments
--------
:param creator: a callable function that returns a DB-API
          connection object, same as that of :paramref:`.Pool.creator`.
        :param pool_size: The size of the pool to be maintained,
          defaults to 5. This is the largest number of connections that
          will be kept persistently in the pool. Note that the pool
          begins with no connections; once this number of connections
          is requested, that number of connections will remain.
          ``pool_size`` can be set to 0 to indicate no size limit; to
          disable pooling, use a :class:`~sqlalchemy.pool.NullPool`
          instead.
        :param max_overflow: The maximum overflow size of the
          pool. When the number of checked-out connections reaches the
          size set in pool_size, additional connections will be
          returned up to this limit. When those additional connections
          are returned to the pool, they are disconnected and
          discarded. It follows then that the total number of
          simultaneous connections the pool will allow is pool_size +
          `max_overflow`, and the total number of "sleeping"
          connections the pool will allow is pool_size. `max_overflow`
          can be set to -1 to indicate no overflow limit; no limit
          will be placed on the total number of concurrent
          connections. Defaults to 10.
        :param timeout: The number of seconds to wait before giving up
          on returning a connection. Defaults to 30.
        :param use_lifo: use LIFO (last-in-first-out) when retrieving
          connections instead of FIFO (first-in-first-out). Using LIFO, a
          server-side timeout scheme can reduce the number of connections used
          during non-peak periods of use.   When planning for server-side
          timeouts, ensure that a recycle or pre-ping strategy is in use to
          gracefully handle stale connections.
          .. versionadded:: 1.3
          .. seealso::
            :ref:`pool_use_lifo`
            :ref:`pool_disconnects`
        :param **kw: Other keyword arguments including
          :paramref:`.Pool.recycle`, :paramref:`.Pool.echo`,
          :paramref:`.Pool.reset_on_return` and others are passed to the
          :class:`.Pool` constructor.
"""
function QueuePool(creator::Function; poolsize::Integer=5, max_overflow::Integer=10,
                   timeout::Integer=30, use_lifo::Bool=false, recycle::Bool=false,
                   reset_on_return::Bool=false)
    return QueuePool(creator, Queue(poolsize, use_lifo), 0-poolsize,
                     max_overflow, timeout, use_lifo, ReentrantLock(), recycle,
                     reset_on_return)
end
function _do_return_conn(p::QueuePool, conn)
    try
        push!(p._pool, conn, false)
    catch e
        if isa(e, Full)
            try
                close(conn)
            finally
                _dec_overflow(p)
            end
        end
    end
end
function _do_get(q::QueuePool)
    use_overflow = q._max_overflow > -1
    try
        wait = use_overflow && (q._overflow >= q._max_overflow)
        return get(q._pool, wait, self._timeout)
    catch e
                # don't do things inside of "except Empty", because when we say
        # we timed out or can't connect and raise, Python 3 tells
        # people the real error is queue.Empty which it isn't.

        if !isa(e, Empty)
            throw(RuntimeError())
        end

    end
    if use_overflow && (q._overflow >= q._max_overflow)
        if  wait
            return _do_get(q)
        else
            throw(TimeoutError(
                """
                QueuePool limit of size $(length(d)) overflow $(overflow(q)) reached,
                connection timed out, timeout $(q._timeout)
                """
            ))
        end
    end
    if _inc_overflow(q)
        try
            return _create_connection(q)
        catch
            _dec_overflow(q)
        end
    else
        return _do_get(q)
    end
end
function _inc_overflow(q::QueuePool)
    if self._max_overflow == -1
        self._overflow += 1
        return true
    end
    b = true
    lock(q._overflow_lock)
    if self._overflow < self._max_overflow
        self._overflow += 1
        b =  true
    else
        b = false
    end
    unlock(q._overflow_lock)
    return b
end

function _dec_overflow(q::QueuePool)
    if q._max_overflow == -1
        q._overflow -= 1
        return true
    end
    lock(q._overflow_lock)
    q._overflow -= 1
    unlock(q._overflow_lock)
    return true
end

function dispose(q::QueuePool)
    while true
        try
            conn = get(q._pool, false)
            close(conn)
        catch Empty
            break
        end
        q._overflow = 0 - length(q)
        @info("Pool disposed. %s", self.status())
    end
end

function status(q::QueuePool)
    return """
                    Pool size: $(length(q))  Connections in pool: $(checkedin(q))
                    Current Overflow: $(overflow(q)) Current Checked out
                connections: $(checkedout(q))
                """
    end

function size(q::QueuePool)
    return q._pool.maxsize
end
function timeout(q::QueuePool)
    return q._timeout
end
function checkedin(q::QueuePool)
    return length(q._pool)
end
function overflow(q::QueuePool)
    return q._overflow
end
function checkedout(q::QueuePool)
    return q._pool.maxsize - length(q._pool) + self._overflow
end

max_stars_repo_name: lucianolorenti/StructDatabaseMapping

max_issues_repo_name: lucianolorenti/StructDatabaseMapping

max_forks_repo_name: lucianolorenti/StructDatabaseMapping


text:
module SDEPlot
using SDE
using Winston
export visualize_posterior

function graph(X::MvPath)
    p = FramedPlot()
    if ndims(X.yy) == 1 || (ndims(X.yy) == 2 && size(X.yy,1) == 1)
        setattr(p, "xrange", SDE.hrange(X.tt))
        setattr(p, "yrange", SDE.hrange(X.yy[:]))
        add(p, Curve(X.tt,X.yy[:], "color","black", "linewidth", 0.5))

    elseif ndims(X.yy) == 2 && size(X.yy,1) == 2
        setattr(p, "xrange", SDE.hrange(X.yy[1,:]))
        setattr(p, "yrange", SDE.hrange(X.yy[2,:]))
        add(p, Curve(X.yy[1,:],X.yy[2,:], "color","black", "linewidth", 0.5))

    end
    p
end

#%  .. function:: visualize_posterior(post[, truedrift])
#%
#%  	Plot 2r*se wide marginal credibility bands, where ``post`` is the result of
#%  	bayes_drift and truedrift the true drift (if known :-) ).
#%

function visualize_posterior(post, truedrift, r)
	x = post[:,1]
	bhat = post[:,2]
	c = post[:,3].*r #standard errors method 1
	xfull = linspace(x[1], x[end], 1000)
	b = map(truedrift,xfull)

	p = Winston.FramedPlot()
	Winston.setattr(p, "aspect_ratio", 1.)
	Winston.setattr(p.frame, "tickdir", +1)
	Winston.setattr(p.frame, "draw_spine", false)
	Winston.add( p, Winston.FillBetween(x, bhat+c, x, bhat-c, "color", "grey") )
	Winston.add( p, Winston.Curve(x, bhat, "type", "solid", "color", "blue") )
	Winston.add( p, Winston.Curve(xfull, b, "type", "dashed", "color", "red") )

	return(p)
end
function visualize_posterior(post, r)
	x = post[:,1]
	bhat = post[:,2]
	c = post[:,3] .* r

	p = Winston.FramedPlot()
	Winston.setattr(p, "aspect_ratio", 1.)
	Winston.setattr(p.frame, "tickdir", +1)
	Winston.setattr(p.frame, "draw_spine", false)
	Winston.add( p, Winston.FillBetween(x, bhat+c, x, bhat-c, "color", "grey") )
	Winston.add( p, Winston.Curve(x, bhat, "type", "solid", "color", "blue") )

	return(p)
end

#plot (2d) sample path

function pl(x::Array{ FloatingPoint,2})
	p = FramedPlot()
	#compute range
	R1 = range(x[1,:])
	R2 = range(x[2,:])

	#widen a bit
	R1 = (R1[1] -0.1*(R1[2]-R1[1]), R1[2] + 0.1*(R1[2]-R1[1]))
	R2 = (R2[1] -0.1*(R2[2]-R2[1]), R2[2] + 0.1*(R2[2]-R2[1]))

	setattr(p, "xrange", R1)
	setattr(p, "yrange", R2)

	add(p, Curve(x[1,:],x[2,:]))
	Winston.display(p)

	p
end

end


text:
function version()
  SFC_GET_LIB_VERSION	= 0x1000
  buf = zeros(Cchar,256)
  v = Cstring(pointer(buf))
  ccall((:sf_command, libsndfile), Int64,
        (Ptr{Cvoid}, UInt, Cstring, UInt),
        C_NULL, SFC_GET_LIB_VERSION, v, sizeof(buf))
  unsafe_string(v)
end

function sf_strerror(filePtr)
  errmsg = ccall((:sf_strerror, libsndfile), Ptr{UInt8}, (Ptr{Cvoid},), filePtr)
  unsafe_string(errmsg)
end


text:
config = MOI.Test.TestConfig()
optimize!(mock) = MOIU.mock_optimize!(mock, MOI.INFEASIBLE, tuple(), MOI.INFEASIBILITY_CERTIFICATE)
for mock in mocks(optimize!)
    Tests.sosdemo5_infeasible_test(mock, config)
end
# The test does not check the solution so we just set zeros.
optimize!(mock) = MOIU.mock_optimize!(mock, zeros(MOI.get(mock, MOI.NumberOfVariables())))
for mock in mocks(optimize!)
    Tests.sosdemo5_feasible_test(mock, config)
end


text: combiner_parsejson(input) = [chomp(line) for line in input]

max_stars_repo_name: deyandyankov/nmr

max_issues_repo_name: deyandyankov/nmr

max_forks_repo_name: deyandyankov/nmr


text:
# Abstract methods

"Initialize the configuration."
function start(cfg::Configuration)
    debug(LOGGER, "Starting configuration $cfg")
    state!(cfg, LifeCycle.STARTING)

    configure(cfg)

    # start context-wide filters
    start(filter(cfg))

    # start logger configurations
    for lc in values(loggers(cfg))
        start(lc)
    end

    # Start all appenders
    for apnd in values(appenders(cfg))
        start(apnd)
    end

    # start(root) #TODO: start root it's needed for filters

    state!(cfg, LifeCycle.STARTED)
    debug(LOGGER, "Started configuration $cfg OK.")
end

"Shutdown the configuration."
function stop(cfg::Configuration)
    state!(cfg, LifeCycle.STOPPING)
    trace(LOGGER, "Stopping $(cfg)...")

    # stop appenders
    c = 0
    for apnd in values(appenders(cfg))
        if state(apnd) == LifeCycle.STARTED
            stop(apnd)
            c+=1
        end
    end
    trace(LOGGER, "Stopped $c appenders in $(cfg)")

    # stop context-wide filters
    stop(filter(cfg))

    # stop logger configuration filters
    c = 0
    for lc in values(loggers(cfg))
        stop(lc)
        c+=1
    end
    trace(LOGGER, "Stopped $c loggers in $(cfg)")

    # stop(root) #TODO: stop root, it's needed for filters

    state!(cfg, LifeCycle.STOPPED)
    trace(LOGGER, "Stopped $(cfg)")
end

"""Locates the appropriate `LoggerConfig` for a specified name.

 This will remove tokens from the name as necessary or return the root `LoggerConfig` if no other matches were found.
"""
function logger(cfg::Configuration, lcname::AbstractString)
    lgrs = loggers(cfg)
    haskey(lgrs, lcname) && return lgrs[lcname]
    lcpname = lcname
    while (pos = rsearch(lcpname, '.') ) != 0
        lcpname = lcpname[1:pos-1]
        haskey(lgrs, lcpname) && return lgrs[lcpname]
    end
    return root(cfg)
end

"Add explicitly a `LoggerConfig` to the configuration."
function logger!(cfg::Configuration, lcname::AbstractString, lc::LoggerConfig)
    if isdefined(cfg, :loggers) && isa(cfg.loggers, LOGCONFIGS)
        cfg.loggers[lcname] = lc
        return lc
    else
        error(LOGGER, "Configuration does not have `loggers` field of type $LOGCONFIGS")
        return nothing
    end
end
function logger!(cfg::Configuration, lcname::AbstractString,
                lvl::Level.EventLevel = Level.ALL,
                flt::FILTER = FILTER())
    lc = LoggerConfig(lcname, level = lvl, filter=flt)
    return logger!(cfg, lcname, lc)
end

"Add explicitly an appender to the configuration."
function appender!(cfg::Configuration, apndname::AbstractString, apnd::Appender)
    if isdefined(cfg, :appenders) && isa(cfg.appenders, APPENDERS)
        cfg.appenders[apndname] = apnd
        return apnd
    else
        error(LOGGER, "Configuration does not have `appenders` field of type $APPENDERS")
        return nothing
    end
end

"Add an appender to the configuration as a kw pair."
function appender!(cfg::Configuration; kwargs...)
    for (k,w) in kwargs
        if isa(w, Appender)
            appender!(cfg, string(k), w)
        else
            error(LOGGER, "Invalid appender: $k")
        end
    end
end

"Cross-reference a logger configuration with an appender in the configuration."
function reference!{T<:AbstractString}(cfg::Configuration, refcfg::Pair{T,T})
    logcfg = logger(cfg, refcfg[1])
    apnd = appender(cfg, refcfg[2])
    apnd === nothing && return nothing
    return reference!(logcfg, apnd)
end

"Set a default configuration (i.e. the root logger is set with the console appender)."
function default!(cfg::Configuration)
    appender!(cfg, Default = Appenders.Console(layout = Layouts.BasicLayout()))
    return reference!(cfg, ROOT_LOGGER_NAME => "Default")
end

"Build logger configuration hierarchy"
function parents!(cfg::Configuration)
    for (lcname,lc) in loggers(cfg)
        lname = name(lc)
        i = rsearch(lname, '.')
        if i > 0
            parent = logger(cfg, lname[1:i-1])
            parent!(lc, parent)
        else
            parent!(lc, root(cfg))
        end
    end
end

# Some configuration implementations
include("config/default.jl")
include("config/yaml.jl")

# helper hunctions

"Evaluate a configuration"
function evalConfiguration(config_eval)
    if isnull(config_eval)
        trace(LOGGER, "Configuration is empty. Using default configuration.")
        DefaultConfiguration()
    else
        try
            eval(Log4jl, get(config_eval)) # evaluate configuration in Log4jl module
        catch err
            error(LOGGER, "Configuration failed. Using default configuration. Error: $(err)")
            DefaultConfiguration()
        end
    end
end

"Search a configuration file in a specified directory and identify its parser"
function searchConfiguration(search_dir)
    parser_type = Nullable{Symbol}()
    config_file = ""
    # Search for default configuration file
    for (p,exts) in LOG4JL_CONFIG_EXTS
        for ext in exts
            cf = joinpath(search_dir, LOG4JL_CONFIG_DEFAULT_PREFIX*ext)
            if isfile(cf)
                config_file = cf
                parser_type = Nullable(p)
                break
            end
        end
        !isempty(config_file) && break
    end
    debug(LOGGER, "Found configuration file: $config_file. Parser: $(get(parser_type, :NA))")
    config_file, parser_type
end

"Detect a configuration parser from a configuration file"
function findConfigurationParser(config_file)
    parser_type = Nullable{Symbol}()
    !isfile(config_file) && return parser_type
    cfg_prefix, cfg_ext= splitext(config_file)
    for (p,exts) in LOG4JL_CONFIG_EXTS
        if cfg_ext in exts
            parser_type = Nullable(p)
            break
        end
    end
    debug(LOGGER, "Parser detected: $(get(parser_type, :NA))")
    return parser_type
end

"Locate configuration resource"
function locateconfig(cfgloc::AbstractString, loader=nothing)
    # get module location
    cmdir = isa(loader, Module) ? moduledir(loader) : pwd()

    config_file, parser_type = if isempty(cfgloc)
        # search the module directory for configurations
        searchConfiguration(cmdir)
    else
        (isabspath(cfgloc) ? cfgloc : joinpath(cmdir, cfgloc), nothing)
    end

    !isfile(config_file) && return ""

    return config_file
end

"Returns the `Configuration` from a module or loaded from a location."
function getconfig(cfgloc::AbstractString, cfgname::AbstractString, loader=nothing)

    # locate configuration
    config_file = locateconfig(cfgloc, loader)

    if !isempty(config_file)
        # find parser for provided configuration
        parser_type = findConfigurationParser(config_file)
        if !isnull(parser_type)
            cfgtype = LOG4JL_CONFIG_TYPES[get(parser_type, :DEFAULT)]
            try
                return isempty(cfgname) ? getconfig(cfgtype, config_file) :
                                          getconfig(cfgtype, config_file, cfgname)
            catch err
                error(LOGGER, "Configuration initialization error: $err.")
            end
        end
    end

    error(LOGGER, "No Log4jl configuration file found. Using default configuration: logging only errors to the console.")
    return DefaultConfiguration()
end