//
//pipe.c
//
//
//
//-UserX 2001/11/07

/**
Functions for handling \Ref{Pipe}s. <P>

All pipes are built upon the \Ref{Pipe} structure.<P>

Pipes provide conduits and filters for data. <P>

//todo: describe pipe chains
@author UserX
@name pipe
*/
//@{

#include <stdlib.h>
#include "pipe/pipe.h"
#include "base/logger.h"
#include "base/str.h"

Pipe blankpipenone = BLANKPIPE_NONE;

/**
Initialize an existing Pipe. Initializes the \Ref{DataBlock}s for inBuffer and outBuffer. 
@param Pipe A pointer to the pipe that is to be initialized. 
*/
void pipeInit(Pipe *pipe){
	LOGDEBUG (stringJoin("pipeInit:", ptrToString(pipe)));
	if(pipe == NULL) {
		return;
	}
	if(pipe->inBuffer == NULL) {
		pipe->inBuffer = dblockEmpty("inBuffer");
	}
	if(pipe->outBuffer == NULL) {
		pipe->outBuffer = dblockEmpty("outBuffer");
	}
	//pipe->status = PSTATUS_NORMAL;
}

/**
Initialize an existing Pipe with custom functions.
todo: describe the parameters
@param pipe A pointer to the pipe that is to be initialized. 
*/
//todo: changes this over to PipeFunc* typedefs
void pipeInitFunctions(Pipe *pipe,
					   void (*backpipein)(struct Pipe *, struct Pipe **), void (*backpipeout)(struct Pipe *, struct Pipe **), 
					   void (*attach)(struct Pipe *), void (*detach)(struct Pipe *), 
					   void (*closefunc)(struct Pipe *), void (*status)(struct Pipe *, int *)) {
	pipe->backPipeInFunction = backpipein;
	pipe->backPipeOutFunction = backpipeout;
	pipe->attachFunction = attach;
	pipe->detachFunction = detach;
	pipe->closeFunction = closefunc;
	pipe->statusFunction = status;

}

/**
Releases any memory used by this Pipe, but does not release the pipe itself or detach other pipes. 
@param pipe Pointer to the Pipe to be released.
*/
void pipeFree(Pipe *pipe) {
	LOGDEBUG (stringJoin("pipeFree:", ptrToString(pipe)));
	if(pipe == NULL) {
		return;
	}
	dblockFree(pipe->inBuffer);
	dblockFree(pipe->outBuffer);
	pipe->inBuffer = NULL;
	pipe->outBuffer = NULL;
	//LOGDEBUG ("pipeFree:Done.");
}

//todo: pipeError(Pipe *pipe)

/**
Reads data from the backpipe into <B>thispipe</B>. <P>
If backPipeInFunction is define that will be called instead to do
the reading of the backpipe. <P>
If backpipe is NULL it will return with a NULL. <P>
If backPipeInFunction is NULL then it will call pipeGenericRead(backpipe). 
Then it will move all data from backpipe->inBuffer to thispipe->inBuffer.
@param thispipe The pipe we are readying data into.
@return A Pipe that has encountered an error, otherwise NULL if no error.
*/
Pipe *pipeGenericRead(Pipe *thispipe) {
	Pipe *errpipe = NULL;
	if(thispipe == NULL) {
		return NULL;
	}
	if(thispipe->backPipeInFunction != NULL) { //custom function for handling the stream
		thispipe->backPipeInFunction(thispipe, &errpipe);
		return errpipe;
	}

	if(thispipe->backPipe == NULL) {
		return NULL;	//end of the chain
	}

	pipeGenericRead(thispipe->backPipe);

	thispipe->inBuffer = dblockMoveAll(thispipe->inBuffer, thispipe->backPipe->inBuffer);
	// //copy the backpipe's inbuffer onto the end of ours
	//thispipe->inBuffer = dblockAppendBlock(thispipe->inBuffer, thispipe->backPipe->inBuffer);
	//thispipe->backPipe->inBuffer->size = 0; //then clear the backpipe's inbuffer
	return NULL;
}

//writes data from this pipe into the backpipe
/**
Reads data from the backpipe into <B>thispipe</B>. <P>
If backPipeOutFunction is define that will be called instead to do
the reading of the backpipe. <P>
If backpipe is NULL it will return with a NULL. <P>
If backPipeInFunction is NULL then it will move all data from 
thispipe->outBuffer to backpipe->outBuffer. 
Then it will call pipeGenericWrite(backpipe). 
@param thispipe The pipe we are readying data into. 
@return A Pipe that has encountered an error, otherwise NULL if no error. 
*/
Pipe *pipeGenericWrite(Pipe *thispipe) {
	Pipe *errpipe = NULL;
	if(thispipe == NULL) {
		return NULL;
	}

	if(thispipe->backPipeOutFunction != NULL) { //custom function for handling the stream
		thispipe->backPipeOutFunction(thispipe, &errpipe);
		return errpipe;
	}

	if(thispipe->outBuffer->size < 0) {
		errpipe = NULL;//todo: probably flag the undersize error and close the pipe. or abort
	}
	if(thispipe->backPipe == NULL) {
		return NULL;
	}

	if(thispipe->backPipe->outBuffer->size < 0) {
		errpipe = NULL;//todo: probably flag the undersize error and close the pipe. or abort
	}
	thispipe->backPipe->outBuffer = dblockMoveAll(thispipe->backPipe->outBuffer, thispipe->outBuffer);
	// //copy outbound data to the back pipe
	//thispipe->backPipe->outBuffer = dblockAppendBlock(thispipe->backPipe->outBuffer, thispipe->outBuffer);
	//thispipe->outBuffer->size = 0; //then clear our outbuffer

	return pipeGenericWrite(thispipe->backPipe);
}


/**
Reads data from a pipe. <P>
If <B>maxlength</B> < 0 then all available data will be read. <BR>
If <B>maxlength</B> = 0 then no data will be read but the backpipe will be called to pull data through. <BR>
If <B>maxlength</B> > 0 then up to <B>maxlength</B> characters will be read. <BR>
@param thispipe The pipe to read data from.
@param db Pointer to a DataBlock pointer, data will be appended to this.
@param maxlength The maximum number of bytes to read.
@return The pipe to cause an error.
*/
Pipe *pipeRead(Pipe *thispipe, DataBlock **db, int maxlength) {
	Pipe *errpipe;
	if(thispipe == NULL) {
		return NULL;
	}
	errpipe = pipeGenericRead(thispipe);
	if(errpipe != NULL) {
		return errpipe;
	}
	if(maxlength == 0) {
		return NULL;
	}
	if(maxlength >= thispipe->inBuffer->size) {
		maxlength = -1;
	}
	if(maxlength < 0) {
		*db = dblockAppendBlock(*db, thispipe->inBuffer);
		thispipe->inBuffer->size = 0;
		return NULL;
	}
	*db = dblockInsertPart(*db, (*db)->size, thispipe->inBuffer, 0, maxlength);
	thispipe->inBuffer = dblockDelete(thispipe->inBuffer, 0, maxlength);
	return NULL;

}

/**
Reads data from a pipe. <P>
If <B>maxlength</B> < 0 then all available data will be written. <BR>
If <B>maxlength</B> = 0 then no data will be written but the backpipe will be called to push data through. <BR>
If <B>maxlength</B> > 0 then up to <B>maxlength</B> characters will be written. <BR>
@param thispipe The pipe to write data to. 
@param db Pointer to a DataBlock pointer, data will be written from this. 
@param maxlength The maximum number of bytes to write. 
@return The pipe to cause an error.
*/
Pipe *pipeWrite(Pipe *thispipe, DataBlock **db, int maxlength) {

	if(maxlength != 0) {
		if(maxlength > (*db)->size) {
			maxlength = -1;
		}
		if(maxlength < 0) {
			thispipe->outBuffer = dblockAppendBlock(thispipe->outBuffer, *db);
			(*db)->size = 0;
		} else {
			thispipe->outBuffer = dblockInsertPart(thispipe->outBuffer, thispipe->outBuffer->size, *db, 0, maxlength);
			*db = dblockDelete(*db, 0, maxlength);
		}
	}
	return pipeGenericWrite(thispipe);
}

//read a string from the entire inbuffer.
//will discard any '\000' characters
//also calls generic read
Pipe *pipeReadString(Pipe *thispipe, char **s) {
	Pipe *errpipe;
	int i;
	int j;
	errpipe = pipeGenericRead(thispipe);
	if(errpipe != NULL) {
		return errpipe;
	}
	if(s == NULL) {
		return NULL;
	}
	*s = stringMake(thispipe->inBuffer->size);
	for(i = 0, j = 0; i <= thispipe->inBuffer->size; i++) {
		switch(thispipe->inBuffer->data[i]) {
		case 0:
			break;
		default:
			(*s)[j] = thispipe->inBuffer->data[i];
			j++;
		}
	}
	thispipe->inBuffer->size = 0;
	(*s)[j] = 0;

	return NULL;
}

//writes a string out to buffer
//releases the string afterwards
//also calls generic write
Pipe *pipeWriteString(Pipe *thispipe, char *s) {
	thispipe->outBuffer = dblockAppendString(thispipe->outBuffer, s);
	stringFree(s);
	return pipeGenericWrite(thispipe);
}


//trace a chain of pipes to the end.
Pipe *pipeGetEnd(Pipe *pipe) {
	Pipe *lastpipe = pipe;
	while(pipe != NULL) {
		lastpipe = pipe;
		pipe = pipe->backPipe;
	}
	return lastpipe;
}

//trace a chain of pipes to the start.
Pipe *pipeGetStart(Pipe *pipe) {
	Pipe *lastpipe = pipe;
	while(pipe != NULL) {
		lastpipe = pipe;
		pipe = pipe->forwardPipe;
	}
	return lastpipe;
}


//
//todo: error codes
int pipeAttach(Pipe *oldpipe, Pipe *newpipe) {
	LOGDEBUG (stringJoinMany(
			"pipeAttach:", 
			ptrToString(oldpipe),
			", forwardPipe ",
			ptrToString(newpipe),
			NULL));
	if(oldpipe == NULL) {
		return 1;
	}
	if(newpipe == NULL) {
		return 1;
	}

	if(oldpipe->forwardPipe != NULL) {
		//todo: check error code
		pipeDetach(oldpipe);
	}

	//call pipeInit to insure that both pipes have buffers allocated.
	pipeInit(oldpipe);
	pipeInit(newpipe);

	newpipe->backPipe = oldpipe;
	oldpipe->forwardPipe = newpipe;

	if(oldpipe->attachFunction != NULL) {
		oldpipe->attachFunction(oldpipe);
	}

	return 0;
}

//
//calls pipeFree on it's self.
//todo: error codes
int pipeDetach(Pipe *pipe) {
//todo: sanity check on pipe->forwardPipe != NULL
	LOGDEBUG (stringJoin("pipeDetach:", ptrToString(pipe)));
	if(pipe == NULL) {
		return 1;
	}
	if(pipe->forwardPipe != NULL) {
		pipe->forwardPipe->backPipe = NULL;
		pipe->forwardPipe = NULL;
	}

	pipeFree(pipe);

	if(pipe->detachFunction != NULL) {
		pipe->detachFunction(pipe);
	}

	return 0;
}

//todo: fix this statement: calls pipeDetach then the custom closeFunction then calls pipeClose(pipe->backPipe)
//this should ONLY be called at the top of a chain.
//todo error codes
int pipeClose(Pipe *pipe) {
	int err;
	Pipe *backpipe;
	LOGDEBUG (stringJoin("pipeClose:", ptrToString(pipe)));
	if(pipe == NULL) {
		return 1;
	}

	//todo: check for a non-NULL forwardPipe

	backpipe = pipe->backPipe;

	pipeFree(pipe);

	pipe->status |= PSTATUS_CLOSED;

	if(pipe->closeFunction != NULL) {
		pipe->closeFunction(pipe);
	}

	if(backpipe != NULL) {
		err = pipeClose(backpipe);
		if(err) {
			return err;
		}
	}

	

	if(backpipe != NULL) {
		err = pipeDetach(backpipe);
		if(err) {
			return err;
		}
	}

	return 0;
}

//trace a chain of pipes back and get all the set flags.
int pipeStatus(Pipe *pipe) {
	int status = PSTATUS_NORMAL;
	int temp = PSTATUS_NORMAL;

	if(pipe == NULL) {
		return status;
	}

	if(pipe->backPipe != NULL) {
		status |= pipeStatus(pipe->backPipe);
	}
	if(pipe->statusFunction != NULL) {
		pipe->statusFunction(pipe, &status);
	}

	status |= pipe->status & ~PSTATUS_READY;
	temp = status & ~PSTATUS_READY;
	status = temp | (status & pipe->status & PSTATUS_READY);

	if(pipe->inBuffer->size > 0) {
		status |= PSTATUS_READ | PSTATUS_READX;
	}
	if(pipe->outBuffer->size > 0) {
		status |= PSTATUS_WRITE | PSTATUS_WRITEX;
	}
	return status;
	
}

//@}
