require "lib/core.nom"
require "lib/secrets.nom"
require "lib/plurals.nom"


# Users:
with secrets:
    secret %users =: lua expr ".."
        |setmetatable({}, {__index=
        |    {by_name=function(self, key) return "User<"..key..">" end,
        |     add=function(self, key) self[key] = self:by_name(key) end}
        |})
    rule: find user %name ..=:
        lua expr "secrets.users:by_name(vars.name) or compiler:error('Failed to find user: '..tostring(vars.name))"
    rule: add user %name ..=:
        lua expr "secrets.users:add(vars.name)"
    macro: @ %name_block ..=:
        %name_str =: lua expr "vars.name_block.value.value.src"
        ".."
            |compiler:call("find user %", \repr %name_str\)
    rule:
        everybody
        everyone
    ..=:
        (%entry's "key") for %entry in (entries in (secret %users))

    rule: rules that change users ..=: ["add user %"]

# Inventory:
with secrets:
    secret %inventory =: lua expr ".."
        |setmetatable({}, {__index=function(self,key)
        |    local t = {}
        |    self[key] = t
        |    return t
        |end})

    rule: inventory ..=:
        dict (..)
            [%inv's "key", dict (%entry for %entry in (entries in (%inv's "value")))]
            for %inv in (entries in (secret %inventory))

    rule: %person's inventory ..=:
        dict (%entry for %entry in (entries in ((secret %inventory)->%person)))

    rule: %person's stock of %item ..=:
        %item =: canonicalize %item
        ((%person's inventory)->%item) or 0

    rule: %person's stock of %item as str ..=:
        %item =: canonicalize %item
        %count =: %person's stock of %item
        ".."
           |\%count\ \(singular %item) if (%count == 1) else (plural %item)\

    rule: give %person %count %item ..=:
        %item =: canonicalize %item
        (%person's inventory)-> %item =: (%person's stock of %item) + %count

    rule: give %person %count %item from %donor ..=:
        %item =: canonicalize %item
        if ((%donor's stock of %item) < %count):
            say ".."
                |\%donor\ does not have enough \%item\ to transfer
        ..else:
            (%person's inventory)->%item =: (%person's stock of %item) + %count
            (%donor's inventory)->%item =: (%donor's stock of %item) - %count

    rule:
        %person has %item
        %person has a %item
        %person has an %item
    ..=:
        (%person's stock of %item) > 0

    rule: rules that change the inventory ..=: [..]
        "give % % %", "give % % % from %"

    rule: rules that view the inventory ..=: [..]
        "inventory", "%'s inventory", "%'s stock of %", "%'s stock of % as str"

rule: you ..=:
    lua expr "(compiler.you or 'Anonymous')"

rule:
    make %person %action
    make %person do %action
..=:
    lua block ".."
        |do
        |    local old_you = compiler.you
        |    compiler.you = vars.person
        |    ret = compiler:call('do %', vars.action)
        |    compiler.you = old_you
        |end

say "===================================================="
say "                    NEW GAME"
say "===================================================="

# Unanimity for proposals
with secrets:
    secret %approvals =: []
    rule: pending proposal ..=:
        secret %pending

    rule: propose source %src ..=:
        secret %pending =: %src
        say ".."
            |Proposal\%src\

    macro block: propose %action ..=: ".."
        |compiler:call("propose source %", \repr (%action's "src")\)

    rule: with everyone's approval do %action ..=:
        do (..)
            eval ".."
                |return: \%action\

    rule: mark %who as approving ..=:
        if (not (pending proposal)):
            say "No action pending"
            return

        (secret %approvals)-> %who =: yes

        # Check for uncounted votes:
        for %user in (everybody):
            if (not ((secret %approvals)->%user)):
                return

        # No one dissents
        with everyone's approval do (pending proposal)

    rule: mark %who as rejecting ..=:
        secret %pending =: nil

    rule:
        approve
        vote yes
        vote yea
    ..=:
        mark (you) as approving

    rule:
        reject
        vote no
        vote nay
        veto
        disapprove
    ..=:
        mark (you) as rejecting

    restrict "with everyone's approval do %" to within "mark % as approving"
    restrict "mark % as approving" to within "approve"
    restrict "mark % as rejecting" to within "reject"

rule: join ..=:
    add user (you)
    say ".."
        |Welcome to the game, \you\!

restrict (rules that change users) to within "join"


restrict (..)
    flatten [..]
        ["rule % = %", "make % %", "restrict % to within %"]
        (rules that change the inventory)
..to within "with everyone's approval do %"
say "Rule making is now restricted"

join

propose:
    say "fart"
approve

propose:
    give "democracy" 1 "possibility"
    if ("democracy" has "possibility"):
        say "DEMOCRACY WORKS!!!"
approve

propose:
    rule: fart ..=:
        say "poot"
    say "fart should have been defined"
approve
say "doop"
fart

propose:
    with secrets:
        rule: open election %candidates %action ..=:
            if (secret %candidates):
                error "An election is already in progress."
            ..else:
                secret %candidates =: %candidates
                secret %votes =: []
                secret %action =: %action
                secret %winner =: nil

        rule: close the election ..=:
            secret %candidates =: nil
            secret %votes =: nil
            secret %action =: nil
            secret %winner =: nil

        rule: votes for %candidate ..=:
            %votes =: []
            for %entry in (entries in (secret %votes)):
                if ((%entry's "value") == %candidate):
                    add (%entry's "key") to %votes
            %votes

        rule: after winning a fair election do %action ..=:
            do %action

        rule: the winner ..=: secret %winner

        rule: vote for %candidate ..=:
            for %c in (secret %candidates):
                if (%c == %candidate):
                    go to %candidate-is-legit
            error ".."
                |Invalid candidate: \%candidate\
            -> %candidate-is-legit

            (secret %votes)->(you) =: %candidate
            %vote-percent =: (number of (votes for %candidate)) / (number of (everyone))
            say ".."
                |Vote cast. \%candidate\ now has \100 * %vote-percent\% of the votes.
            if (%vote-percent > 0.5):
                secret %winner =: %candidate
                after winning a fair election do (secret %action)
                close the election

        rule: rules that change the election ..=: [..]
            "open election %", "close the election", "vote for %"

        rule: rules that view the election ..=: [..]
            "votes for %"

approve

propose:
    rule: as bill %action ..=:
        if ((you) == "Anonymous"):
            make "bill" do %action
        ..else:
            say ".."
                |Who do you think you are, \you\?
    allow ["as bill %"] to use ["make % %"]
approve
as bill: join

propose:
    rule: as dave %action ..=:
        if ((you) == "Anonymous"):
            make "dave" do %action
        ..else:
            say ".."
                |Who do you think you are, \you\?
    allow ["as dave %"] to use ["make % %"]
approve
as bill: approve
as dave: join

propose:
    rule: declare war ..=: say "WAR!!!"
    with secrets:
        secret %president =: nil
        rule: elect president from %candidates ..=:
            open election %candidates:
                say ".."|Hail to the chief: \the winner\
                secret %president =: the winner

        rule: as the president do %action ..=: do %action

        restrict "declare war" to within "as the president do %"
approve
as bill: approve
as dave: approve

elect president from ["tom", "dick", "harry"]
vote for "dick"
as bill: vote for "dick"
as dave:
    as the president do:
        declare war

propose:
    rule: take a shit ..=: say "shit taken."

approve
as bill: approve
as dave: approve
take a shit
say "Done."