/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>


#include <dx/dx.h>
#include <string.h>
#include "graph.h"
#include "_variable.h"
#include "vcr.h"
#include "sysvars.h"
#include "log.h"
#include "packet.h"
#include "rq.h"
#include "distp.h"

#define MIN(x,y) ((x) < (y)? (x): (y))
#define MAX(x,y) ((x) > (y)? (x): (y))

typedef enum
{
    VCR_M_STOP = 0,
    VCR_M_PAUSE,
    VCR_M_STEP,
    VCR_M_PLAY
} vcr_mode;


typedef struct
{
    lock_type	lock;			/* vcr semaphore		*/
    int		dir;			/* current direction		*/
    vcr_mode	mode;			/* play mode			*/

    int		loop;			/* loop step/play		*/
    int		palin;			/* palindrome step/play		*/
    int		flip;			/* palindrome has flipped	*/
    int		steps;

    int		ngraphs;		/* number of outstanding graphs	*/
    int		graphId;		/* Id of most recent graph	*/	

    int		nframe;			/* what we think next frame is	*/

    struct node	*tree;			/* parse tree to execute	*/
    Program	*graph;
    int		change;
} _vcr;


#define	VCR_DEFAULT_START	1
#define	VCR_DEFAULT_END		100
#define	VCR_DEFAULT_DELTA	1
#define	VCR_DEFAULT_NEXT	VCR_DEFAULT_START
#define	VCR_DEFAULT_FRAME	VCR_DEFAULT_START

static	_vcr	*vcr = NULL;
static	EXDictionary	vcr_dict;

typedef struct {
    int	s_frame;
    int	e_frame;
    int	n_frame;
    int	d_frame;
    int	c_frame;
} VCRState;

static VCRState now = {		/* The frame that has a graph. */
    VCR_DEFAULT_START, 
    VCR_DEFAULT_END,
    VCR_DEFAULT_NEXT,
    VCR_DEFAULT_DELTA,
    VCR_DEFAULT_FRAME
};
static VCRState old = {		/* The frame that is currently running. */
    VCR_DEFAULT_START, 
    VCR_DEFAULT_END,
    VCR_DEFAULT_NEXT,
    VCR_DEFAULT_DELTA,
    VCR_DEFAULT_FRAME
};
static VCRState prior = {	/* The frame that's on the screen. */
    VCR_DEFAULT_START, 
    VCR_DEFAULT_END,
    VCR_DEFAULT_NEXT,
    VCR_DEFAULT_DELTA,
    VCR_DEFAULT_FRAME
};

typedef struct {
    int comm;
    long arg1;
    int arg2;
} VCRCommandTaskArg;

/*
 * Externally visible functions.
 */
Error	_dxf_ExInitVCR	(EXDictionary dict);
void	_dxf_ExCleanupVCR	(void);
int	_dxf_ExInitVCRVars	(void);
void 	_dxf_ExVCRCommand 	(int comm, long arg1, int arg2);

/*
 * Static functions.
 */
static	void	ExVCRSet	(char *var, int val);
static 	int	ExVCRGet	(char *name);
static	void	ExVCRLoad	(void);
static	void	ExVCRAdvance	(void);

/*
 * Extern functions.
 */
extern	gvar	*create_gvar	(_gvtype type);


/*************************************************************************/

/*
 * Top level initialization of the VCR by the executive.
 */

Error _dxf_ExInitVCR (EXDictionary dict)
{
    int		size;

    vcr_dict = dict;
    size = sizeof (_vcr);

    if (vcr)
	_dxf_ExCleanupVCR ();

    if ((vcr = (_vcr *) DXAllocate (size)) == NULL)
	return (ERROR);

    memset (vcr, NULL, size);

    if (DXcreate_lock (&vcr->lock, "vcr lock") != OK)
    {
	DXFree ((Pointer) vcr);
	vcr = NULL;
	return (ERROR);
    }

    _dxf_ExInitVCRVars ();
    vcr->change = TRUE;
    vcr->graph = NULL;
    return (OK);
}


/*
 * Cleanup function.
 */

void _dxf_ExCleanupVCR ()
{
    if (vcr == NULL)
	return;

    DXdestroy_lock (&vcr->lock);
    DXFree ((Pointer) vcr);
    vcr = NULL;
}


int _dxf_ExVCRRunning ()
{
    int		ret;

    /* a quick out		*/
    if (vcr->mode != VCR_M_STOP && vcr->mode != VCR_M_PAUSE)
	return (TRUE);
    
    if (! DXtry_lock (&vcr->lock, 0))		/* Someone else has it	*/
	return (TRUE);

    /* now really make sure	*/
    ret = (vcr->mode != VCR_M_STOP && vcr->mode != VCR_M_PAUSE);

    DXunlock (&vcr->lock, 0);

    return (ret);
}


int _dxf_ExVCRCallBack (int n)
{
    vcr_mode	mode;
    int		ngraphs;

    DXlock (&vcr->lock, 0);

    ngraphs = --(vcr->ngraphs);
    mode = vcr->mode;

    ExDebug ("5", "_dxf_ExVCRCallBack,%d: if ngraphs(%d) == 0 && mode (%d) == %d or %d\n                     _dxf_ExSPack \"stop\"", 
	__LINE__, ngraphs, mode, VCR_M_STOP, VCR_M_PAUSE);
    if ((ngraphs == 0) && (mode == VCR_M_STOP || mode == VCR_M_PAUSE))
    {
	_dxf_ExSPack  (PACK_INTERRUPT, n, "stop", 4);
    }

    DXunlock (&vcr->lock, 0);

    return (1);
}


/*
 * Resets the VCR and its associated variables back to its initial state.
 */

int _dxf_ExInitVCRVars ()
{
    /* Initialize the global variables used by the VCR */

    ExVCRSet (VCR_ID_START, VCR_DEFAULT_START);
    ExVCRSet (VCR_ID_END,   VCR_DEFAULT_END);
    ExVCRSet (VCR_ID_DELTA, VCR_DEFAULT_DELTA);
    ExVCRSet (VCR_ID_NEXT,  VCR_DEFAULT_NEXT);
    ExVCRSet (VCR_ID_FRAME, VCR_DEFAULT_FRAME);

    /* Initialize the internal variables used by the VCR */

    vcr->dir	= VCR_D_FORWARD;
    vcr->mode	= VCR_M_STOP;

    vcr->loop	= FALSE;
    vcr->palin	= FALSE;
    vcr->flip	= FALSE;

    vcr->steps	= 0;

    /* Clean up any prior incarnations */

    if (vcr->tree)
	_dxf_ExPDestroyNode (vcr->tree);
    vcr->tree    = NULL;

    return (OK);
}


/*
 * Sets a specific variable with the given integer value.
 */
static void ExVCRSet (char *var, int val)
{
    gvar	*gv;
    Array	arr;
    GDictSend   pkg;

    /* Make sure we remember what the next frame is */
    if (! strcmp (var, VCR_ID_NEXT))
	vcr->nframe = val;

    arr = DXNewArray (TYPE_INT, CATEGORY_REAL, 0);
    DXAddArrayData (arr, 0, 1, (Pointer) &val);
    gv = _dxf_ExCreateGvar (GV_UNRESOLVED);
    _dxf_ExDefineGvar(gv, (Object) arr);
    gv->cost = 1.0;
    _dxf_ExVariableInsert (var, vcr_dict, (EXO_Object) gv);
    _dxf_ExCreateGDictPkg(&pkg, var, gv);
    _dxf_ExDistributeMsg(DM_INSERTGDICT, (Pointer)&pkg, 0, TOSLAVES);
}


/*
 * Retrieves the specified value from the environment, converting it to
 * integer as necessary.
 */

static int ExVCRGet (char *var)
{
    gvar	*gv;
    Array	arr;
    int		val = 0;

    if ((gv = (gvar *) _dxf_ExVariableSearch (var, vcr_dict)) == NULL)
	return (0);

    arr = (Array)  gv->obj;

    DXExtractInteger ((Object) arr, &val);

    ExDelete (gv);

    return (val);
}


/*
 * Loads the local copies of the VCR's frame variables from the environment.
 */

static void ExVCRLoad ()
{
    now.s_frame = ExVCRGet (VCR_ID_START);
    now.e_frame = ExVCRGet (VCR_ID_END);
    now.n_frame = ExVCRGet (VCR_ID_NEXT);
    now.d_frame = ExVCRGet (VCR_ID_DELTA);
    now.c_frame = now.n_frame;

    if (now.d_frame < 1)
    {
	now.d_frame = (now.d_frame == 0) ? 1 : - now.d_frame;
	ExVCRSet (VCR_ID_DELTA, now.d_frame);
    }

    now.n_frame = (now.n_frame < now.s_frame) ? now.s_frame : now.n_frame;
    now.n_frame = (now.n_frame > now.e_frame) ? now.e_frame : now.n_frame;
}


/*
 * Advances the VCR one frame
 */

static void ExVCRAdvance ()
{
    int		n;

    n = now.n_frame + (now.d_frame * vcr->dir * (vcr->flip ? -1 : 1));

    /* We are still within the valid region, this was a simple advance */
    if (n >= now.s_frame && n <= now.e_frame)
    {
	now.n_frame = n;
	return;
    }
    
    /* Now we have to decide what to do based upon the state of the VCR */
    if (n > now.e_frame)
    {
	if (vcr->loop)
	{
	    if (vcr->palin)
	    {
		vcr->flip = ! vcr->flip;
		n = now.n_frame + (now.d_frame * vcr->dir * (vcr->flip ? -1 : 1));
	    }
	    else
	    {
		n = now.s_frame;
	    }
	}
	else	/* no vcr->loop */
	{
	    if (vcr->palin)
	    {
		if (vcr->flip)
		{
		    vcr->flip = FALSE;
		    vcr->mode = VCR_M_STOP;
		    n = now.e_frame;
		}
		else
		{
		    vcr->flip = ! vcr->flip;
		    n = now.n_frame + (now.d_frame * vcr->dir * (vcr->flip ? -1 : 1));
		}
	    }
	    else
	    {
		n = now.s_frame;
		vcr->mode = VCR_M_STOP;
	    }
	}
    }
    else	/* n < now.s_frame */
    {
	if (vcr->loop)
	{
	    if (vcr->palin)
	    {
		vcr->flip = ! vcr->flip;
		n = now.n_frame + (now.d_frame * vcr->dir * (vcr->flip ? -1 : 1));
	    }
	    else
	    {
		n = now.e_frame;
	    }
	}
	else	/* no vcr->loop */
	{
	    if (vcr->palin)
	    {
		if (vcr->flip)
		{
		    vcr->flip = FALSE;
		    vcr->mode = VCR_M_STOP;
		    n = now.s_frame;
		}
		else
		{
		    vcr->flip = ! vcr->flip;
		    n = now.n_frame + (now.d_frame * vcr->dir * (vcr->flip ? -1 : 1));
		}
	    }
	    else
	    {
		n = now.e_frame;
		vcr->mode = VCR_M_STOP;
	    }
	}
    }

    now.n_frame = (n < now.s_frame) ? now.s_frame : ((n > now.e_frame)  ? now.e_frame : n);
    return;
}

static int
VCRCommandTask(Pointer pArg)
{

    VCRCommandTaskArg *arg = (VCRCommandTaskArg*)pArg;
    _dxf_ExVCRCommand (arg->comm, arg->arg1, arg->arg2);
    DXFree (pArg);

    return (OK);
}

/*
 * VCR command interpreter.  Sets the values of the VCR's internal data
 * structure.
 */

void _dxf_ExVCRCommand (int comm, long arg1, int arg2)
{
    struct node		*oldtree = NULL;
    vcr_mode		mode;

    if (exJID != 1) 
    {
	VCRCommandTaskArg *arg = 
	    (VCRCommandTaskArg *)DXAllocate (sizeof (VCRCommandTaskArg));
	if (arg == NULL)
	    _dxf_ExDie ("_dxf_ExVCRCommand: can't allocate");


	arg->comm = comm;
	arg->arg1 = arg1;
	arg->arg2 = arg2;
	_dxf_ExRQEnqueue (VCRCommandTask, (Pointer) arg, 1, 0, 1, FALSE);
	return;
    }

    if (vcr == NULL)
	return;

    DXlock (&vcr->lock, 0);

    /* In most cases we want to delete the next graph that is queued   */
    /* because we are changing the VCR and the next graph to run       */
    /* will probably be different from the one currently running.      */
    /* This has a bad effect though if we are reading a script through */
    /* stdin. At the end of the script we receive and EOF which will   */
    /* call VCRCommand to turn off looping. In this case we do not     */
    /* wish to delete the next graph element.                          */
    if(! *_dxd_exTerminating) {
        if (vcr->graph != NULL)
	    _dxf_ExGraphDelete (vcr->graph);
        vcr->graph = NULL;
    }

    /* The one that I want to advance past is the one on the screen. */
    now = old;

    switch (comm)
    {
    case VCR_C_STOP:
	ExDebug ("1", "VCR  VCR_C_STOP");
	mode = vcr->mode;
	vcr->mode  = VCR_M_STOP;
	vcr->flip  = FALSE;
	vcr->steps = 0;

	if (mode != VCR_M_STOP &&
	    mode != VCR_M_PAUSE)
	{
	    if (!_dxf_ExGQAllDone())
	    {
		_dxf_ExExecCommandStr ("kill");
		ExDebug ("1", "Killing frame %d", old.c_frame);
		old = prior;
	    }
	    ExDebug ("5", "_dxf_ExVCRCommand,%d: if ngraphs(%d) == 0 _dxf_ExSPack \"stop\"",
		__LINE__, vcr->ngraphs);
	    if (vcr->ngraphs == 0)
	    {
		_dxf_ExSPack  (PACK_INTERRUPT, vcr->graphId, "stop", 4);
	    }
	    now = old;
	    ExVCRSet (VCR_ID_FRAME, now.c_frame);
	    ExVCRSet (VCR_ID_NEXT,  now.n_frame);
	}
	break;

    case VCR_C_PAUSE:
	ExDebug ("1", "VCR  VCR_C_PAUSE");
	mode = vcr->mode;
	vcr->mode  = VCR_M_PAUSE;
	if (mode != VCR_M_STOP &&
	    mode != VCR_M_PAUSE)
	{
	    if (!_dxf_ExGQAllDone())
	    {
		_dxf_ExExecCommandStr ("kill");
		ExDebug ("1", "Killing frame %d", old.c_frame);
		old = prior;
	    }
	    ExDebug ("5", "_dxf_ExVCRCommand,%d: if ngraphs(%d) == 0 _dxf_ExSPack \"stop\"",
		__LINE__, vcr->ngraphs);
	    if (vcr->ngraphs == 0)
		_dxf_ExSPack  (PACK_INTERRUPT, vcr->graphId, "stop", 4);
	    now = old;
	    ExVCRSet (VCR_ID_FRAME, now.c_frame);
	    ExVCRSet (VCR_ID_NEXT,  now.n_frame);
	}
	break;

    case VCR_C_PLAY:
	if  (! vcr->tree)
	    break;
	ExDebug ("1", "VCR  VCR_C_PLAY");
	vcr->mode  = VCR_M_PLAY;
	vcr->flip  = FALSE;
	vcr->steps = 0;
	break;

    case VCR_C_STEP:
	if  (! vcr->tree)
	    break;
	ExDebug ("1", "VCR  VCR_C_STEP");
	mode = vcr->mode;
	vcr->steps += vcr->dir;
	vcr->mode = vcr->steps ? VCR_M_STEP : VCR_M_STOP;
	ExDebug ("5", "_dxf_ExVCRCommand,%d: if ngraphs(%d) == 0 && mode (%d) == %d or %d\n                     _dxf_ExSPack \"stop\"", 
	__LINE__, vcr->ngraphs, mode, VCR_M_STOP, VCR_M_PAUSE);
	if (vcr->ngraphs == 0  &&
	    mode != VCR_M_STOP &&
	    mode != VCR_M_PAUSE)
	    _dxf_ExSPack  (PACK_INTERRUPT, vcr->graphId, "stop", 4);
	break;

    case VCR_C_DIR:
	ExDebug ("1", "VCR  VCR_C_DIR");
	if (vcr->dir == (int)arg1)
	    break;
	vcr->dir  = (int)arg1;
	/* vcr->flip = FALSE; */

	/*
	 * Only reposition the current position if the user hasn't
	 * explicitly done it by changing @nextframe.
	 */

	ExVCRLoad ();
	if (vcr->nframe == now.n_frame)
	{
	    ExVCRAdvance ();
	    ExVCRAdvance ();
	    ExVCRSet (VCR_ID_NEXT,  now.n_frame);
	}
	break;

    case VCR_C_FLAG:
	ExDebug ("1", "VCR  VCR_C_FLAG");
	switch ((int)arg1)
	{
	    case VCR_F_LOOP:
		vcr->loop  = arg2;
		break;

	    case VCR_F_PALIN:
		vcr->palin  = arg2;
                if(vcr->flip) {
		    vcr->flip = FALSE;
                    /*
                     * Only reposition the current position if the user 
                     * hasn't explicitly done it by changing @nextframe.
                     */

                    ExVCRLoad ();
                    if (vcr->nframe == now.n_frame)
                    {
                        ExVCRAdvance ();
                        ExVCRAdvance ();
                        ExVCRSet (VCR_ID_NEXT,  now.n_frame);
                    }
                }
		break;
	}
	break;

    case VCR_C_TREE:
	ExDebug ("1", "VCR  VCR_C_TREE");
	oldtree = vcr->tree; 
	vcr->tree = (struct node *) arg1;
	break;
    }

    DXunlock (&vcr->lock, 0);

    if (oldtree)
	_dxf_ExPDestroyNode (oldtree);
}

void
_dxf_ExVCRRedo(void)
{
    if(_dxd_exRemoteSlave) {
        _dxf_ExDistributeMsg(DM_VCRREDO, NULL, 0, TOPEER0);
        return; 
    }

    if (vcr == NULL || vcr->tree == NULL)
	return;

    if (vcr->graph != NULL)
    {
	_dxf_ExGraphDelete(vcr->graph);
	vcr->graph = NULL;
    }
    vcr->change = TRUE;
}

void
_dxf_ExVCRChange(void)
{
    if (vcr == NULL || vcr->tree == NULL)
	return;
    vcr->change = TRUE;
}

void
_dxf_ExVCRChangeReset(void)
{
    if (vcr == NULL || vcr->tree == NULL)
	return;
    vcr->change = FALSE;
}

int
_dxf_ExCheckVCR (EXDictionary dict, int multiProc)
{
    Program		*graph;
    int			doGraph;
    int			ret;
    VCRState		lastGraph;

    multiProc = TRUE; /* for debugging */

    /* Early outs */
    if (vcr == NULL)
	return (0);

    if (vcr->tree == NULL)
	return (0);
    
    if ((!multiProc || vcr->graph == NULL) && 
	(vcr->mode == VCR_M_STOP || vcr->mode == VCR_M_PAUSE))
	return (0);

    if (! DXtry_lock (&vcr->lock, 0))		/* Someone else has it	*/
	return (0);

    if (vcr->mode != VCR_M_STOP && vcr->mode != VCR_M_PAUSE)
    {
	/* something still running */
	if ((multiProc && vcr->graph == NULL) || (!multiProc && _dxf_ExGQAllDone ()))
	{
	    ExVCRLoad ();			/* Load vars from dict	*/

	    switch (vcr->mode)
	    {
	    case VCR_M_STEP:
		ExVCRAdvance ();
		vcr->steps += (vcr->steps < 0) ? 1 : -1;
		if (vcr->steps == 0)
		    vcr->mode = VCR_M_STOP;
		break;

	    case VCR_M_PLAY:
		ExVCRAdvance ();
		break;
	    }
	    doGraph = TRUE;
	}
	/* Else if they've changed something since the last graph,
	 * incorporate the changes (and if they've changed @nextframe, chane
	 * @frame and redo the graph.
	 */
	else if (multiProc && vcr->change)
	{
	    lastGraph = now;
	    ExVCRLoad();
	    /* If they changed anything, reset next and readvance
	     * to make sure that everything is still OK.
	     * If they changed "next", do the one they want.
	     */
	    if (now.s_frame != lastGraph.s_frame ||
		now.e_frame != lastGraph.e_frame ||
		now.d_frame != lastGraph.d_frame ||
		now.n_frame != lastGraph.c_frame)
	    {
		if (now.n_frame == lastGraph.n_frame)
		    now.n_frame = lastGraph.c_frame;

		now.c_frame = now.n_frame;
		switch (vcr->mode)
		{
		case VCR_M_STEP:
		    ExVCRAdvance ();
		    vcr->steps += (vcr->steps < 0) ? 1 : -1;
		    if (vcr->steps == 0)
			vcr->mode = VCR_M_STOP;
		    break;

		case VCR_M_PLAY:
		    ExVCRAdvance ();
		    break;
		}
	    }

	    doGraph = TRUE;
	    if (vcr->graph != NULL)
		_dxf_ExGraphDelete (vcr->graph);
	    vcr->graph = NULL;
	}
	else
	    doGraph = FALSE;

	if (doGraph)
	{
	    ExVCRSet (VCR_ID_FRAME, now.c_frame);
	    ExVCRSet (VCR_ID_NEXT,  now.n_frame);

	    vcr->change = FALSE;

	    _dxf_ExGraphInit ();
	    if ((vcr->graph = graph = _dxf_ExGraph (vcr->tree)) != NULL)
	    {

		graph->origin     = GO_VCR;
		graph->vcr.frame  = now.c_frame;
		graph->vcr.nframe = now.n_frame;
		graph->vcr.stop   = (vcr->mode == VCR_M_STOP) ? TRUE : FALSE;
	    }
	    ExDebug ("1", "VCR Creating  graph %x for frame %3d", 
		graph, now.c_frame);
	}
    }

    /* If we have a graph to do, and there is none being done, do this
     * graph, and indicate that we're working on the old now, and we're
     * done with the old old.
     */
    if (_dxf_ExGQAllDone() && vcr->graph)
    {
	vcr->ngraphs++;
	vcr->graphId      = vcr->graph->graphId;
        /* we are the master, send a copy of the parse tree
         * to all slaves
         */
        _dxf_ExSendParseTree(vcr->tree);
	_dxf_ExGQEnqueue (vcr->graph);
	ExDebug ("1", "VCR Enqueuing graph 0x%x for frame %3d", 
	    vcr->graph, now.c_frame);
	prior = old;
	old = now;
	vcr->graph = NULL;
	ret = TRUE;
    }
    else 
	ret = FALSE;

    DXunlock (&vcr->lock, 0);

    return (ret);
}
