/*
 * Copyright (c) 2021 Ross Cunniff
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "libadv.oah"

namespace stdadv {

public
    pSeen,              /* I've been here/seen this */
    pOpens,             /* This can be opened */
    pLocks,             /* This can be locked */
    pOpened,            /* This is opened */
    pLocked,            /* This is locked */
    pTrans,             /* This is transparent */
    pLight,             /* This gives off light */
    pFlame,             /* This is on fire */
    pNoTake,            /* Ignore this object for "take all" */
    pAllLink,           /* Link for objs used in "take" and "drop" */
    pSaveSent;          /* First variable in a sentence save area */

/* Strings used more than once */
const
    YouSeeNo = "You see no ",
    Huh = "Huh?\n",
    CR = "\n",
    EOL = ".\n",
    MeanMsg = "What do you mean by \"",
    ItConfused = "I can't seem to figure out what you mean by 'it'.\n",
    Dropped = "dropped";

/* Flags for Expect */
const
    NO_OBJ = 1,         /* It's valid to have no objects */
    ONE_OBJ = 2,        /* It's valid to have one object */
    MULT_OBJ = 4,       /* It's valid to have multiple objects */
    STR_OBJ = 8,        /* It's valid to have string objects */
    PLAIN_OBJ = 16;     /* It's valid to have plain objects */

/* Global variables */
var
    First = true,       /* Is the current adv::Dobj the first in the adv::Dobj list? */
    AllSeen,            /* Did the player type "all" in this sentence? */
    MultList = {},      /* Array of multiple objects */
    MyConj,             /* Records where "but" has been seen */
    NumSeen,            /* Number of adv::Dobj's seen by "take" or "drop" so far */
    adv::IobjSave,           /* Save for the adv::Iobj (for TAKE and DROP) */
    Skip,               /* Should TorDACT skip this object? */
    Scripting,          /* Are we writing a script file? */

    Conts,              /* Have we already printed out "You can see:"? */
    Indent,             /* Indent outer object descriptions? */

    Dark,               /* Is it dark? */
    MyLoc = nil,        /* My last location */
    adv::Verbose;            /* Does the player want verbose output? */

/* Derived classes */
class StdLoc(adv::Location) {
    public var pSeen = false;
}

class LitRoom(StdLoc) {
    public var pLight = true;
}

class FixedThing(adv::Thing) {
    public var pNoTake = true;
}

/* Standard words from English */
adv::SepClass thenSep("then");

adv::ConjClass andConj("and");
adv::ConjClass butConj("but");
adv::ConjClass orConj("or");

adv::PrepClass withPrep("with");
adv::PrepClass toPrep("to");
adv::PrepClass inPrep("in", "into");
adv::PrepClass atPrep("at");
adv::PrepClass underPrep("under");
adv::PrepClass fromPrep("from");
adv::PrepClass offPrep("off");
adv::PrepClass onPrep("on");

adv::ArtClass theArticle("the");
adv::ArtClass aArticle("a", "an");

proc
    StdInit,            /* StdInit(actor) Standard game with actor playing */
    Reach,              /* Reach(Obj,Where) True IFF I can reach Obj in Where */
    See,                /* See(Obj,Where) True IFF I can see Obj in Where */
    Lit,                /* LitP() True IFF something is lit or burning */
    Describe,           /* Describe(depth,obj,rout) Describe obj */
    Avail,              /* Avail(Obj) Is Obj available? */
    CheckAvail,         /* CheckAvail() check availability of adv::Dobj and adv::Iobj */
    Expect,             /* Expect(adv::DobjFlags,adv::IobjFlags) Check the form */
    Preact,             /* Standard verb preact */
    Looker,             /* Looking daemon */
    Prompter,           /* User prompt */
    ActAction,          /* Standard actor ACTION */
    SaveSentence,       /* SaveSentence() - save the value of the curr. sent. */
    TakeAct,            /* User defined take action */
    DropAct,            /* User defined drop action */
    Dwimmer;            /* Dwimmer(Obj) - is Obj the one I want? */

/* Standard objects */
adv::Thing all(nil);
adv::Thing it(nil);

/* adv::Verbs - NOTE: do not change the PREACT or ACTION of any of these
 * without carefully considering the consequences
 */
adv::VerbClass take;
adv::VerbClass north();      adv::Synonym _n(north,"n");
adv::VerbClass south();      adv::Synonym _s(south,"s");
adv::VerbClass east();       adv::Synonym _e(east,"e");
adv::VerbClass west();       adv::Synonym _w(west,"w");
adv::VerbClass northeast();  adv::Synonym _ne(northeast, "ne");
adv::VerbClass southeast();  adv::Synonym _se(southeast, "se");
adv::VerbClass northwest();  adv::Synonym _nw(northwest, "nw");
adv::VerbClass southwest();  adv::Synonym _sw(southwest, "sw");
adv::VerbClass up();         adv::Synonym _up(up, "u");
adv::VerbClass down();       adv::Synonym _down(down, "d");
adv::VerbClass enter();
adv::VerbClass exit();
adv::VerbClass again();      adv::Synonym _again(again, "g");

/* StdInit(actor) - initializes the ACTION routine of actor, sets
 *  up the prompter, and sets up the looking daemon.
 */

class StdActor(adv::Actor) {
    public var action;
    public var pNoTake;
    public var pSaveSent;
    public var pTrans;
    public var pOpened;
    public var pSeen;
}

proc StdInit(actor)
{
    oadl::srandom();
    actor.action = ActAction;
    actor.pNoTake = true;
    actor.pSaveSent = new Array(6);
    actor.activate(nil, true);
    adv::Prompt = Prompter;
    adv::Setdaemon(Looker);
    adv::dirVec = {north,south,east,west,
                   northeast,southeast,northwest,southwest,
                   up,down};
}

/* RandInt(min,max) - return a random integer between min and max, inclusive */
proc RandInt(min, max)
{
    return min + Int(0.5 + oadl::random()*(max-min));
}

/* Percent(n) - returns 1 n % of the time */
proc Percent(n)
{
    return RandInt(0,100) <= n;
}

/* FindIt(obj) - figure out what an 'it' in a player's sentence refers to */

proc FindIt()
{
    var
        SavePlace,      /* The value of adv::$ME.pSaveSent */
        LastDobj,       /* The last DIRECT OBJECT typed */
        LastIobj,       /* The last INDIRECT OBJECT typed */
        LastNumd;       /* The previous NUMBER OF DIRECT OBJECTS typed */

    /* Retrieve the pertinent info from pSaveSent */
    SavePlace = adv::$ME.pSaveSent;
    if( !SavePlace ) {
        say(ItConfused);
        adv::ExitPhase(adv::PHX_ACTOR);
    }

    LastNumd = SavePlace[1];
    LastDobj = SavePlace[3];
    LastIobj = SavePlace[5];

    if( LastNumd > 1 ) {
        say(ItConfused);
        adv::ExitPhase(adv::PHX_ACTOR);
    }

    if( (LastDobj != nil) && (LastIobj == nil) ) {
        return LastDobj;
    }
    else if( (LastDobj == nil) && (LastIobj != nil) ) {
        return LastIobj;
    }
    else {
        say(ItConfused);
        adv::ExitPhase(adv::PHX_ACTOR);
    }
}

/*  ActAction - the default Actor Action */

proc ActAction()
{
    var SavePlace;

    if( adv::Verb == again ) {
        SavePlace = adv::$ME.pSaveSent;
        if( !SavePlace ) {
            "I can't do that.\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        if( adv::Dobj || adv::Iobj ) {
            "You may not use objects with 'again'.\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        if( SavePlace[1] > 1 ) {
            "You can't use 'again' with multiple direct objects.\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        adv::Verb = SavePlace[0];
        adv::Numd = SavePlace[1];
        adv::Conj = SavePlace[2];
        adv::Dobj = SavePlace[3];
        adv::Prep = SavePlace[4];
        adv::Iobj = SavePlace[5];
        adv::ExitPhase(adv::PHX_CURRENT);
    }
    if( (adv::Dobj == it) && (adv::Iobj != it) ) {
        adv::Dobj = FindIt();
    }
    else if( (adv::Iobj == it) && (adv::Dobj != it) ) {
        adv::Iobj = FindIt();
    }
    else if( (adv::Dobj == it) && (adv::Iobj == it) ) {
        "You may only use the word 'it' once in a sentence.\n";
        adv::ExitPhase(adv::PHX_ACTOR);
    }
    SaveSentence();
}

proc Prompter() { say( "> " ); }

/* CheckAvail() - checks to see whether the objects named by the
   player are indeed available */

proc CheckAvail()
{
    if( adv::Dobj ) {
        Avail(adv::Dobj);
    }
    if( adv::Iobj ) {
        Avail(adv::Iobj);
    }
}

/* Expect(adv::DobjFlags, adv::IobjFlags) - Checks for a valid sentence */

proc Expect(DobjFlags, IobjFlags)
{
    /* Check the number of direct objects */
    if( adv::Numd == 0 ) {
        if( !(DobjFlags & NO_OBJ) ) {
            "You must tell me what to ", adv::Verb.name, EOL;
            adv::ExitPhase(adv::PHX_SENTENCE);
        }
    }
    else if( (adv::Numd == 1) & (adv::Dobj != all) ) {
        if( !(DobjFlags & MULT_OBJ) && !(DobjFlags & ONE_OBJ) ) {
            "You may not use a direct object with ", adv::Verb.name, EOL;
            adv::ExitPhase(adv::PHX_ACTOR);
        }
    }
    else {
        if( !(DobjFlags & MULT_OBJ) ) {
            "You may not use multiple direct objects with ",
                adv::Verb.name, EOL;
            adv::ExitPhase(adv::PHX_ACTOR);
        }
    }

    /* Check the number of Indirect objects */
    if ((!adv::Iobj) && !(IobjFlags & NO_OBJ)) {
        "How would you like to do that?\n";
        adv::ExitPhase(adv::PHX_SENTENCE);
    }
    else if (adv::Iobj && !(IobjFlags & ONE_OBJ)) {
        "You may not use an indirect object with ", adv::Verb.name, EOL;
        adv::ExitPhase(adv::PHX_ACTOR);
    }

    /* Check the type of the objects */
    if ( ((typeof(adv::Dobj) == String) && !(DobjFlags & STR_OBJ)) ||
         ((typeof(adv::Iobj) == String) && !(IobjFlags & STR_OBJ)))
    {
        "You may not use strings with ", adv::Verb.name, EOL;
        adv::ExitPhase(adv::PHX_ACTOR);
    }
    if ( (adv::Dobj && (typeof(adv::Dobj) != String) && !(DobjFlags & PLAIN_OBJ)) ||
         (adv::Iobj && (typeof(adv::Iobj) != String) && !(IobjFlags & PLAIN_OBJ)) )
    {
        "You must use a string with ", adv::Verb.name, EOL;
        adv::ExitPhase(adv::PHX_ACTOR);
    }
}

/* Preact - the default verb Preact */
proc Preact()
{
    Expect(ONE_OBJ|PLAIN_OBJ, NO_OBJ|ONE_OBJ|PLAIN_OBJ);
    CheckAvail();
}

/* Visible(List,Propno) - Returns 1 IFF an object is visible on List that
  has a nonzero prop Propno */

proc Visible(arr, Propno)
{
    forall (arr[i]) {
        var o = arr[i];
        if (o.(Propno)) {
            /* This is it! */
            return true;
        }
        else if (o.pOpened || o.pTrans) {
            /* Look inside */
            if (Visible(o.cont, Propno)) {
                  return true;
            }
        }
    }
    return false;
}


/* Reach(Obj, Loc) - Returns 1 IFF Obj == Loc, or can (recursively) be
  reached via the Loc */

proc Reach(Obj, Loc)
{
    var
       o;

    forall (Loc[i]) {
        o = Loc[i];
        if (Obj == o) {
            /* This is the one! */
            return true;
        }
        else if (o.pOpened) {
            /* Still explore inside */
            if (Reach(Obj, o.cont)) {
                return true;
            }
        }
    }
    return false;
}

/* See(Obj,Loc) - Returns 1 IFF the Obj == Loc, or can be reached
  via the Loc (similar to Reach, above) */

proc See(Obj, Loc)
{
    if (Dark) {
        /* Can't see in a dark room! */
        return false;
    }
    forall (Loc[i]) {
        var o = Loc[i];
        if (Obj == o) {
            /* This is the one! */
            return true;
        }
        else if (o.pTrans || o.pOpened) {
            /* Still explore inside */
            if (See(Obj, o.cont)) {
                return true;
            }
        }
    }
    return false;
}

/* Avail(Obj) - Returns 1 IFF I can see Obj or I can reach Obj,
  performs a (exit 1) otherwise */

proc Avail(Obj)
{
    if (!Obj) {
        "The what?\n";
        adv::ExitPhase(adv::PHX_ACTOR);
    }
    else if (!(See(Obj, adv::$ME.loc.cont) || See(Obj,adv::$ME.cont))) {
        "I can't see that item here.\n";
        adv::ExitPhase(adv::PHX_ACTOR);
    }
    else if (!(Reach(Obj, adv::$ME.loc.cont) || Reach(Obj,adv::$ME.cont))) {
        "I can't get at that item.\n";
        adv::ExitPhase(adv::PHX_ACTOR);
    }
    return true;
}

/* Lit(Room) - Returns true IFF Room is lit */

proc Lit(Room)
{
    if (Room.pLight) {
        /* Intrinsically lit */
        return true;
    }
    else if (Visible(Room.cont, pLight) || Visible(Room.cont, pFlame)) {
        return true;                    /* I can see a light */
    }
    else if (Visible(adv::$ME.cont, pLight) || Visible(adv::$ME.cont, pFlame)) {
        return true;                    /* I have a light */
    }
    else {
        return false;
    }
}

/* Blank(num) - Type 2*n blanks */
proc Blank(num)
{
    var
        idx;

    if (!Indent) return;

    for (idx = 0; idx < num; idx++ ) {
        "  ";
    }
}

/* Describe(Level,Obj,Rout) - Describes Obj using Rout (which is a public that
  indexes a ROUTINE that describes Obj, typically sdesc or ldesc),
  and also describes the contents of Obj */

proc Describe(Level, Obj, Rout)
{
    if (!Obj) {
        /* Null list */
        return 0;
    }
    else if (!Level) {
        /* Level 0 == This is a room.  Check lighting */
        Conts = false;
        if (Lit(Obj)) {
            Dark  = false;      /* Can't be dark in a lit room! */
            Obj.(Rout)();       /* Talk about the room */
            if (!Dark) {
                Describe(1, Obj.cont, Rout);  /* Talk about its contents */
            }
        }
        else {
            "It's mighty dark in here!\n";
            Dark  = true;
        }
    }
    else {
        /* Level > 0 == This is a list of objs */
        forall (Obj[i]) {
            var o = Obj[i];
            if (o.(Rout)) {
                /* Talk (only) about the visible */
                if ((Rout == sdesc) && !Conts) {
                    Blank(Level - 1);
                    "You can see:\n";
                }
                Conts = true;
                Blank(Level);   /* Indent */
                o.(Rout)();     /* Blurb the object */
                if (o.cont.length()) {
                    /* something inside it...*/
                    if (o.pOpened || o.pTrans) {
                        if (Rout == ldesc) {
                            Blank(Level);
                            "It contains:\n";
                        }
                        else {
                            ", containing\n";
                        }
                        o.pSeen = true;
                        /*Short descs for conts*/
                        Describe(Level+1, o.cont, sdesc);
                    }
                    else if (Rout == sdesc) {
                        say(CR);
                    }
                }
                else if (Rout == sdesc) {
                    say(CR);
                }
            }
        }
    }
}

/* SaveSentence() - save the value of the current sentence */

proc SaveSentence()
{
    var SavePlace;

    SavePlace = adv::$ME.pSaveSent;
    if (!SavePlace) {
        return 0;
    }
    SavePlace[0] = adv::Verb;
    SavePlace[1] = adv::Numd;
    SavePlace[2] = adv::Conj;
    SavePlace[3] = adv::Dobj;
    SavePlace[4] = adv::Prep;
    SavePlace[5] = adv::Iobj;
}

/* Looker() - The standard Looking daemon.  Usually only mentioned
  in START. */

proc Looker()
{
    adv::$ME.pTrans = false;
    MyConj = false;
    First = true;
    adv::IobjSave = nil;
    AllSeen = false;
    if (MyLoc != adv::$ME.loc) {
        if (adv::$ME.loc.pSeen && !adv::Verbose) {
            Describe(0, adv::$ME.loc, sdesc);
        }
        else {
            adv::$ME.loc.sdesc();
            Describe(0, adv::$ME.loc, ldesc);
            adv::$ME.loc.pSeen = true;
        }
        if (Dark) {
            adv::$ME.loc.pSeen = false;
        }
        MyLoc = adv::$ME.loc;
    }
    adv::$ME.pTrans = true;
    adv::$ME.pOpened = true;
}

/*
  The following are routines relating to sentence constructions such
  as "take all but rock and cow.  drop all but sword."
*/


/* DelList(Obj) -- Deletes Obj from the list of multiple direct objects */

proc DelList(Obj)
{
    var
        i, n;

    if (Obj == all) {
        /* The player typed something like "take all but all" */
        "I don't understand that.\n";
        adv::ExitPhase(adv::PHX_ACTOR);
    }

    // Find the object on the list
    n = MultList.length();
    for (i = 0; i < n; i++) {
        if( MultList[i] == Obj ) {
            break;
        }
    }

    if( i < n ) {
        // Reconstitute the list without the object
        MultList = MultList[:i-1] ## MultList[i+1:];
    }
    else {
        // Was not on the list.
        "", YouSeeNo, Obj.name, " here.\n";
        adv::ExitPhase(adv::PHX_ACTOR);
    }
}



/* AddList(Obj) -- Adds Obj to the list of multiple direct objects */

proc AddList(Obj)
{
    if (Obj == all) {
        /* The player typed something like "Take rock and all" */
        "I don't understand that.\n";
        adv::ExitPhase(adv::PHX_ACTOR);
    }
    MultList = MultList ## { Obj };
}



/* InitList(Where) --  Adds each object contained in Where to MultList */

proc InitList(Where)
{
    MultList = {};
    AllSeen = true;
    forall (Where[i]) {
        var o = Where[i];
        if (!o.pNoTake) {
            MultList = MultList ## o;
        }
    }
}



/* Mover(Where,String) - Moves each object on MultList to Where, printing
  String as it does so. */

proc Mover(Where, Str)
{
    var
        i, n;

    n = MultList.length();
    if (!n) {
        "There is nothing to ", adv::Verb.name, EOL;
        adv::ExitPhase(adv::PHX_ACTOR);
    }
    for (i = 0; i < n; i++) {
        adv::Dobj = MultList[i];
        adv::Iobj = adv::IobjSave;
        Skip = false;
        adv::Dobj.action();               /* Call the ACTION routines */
        if (!Skip) {
            adv::Iobj.action();           /*   for the adv::Dobj and adv::Iobj */
        }
        if (!Skip) { /* Call the ACTIONs for the verb */
            if (adv::Verb == take) {
                  TakeAct();
            }
            else {
                /* adv::Verb == drop */
                  DropAct();
            }
        }
        if (!Skip) {
            adv::Dobj.move(Where);        /* Do the moving */
            "  ", adv::Dobj.name, " - ", Str, CR;
        }
    }
    MultList = {};
}



/* CheckLoc(Obj,Where) -  Checks whethere Obj can be seen on Where
  and can be reached on Where */

proc CheckLoc(Obj, Where, Loc)
{
    if (!See(Obj, Where)) {
        if (Loc == adv::$ME) {
            "You have no ", Obj.name, EOL;
        }
        else {
            "", YouSeeNo, Obj.name, " here.\n";
        }
        adv::ExitPhase(adv::PHX_ACTOR);
    }
    else if (!Reach(Obj, Where)) {
        "You can't reach the ", Obj.name, EOL;
        adv::ExitPhase(adv::PHX_ACTOR);
    }
}



/* TorDPRE(Where) -- Uses Where as the context for a multiple
  direct object (with "all" as a possible object) list. */

proc TorDPRE(Where, Loc)
{
    if (!First) {
        // The MultList is initialized
        if (adv::Conj == butConj) {
            if (!AllSeen) {
                // The player typed something like "take a, b but c"
                "I don't understand that.\n";
                adv::ExitPhase(adv::PHX_ACTOR);
            }
            MyConj = true;
        }
        if (MyConj) {
            // We have seen "but" in the sentence, so delete this object
            // from the list
            DelList(adv::Dobj);
        }
        else {
            // We have NOT seen "but"
            // See if the obj is in the right place
            CheckLoc(adv::Dobj, Where, Loc);
            // if so, add the object to the mult list
            AddList(adv::Dobj);
        }
    }
    else {
        // The MultList is NOT initialized, but there are objects in
        // the sentence
        if (adv::Dobj == all) {
            // The direct obj. is "all", so set the MultList to the cont of
            // the loc of adv::$ME
            InitList(Where);
        }
        else {
            // The dir obj. is NOT all so set MultList to be the direct object.
            CheckLoc(adv::Dobj, Where, Loc);
            MultList = MultList ## {adv::Dobj};
        }
        First = false;
        MyConj = false;
        NumSeen = 1;
    }
    // We will call the ACTION routines later...
    adv::Dobj = nil;
}



/* (TorDACT Where String) -- Moves all objects on the multlist to Where
  (using Mover) if all of the objects have been seen;  otherwise it waits.
  String is the past participle of verb. (e.g. "taken", "dropped" */

proc TorDACT(Where, Str)
{
    if (adv::Numd <= NumSeen) {
        Mover(Where, Str);
    }
    else {
        NumSeen = NumSeen + 1;
    }
}



/* The following objects are for things like "go north" */
adv::NounClass DIR("DIR");

adv::Thing n_DIR {noun = DIR adjec = north}
adv::Thing s_DIR {noun = DIR adjec = south}
adv::Thing e_DIR {noun = DIR adjec = east}
adv::Thing w_DIR {noun = DIR adjec = west}
adv::Thing ne_DIR {noun = DIR adjec = northeast}
adv::Thing se_DIR {noun = DIR adjec = southeast}
adv::Thing nw_DIR {noun = DIR adjec = northwest}
adv::Thing sw_DIR {noun = DIR adjec = southwest}
adv::Thing u_DIR {noun = DIR adjec = up}
adv::Thing d_DIR {noun = DIR adjec = down}

/* We keep them in this array for PORTABLE referencing */
var
    DirArray = {
        n_DIR, s_DIR, e_DIR, w_DIR, ne_DIR,
        se_DIR, nw_DIR, sw_DIR, u_DIR, d_DIR
    };

adv::VerbClass go {
    preact = proc()
    {
        var
            i;

        Expect(ONE_OBJ|PLAIN_OBJ, NO_OBJ);

        /* Try to find the adv::Dobj in the list of Directions */
        for (i = 0; i < 10; i++) {
            if (DirArray[i] == adv::Dobj) {
                /* We found it.  Set the adv::Verb and adv::Dobj appropriately */
                adv::Verb = adv::Dobj.adjec;
                adv::Dobj = nil;
                adv::Verb.preact();
                return;
            }
        }

        /* if we get here, we didn't find the adv::Dobj */
        "", Huh;
        adv::ExitPhase(adv::PHX_ACTOR);
    }
}



proc Silly()
{
    "That's silly!\n";
    adv::ExitPhase(adv::PHX_ACTOR);
}


adv::Thing adv::NONOUN { }

adv::VerbClass adv::NOVERB {
    preact = proc()
    {
        if (adv::Iobj == adv::NONOUN) {
            // The user just typed a preposition
            if (adv::Prep == inPrep) {
                // Transform this sentence into "enter"
                adv::Verb = enter;
                return enter.preact();
            }
        }

        if (typeof(adv::Dobj) == Object) {
            "What do you want to do with the ", adv::Dobj.name, "?\n";
            adv::ExitPhase(adv::PHX_SENTENCE);
        }
        else if (typeof(adv::Dobj) == String) {
            "", MeanMsg, adv::Dobj, "\"?\n";
          adv::ExitPhase(adv::PHX_SENTENCE);
        }
        else if (typeof(adv::Iobj) == Object) {
            "What to you want to do ", adv::Prep.name, " the ", adv::Iobj.name, "?\n";
            adv::ExitPhase(adv::PHX_SENTENCE);
        }
        else if (typeof(adv::Iobj) == String) {
            "", MeanMsg, adv::Iobj, "\"?\n";
            adv::ExitPhase(adv::PHX_SENTENCE);
        }
        else {
            "I beg your pardon?\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
    }
}

adv::VerbClass wait {
    preact = proc()
    {
        Expect(NO_OBJ, NO_OBJ);

        "Time passes...\n";

        adv::ExitPhase(adv::PHX_ACTOR);
    }
}
adv::Synonym _z(wait, "z");

adv::VerbClass wear {
    preact = Preact
    action = Silly
}


adv::VerbClass remove {
    preact = Preact
    action = Silly
}

adv::VerbClass verbose {
    preact = proc() { Expect(NO_OBJ, NO_OBJ); }
    action = proc()
    {
        "Maximum verbosity.\n";
        adv::Verbose = true;
    }
}

adv::VerbClass terse {
    preact = proc() { Expect(NO_OBJ, NO_OBJ); }
    action = proc()
    {
        "Minimum verbosity.\n";
        adv::Verbose = false;
    }
}

adv::VerbClass take {
    preact = proc()
    {
        Expect(ONE_OBJ|MULT_OBJ|PLAIN_OBJ, NO_OBJ|ONE_OBJ|PLAIN_OBJ);
        if (adv::Iobj) {
            if (!adv::Prep) {
                /* The sentence was "take X Y" */
                say(Huh);
                adv::ExitPhase(adv::PHX_ACTOR);
            }
            if (adv::Iobj.pOpened) {
                TorDPRE(adv::Iobj.cont, adv::Iobj);
            }
            else {
                "You can't reach into the ", adv::Iobj.name, CR;
                adv::ExitPhase(adv::PHX_ACTOR);
            }
        }
        else {
            adv::$ME.pOpened = false; adv::$ME.pTrans = false;
            TorDPRE(adv::$ME.loc.cont, adv::$ME.loc);
            adv::$ME.pOpened = true; adv::$ME.pTrans = true;
        }
    }
    action = proc() { TorDACT(adv::$ME, "taken"); }
}

adv::VerbClass drop {
    preact = proc()
    {
        Expect(ONE_OBJ|MULT_OBJ|PLAIN_OBJ, NO_OBJ|ONE_OBJ|PLAIN_OBJ);
        if (adv::Iobj) {
            if (!adv::Prep) {
                /* The sentence was "drop X Y" */
                say(Huh);
                adv::ExitPhase(adv::PHX_ACTOR);
            }
            if (!adv::Iobj.pOpened) {
                "You can't put that into the ", adv::Iobj.name, EOL;
                adv::ExitPhase(adv::PHX_ACTOR);
            }
            adv::IobjSave = adv::Iobj;
            adv::Iobj = 0;
        }
        TorDPRE(adv::$ME.cont, adv::$ME);
    }
    action = proc()
    {
        if (adv::IobjSave) {
            TorDACT(adv::IobjSave, Dropped);
        }
        else {
            TorDACT(adv::$ME.loc, Dropped);
        }
    }
}


adv::Synonym put(drop);
adv::Synonym get(take);

adv::VerbClass open {
    preact = Preact
    action = proc()
    {
        if (!adv::Dobj.pOpens) {
            "I don't know how to open that!\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        else if (adv::Dobj.pLocks && adv::Dobj.pLocked) {
            "I can't open it, it's locked!\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        else if (adv::Dobj.pOpened) {
            "It's already open!\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        else {
            adv::Dobj.pOpened = true;
            if (adv::Dobj.cont.length() && !adv::Dobj.pTrans) {
                "Opening the ", adv::Dobj.name, " reveals:\n";
                Describe(1, adv::Dobj.cont, sdesc);
            }
            else {
                "Opened.\n";
            }
        }
    }
}


adv::VerbClass close {
    preact = Preact
    action = proc()
    {
        if (!adv::Dobj.pOpens) {
            "I don't know how to close that!\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        else if (!adv::Dobj.pOpened) {
            "It's already closed!\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        else {
            adv::Dobj.pOpened = false;
            "Closed.\n";
        }
    }
}


proc Lockact()
{
    if (adv::Dobj.pLocks) {
        "Hmm, you don't seem to have the right key.\n";
    }
    else {
        "I don't know how to lock or unlock such a thing.\n";
    }
}

adv::VerbClass lock {
    preact = Preact
    action = Lockact
}

adv::VerbClass unlock {
    preact = Preact
    action = Lockact
}

adv::VerbClass move {
    preact = Preact
    action = proc() {"Nothing seems to happen.\n";}
}

adv::VerbClass V_break("break") {    // "break" is a keyword
    preact = Preact
    action = proc() {"It seems to be unbreakable.\n";}
}

adv::VerbClass touch {
    preact = Preact
    action = proc()
    {
        "Touching the ", adv::Dobj.name, " doesn't seem too useful.\n";
    }
}

adv::VerbClass rub {
    preact = Preact
    action = proc()
    {
        "Nothing happens when you rub the ", adv::Dobj.name, EOL;
    }
}

adv::VerbClass V_throw("throw") {    // "throw" is a keyword
    preact = Preact
    action = proc() { adv::Dobj.move(adv::$ME.loc); "Thrown.\n"; }
}

adv::VerbClass turn {
    preact = Preact
    action = Silly
}

adv::VerbClass light {
    preact = Preact
    action = Silly
}

adv::VerbClass douse {
    preact = Preact
    action = Silly
}

adv::VerbClass V_read("read") {      // "read" is a builtin
    preact = proc()
    {
        Expect(ONE_OBJ|PLAIN_OBJ, NO_OBJ|ONE_OBJ|PLAIN_OBJ);
        if (!(See(adv::Dobj, adv::$ME.cont) || See(adv::Dobj, adv::$ME.loc.cont))) {
            "You don't see that here.\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
    }
    action = proc() { "It doesn't have anything on it to read.\n"; }
}

adv::VerbClass burn {
    preact = Preact
    action = proc() { "That doesn't seem to work.\n"; }
}

adv::VerbClass examine {
    preact = Preact
    action = proc()
    {
        "You see nothing special about the ", adv::Dobj.name, EOL;
    }
}
adv::Synonym _x(examine,"x");


adv::VerbClass look {
    preact = proc()
    {
        Expect(NO_OBJ, NO_OBJ|ONE_OBJ|PLAIN_OBJ);
        CheckAvail();
    }
    action = proc() { Describe(0, adv::$ME.loc, ldesc); }
}
adv::Synonym _l(look,"l");

adv::VerbClass inventory {
    preact = proc() { Expect(NO_OBJ, NO_OBJ); }
    action = proc()
    {
        if (!adv::$ME.cont.length()) {
            "You are empty-handed.\n";
            adv::ExitPhase(adv::PHX_ACTOR);
        }
        adv::$ME.pSeen = true;
        "You are carrying:\n";
        Conts = true;
        Describe(1, adv::$ME.cont, sdesc);
    }
}
adv::Synonym _i(inventory,"i");

proc yorn()
{
    var str;
    while (true) {
        str = adv::Getstr();
        if ((str == "y") || (str == "Y")) {
            return 1;
        }
        else if ((str == "n") || (str == "N")) {
            return 0;
        }
        else {
            "(Y or N) ";
        }
    }
}

adv::VerbClass quit {
    preact = proc() { Expect(NO_OBJ, NO_OBJ); }
    action = proc()
    {
        "Are you sure that you want to quit? ";
        if (yorn()) {
            adv::Quit();
        }
    }
}
adv::Synonym _q(quit,"q");

adv::VerbClass save {
    preact = proc() { Expect(NO_OBJ, NO_OBJ); }
    action = proc()
    {
        var
            str,
            n;

        MyLoc = nil;
        adv::$ME.loc.pSeen = false;
        "Save to which file? ";
        str = adv::Getstr();
        if (str.length()) {
            n = 1;//save(str);
            if (n < 0) {
                "Save failed.\n";
            }
            else if (n == 0) {
                "Restored.\n";
            }
            else {
                "Saved.\n";
            }
        }
        adv::$ME.loc.pSeen = true;
        MyLoc = adv::$ME.loc;
    }
}

adv::VerbClass restore() {
    preact = proc() { Expect(NO_OBJ, NO_OBJ); }
    action = proc()
    {
        var
            str;
            
        "Restore from which file? ";
        str = adv::Getstr();
        if (str.length()) {
            if (false/*restore(str) < 0*/) {
                "Restore failed.\n";
            }
        }
    }
}

adv::VerbClass restart() {
    preact = proc() { Expect(NO_OBJ, NO_OBJ); }
    action = proc()
    {
        "Are you sure that you want to restart? ";
        if (yorn()) {
            adv::Restart();
        }
    }
}

adv::VerbClass script();
//preact = Proc() { Expect(NO_OBJ, NO_OBJ); }
//action = Proc()
//{
//    Var
//        str;
//
//    if (Scripting) {
//      spec(SCRIPT, 0);
//      "Scripting turned off.\n";
//      Scripting = false;
//    }
//    else {
//      "Script to which file? ";
//      str = Getstr();
//      if (leng(str)) {
//          "Scripting turned on.\n";
//          spec(SCRIPT, str);
//          Scripting = true;
//      }
//    }
//}


/* Dwimmer(Obj) - Returns 1 if the object is "possibly the one the
  user meant."  Returns 0 otherwise. */

proc Dwimmer(Parser, Obj)
{
    var
        Trans,
        Opened,
        CanSee,
        i;

    if (Parser.pVerb == go) {
        /* Try to find Obj in the list of Directions */
        i = 0;
        while (i < 10) {
            if (DirArray[i] == Obj) {
                /* We found it! */
                return true;
            }
            i = i + 1;
        }
        /* if we get here, we didn't find it. */
        return 0;
    }
    else if (Parser.pVerb == take) {
        /* We don't want to look at stuff adv::$ME is already carrying */
        Trans = adv::$ME.pTrans; Opened = adv::$ME.pOpened;
        adv::$ME.pTrans = false; adv::$ME.pOpened = false;

        CanSee = See(Obj, adv::$ME.loc.cont);

        adv::$ME.pTrans = Trans; adv::$ME.pOpened = Opened;
        return CanSee;
    }
    else if (Parser.pVerb == drop) {
        /* We need to be transparent */
        Trans = adv::$ME.pTrans;

        CanSee = See(Obj, adv::$ME.cont);

        adv::$ME.pTrans = Trans;
        return CanSee;
    }
    else {
        /* This is the default case - it works pretty well */
        return See(Obj, adv::$ME.cont) || See(Obj, adv::$ME.loc.cont);
    }
}

proc Die()
{
    var
        Str;

    while (true) {
        "Do you want to RESTART, RESTORE, or QUIT? ";
        Str = adv::Getstr();
        if ((Str == "restart") || (Str == "RESTART")) {
            restart.action();
        }
        else if ((Str == "restore") || (Str == "RESTORE")) {
            /* Execute restore.ACTION */
            restore.action();
        }
        else if ((Str == "quit") || (Str == "QUIT")) {
            quit.action();
        }
    }
}

adv::Synonym putOn({put,onPrep},wear);
adv::Synonym takeOff({take,offPrep},remove);
adv::Synonym turnOn({turn,onPrep},light);
adv::Synonym turnOff({turn,offPrep},douse);
adv::Synonym lookAt({look,atPrep},examine);

}

/**** EOF std.oad ****/