#charset "us-ascii"
/* 
 *  Copyright (c) 2006 by Kevin Forchione. All rights reserved.
 *   
 *  This file is part of the TADS 3 Diff Library Extension
 *
 *  diff.t
 *
 *  version 1.0
 *
 *  Diff provides a mechanism for silently testing an
 *  action, rolling back any state changes that may 
 *  have resulted, and then restoring game state to
 *  that prior to the action's execution. 
 */

#include <tads.h>
#include <t3.h>
#include <vector.h>
#include <lookup.h>


diff(func)
{
    local aLu, bLu, cLu, propList, dispVec;

    /*
     *  Save the current game state
     */
    savepoint();

    /*
     *  Turn on our diff display capturing
     */
    DiffDisplayCapture.on();

    /*
     *  Create a lookup table prior to our test and 
     *  load it with all object states.
     */
    aLu = new LookupTable();
    for (local o = firstObj(Object, ObjAll); o != nil; o = nextObj(o, Object, ObjAll))
    {
        propList    = o.getPropList();
        foreach (local prop in propList)
        {
            if (o.propType(prop) is in (TypeNil, TypeTrue, TypeObject, TypeInt, 
                TypeSString, TypeList, TypeFuncPtr, TypeEnum))
            {
                aLu[[o, prop]] = o.(prop);
            }
        }
    }

    /*
     *  Perform our test
     */
    (func)();


    /*
     *  Create a lookup table after our test and 
     *  load it with all object states.
     */
    bLu = new LookupTable();
    for (local o = firstObj(Object, ObjAll); o != nil; o = nextObj(o, Object, ObjAll))
    {
        propList    = o.getPropList();
        foreach (local prop in propList)
        {
            if (o.propType(prop) is in (TypeNil, TypeTrue, TypeObject, TypeInt, 
                TypeSString, TypeList, TypeFuncPtr, TypeEnum))
            {
                bLu[[o, prop]] = o.(prop);
            }
        }
    }

    /*
     *  Turn off our diff display capturing
     *  and retrieve any captured display.
     */
    dispVec = DiffDisplayCapture.off();
    
    /*
     *  Restore the previous game state
     */
    undo();

    /*
     *  Create a lookup table and load it with
     *  all object state differences.
     */
    cLu = new LookupTable();
    foreach (local key in aLu.keysToList())
    {
        if (bLu.isKeyPresent(key))
        {
            if (aLu[key] != bLu[key])
                cLu[key] = [aLu[key], bLu[key]];
        }
        else
            cLu[key] = [aLu[key], []];
    }
    foreach (local key in bLu.keysToList())
    {
        if (!aLu.isKeyPresent(key))
        {
            cLu[key] = [[], bLu[key]];
        }
    }

    /*
     *  Return the captured display vector and changes 
     *  lookup table.
     */
    return [dispVec, cLu];
}

/*
 *  This object is used to capture any display 
 *  produced during our test. 
 */
DiffDisplayCapture: object
{
    dispVec         = nil
    oldDispFunc     = nil
    oldDispMeth     = nil

    /*
     *  This method is called for both object display
     *  method and function display method calls and
     *  loads all display into a vector.
     */
    captureDisplay(obj, val)
    {
        dispVec.append([obj, val]);
    }

    /*
     *  Turn on diff display capturing. We create 
     *  a new display vector and set display methods
     *  and functions for our diff capture.
     */
    on()
    {
        dispVec     = new Vector(100);
        oldDispFunc = t3SetSay(diffDisplayFunction);
        oldDispMeth = t3SetSay(&diffDisplayMethod);
    }

    /*
     *  Turn off diff display capture. We reset the
     *  old display method and function values and 
     *  return a copy of the diff display vector.
     */
    off()
    {
        local vec;
        
        if (oldDispFunc)
            t3SetSay(oldDispFunc);
        else t3SetSay(T3SetSayNoFunc);

        if (oldDispMeth)
            t3SetSay(oldDispMeth);
        else t3SetSay(T3SetSayNoMethod);

        vec     = new Vector(dispVec.length());
        vec.copyFrom(dispVec, 1, 1, dispVec.length());

        return vec;
    }
}

/*
 *  Define a diff display method to all game objects
 *  in order to identify where text is being produced
 *  in game objects.
 */
modify Object
{
    diffDisplayMethod(val)
    {
        DiffDisplayCapture.captureDisplay(self, val);
    }
}

/*
 *  Define a diff display function to capture any display
 *  from functions or double-quoted strings and embedded
 *  evaluations.
 */
diffDisplayFunction(val)
{
    DiffDisplayCapture.captureDisplay(nil, val);
}