/*
 * 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.
 */

/******************************************************************************
 * Interface
 ******************************************************************************/
#include "libstd.oah"   /* Uses standard library routines */
#include "libterm.oah"  /* And possibly uses libterm */

namespace adv {

var
    ShowStatus, // True iff we have a status line
    Verb,       // The current verb in the current actor's sentence
    Conj,       // The current conjunction in the current actor's sentence
    Dobj,       // The current direct object in the current actor's sentence
    Prep,       // The current preposition in the current actor's sentence
    Iobj,       // The current indirect object in the current actor's sentence
    Numd,       // The current direct obj count in the current actor's sentence
    $ME,        // The current actor
    Phase,      // The current phase of execution (see dox)
    Prompt,     // Routine used to prompt the user for input
    echoInput,  // True iff we want to echo the input.  Useful for scripting.
    dirVec;     // Vector of verbs used for hit / miss

public
    loc, cont,         // Graph of Things/Locations
    addCont, delCont,  // Graph maint. procs
    preact,            // Property of verb - called before other sentence procs
    action,            // Verb or noun or location - action proc
    ldesc,             // Proc which prints a long description of something
    sdesc,             // Proc which prints a short description of something
    noun,              // Noun of a Thing (could be a List)
    adjec,             // Adjec of a thing (could be a List)
    name,              // Name of a word / thing / location
    activate,          // Activates an actor
    setfuse,           // Sets a fuse to execute in given number of turns
    delfuse,           // Deletes a fuse
    move;              // Method to move an object to another location

proc
    Exit,      // End current phase of execution, continue to another phase
    Miss,      // Compare dirVec to current verb, execute corresponding proc
    Hit,       // Compare dirVec to curr. verb, move obj to corresp. loc
    Quit,      // Quits OADL/adv
    Restart,   // Restarts the game
    Setdaemon, // Sets a daemon to execute every turn
    Incturn;   // Increments the turn counter (by optional number)

class
    ObjBase,  // Base class of all locations/things/etc
    Actor,      // A self-actualizing Thing
    Thing,      // A movable Thing
    Location,   // A place where things happen
    VerbClass,  // Action word, of course
    PrepClass,  // "under", "into", etc.
    AdjecClass, // "red", "green", etc.
    NounClass,  // "ball", etc. - note: this is *not* a Thing
    ArtClass,   // "the", "a", "an"...
    ConjClass,  // "and", "but", "or", ...
    SepClass,   // ".", "then", etc.
    Synonym;    // Declare a single-word synonym to a word or thing

ObjBase
    $ALL();     // Root of the object tree

proc
    START,      // User-defined start procedure
    DWIMD,      // User-defined indirect object dwimmer
    DWIMI;      // User-defined direct object dwimmer

VerbClass
    NOVERB;     // Verb to use if no verb was given in input sentence
VerbClass
    TELLER;     // Verb to use in a "actor, do this" construction
Thing
    NONOUN,     // Thing to use if no noun in a prepositional phrase
    STRING;     // Thing to use if a string is used as an object

/******************************************************************************
 * Phases of execution and possible ExitPhase args
 ******************************************************************************/
const
    PHASE_START = 0,
    PHASE_DAEMONS = 1,
    PHASE_ACTOR = 2,
    PHASE_VERBPRE = 3,
    PHASE_IOBJ = 4,
    PHASE_DOBJ = 5,
    PHASE_VERBACT = 6,
    PHASE_ROOM = 7;

const
    PHX_CURRENT = 0,
    PHX_ACTOR = 1,
    PHX_DOBJS = 2,
    PHX_SENTENCE = 3;

/******************************************************************************
 * Implementation
 ******************************************************************************/

const INIT_STATUS_LINE = 1; // Will be using libterm / status line stuff
extern Tokenize, Initadv, Width, Getstr;
extern Insert, Lookup;

public
    GetToken, prevAct, nextAct, detokenize, finishTokens,
    ObjList;    // List of objects that share this noun/adjec
var
    ScreenRows, ScreenCols, ScreenWidth,
    actList,
    lastAct,
    exitLocs,
    exitOK,
    Dictionary,
    Daemons = {},
    Turns = 0;

#define SET_EXIT(r, n) {
    exitOK[n] = 1;
    r = oadl::setjmp(exitLocs[n]);
}

proc $ClrExit(n)
{
    exitOK[n] = 0;
}

/******************************************************************************
 * Our dictionary class. We use this instead of OADL dict because it
 * supports abbreviations.
 ******************************************************************************/

class $TrieClass {
    var conts;

    const STRIDE = 4; // The conts are an array of [n*STRIDE] quads

    // These are the offsets of the subcomponents of each triple.
    const CHAR = 0, CHILD = 1, NEXT = 2, VAL = 3;

    const INITSIZE = 128; // Pick a reasonable size

    // Allocate the initial conts array with an optional first argument size
    public proc create() {
        var size = INITSIZE;
        if (oadl::nargs()) size = oadl::arg(0);
        conts = new Array(size, STRIDE);
        conts[0,CHAR] = "TRIE"; // Just for debugging
        conts[0,NEXT] = 1; // Allocate first slot to track # used
    }

    // Overload array index assignment as "insert", just like a Dict
    operator [=] (key, val) {
        conts = Insert(conts, key, val);
    }

    // Overload array index fetch as "lookup", just like a dict
    operator [] (key) {
        return Lookup(conts, key);
    }

#if (0) // For debugging
    public proc print() {
        var used = conts[0,NEXT];
        // SL - left adjust; / - advance to next line after format
        ::print("SL,4V6,V,/",
                        // The header row
                        {"#", "CH","CHLD","NEXT","VAL"},
                        // The number column
                        [used,1].iterate(),
                        // The chars
                        WideChar(conts[0:used-1,0]),
                        // The contents
                        conts[0:used-1,1:]);
    }
#endif
}

/* Token types for the dictionary */
const
    T_PREP = 0,
    T_VERB = 1,
    T_STRING = 2,
    T_ADJEC = 3,
    T_NOUN = 4,
    T_NOUN_SYN = 5,
    T_ARTICLE = 6,
    T_CONJUNCTION = 7,
    T_COMMA = 8,
    T_SEPARATOR = 9;

/* Temporary class used for intermediate sentences */
class $DirObj {
    protected var
        adjec, noun, conj;
    public proc create(aa,nn,cc) {
        adjec = aa;
        noun = nn;
        conj = cc;
    }
}

/* The parser - stolen from ADL */
class ParseClass {
    const
        ERR = -100,
        XXX = -100,
        FIN = 100;
    public var
        pVerb,                  /* Single value - the verb */
        pConj,                  /* List of conjunctions */
        pDobj,                  /* List of direct objects */
        pPrep,                  /* Single value - the prep creating the Iobj */
        pIobj;                  /* The indirect object */
    var
        dobjList,               /* Array of {mod, noun, conj} tuples */
        Imod,                   /* Single value */
        Inoun,                  /* Single value */
        NumImod,                /* Active size of Imod array */
        parseDict,
        C1, P1, P2,
        PP_List,
        VP_List,
        s_str,
        tokType,
        tokVal,
        currTok,
        readTok = 1,
        Tmod,
        Tnoun,
        Tconj,
        state,
        parseErrJmp,

        parseTransitions = {
    /*
     *          Transit table for state machine
     *
     *                                 NOUN
     *        PREP VERB STR  ADJ  NOUN SYN  ART  CONJ ","  SEP
     */
    /* 0 */ {   4,   1,   2,  12,  12,  12,  12, ERR, ERR, FIN },
    /* 1 */ {   4,   2,   2,   2,   2,   2,   2, ERR, ERR, FIN },
    /* 2 */ {   4,   3,   3,   3,   3,   3,   3,   5,   5, FIN },
    /* 3 */ {  11, ERR, ERR, ERR, ERR, ERR, ERR,   6,   6, FIN },
    /* 4 */ {   9,   7,   7,   7,   7,   7,   7, ERR, ERR, FIN },
    /* 5 */ { ERR,   8,   8,   8,   8,   8,   8,   5,   5, ERR },
    /* 6 */ { ERR,   3,   3,   3,   3,   3,   3,   6,   6, ERR },
    /* 7 */ {   9, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, FIN },
    /* 8 */ {   4, ERR, ERR, ERR, ERR, ERR, ERR,   5,   5, FIN },
    /* 9 */ { ERR,  10,  10,  10,  10,  10,  10, ERR, ERR, FIN },
    /*10 */ {  11, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, FIN },
    /*11 */ { ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, FIN },
    /*12 */ {   4,   3,   3,   3,   3,   3,   3,   5,  13, FIN },
    /*13 */ { ERR,  14,  14,   8,   8,   8,   8, ERR, ERR, ERR },
    /*14 */ { ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, ERR, FIN }
        },
        parseActions = {
    /*
     *          Action table for state machine
     *                                 NOUN
     *        PREP VERB STR  ADJ  NOUN SYN  ART  CONJ ","  SEP
     */
    /* 0 */ {   2,   0,   1,   1,   1,   1,   1, 500, 500, XXX },
    /* 1 */ {   3,   1,   1,   1,   1,   1,   1, 501, 501, XXX },
    /* 2 */ {   7,   5,   5,   5,   5,   5,   5,   6,   6,   4 },
    /* 3 */ {   2, 502, 502, 502, 502, 502, 502,   8,   8, XXX },
    /* 4 */ {   9,   1,   1,   1,   1,   1,   1, 501, 501,  10 },
    /* 5 */ { 501,  11,  11,  11,  11,  11,  11,   8,   8, 501 },
    /* 6 */ { 501,  11,  11,  11,  11,  11,  11,   8,   8, 501 },
    /* 7 */ {  13, 502, 502, 502, 502, 502, 502, 502, 502,  12 },
    /* 8 */ {   2, 502, 502, 502, 502, 502, 502,   8,   8, XXX },
    /* 9 */ { 501,  15,  15,  15,  15,  15,  15, 501, 501,  14 },
    /*10 */ {   2, 502, 502, 502, 502, 502, 502, 502, 502, XXX },
    /*11 */ { 503, 503, 503, 503, 503, 503, 503, 503, 503,  10 },
    /*12 */ {   7,   5,   5,   5,   5,   5,   5,   6,  16,   4 },
    /*13 */ { 501,  19,  18,  17,  17,  17,  17, 501, 501, 501 },
    /*14 */ { 503, 503, 503, 503, 503, 503, 503, 503, 503, XXX }
        };

    proc GetToken()
    {
        var
            i, n, str, ch, tok;

        if( !readTok ) {
            readTok = 1;
            return;
        }

        tok = $ME.GetToken();
        if( !tok ) {
            oadl::longjmp(parseErrJmp, 1);
            //return nil;
        }

        switch( tok[0] ) {
        case '.' :
            tokType = T_SEPARATOR;
            tokVal = '.';
        case ',' :
            tokType = T_COMMA;
            tokVal = ',';
        case '"', '\'' :
            tokType = T_STRING;
            ch = tok[0];
            tokVal = tok[1:];
            n = tokVal.length();
            if( (n > 0) && (tokVal[n-1] == ch) ) {
                /* Remove trailing quote */
                tokVal = tokVal.subr(0,n-2);
            }
        default :
            if( std::isdigit(tok[0]) ) {
                tokType = T_STRING;
                tokVal = tok;
            }
            else {
                n = parseDict[std::tolower(tok)];
                if( n == nil ) {
                    tokType = -1;
                    tokVal = -1;
                }
                else {
                    tokType = n[0];
                    tokVal = n[1];
                }
            }
        }
        currTok = tok;
        return tok;
    }

    proc parseError( which )
    {
        switch( which ) {
        case -1  : "I don't know the word \"", currTok, "\"\n";
        case 500 : "\"", currTok, "\" is not a verb.\n";
        case 501 : "\"", currTok, "\" is not part of a noun phrase.\n";
        case 502 : "\"", currTok, "\" is not a preposition.\n";
        case 503 : "End of sentence expected.\n";
        case 504 : "Illegal multiple word verb phrase\n";
        case 506 : "Illegal multiple word preposition\n";
        default  : "I don't understand that\n";
        }
        state = ERR;
    }

    proc getNoun()
    {
        Tmod = nil;
        Tnoun = nil;
        if( tokType == T_STRING ) {
            Tnoun = tokVal;
            return 1;
        }

        if( tokType == T_ARTICLE ) {
            GetToken();
        }

        if( tokType == T_NOUN_SYN ) {
            assert tokVal ?= Thing;
            Tmod = tokVal.adjec;
            Tnoun = tokVal.noun;
            return 1;
        }

        if( (tokType == T_ADJEC) || (tokType == T_VERB) ) {
            Tmod = tokVal;
            GetToken();
        }

        if( tokType == T_NOUN ) {
            Tnoun = tokVal;
            return 1;
        }

        if( tokType < 0 ) {
            parseError( tokType );      /* Dictionary error */
            return nil;
        }
        else if( (!Tmod) && (!Tnoun) ) {
            parseError( 501 );  /* S is not a noun */
            return nil;
        }
        else {
            readTok = 0;        /* Skip next token */
            return 1;           /* Found adjecs but no noun; DWIM later */
        }
    }

    proc findNoun(modObj, nounObj) {
        var others = nounObj.ObjList;
        if (!others) return nil;

        forall (others[i]) {
            var obj = others[i];

            var aa = obj.adjec;
            var af = false;
            if (aa.isarray()) {
                forall(aa[j]) {
                    af |= (modObj == aa[j]);
                }
            }
            else {
                af |= (modObj == aa);
            }
            if (!af) continue;

            var nn = obj.noun;
            var nf = false;
            if (nn.isarray()) {
                forall(nn[j]) {
                    nf |= (nounObj == nn[j]);
                }
            }
            else {
                nf |= (nounObj == nn);
            }
            if (nf) return obj;
        }

        return nil;
    }

    proc findVP( vrb, prp ) {
        var i, n;
        n = VP_List.length();
        for( i = 0; i < n; i++ ) {
            if( (VP_List[i][0] == vrb) && (VP_List[i][1] == prp) ) {
                return VP_List[i][2];
            }
        }
        return nil;
    }

    proc findPP( prep1, mod, noun, prep2 ) {
        var
            i, n, obj;
        if( mod || noun ) {
            obj = findNoun(mod, noun);
            if( !obj ) return nil;
        }
        else {
            obj = nil;
        }
        n = PP_List.length();
        for( i = 0; i < n; i++ ) {
            if( (PP_List[i][0] == prep1) &&
                (PP_List[i][1] == obj) &&
                (PP_List[i][2] == prep2) )
            {
                return PP_List[i][3];
            }
        }
        return nil;
    }

    proc performAction( which )
    {
        var
            x;                  /* Temporary used for lookups */

        switch( which ) {
        case  0 :
            pVerb = tokVal;
        case  1 :
            getNoun();
        case  2 :
            P1 = tokVal;
        case  3 :
            x = findVP( pVerb, tokVal );
            if( x == nil ) {
                P1 = tokVal;
            }
            else {
                pVerb = x;
                state = 1;
            }
        case  4 :
            dobjList = {new $DirObj(Tmod, Tnoun, nil)};
        case  5 :
            Imod = Tmod;        Inoun = Tnoun;
            getNoun();
            dobjList = {new $DirObj(Tmod, Tnoun, nil)};
        case  6 :
            Tconj = tokVal;
            dobjList = {new $DirObj(Tmod, Tnoun, nil)};
        case  7 :
            dobjList = {new $DirObj(Tmod, Tnoun, nil)};
            P1 = tokVal;
        case  8 :
            Tconj = tokVal;
        case  9 :
            P2 = tokVal;        Tmod = nil; Tnoun = nil;
        case 10 :
            x = findVP( pVerb, P1 );
            if( x == nil ) {
                pPrep = P1;
                Imod = nil;
                Inoun = nil;
            }
            else {
                pVerb = x;
            }
        case 11 :
            getNoun();
            dobjList = dobjList ## new $DirObj(Tmod, Tnoun, Tconj);
        case 12 :
            Imod = Tmod;                Inoun = Tnoun;
            pPrep = P1;
        case 13 :
            P2 = tokVal;
        case 14 :
            if( (Tnoun != nil) || (Tmod != nil) ) {
                Imod = Tmod;    Inoun = Tnoun;  pPrep = P1;
                x = findVP( pVerb, P2 );
                if( x == nil ) {
                    parseError( 504 );  /* Illegal verb phrase */
                }
                else {
                    pVerb = x;
                }
            }
            else {
                x = findPP( P1, Tmod, Tnoun, P2 );
                if( x  == nil ) {
                    parseError( 506 );  /* Illegal prep phrase */
                }
                else {
                    P1 = x;
                    x = findVP( pVerb, P1 );
                    if( x == nil ) {
                        parseError( 504 );      /* Illegal verb phrase */
                    }
                    else {
                        pVerb = x;
                    }
                }
            }
        case 15 :
            x = findPP( P1, Tmod, Tnoun, P2);
            if( x == nil ) {
                parseError( 506 );              /* Illegal prep phrase */
            }
            else {
                pPrep = x;
                getNoun();
                Imod = Tmod;    Inoun = Tnoun;
            }
        case 16 :
            C1 = tokVal;
            s_str = $ME.detokenize();
        case 17 :
            var prevMod = Tmod, prevNoun = Tnoun, currConj = C1;
            getNoun();
            dobjList = {new $DirObj(prevMod, prevNoun, nil),
                        new $DirObj(Tmod, Tnoun, currConj)};
        case 18 :
            Imod = Tmod;        Inoun = Tnoun;
            dobjList = {new $DirObj(nil, tokVal, nil)};
            pVerb = TELLER;
        case 19 :
            Imod = Tmod;        Inoun = Tnoun;
            dobjList = {new $DirObj(nil, s_str, nil)};
            pVerb = TELLER;
            $ME.finishTokens();
        }
    }

    proc dwimmer( dwimProc, modObj, nounObj )
    {
        var
            msg = nil,
            ex,
            obj,
            others,
            result;

        SET_EXIT(ex, PHX_CURRENT)
        if( ex ) {
            return nil;
        }

        result = nil;
        if( nounObj == nil ) {
            /* We must DWIM for the noun */
            others = modObj.ObjList;
            if (others) {
                forall (others[i]) {
                    obj = others[i];
                    if( dwimProc( self, obj ) ) {
                        if( result != nil ) {
                            msg = "You will have to be more specific\n";
                            result = nil;
                        }
                        else {
                            result = obj;
                        }
                    }
                }
            }
        }
        else if( typeof(nounObj) == String ) {
            /* No DWIMming for strings (???) */
            result = nounObj;
        }
        else if( modObj == nil ) {
            /* We may have to dwim for the adjec */
            others = nounObj.ObjList;
            if (others) {
                forall (others[i]) {
                    obj = others[i];
                    if( obj.adjec == nil ) {
                        /* Aha!  This is the one. */
                        if( result != nil ) {
                            msg = "You will have to be more specific\n";
                            result = nil;
                        }
                        else {
                            result = obj;
                        }
                    }
                    else if( dwimProc( self, obj ) ) {
                        if( result != nil ) {
                            result = nil;
                        }
                        else {
                            result = obj;
                        }
                    }
                }
            }
        }
        else {
            /* No DWIMming needed */
            result = findNoun(modObj, nounObj);
        }
        if( result == nil ) {
            if( msg ) {
                say(msg);
            }
            else {
                "You don't see anything like that.\n";
            }
        }
        $ClrExit(PHX_CURRENT);
        return result;
    }

    public proc initvars()
    {
        var
            i;

        dobjList = {};

        Imod = nil;
        Inoun = nil;

        pVerb = NOVERB;
        pPrep = nil;
        pIobj = nil;
    }

    public proc parse()
    {
        var
            done, act;

        readTok = 1;
        state = 0;
        Tmod = nil; Tnoun = nil; Tconj = nil;
        P1 = 0; P2 = 0; C1 = 0;

        if( oadl::setjmp(parseErrJmp) ) {
            return -1;
        }

        done = 0;
        while( !done ) {
            GetToken();
            if( tokType < 0 ) {
                parseError( tokType );  /* Dictionary error */
            }
            else {
                act = parseActions[state][tokType];
                state = parseTransitions[state][tokType];
                if( state == ERR ) {
                    parseError( act );
                }
                else {
                    performAction( act );
                }
            }
            if( state == FIN ) {
                done = 2;
            }
            else if( state == ERR ) {
                done = 1;
            }
        }

        /* OK, now do the DWIMMING */
        if( done == 2 ) {
            var pNumDobj = dobjList.length();
            pDobj = new List(pNumDobj);
            pConj = new List(pNumDobj);
            forall (dobjList[i]) {
                var dobj = dobjList[i], conj = dobj.conj;
                dobj = dwimmer(DWIMD, dobj.adjec, dobj.noun);
                if (!dobj) return 0;
                pConj[i] = conj;
                pDobj[i] = dobj;
            }

            if (Imod || Inoun) {
                pIobj = dwimmer(DWIMI, Imod, Inoun);
                if (!pIobj) return 0;
            }
            else if (pPrep) {
                pIobj = NONOUN;
            }
        }
        return done - 1;
    }

    public proc definePP(w1,w2,w3,syn) {
        PP_List = PP_List ## {{w1,w2,w3,syn}};
    }

    public proc defineVP(w1,w2,syn) {
        VP_List = VP_List ## {{w1,w2,syn}};
    }

    public proc create()
    {
        var
            i;

        if (Dictionary == nil) {
            Dictionary = new $TrieClass();
        }
        parseDict = Dictionary;
        parseErrJmp = new Array(oadl::JMP_SIZE);
        PP_List = {};
        VP_List = {};
        dobjList = {};
    }
} /* End of class ParseClass */

ParseClass parser();

/* A thing is an object or a location */
class ObjBase {
    protected var
        loc, cont = {};         // Graph of objs

    // Add given object to contents. Note that the object is only
    // present once on this list.
    public proc addCont(obj) {
        if (cont.position(obj) < 0) {
            cont = obj ## cont;
        }
    }

    // Remove given object from contents. It assumes that the object
    // is only present once on the list.
    public proc delCont(obj) {
        cont = cont.without(obj);
    }

    public proc move(newLoc) {
        // Remove myself from location's list of contents
        // TBD: Should we remove it from *all* of the other locations?
        if (!loc.isarray()) loc.delCont(self);

        // Add myself to new location
        loc = newLoc;
        newLoc.addCont(self);
    }

    public proc create() {
        // Insert into location(s) (if not AllObjs)
        if (self != $ALL) {
            switch (oadl::nargs()) {
            case 0 :  loc = $ALL;
            case 1 :  loc = oadl::arg(0);
            default : loc = @oadl::argvec();
            }
            var tmpLoc = loc.isarray() ? loc : {loc};
            forall (tmpLoc[i]) {
                tmpLoc[i].addCont(self);
            }
        }
    }
}

proc Status(rname, score, moves)
{
    var cursRow, cursCol;
    var spaces;

    if (!ShowStatus) return;

    term::ScrollRegion(1,ScreenRows-1);

    cursRow = term::Query(term::CURS_ROW);
    cursCol = term::Query(term::CURS_COL);

    score = "Score: " ## String(score) ## "  Moves: " ## String(moves);
    spaces = ScreenWidth - (rname.length() + score.length()) - 1;
    spaces = ' '.reshape(spaces);

    term::GotoRC(0,0);
    term::EEOL();
    term::Reverse(1);

    "", rname, spaces, score;

    term::Reverse(0);

    term::GotoRC(cursRow,cursCol);
}


proc Quit()
{
    if (ShowStatus) {
        "Hit return to continue...";
        Getstr();
        term::Stop();
    }
    oadl::halt();
}

proc Restart()
{
    if (ShowStatus) {
        "Hit return to continue...";
        Getstr();
        term::Stop();
    }
    oadl::restart();
}

proc GetObjName(obj)
{
    var name, i, n;
    name = oadl::objname(obj);
    n = name.length();
    for( i = 0; i < n; i++ ) {
        if( name[i] == ':' ) return name[i+2: ];
    }
    return name;
}

/* This is a non-location thing */
class Thing(ObjBase) {
    public var
        noun,                   /* The one-word noun describing this thing */
        adjec,                  /* Modifier to this thing */
        name;                   /* The full string name of this object */

    operator {} (complete)       // Completion operator
    {
        var
            aa, nn;

        if (!complete) return;

        /* Create the default noun, if not present */
        if (!noun) {
            noun = new NounClass(GetObjName(self)) {};
        }

        if (!name) {
            /* Create the default composite name from the first adjec/noun */
            aa = adjec.isarray() ? adjec[0] : adjec;
            nn = noun.isarray() ? noun[0] : noun;
            if( aa && nn ) {
                name = aa.name ## " " ## nn.name;
            }
            else {
                name = nn.name;
            }
        }

        /* Now, add ourselves to "list of objects" for all of the nouns
         * and adjectives
         */
        if (adjec.isarray()) {
            forall (adjec[i]) {
                adjec[i].ObjList = adjec[i].ObjList ## self;
            }
        }
        else if (adjec) {
            adjec.ObjList = adjec.ObjList ## self;
        }
        if (noun.isarray()) {
            forall (noun[i]) {
                noun[i].ObjList = noun[i].ObjList ## self;
            }
        }
        else {
            noun.ObjList = noun.ObjList ## self;
        }
    }

    /* Inherit create routine */
    public var
        ldesc,
        sdesc,
        action;
}

/* This is a location thing */
class Location(ObjBase) {
    public proc create()
    {
        /* Pass nil to the parent so this location is placed in $ALL */
        (parent.create)( nil );
    }

    public var ldesc;
    public var sdesc;
    public var action;
}

/* We need the word class so that dictionary lookup stuff can
 * go well.  Basically, you never directly declare instances
 * of the word class.  Rather, you subclass it as below,
 * changing the tokType in each subclass to correspond with
 * how you want it parsed.
 */
class WordClass {
    public var name;
    public var otherNames = {};
    var tokType;
    public proc GetType() { return tokType; }
    public proc create()
    {
        var
            i, n;

        n = oadl::nargs();
        if (n == 0) {
            name = GetObjName(self);
            // Don't insert names that have non-lowercase letters
            if ((name == nil) || !name.islower().reduce(`&)) name = "";
        }
        else {
            name = oadl::arg(0);
        }
        if (n > 1) {
            otherNames = oadl::argvec()[1:];
        }
    }
    operator {} ()      // Completion operator
    {
        var
            i, n, val;

        val = {tokType, self};
        Dictionary[name] = val;
        n = otherNames.length();
        for( i = 0; i < n; i += 1 ) {
            Dictionary[otherNames[i]] = val;
        }
    }
}

/* A class to create single-word synonyms */
class Synonym {
    public proc create(orig)
    {
        var
            syn, typ, obj,
            i, n;

        n = oadl::nargs();

        if( orig.isarray() ) {
            typ = nil;
        }
        else if( orig ?= WordClass ) {
            typ = orig.GetType();
        }
        else if( orig ?= Thing ) {
            typ = T_NOUN_SYN;
        }

        if( n <= 1 ) {
            syn = GetObjName(self);        // Default to what program named it
            if( orig.isarray() ) {
                if( orig.length() == 2 ) {
                    parser.defineVP(orig[0], orig[1], syn);
                }
                else {
                    parser.definePP(orig[0], orig[1], orig[2], syn);
                }
            }
            else {
                Dictionary[syn] = {typ, orig};
            }
        }
        else {
            for( i = 1; i < n; i++ ) {
                syn = oadl::arg(i);          // As requested by program
                if( orig.isarray() ) {
                    if( orig.length() == 2 ) {
                        parser.defineVP(orig[0], orig[1], syn);
                    }
                    else {
                        parser.definePP(orig[0], orig[1], orig[2], syn);
                    }
                }
                else {
                    Dictionary[syn] = {typ, orig};
                }
            }
        }
    }
}

class PrepClass(WordClass) { var tokType = T_PREP; }

class VerbClass(WordClass) {
    var tokType = T_VERB;
    public var ObjList = {};
    public var  preact;
    public var  action;
}

class AdjecClass(WordClass) {
    var tokType = T_ADJEC;
    public var ObjList = {};
}

class NounClass(WordClass) {
    var tokType = T_NOUN;
    public var ObjList = {};
}

class ArtClass(WordClass) { var tokType = T_ARTICLE; }

class ConjClass(WordClass) { var tokType = T_CONJUNCTION; }

class SepClass(WordClass) { var tokType = T_SEPARATOR; }

class $Fuse {
    protected var fuseProc, fuseTime;
    public proc create(pp, tt) { fuseProc = pp; fuseTime = tt; }
    public operator == (pp) { return fuseProc == pp; }
    public operator #= (pp) { return fuseProc == pp; }
    public operator <= (tt) { return fuseTime <= tt; }
}

class Actor(Thing) {
    public var prevAct, nextAct;      // Linked list of actors

    var fuses = {};
    var tokList, numTokens, tokIndex;
    var interactive;

    public proc setfuse( fuse, nTurns ) {
        fuses = fuses ## new $Fuse(fuse, nTurns+Turns);
    }

    public proc delfuse(fuse) {
        var cmp = fuses #= fuse;
        fuses = fuses.replicate(Int(cmp));
    }

    public proc checkFuses() {
        var cmp = fuses <= Turns;
        var n = 0;
        forall (cmp[i]) {
            if (cmp[i]) {
                fuses[i].fuseProc();
            }
            else {
                fuses[n] = fuses[i];
                n++;
            }
        }
        fuses = fuses[:n-1];
    }

    public proc activate(str, flag) {
        if( prevAct || nextAct ) {
            // Already active
            return;
        }
        prevAct = nil;
        nextAct = actList;
        if( actList ) {
            actList.prevAct = self;
        }
        else {
            lastAct = self;
        }
        actList = self;
        fuses = {};
        interactive = flag;
        if( str ) {
            tokList = Tokenize(str);
            numTokens = tokList.length();
        }
        else {
            numTokens = 0;
        }
        tokIndex = 0;
    }

    public proc deactivate() {
        if( !(prevAct || nextAct) ) {
            // Not active
            return;
        }
        if( prevAct )
            prevAct.nextAct = nextAct;
        else
            actList = nextAct;
        if( nextAct )
            nextAct.prevAct = prevAct;
        else
            lastAct = prevAct;
        prevAct = nil;
        nextAct = nil;
    }
    public proc GetToken() {
        var str, i, n;

        if( tokIndex == numTokens ) {
            /* If we're at the end and we haven't encountered a
             * separator yet, return a separator to close out
             * the sentence.
             */
            tokIndex++;
            if( numTokens > 0 ) {
                if( tokList[numTokens-1][0] != '.' ) {
                    return ".";
                }
            }
        }

        if( tokIndex > numTokens ) {
            if( !interactive ) {
                /* All done! */
                deactivate();
                return nil;
            }

            /* If we get here, we need to read a new line of input */
            do {
                Prompt();
                str = Getstr();
                if (echoInput) {
                    say(str, "\n");
                }
                tokList = Tokenize( str );
                numTokens = tokList.length();
                if( numTokens == 0 ) {
                    "I beg your pardon?\n";
                }
            } while( numTokens == 0 );
            tokIndex = 0;
        }

        tokIndex++;
        return tokList[tokIndex-1];
    }
    public proc detokenize()
    {
        var i, s;
        s = "";
        for( i = tokIndex; i < numTokens; i++ ) {
            if( s ) {
                s = s ## " " ## tokList[i];
            }
            else {
                s = tokList[i];
            }
        }
        return s;
    }

    public proc finishTokens()
    {
        tokIndex = numTokens;
    }
}

proc Setdaemon( daemon )
{
    Daemons = Daemons ## { daemon };
}

proc Incturn( nTurns )
{
    var
        i, j, n;

    if( oadl::nargs() > 0 ) {
        Turns += nTurns;
    }
    else {
        Turns++;
    }
}

proc ExitPhase( num )
{
    if( !exitOK[num] ) {
        "Invalid exit(",num,")\n";
        throw oadl::RangeCheck;
    }
    exitOK[num] = 0;
    oadl::longjmp( exitLocs[num], 1 );
}

// miss - Scans dirVec for the current Verb.  If found, the corresponding
// rout in the (variable) arglist is called.  Nothing happens if no match
// is found.  An attempt to call a nil routine does nothing.
proc Miss()
{
    for( var i = 0; i < oadl::nargs(); i++ ) {
        if( Verb == dirVec[i] ) {
            if( oadl::arg(i) ) {
                (oadl::arg(i))();
            }
            break;
        }
    }
}

// hit - Scans dirVec for the current Verb.  If found, 'obj' is moved to
// the corresponding loc in the (variable) arglist.  Nothing happens if no
// match is found.  An attempt to move an object to nil is ignored.
proc Hit(obj)
{
    for( var i = 1; i < oadl::nargs(); i++ ) {
        if( Verb == dirVec[i-1] ) {
            if( oadl::arg(i) ) {
                obj.move(oadl::arg(i));
            }
            break;
        }
    }
}

proc ::main(args)
{
    var
        ex, currAct, nextAct, lastTurn,
        startArgs = {},
        initFlags = 0,
        i, n;

    // See if we want a status line
    args = args[1:];
    forall (args[a]) {
        if (args[a] == "-status") {
            ShowStatus = 1;
            initFlags |= INIT_STATUS_LINE;
        }
        else if (args[a] == "-echo") {
            echoInput = true;
        }
        else {
            startArgs = startArgs ## args[a];
        }
    }

    if (ShowStatus) {
        term::Start(term::CAN_SCROLL, term::IS_RAW_IN);
        ScreenRows = term::Query(term::HEIGHT);
        ScreenCols = term::Query(term::WIDTH);
        ScreenWidth = ScreenCols - 8;
        Width(ScreenWidth);
        "\n"; // Need a space for the status line
    }

    // Call the initialization routine
    Initadv(initFlags);

    exitLocs = { new Array(oadl::JMP_SIZE), new Array(oadl::JMP_SIZE),
                  new Array(oadl::JMP_SIZE), new Array(oadl::JMP_SIZE) };
    exitOK = {0, 0, 0, 0};

    lastTurn = 0;

    Phase = PHASE_START;

    START(startArgs);

    // This double-loop structure is so things can continue
    // if we get an unanticipated exception.  And so we can
    // use continue in the inner loop (you cannot continue
    // out of a try/except)
    while( 1 ) {
        try {
            while( 1 ) {
                Phase = PHASE_DAEMONS;

                SET_EXIT(ex, PHX_CURRENT)
                if( !ex ) {
                    // Exec fuses if the turn counter has changed
                    if( Turns != lastTurn ) {
                        for( currAct = actList; currAct; currAct = nextAct ) {
                            nextAct = currAct.nextAct;
                            $ME = currAct;
                            currAct.checkFuses();
                        }
                        lastTurn = Turns;
                    }
                    // Exec daemons. Set ME to the last actor on the
                    // list (which would be the first one activated)
                    $ME = lastAct;
                    n = Daemons.length();
                    for( i = 0; i < n; i += 1 ) {
                        Daemons[i]();
                    }
                }

                for( currAct = actList; currAct; currAct = nextAct ) {
                    nextAct = currAct.nextAct;
                    $ME = currAct;

                    SET_EXIT(ex, PHX_ACTOR)
                    if( ex ) {
                        continue;
                    }

                    parser.initvars();

                    while( 1 ) {
                        SET_EXIT(ex, PHX_SENTENCE)
                        if( !ex ) {
                            break;
                        }
                    }

                    do {
                        n = parser.parse();
                    } while( n  == 0 );

                    if( n < 0 ) continue;

                    Numd = parser.pDobj.length();
                    n = Numd;
                    if (!n) n = 1;

                    for( i = 0; i < n; i += 1 ) {
                        SET_EXIT(ex, PHX_DOBJS)
                        if( ex ) {
                            continue;
                        }

                        Verb = parser.pVerb;
                        Conj = Numd ? parser.pConj[i] : nil;
                        Dobj = Numd ? parser.pDobj[i] : nil;
                        Prep = parser.pPrep;
                        Iobj = parser.pIobj;

                        Phase = PHASE_ACTOR;
                        SET_EXIT(ex, PHX_CURRENT)
                        if( !ex ) {
                            $ME.action();
                        }

                        Phase = PHASE_VERBPRE;
                        SET_EXIT(ex, PHX_CURRENT)
                        if( Verb && !ex ) {
                            Verb.preact();
                        }

                        Phase = PHASE_IOBJ;
                        SET_EXIT(ex, PHX_CURRENT)
                        if( Iobj && !ex ) {
                            if (typeof( Iobj ) == String) {
                                STRING.action();
                            }
                            else {
                                Iobj.action();
                            }
                        }

                        Phase = PHASE_DOBJ;
                        SET_EXIT(ex, PHX_CURRENT)
                        if( Dobj && !ex ) {
                            if (typeof( Dobj ) == String) {
                                STRING.action();
                            }
                            else {
                                Dobj.action();
                            }
                        }

                        Phase = PHASE_VERBACT;
                        SET_EXIT(ex, PHX_CURRENT)
                        if( Verb && !ex ) {
                            Verb.action();
                        }
                        $ClrExit(PHX_DOBJS);
                    }

                    Phase = PHASE_ROOM;
                    SET_EXIT(ex, PHX_CURRENT)
                    if( !ex ) {
                        $ME.loc.action();
                    }

                    $ClrExit(PHX_CURRENT);
                    $ClrExit(PHX_ACTOR);
                    $ClrExit(PHX_SENTENCE);
                }
            }
        }
        catch (e,f,l) {
            "Caught exception: ", e, " from file ", f, " line ", l, "\n";
            $ClrExit(PHX_CURRENT);
            $ClrExit(PHX_ACTOR);
            $ClrExit(PHX_DOBJS);
            $ClrExit(PHX_SENTENCE);
            if (e == oadl::InterruptCheck) break;
            if (e == oadl::EndOfFile) break;
        }
    }
    if (ShowStatus) {
        term::Stop();
    }
}

}

/* Redfines say and read, ultimately does header line */
using extern "libadv";