#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);
}