/***********************************************************************/
/* 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 "pmodflags.h"
#include "cache.h"
#include "utils.h"
#include "config.h"
#include "graph.h"
#include "log.h"

#define STATIC_TAGS  100

extern LIST(pathtag)  _dxd_pathtags;

/*
 * $$$$$ THIS ROUTINE SHOULD BE REPLACED WITH ONES TO DEAL WITH OBJECTS
 * $$$$$ PROPERLY.
 * The routine is called to compute the recipes for non-array objects.
 */

static uint32
computeDefaultRecipe (Object obj)
{
    uint32  crc = 0xFFFFFFFF;

    if (obj)
    {
	DXWarning ("#4610", obj);
	crc = _dxf_ExCRCInt (crc, (int) obj);
    }
    else
	crc = _dxf_ExCRCInt (crc, 0);
    
    return (EXTAG(crc));
}

/* 
 * Compute the recipe for a string object.  The crc is generated from the
 * bytes representing the string
 */

static
uint32 computeStringRecipe (String a)
{
    uint32 		crc;
    unsigned char	*p;

    crc = 0xFFFFFFFF;

    p = (unsigned char *) DXGetString (a);

    if (p)
    {
	int32		size;

	size = strlen ((char *)p);
	crc = _dxf_ExCRCByteV (crc, p, 1, size);
    }
    else
    {
	crc = _dxf_ExCRCInt (crc, 0);
    }

    return (EXTAG (crc));
}

/* 
 * Compute the recipe for an array object.  The crc is generated from the
 * parameters to the array (type, category, etc.) and the crc over the
 * bytes representing the data stored in the array.
 */

static uint32
computeArrayRecipe (Array a)
{
    uint32 		crc;
    Type		type;
    Category		category;
    int			items;
    int			rank;
    int			shape[32];
    int			i, size, tsize;
    unsigned char	*p;

    crc = 0xFFFFFFFF;

    DXGetArrayInfo (a, &items, &type, &category, &rank, shape);
    size = DXGetItemSize(a);
    tsize = DXTypeSize(type);

    crc = _dxf_ExCRCByte (crc, type);
    crc = _dxf_ExCRCByte (crc, category);
    crc = _dxf_ExCRCInt  (crc, items);
    crc = _dxf_ExCRCByte (crc, rank);

    if(DXGetArrayClass(a) == CLASS_REGULARARRAY) {
        Pointer origin;
        Pointer delta; 

        origin = DXAllocateLocal(2*size);
        delta = (Pointer)((char *)origin + size);
        DXGetRegularArrayInfo((RegularArray)a, NULL, origin, delta);
	crc = _dxf_ExCRCByteV (crc, "regulararray", 1, 12);
	crc = _dxf_ExCRCByteV (crc, origin, tsize, 2*size/tsize);
        DXFree(origin);
    }
    else {
        p = (unsigned char *) DXGetArrayData (a);
        if (p)
        {
	    if (rank)
	        for (i=0; i < rank; i++)
		    crc = _dxf_ExCRCInt (crc, shape[i]);
	    size *= items;
	    crc = _dxf_ExCRCByteV (crc, p, tsize, size/tsize);
        }
        else
        {
            crc = 0xFFFFFFFF;
	    crc = _dxf_ExCRCInt (crc, 0);
        }
    }

    return (EXTAG(crc));
}

static uint32 
computeRecipe(Program *p, int ind)
{
    ProgramVariable *pv;
    Object obj;
    uint32 tcrc;
    gvar *gv;
    int i;

    if (ind == -1) 
        return(computeDefaultRecipe (NULL));

    pv = FETCH_LIST(p->vars, ind);
    gv = pv->gv;
    /* gv will be null for prehidden inputs for outboards, anything else?*/
    if(!gv)
        gv = _dxf_ExCreateGvar(GV_UNRESOLVED);

    /* if input already has a recipe, just use it,
     * Otherwise, it must be some kind of known variable
     */
    if (gv->reccrc == 0)
    {
	obj = gv->obj;
	if (obj == NULL)
	    obj = pv->dflt;

	if (obj == NULL)
	    tcrc = computeDefaultRecipe (obj);
	else
	{
	    switch (DXGetObjectClass (obj))
	    {
		case CLASS_ARRAY:
		    tcrc = computeArrayRecipe ((Array) obj);
		    break;

		case CLASS_STRING:
		    tcrc = computeStringRecipe ((String) obj);
		    break;

		default:
		    tcrc = computeDefaultRecipe (obj);
		    break;
	    }
	}
        pv->reccrc = gv->reccrc = tcrc;
	DXSetObjectTag (obj, tcrc);
    }

    return(gv->reccrc);
}

/*---------------------------------------------------------------*/
/* This function maintains a table with path/cache tag entries   */
/* It searches a table looking for the current path and if found */
/*   1. does nothing if the cache tag hasn't changed             */ 
/*   2. replaces the current entry with the new path/tag entry   */
/*      if the cache tag has changed, and deletes the object     */
/*      identified by the old cache tag from the cache           */
/*  Note: cache entry may not necessarily be in the cache ...    */
/*  Question: Should this be conditional -i.e. based on the      */
/*            setting of a user specified "cache once" switch?   */  
/*---------------------------------------------------------------*/
int
_dxf_ExManageCacheTable(char *cpath, uint32 reccrc, int outnbr)
{
    int i, limit;
    pathtag  *pt;
    pathtag  npt;
    int	     ret = FALSE;

    DXDebug ("1", 
     "cache table entry: cpath = %s, reccrc = 0x%08x, outnbr = %d",
             cpath, reccrc, outnbr);	

    limit = SIZE_LIST(_dxd_pathtags); 

    for (i = 0; i < limit; i++ ) { 
        pt = FETCH_LIST(_dxd_pathtags,i);
        if (pt->entry_inuse == TRUE) {
            if (! strcmp(pt->cpath, cpath) && pt->outnbr == outnbr) {
                DXDebug ("1", "  Found cache table entry %s / 0x%08x.",
                    pt->cpath, pt->reccrc);	
                if (pt->reccrc != reccrc ) {
                    DXDebug ("1",
                     "Changing tag 0x%08x to 0x%08x for cache table entry %s.",
                          pt->reccrc, reccrc, pt->cpath);	
                    if(pt->reccrc != 0)
                        _dxf_ExCacheDelete(pt->reccrc);
                    pt->reccrc = reccrc;
                    ret = TRUE;
                }
                return(ret);
            }
        }
    }

    /* Create new entry in pathtag table unless there is one 
     * available for reuse 
     */
    for (i = 0; i < limit; i++ ) { 
        pt = FETCH_LIST(_dxd_pathtags,i);
        if (pt->entry_inuse == FALSE) {
            DXDebug ("1", "  Reusing cache table entry %s.%d / 0x%08x.",
                pt->cpath, pt->outnbr, pt->reccrc);	
            pt->reccrc = reccrc;
            DXFree(pt->cpath);
            pt->cpath = (char *)DXAllocate(sizeof(char) * (strlen(cpath)+1));
            pt->outnbr = outnbr;
            pt->entry_inuse = TRUE;
            strcpy(pt->cpath,cpath);
            return(TRUE);
        }
    }

    npt.reccrc = reccrc;
    npt.cpath = (char *) DXAllocate (sizeof (char) * (strlen(cpath) + 1 ));
    npt.outnbr = outnbr;
    npt.entry_inuse = TRUE;
    strcpy(npt.cpath,cpath);
    DXDebug ("1", "  Appending cache table entry %s.%d / 0x%08x.",
            npt.cpath, npt.outnbr, npt.reccrc);	
    APPEND_LIST(pathtag,_dxd_pathtags,npt);
    return(TRUE); 
}

void _dxf_ExFlushPathTags()
{
    int i;
    pathtag             *pt;

    for (i = 0; i < SIZE_LIST(_dxd_pathtags); i++ ) {
        pt = FETCH_LIST(_dxd_pathtags,i);
        pt->entry_inuse = FALSE;
    }
}

/*
 * recursively generate the recipes for a node in the graph
 */
void _dxf_ExComputeRecipes(Program *p, int funcInd)
{
    gfunc	*n = FETCH_LIST(p->funcs, funcInd);
    int		i, j, size, ntags, macro_tags, extra_tags, async_tags;
    gvar	*gv;
    Object	obj;
    uint32 	tcrc, objecttag;
    gvar	*out;
    uint32 	*inTags, staticTags[STATIC_TAGS];
    int		varInd, *varIndp;
    int		varInd2;
    ProgramVariable *pv, *inpv, *pv_key;
    ProgramRef 	*pr;
    MacroRef 	*mr;
    AsyncVars 	*avars;
    int    	tpvcache;
    _excache    fcache;
    _excache    pvcache;
    Program     *subP;
    int  tag_changed = FALSE;
    uint32 crc = 0xFFFFFFFF;
    char 	*async_name, *name;
    int		passthru = FALSE;

check_async:
    if(n->flags & (MODULE_ASYNC | MODULE_ASYNCLOCAL)) {
        if(p->loopctl.first && (n->flags & MODULE_ASYNCLOCAL)) {
            if(strcmp(n->name, "GetLocal") == 0)
                DXReadyToRunNoExecute(n->cpath);
        }
        varInd = *FETCH_LIST(n->inputs, n->nin - 2);
        pv = FETCH_LIST(p->vars, varInd);
        if(!pv->gv)
            _dxf_ExDie("Cannot compute a cache tag from a NULL gvar");
        if(n->flags & MODULE_ASYNCNAMED) {
            varInd2 = *FETCH_LIST(n->inputs, n->rerun_index);
            pv_key = FETCH_LIST(p->vars, varInd2);
            if(pv_key->gv && pv_key->gv->obj) {
                if(pv->gv->obj)
                    DXDelete(pv->gv->obj);
                pv->gv->obj = DXReference(pv_key->gv->obj);    
            }
        }
        if(!pv->gv->obj)
            _dxf_ExDie("Cannot compute a cache tag from a NULL gvar");
        /* Put an extra reference on this input, we don't want this gvar
           to be deleted until the current graph finishes executing. This
           is incase the results were thrown out of cache and because of
           a switch need to run again. If this is in a loop it will get
           an extra ref each time through the loop which isn't needed but
           will get cleaned up ok in the end.
        */
        ExReference(pv->gv);
        pv->refs++;
        async_name = DXGetString((String)pv->gv->obj);
        varInd = *FETCH_LIST(n->inputs, n->nin - 1);
        pv = FETCH_LIST(p->vars, varInd);
        gv = (gvar *)_dxf_ExGlobalVariableSearch(async_name);
        if(gv) {
            if(gv != pv->gv) {
                _dxf_ExUndefineGvar(pv->gv);
                _dxf_ExDefineGvar(pv->gv, gv->obj);
            }
            ExDelete(gv);
        }
        else {
            Object value;
            value = (Object) _dxfExNewInteger (0);
            _dxf_ExDefineGvar(pv->gv, (Object)_dxfExNewInteger(0));
            _dxf_ExUpdateGlobalDict (async_name, value, 0);
        }
        /* Put an extra reference on this input, we don't want this gvar
           to be deleted until the current graph finishes executing. This
           is incase the results were thrown out of cache and because of
           a switch need to run again. If this is in a loop it will get
           an extra ref each time through the loop which isn't needed but
           will get cleaned up ok in the end.
         */
         ExReference(pv->gv);
         pv->refs++;
    }

    if(n->ftype == F_MACRO) {
        subP = n->func.subP;
        macro_tags = SIZE_LIST(subP->macros);
        async_tags = SIZE_LIST(subP->async_vars);
        extra_tags = macro_tags + async_tags;
    }
    else if(n->flags & MODULE_LOOP) {
        /* to cause the cache tag of loop modules to change even though */
        /* their inputs don't change, use the counter for the loop */
        extra_tags = 1;
    }
    else extra_tags = 0;
    ntags = n->nin+extra_tags;

    if(ntags > STATIC_TAGS)
        inTags = (uint32 *)DXAllocate(ntags*sizeof(uint32));
    else inTags = staticTags;

    /* add the crc of all of the module inputs to the total crc */
    for (i = 0; i < SIZE_LIST(n->inputs); i++)
    {
	varIndp = FETCH_LIST(n->inputs, i);
        if(varIndp == NULL)
            _dxf_ExDie("Executive Inconsistency: _dxf_ExComputeRecipes");
        varInd = *varIndp;
      
        inTags[i] = computeRecipe(p, varInd);
	ExDebug ("1", "Cache value for input %s.%d = 0x%08x ",
			    n->name, i, inTags[i]);
    }

    if(n->ftype == F_MACRO) {
        for (j = 0; j < macro_tags; j++, i++)
        {
            crc = 0xFFFFFFFF;
            mr = FETCH_LIST(subP->macros, j);
            inTags[i] = _dxf_ExCRCInt(crc, mr->index);
        }
        for (j = 0; j < async_tags; j++, i++) {
            avars = FETCH_LIST(subP->async_vars, j);
            pv = FETCH_LIST(p->vars, avars->nameindx);
            if(!pv->gv || !pv->gv->obj)
                _dxf_ExDie("Cannot compute a cache tag from a NULL async name");
            name = DXGetString((String)pv->gv->obj);
            pv = FETCH_LIST(p->vars, avars->valueindx);
            gv = (gvar *)_dxf_ExGlobalVariableSearch(name);
            if(gv) {
                if(gv != pv->gv) {
                    _dxf_ExUndefineGvar(pv->gv);
                    _dxf_ExDefineGvar(pv->gv, gv->obj);
                }
                ExDelete(gv);
            }
            else {
                Object value;
                value = (Object) _dxfExNewInteger (0);
                _dxf_ExDefineGvar(pv->gv, (Object)_dxfExNewInteger(0));
                _dxf_ExUpdateGlobalDict (name, value, 0);
            }
            inTags[i] = computeRecipe(p, avars->valueindx);
        }
    }
    else if(n->flags & MODULE_LOOP) {
        /* to cause the cache tag of loop modules to change even though */
        /* their inputs don't change, use the counter for the loop */
        crc = 0xFFFFFFFF;
        inTags[i] = _dxf_ExCRCInt(crc, p->loopctl.counter);
    }

    if((n->flags & MODULE_PASSTHRU) && (n->nin == n->nout+1))
        passthru = TRUE;
        
    if(n->flags & (MODULE_CONTAINS_STATE | MODULE_CHANGES_STATE))
    {
        Object new_tag_obj, old_tag_obj;
        uint32 oldtag, newtag;

        newtag = _dxf_ExGenCacheTag (n->name,0,ntags,inTags);
        new_tag_obj = (Object)DXMakeInteger(newtag);
        old_tag_obj = DXGetCacheEntry(n->cpath, ntags, 0);
        if(!old_tag_obj) {
            DXSetCacheEntry(new_tag_obj, CACHE_PERMANENT, n->cpath, ntags, 0);
        }
        else {
            /* we don't want an extra reference on this */
            DXDelete(old_tag_obj); 
            if(!DXExtractInteger(old_tag_obj, (int *)&oldtag))
                oldtag = 0;
            /* this means we just caused a DXReadyToRun so we know */
            /* this macro will run this time, save the new cache tag */
            if(oldtag == 0) {
                DXSetCacheEntry(new_tag_obj,CACHE_PERMANENT,n->cpath,ntags,0);
                DXDelete(old_tag_obj);
            }
            else if(oldtag != newtag) {
                /* An input has changed since last time, we need to */ 
                /* cause execution of this macro and we need to recompute */
                /* the cache tag. */
                DXReadyToRunNoExecute(async_name);
                DXDelete(new_tag_obj);
                new_tag_obj = (Object)DXMakeInteger(0);
                DXSetCacheEntry(new_tag_obj,CACHE_PERMANENT,n->cpath,ntags,0);
                DXDelete(old_tag_obj);
                goto check_async;
            }
            else DXDelete(new_tag_obj);
        }
    }

    /* generate the recipes for all of the outputs from this module */
    /* Update the pathtag table if module cache attribute is "cache last" */
    n->tag_changed = FALSE;
    for (i = 0; i < SIZE_LIST(n->outputs); i++)
    {
	varIndp = FETCH_LIST(n->outputs, i);
        if(varIndp == NULL)
            _dxf_ExDie("Executive Inconsistency: _dxf_ExComputeRecipes");
        varInd = *varIndp;
	pv = FETCH_LIST(p->vars, varInd);
        pv->is_output = TRUE;
	out = pv->gv;

	if (out->reccrc == 0 || 
	    (n->required != -1 && (!IsSwitch(n) || !IsFastSwitch(n)))) {
            /* This code is currently just intended for macrostart */
            if(passthru) {
	        varInd2 = *FETCH_LIST(n->inputs, i+1);
		if (varInd2 != -1) {
                    inpv = FETCH_LIST(p->vars, varInd2);
                    tcrc = inpv->gv->reccrc;
		} else
	            tcrc = _dxf_ExGenCacheTag (n->name,i,n->nin+extra_tags,inTags);
            }
            else
	        tcrc = _dxf_ExGenCacheTag (n->name,i,n->nin+extra_tags,inTags);
            if(out->reccrc != 0 && tcrc != out->reccrc)
                _dxf_ExUndefineGvar(out);
            pv->reccrc = out->reccrc = tcrc;
	    /* make process group name part of cache tag */
	    if(n->procgroupid) {
		size = strlen (n->procgroupid);
		out->reccrc = EXTAG(_dxf_ExCRCByteV(out->reccrc & 0x7fffffff,
		    n->procgroupid, 1, size));
	    }
	    ExDebug ("1", "Cache value for %s.%d = 0x%08x ",
			    n->name, i, out->reccrc);
	}
        
        pvcache = pv->excache;
        fcache  = n->excache;
       
        if((n->flags & (MODULE_ASYNC | MODULE_ASYNCLOCAL)) && 
           (pvcache == CACHE_ALL)) 
            pvcache = pv->excache = CACHE_LAST;
  
        /* I don't think we allow the user to turn off cacheing for */
        /* macros that have state in them. */
        if(n->flags & MODULE_CHANGES_STATE)
            pvcache = pv->excache = CACHE_LAST_PERM;
        if(n->flags & MODULE_CONTAINS_STATE)
            pvcache = pv->excache = CACHE_LAST;

        /* object lvl cache attribute overrides function lvl cache attribute */ 
        
        if (pvcache == CACHE_LAST || pvcache == CACHE_LAST_PERM) 
            tag_changed = _dxf_ExManageCacheTable(n->cpath, out->reccrc, i);
        else 
            if(fcache == CACHE_LAST && !pv->cacheattr) 
                tag_changed = _dxf_ExManageCacheTable(n->cpath, out->reccrc, i);
        n->tag_changed = tag_changed;
    }

    if(n->nout == 0 && !(n->flags & MODULE_SIDE_EFFECT)) {
        tcrc = _dxf_ExGenCacheTag (n->name, 0, n->nin+extra_tags, inTags);
        n->tag_changed = _dxf_ExManageCacheTable(n->cpath, tcrc, 0);
    }

    if(ntags > STATIC_TAGS)
        DXFree(inTags);
}

#if 0
/* This routine is for calculating the output tag of a macro */
void _dxf_ExComputeOutTags(Program *p)
{
    uint32 inTags[100];
    int	varInd, *varIndp;
    ProgramVariable *pv;
    ProgramRef *pr;
    MacroRef *mr;
    int i, j, extra_tags, macro_tags;
    Program *subP;
    gfunc *gf;
    uint32 crc = 0xFFFFFFFF;
 
    gf = FETCH_LIST(p->funcs, p->cursor);

    for (i = 0; i < SIZE_LIST(gf->inputs) && i < 100; i++)
    {
	varIndp = FETCH_LIST(gf->inputs, i);
        if(varIndp == NULL)
            _dxf_ExDie("Executive Inconsistency: _dxf_ExComputeOutTags");
        varInd = *varIndp;
      
	if (varInd == -1)
	    inTags[i] = computeDefaultRecipe (NULL);
	else
	{
	    pv = FETCH_LIST(p->vars, varInd);
            inTags[i] = pv->gv->reccrc;
         }
    }

    subP = gf->func.subP;

    macro_tags = SIZE_LIST(subP->macros);
    for (j = 0; j < macro_tags && i < 100; j++, i++)
    {
        mr = FETCH_LIST(subP->macros, j);
        inTags[i] = _dxf_ExCRCInt(crc, mr->index);
    }

    extra_tags = macro_tags;

    for (i = 0; i < SIZE_LIST(gf->outputs); i++)
    {
	varIndp = FETCH_LIST(gf->outputs, i);
        if(varIndp == NULL)
            _dxf_ExDie("Executive Inconsistency: _dxf_ExComputeOutTags");
        varInd = *varIndp;
	pv = FETCH_LIST(p->vars, varInd);
        pv->reccrc = pv->gv->reccrc = _dxf_ExGenCacheTag(gf->name, i, 
                                                         gf->nin + extra_tags,
                                                         inTags);
    }
}
#endif
