/*
   ds_rrlist.c: implementation of ds_rrlist's.
*/

#include <odytypes.h>

#include "ds_list.h"
#include "ds_hash.h"
#include "ds_rrlist.h"
#include "ds_rrlist.private.h"

/* magic numbers */

const magic_t ds_request_magic = 329145230;
const magic_t ds_rrlist_magic = 219487091;
const magic_t ds_rrlist_reqtab_elt_magic = 303126476;


static long       lastreqid=0;      /* last request id assigned */
static ds_hash_t *reqtab=NULL;      /* request=>rrlist mapping */

/* static functions: hash and comparison functions */

static long
hash_reqtab_elt(void *anon) {
    ds_rrlist_reqtab_elt_t *elt = anon;
    CODA_ASSERT(DS_RRLIST_REQTAB_ELT_VALID(elt));
    return elt->reqid;
}

static long
comp_reqtab_elt(void *a1, void *a2) {
    ds_rrlist_reqtab_elt_t *e1 = a1;
    ds_rrlist_reqtab_elt_t *e2 = a2;
    CODA_ASSERT(DS_RRLIST_REQTAB_ELT_VALID(e1));
    CODA_ASSERT(DS_RRLIST_REQTAB_ELT_VALID(e2));
    return (e1->reqid - e2->reqid);
}

/* 
   pids and reqids are unique in each structure by invariant,
   so they are sufficient for distinction
*/

static long
hashpid(void *anon) {
    ds_request_t *request = anon;
    CODA_ASSERT(DS_REQUEST_VALID(request));
    return request->pid;
}

static long
hashreq(void *anon) {
    ds_request_t *request = anon;
    CODA_ASSERT(DS_REQUEST_VALID(request));
    return request->reqid;
}

static long
comppid(void *a1, void *a2) {
    ds_request_t *r1 = a1;
    ds_request_t *r2 = a2;

    CODA_ASSERT(DS_REQUEST_VALID(r1));
    CODA_ASSERT(DS_REQUEST_VALID(r2));

    return (r1->pid - r2->pid);
}

static long
compreq(void *a1, void *a2) {
    ds_request_t *r1 = a1;
    ds_request_t *r2 = a2;

    CODA_ASSERT(DS_REQUEST_VALID(r1));
    CODA_ASSERT(DS_REQUEST_VALID(r2));

    return (r1->reqid - r2->reqid);
}

/* 
   lists can contain many elements with the same low/high values.  As
   long as they are ordered correctly with respect to those values, we
   can use the pointers themselves to distinguish equal bounds values.
   This is necessary since otherwise equality tests for ds_list_remove
   calls would be too weak.
*/

static long
complow(void *a1, void *a2) {
    ds_request_t *r1 = a1;
    ds_request_t *r2 = a2;
    long result;

    CODA_ASSERT(DS_REQUEST_VALID(r1));
    CODA_ASSERT(DS_REQUEST_VALID(r2));

    result = r1->low - r2->low;
    if (!result)
	result = (r1->reqid - r2->reqid);
    return result;
}

static long
comphigh(void *a1, void *a2) {
    ds_request_t *r1 = a1;
    ds_request_t *r2 = a2;
    long result;

    CODA_ASSERT(DS_REQUEST_VALID(r1));
    CODA_ASSERT(DS_REQUEST_VALID(r2));

    result = r1->high - r2->high;
    if (!result)
	result = (r1->reqid - r2->reqid);
    return result;
}

/* Observers */

bool
ds_rrlist_valid(ds_rrlist_t *l) {
    if (DS_RRLIST_VALID(l))
	return TRUE;
    else
	return FALSE;
}

long
ds_rrlist_value(ds_rrlist_t *l) {
    CODA_ASSERT(DS_RRLIST_VALID(l));
    return l->value;
}

/* Mutators */

ds_rrlist_t *
ds_rrlist_create(long value) {
    ds_rrlist_t *result;
    
    /* Is the universal hashtab setup yet? */
    /* Check here b/c it is cheap. */
    if (reqtab == NULL) {
	/* not yet, do it. */
	reqtab = ds_hash_create(comp_reqtab_elt,
				hash_reqtab_elt,
				DS_RRLIST_HASHSIZE,
				TRUE,FALSE);
    }

    ALLOC(result,ds_rrlist_t);
    result->magic = ds_rrlist_magic;
    result->value = value;
    result->pidhash = ds_hash_create(comppid,hashpid,DS_RRLIST_HASHSIZE,
				     TRUE,FALSE);
    result->reqhash = ds_hash_create(compreq,hashreq,DS_RRLIST_HASHSIZE,
				     TRUE,FALSE);
    result->less = ds_list_create(complow,TRUE,FALSE);
    result->greater = ds_list_create(comphigh,TRUE,FALSE);

    return result;
}

void
ds_rrlist_destroy(ds_rrlist_t *l) {
    CODA_ASSERT(DS_RRLIST_VALID(l));
    l->magic = 0;
    l->value = 0;
    ds_hash_destroy(l->pidhash);
    ds_hash_destroy(l->reqhash);
    ds_list_destroy(l->less);
    ds_list_destroy(l->greater);
    FREE(l);
}

ds_rrlist_return_t
ds_rrlist_request(ds_rrlist_t *l, ds_request_t *r, long *value,
		  ds_request_t **old_req) {
    ds_rrlist_return_t      result = DS_RRLIST_SUCCESS;
    ds_rrlist_reqtab_elt_t *rte;
    ds_rrlist_reqtab_elt_t  dummy;

    CODA_ASSERT(DS_RRLIST_VALID(l));
    CODA_ASSERT(DS_REQUEST_VALID(r));
    CODA_ASSERT(value);
    CODA_ASSERT(old_req);

    r->reqid = 0;

    /* do we have a current request for this pid/resource pair? */
    *old_req = ds_hash_member(l->pidhash,r);
    if (*old_req != NULL) {
	result |= DS_RRLIST_DUPLICATE;
	/* remove from reqtab */
	dummy.magic = ds_rrlist_reqtab_elt_magic;
	dummy.reqid = (*old_req)->reqid;
	CODA_ASSERT((rte = ds_hash_remove(reqtab,&dummy)) != NULL);
	DS_RRLIST_REQTAB_ELT_DESTROY(rte);
	/* remove from rrlist */
	CODA_ASSERT(ds_hash_remove(l->pidhash,*old_req) != NULL);
	CODA_ASSERT(ds_hash_remove(l->reqhash,*old_req) != NULL);
	CODA_ASSERT(ds_list_remove(l->less,*old_req) != NULL);
	CODA_ASSERT(ds_list_remove(l->greater,*old_req) != NULL);
    }
    
    /* Is the request out-of-bounds already? */
    if ((r->low > l->value) || (r->high < l->value)) {
	result |= DS_RRLIST_OUTOFWINDOW;
	*value = l->value;
    } else {
	lastreqid++;
	r->reqid = lastreqid;
	DS_RRLIST_REQTAB_ELT_CREATE(rte,lastreqid,l);
	CODA_ASSERT(ds_hash_insert(reqtab,rte) != NULL);
	/* it is a valid request, insert it. */
	CODA_ASSERT(ds_hash_insert(l->pidhash,r) != NULL);
	CODA_ASSERT(ds_hash_insert(l->reqhash,r) != NULL);
	CODA_ASSERT(ds_list_insert(l->less,r) != NULL);
	CODA_ASSERT(ds_list_insert(l->greater,r) != NULL);
	
    }
    return result;
}

ds_rrlist_return_t
ds_rrlist_cancel(long reqid, ds_request_t **req) {
    ds_rrlist_return_t      result = DS_RRLIST_SUCCESS;
    ds_request_t            req_dummy;
    ds_rrlist_t            *l;
    ds_rrlist_reqtab_elt_t *rte;
    ds_rrlist_reqtab_elt_t  rte_dummy;

    CODA_ASSERT(req);

    /* First, we need to get the list this request is on */
    rte_dummy.magic = ds_rrlist_reqtab_elt_magic;
    rte_dummy.reqid = reqid;
    rte = ds_hash_member(reqtab,&rte_dummy);

    if (rte == NULL) {      /* didn't find the list */
	result |= DS_RRLIST_NOSUCHREQ;
	*req = NULL;
    } else {
	/* we have the list, continue */
	l = rte->list;
	CODA_ASSERT(DS_RRLIST_VALID(l));
	/* See if we can find the item to remove. */
	req_dummy.magic = ds_request_magic;
	req_dummy.reqid = reqid;
	*req = ds_hash_member(l->reqhash,&req_dummy);
	if (*req) {
	    /* found it */
	    CODA_ASSERT(DS_REQUEST_VALID(*req));
	    /* Pull from the global hashtab */
	    CODA_ASSERT(ds_hash_remove(reqtab,rte) == rte);
	    DS_RRLIST_REQTAB_ELT_DESTROY(rte);
	    /* Pull from the resource request list in question */
	    CODA_ASSERT(ds_hash_remove(l->pidhash,*req) != NULL);
	    CODA_ASSERT(ds_hash_remove(l->reqhash,*req) != NULL);
	    CODA_ASSERT(ds_list_remove(l->less,*req) != NULL);
	    CODA_ASSERT(ds_list_remove(l->greater,*req) != NULL);
	} else {
	    result |= DS_RRLIST_NOSUCHREQ;
	}
    }
    return result;
}

ds_rrlist_return_t
ds_rrlist_purge(ds_rrlist_t *l, int pid, ds_request_t **req) {
    ds_rrlist_return_t      result = DS_RRLIST_SUCCESS;
    ds_request_t            req_dummy;
    ds_rrlist_reqtab_elt_t  rte_dummy;
    ds_rrlist_reqtab_elt_t *rte;

    CODA_ASSERT(DS_RRLIST_VALID(l));
    CODA_ASSERT(req);
    
    /* See if we can find the item to remove */
    req_dummy.magic = ds_request_magic;
    req_dummy.pid = pid;
    *req = ds_hash_member(l->pidhash,&req_dummy);
    if (*req) {
	/* found it */
	CODA_ASSERT(DS_REQUEST_VALID(*req));
	/* remove from reqtab */
	rte_dummy.magic = ds_rrlist_reqtab_elt_magic;
	rte_dummy.reqid = (*req)->reqid;
	CODA_ASSERT((rte = ds_hash_remove(reqtab,&rte_dummy)) != NULL);
	DS_RRLIST_REQTAB_ELT_DESTROY(rte);
	/* remove from rrlist */
	CODA_ASSERT(ds_hash_remove(l->pidhash,*req) == *req);
	CODA_ASSERT(ds_hash_remove(l->reqhash,*req) == *req);
	CODA_ASSERT(ds_list_remove(l->less,*req) == *req);
	CODA_ASSERT(ds_list_remove(l->greater,*req) == *req);
    } else {
	result |= DS_RRLIST_NOSUCHPID;
    }
    return result;
}

ds_rrlist_return_t
ds_rrlist_set_value(ds_rrlist_t *l, long newval, ds_list_t **to_notify) {
    ds_rrlist_return_t      result = DS_RRLIST_SUCCESS;
    ds_request_t           *req = NULL;
    ds_rrlist_reqtab_elt_t  dummy;
    ds_rrlist_reqtab_elt_t *rte;

    CODA_ASSERT(DS_RRLIST_VALID(l));
    CODA_ASSERT(to_notify);

    /* zero the list pointer */
    *to_notify = NULL;

    /* 
       check quickly if we have to do anything, and get out if not.
       (the common case)
    */
    if (newval > l->value) {
	req = (ds_request_t *) ds_list_first(l->greater);
	if ((req == NULL) || (newval <= req->high)) {
	    l->value = newval;
	    return result;
	}
    } else {
	req = (ds_request_t *) ds_list_last(l->less);
	if ((req == NULL) || (newval >= req->low)) {
	    l->value = newval;
	    return result;
	}
    }

    /* 
       there is at least one bound that has been exceeded: req
       currently points to it.  l->value has not yet been set,
       and the to_notify list must be constructed
    */

    *to_notify = ds_list_create(NULL,FALSE,FALSE);

    if (newval > l->value) {
	/* check upper bounds */
	while (req != NULL) {
	    CODA_ASSERT(DS_REQUEST_VALID(req));
	    if (req->high >= newval) {
		break;
	    } else {
		/* remove from reqtab */
		dummy.magic = ds_rrlist_reqtab_elt_magic;
		dummy.reqid = req->reqid;
		CODA_ASSERT((rte = ds_hash_remove(reqtab,&dummy)) != NULL);
		DS_RRLIST_REQTAB_ELT_DESTROY(rte);
		/* remove from the rrlist */
		CODA_ASSERT(ds_list_get_first(l->greater) == req);
		CODA_ASSERT(ds_list_remove(l->less,req) == req);
		CODA_ASSERT(ds_hash_remove(l->pidhash,req) == req);
		CODA_ASSERT(ds_hash_remove(l->reqhash,req) == req);
		/* insert into result list */
		CODA_ASSERT(ds_list_insert(*to_notify,req) == req);
		/* step to next pointer in list. */
		req = (ds_request_t *) ds_list_first(l->greater);
	    }
	}
    } else {
	/* check lower bounds */
	while (req != NULL) {
	    CODA_ASSERT(DS_REQUEST_VALID(req));
	    if (req->low <= newval)  {
		break;
	    } else {
		/* remove from reqtab */
		dummy.magic = ds_rrlist_reqtab_elt_magic;
		dummy.reqid = req->reqid;
		CODA_ASSERT((rte = ds_hash_remove(reqtab,&dummy)) != NULL);
		DS_RRLIST_REQTAB_ELT_DESTROY(rte);
		/* remove from the rrlist */
		CODA_ASSERT(ds_list_get_last(l->less) == req);
		CODA_ASSERT(ds_list_remove(l->greater,req) == req);
		CODA_ASSERT(ds_hash_remove(l->pidhash,req) == req);
		CODA_ASSERT(ds_hash_remove(l->reqhash,req) == req);
		/* insert into result list */
		CODA_ASSERT(ds_list_insert(*to_notify,req) == req);
		/* step to next pointer in list. */
		req = (ds_request_t *) ds_list_last(l->less);
	    }
	}
    }
    l->value = newval;
    return result;
}

static void
ds_request_print(ds_request_t *req, FILE *f) {
    CODA_ASSERT(DS_REQUEST_VALID(req));
    fprintf(f,"pid %ld\treqid %ld\tlow %ld\thigh %ld\n",
	    req->pid, req->reqid, req->low, req->high);
}

void
ds_rrlist_dump(ds_rrlist_t *l, FILE *f, char *name) {
    ds_hash_iter_t *hi;
    ds_list_iter_t *li;
    ds_request_t   *req;

    CODA_ASSERT(DS_RRLIST_VALID(l));
    CODA_ASSERT(f);
    CODA_ASSERT(name);
    
    fprintf(f,"Printing resource request list %s\n",name);
    fprintf(f,"========================================================\n");
    fprintf(f,"--------value = %ld\n",l->value);
    fprintf(f,"--------last requid assigned = %ld\n",lastreqid);
    fprintf(f,"--------pidhash table\n");

    hi = ds_hash_iter_create(l->pidhash);
    while ((req = (ds_request_t *) ds_hash_iter_next(hi)) != NULL) {
	ds_request_print(req,f);
    }
    ds_hash_iter_destroy(hi);

    fprintf(f,"--------reqhash table\n");

    hi = ds_hash_iter_create(l->reqhash);
    while ((req = (ds_request_t *) ds_hash_iter_next(hi)) != NULL) {
	ds_request_print(req,f);
    }
    ds_hash_iter_destroy(hi);

    fprintf(f,"--------lower bound list\n");

    li = ds_list_iter_create(l->less);
    while ((req = (ds_request_t *) ds_list_iter_next(li)) != NULL) {
	ds_request_print(req,f);
    }
    ds_list_iter_destroy(li);

    fprintf(f,"--------upper bound list\n");

    li = ds_list_iter_create(l->greater);
    while ((req = (ds_request_t *) ds_list_iter_next(li)) != NULL) {
	ds_request_print(req,f);
    }
    ds_list_iter_destroy(li);

    fprintf(f,"\n\n");
}
