//
//p-steady.c
//
//A pipe that implements a steady data stream.
//
//
//-UserX 2001/11/26



#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "base/dblock.h"
#include "base/mem.h"
#include "pipe/steady.h"
#include "base/logger.h"
#include "crypt/salt.h"

PipeSteady blankpipesteady = BLANKPIPESTEADY;

#define PS_SIZE 2 //number of bytes in the steady pipe header

int pipesteadyminimum = 20;
int pipesteadymaximum = 50;


Pipe *pipesteadyMake(char *pipename, char *options) {
	PipeSteady *ps = memCopy(&blankpipesteady, sizeof(PipeSteady), "PipeSteady", NULL);//xiclone(&blankpipesteady, sizeof(PipeSteady));
	pipeInitFunctions((Pipe *) ps, 
			(PipeFuncIn) pipesteadyRead, (PipeFuncOut) pipesteadyWrite, 
			(PipeFuncAttach) pipesteadyAttach, (PipeFuncDetach) pipesteadyDetach, 
			(PipeFuncClose) pipesteadyClose, (PipeFuncStatus) pipesteadyStatus);
	pipeInit((Pipe *) ps);
	ps->maxBandwidth = pipesteadymaximum;
	ps->lastBandwidth = time(NULL);	
	return (Pipe *)ps;
}

void pipesteadyFree(PipeSteady *ps) {
	pipeFree(&ps->IOPipe);
	memFree(ps);//xifree(ps);
}

void pipesteadyRead(PipeSteady *thispipe, Pipe **errPipe) {
	int v = 0;
	int i;
	*errPipe = pipeGenericRead(thispipe->IOPipe.backPipe);
	if(*errPipe == NULL) {
		switch(thispipe->inHandshake) {
		case 0:
			//todo: maybe: check for flagrant abuse by the remote end
			while(thispipe->IOPipe.backPipe->inBuffer->size >= PS_SIZE + thispipe->maxBandwidth) {
				for(i = 0; i < PS_SIZE; i++) {
					v |= thispipe->IOPipe.backPipe->inBuffer->data[i] << (i << 3);
				}
				LOGDEBUGTRAFFIC(stringJoinMany(
						"pipesteadyRead: ",
						ptrToString(thispipe),
						", read bytes:",
						intToHexString(v),
					NULL));
				if(v > thispipe->maxBandwidth) {
					thispipe->IOPipe.status |= PSTATUS_CLOSED;
					thispipe->inHandshake = 3;
					return;
				}
				if(v != 0) {
					thispipe->IOPipe.inBuffer = dblockInsertPart(thispipe->IOPipe.inBuffer, thispipe->IOPipe.inBuffer->size, thispipe->IOPipe.backPipe->inBuffer, PS_SIZE, v);
				}
				saltAddBuffer(thispipe->IOPipe.backPipe->inBuffer->data + PS_SIZE + v, thispipe->maxBandwidth - v);
				thispipe->IOPipe.backPipe->inBuffer = dblockDelete(thispipe->IOPipe.backPipe->inBuffer, 0, PS_SIZE + thispipe->maxBandwidth);
			}
			break;
		case 1:
			if(thispipe->IOPipe.backPipe->inBuffer->size >= PS_SIZE) {
				for(i = 0; i < PS_SIZE; i++) {
					v |= thispipe->IOPipe.backPipe->inBuffer->data[i] << (i << 3);
				}
				LOGDEBUGTRAFFIC(stringJoinMany(
						"pipesteadyRead: ",
						ptrToString(thispipe),
						", remote max size:",
						intToHexString(v),
					NULL));
				thispipe->IOPipe.backPipe->inBuffer = dblockDelete(thispipe->IOPipe.backPipe->inBuffer, 0, PS_SIZE);
				if(v < pipesteadyminimum) {
					thispipe->IOPipe.status |= PSTATUS_CLOSED;
					thispipe->inHandshake = 3;
					return;
				}

				thispipe->inHandshake = 2;
			}
			//fallthrough
		}
	}
}

void pipesteadyWrite(PipeSteady *thispipe, Pipe **errPipe) {
	DataBlock *db;
	int i;
	time_t now;
	int v = 0;

	switch(thispipe->outHandshake) {
	case 0:
		now = time(NULL);
		thispipe->bandwidthCount += now - thispipe->lastBandwidth;
		thispipe->lastBandwidth = now;
		if(thispipe->bandwidthCount > BANDWIDTHCOUNT_MAX) {
			thispipe->bandwidthCount = BANDWIDTHCOUNT_MAX;
		}
		if(thispipe->bandwidthCount != 0) {
			db = dblockMake(PS_SIZE + thispipe->maxBandwidth, PS_SIZE + thispipe->maxBandwidth, "steadywrite");
			if(thispipe->IOPipe.outBuffer->size != 0) {
				v = thispipe->IOPipe.outBuffer->size;
				if(v > thispipe->maxBandwidth) {
					v = thispipe->maxBandwidth;
				}
				memcpy(db->data + PS_SIZE, thispipe->IOPipe.outBuffer->data, v);
			}
			LOGDEBUGTRAFFIC(stringJoinMany(
					"pipesteadyWrite: ",
					ptrToString(thispipe),
					", write bytes:",
					intToHexString(v),
				NULL));

			for(i = 0; i < PS_SIZE; i++) {
				db->data[i] = v >> (i << 3);
			}

			saltBuffer(db->data + PS_SIZE + v, thispipe->maxBandwidth - v);

			thispipe->IOPipe.backPipe->outBuffer = dblockAppendBlock(thispipe->IOPipe.backPipe->outBuffer, db);

			thispipe->IOPipe.outBuffer = dblockDelete(thispipe->IOPipe.outBuffer, 0, v);

			thispipe->bandwidthCount--;

			dblockFree(db);
		}
		break;
	case 1:
		db = dblockMake(PS_SIZE, PS_SIZE, "steadywritehandshake");
		for(i = 0; i < PS_SIZE; i++) {
			db->data[i] = thispipe->maxBandwidth >> (i << 3);
		}
		LOGDEBUGTRAFFIC(stringJoinMany(
				"pipesteadyWrite: ",
				ptrToString(thispipe),
				", local max size:",
				intToHexString(thispipe->maxBandwidth),
			NULL));
		thispipe->IOPipe.backPipe->outBuffer = dblockAppendBlock(thispipe->IOPipe.backPipe->outBuffer, db);
		thispipe->outHandshake = 2;
		dblockFree(db);
		//fallthrough
	case 2:
		if(thispipe->inHandshake == 2) {
			thispipe->outHandshake = 0;
			thispipe->inHandshake = 0;
			thispipe->IOPipe.status |= PSTATUS_READY;
		}
	}
	*errPipe = pipeGenericWrite(thispipe->IOPipe.backPipe);
}


void pipesteadyAttach(PipeSteady *thispipe) {

}

void pipesteadyDetach(PipeSteady *thispipe) {
	pipesteadyFree(thispipe);
}

void pipesteadyClose(PipeSteady *thispipe) {
}

//the data pending maybe all be unused garbage so clear the READ bit
void pipesteadyStatus(PipeSteady *thispipe, int *status) {
	*status &= ~PSTATUS_READ;
}
