/*
 * precommit.c Copyright 1999 Christopher M Sedore. All Rights Reserved.
 * Please see the "COPYING" file for license details.
 * 
 * Implements a simple precommit cache to prevent us from telling
 * more than one peer to send us an article.
 */

#include "main.h"

struct precentry {
	u_quad_t hash;
	time_t etime;
	struct artent ae;
	int artAttached;
	struct article *art;
	TAILQ_ENTRY(precentry) next;
	TAILQ_ENTRY(precentry) all;
};

TAILQ_HEAD(freeheadn,precentry) freehead;
TAILQ_HEAD(allheadn,precentry) pceinuse;
TAILQ_HEAD(waitheadn,precentry) waithead;
	
struct pctable {
	TAILQ_HEAD(hprecentry,precentry) head;
};

#define PRECOMMIT_SIZE 11111
#define PRECOMMIT_LIFE 300
#define MAX_FREEPCE 300

struct pctable precommit[PRECOMMIT_SIZE];

static int freepce=0;
int pceactive=0;

extern time_t curtime;

inline struct precentry *
FindEntry(u_quad_t hash) 
{
	int x;
	struct precentry *pce,*pcn;

	x=hash%PRECOMMIT_SIZE;

	for (pce=TAILQ_FIRST(&precommit[x].head);pce;pce=pcn) {
		
		pcn=TAILQ_NEXT(pce,next);
		
		if (pce->hash==hash) {
			return pce;
		}
	}
	
	return NULL;
}

int
AddEntry(u_quad_t hash,struct pctable *pct)
{
	struct precentry *pce;
	
	if (pce=TAILQ_FIRST(&freehead)) {
		TAILQ_REMOVE(&freehead,pce,next);
		freepce--;
	} else {
		pce=malloc(sizeof(struct precentry));
	}
	pceactive++;
	
	pce->hash=hash;
	pce->art=NULL;
	pce->artAttached=0;
	
	time(&pce->etime);
	
	TAILQ_INSERT_TAIL(&pct->head,pce,next);
	TAILQ_INSERT_TAIL(&pceinuse,pce,all);
}

int
GetPrecommitArt(u_quad_t hash, struct article **art)
{
	struct precentry *pce;
	
	pce=FindEntry(hash);

	if (pce) 
		if (pce->art) {
			pce->art->refcount++;
			*art=pce->art;
			return 1;
		}
	
	return 0;
}	

int
ReleasePrecommitArt(struct article *art)
{
	
	assert(art->refcount>0);

	art->refcount--;

	if (art->refcount==0) {
		struct precentry *pce,*pcn;


		for (pce=TAILQ_FIRST(&waithead);pce;pce=pcn) {
		
			pcn=TAILQ_NEXT(pce,next);
		
			if (pce->art==art) {
				break;
			}
		}

		if (!pce)
			errx("article not found in precommit when ref=0");


		if (freepce<MAX_FREEPCE) {
			TAILQ_INSERT_HEAD(&freehead,pce,next);
			freepce++;
		} else {
			free(pce);
		}

		FreeMatchData(pce->art); /* XXX need FreeArtStruct */
		free(art->bufp);
		free(art);

		pceactive--;

	}
}

int
AttachPrecommitArt(u_quad_t hash, struct article *art)
{
	struct precentry *pce;

	pce=FindEntry(hash);

	if (!pce) return 0;

	pce->art=art;

}

int
GetPrecommitArtEnt(u_quad_t hash,struct artent *ae)
{
	struct precentry *pce;
	
	pce=FindEntry(hash);
	
	if (!pce) return 0;

	if (pce->artAttached) {
		memcpy(ae,&pce->ae,sizeof(struct artent));
		return 1;
	}
	
}

int
AddPrecommit(u_quad_t hash) 
{
	struct precentry *pce;
	
	pce=FindEntry(hash);

	if (!pce) {
		AddEntry(hash,&precommit[hash%PRECOMMIT_SIZE]);
		return 1;
	}
	
	return 0;
}

int
UpdatePrecommit(u_quad_t hash, struct artent *ae) 
{
	struct precentry *pce;
	
	pce=FindEntry(hash);

	if (!pce) {
		AddEntry(hash,&precommit[hash%PRECOMMIT_SIZE]);
		pce=FindEntry(hash);
	}
		
		
	memcpy(&pce->ae,ae,sizeof(struct artent));
	pce->artAttached=1;		
	TAILQ_REMOVE(&pceinuse,pce,all);
	TAILQ_INSERT_TAIL(&pceinuse,pce,all);
	time(&pce->etime);
	return 1;

}

void
FreePCE(struct precentry *pce)
{
	int x;
	
	pce->artAttached=0;
	x=pce->hash%PRECOMMIT_SIZE;
	TAILQ_REMOVE(&precommit[x].head,pce,next);
	TAILQ_REMOVE(&pceinuse,pce,all);

	if (pce->art) {
		if (pce->art->refcount) {
			TAILQ_INSERT_TAIL(&waithead,pce,next);
		}
		
		FreeMatchData(pce->art);
		free(pce->art->bufp);
		free(pce->art);
	}
	
	if (freepce<MAX_FREEPCE) {
		TAILQ_INSERT_HEAD(&freehead,pce,next);
		freepce++;
	} else {
		free(pce);
	}
	pceactive--;
}

void
PrecommitCleaner(void *unused)
{
	int c=0;
	struct precentry *pce,*pcn;
	
	for (pce=TAILQ_FIRST(&pceinuse);pce;pce=pcn) {
		
		pcn=TAILQ_NEXT(pce,all);
		
		if (pce->etime+PRECOMMIT_LIFE<curtime) {
			c++;
			FreePCE(pce);
			continue;
		} else {
			break; /* these are time sorted, so if this one isn't ready to expire, none following are */
		}
		
	}
	
}

int
PrecommitInit()
{
	int x=0;
	
	while (x<PRECOMMIT_SIZE) {
		TAILQ_INIT(&precommit[x].head);
		x++;
	}
	
	TAILQ_INIT(&freehead);
	TAILQ_INIT(&pceinuse);
	
	ScheduleCallback(5,0x7fffffff,PrecommitCleaner,NULL);
}
