#ifdef __NT_VC__
#include <io.h>
#endif
#include <string.h>
#include <stream.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include "slist.h"
#include "errcode.h"
#include "portable.h"
#include "cgidbg.h"

#include "shared.h"

#include "pipe_com.h"

extern const char * ProgramName ;

int SharedMemoryExitState = 0 ;

static void WriteErrorMessage(const char * where,const char * what)
{
	TheLog << "The system error message is:\n" ;
	if (errno < sys_nerr) TheLog << sys_errlist[errno] ;
	else TheLog << "error number: " << errno <<
		" which is not in the system error table" ;
	TheLog << "." ;
	DbgError(where,what);
}

class ImplementPipeCommunication_ {
	int the_file_desc ;
	int the_write_flag ;

	class QueuedPacketList * Queue ;
	int WritingInProgress ;
	int DeQueuingInProgress ;
	char * BufPtr ;
	int BytesToRead ;
	PacketHeader TempHead ;
	PacketHeader ReturnHead ;
	char LocalBuf[MaxPacketSize] ;

	int block_read_state ;
	int no_block_read_state ;

	enum CheckStatus {NoReadInProgress, HeaderReadInProgress,
		DataReadInProgress} CurrentStatus ;

	int tmp ; // for inline procedures

	inline int Read(char * Buf,int NBytes);
	inline int Write(const char * Buf,int NBytes) ;
	void CheckForQueuedMessages() ;

public:
	ImplementPipeCommunication_(int file_desc, int write_flag);
	void WritePacket(PacketHeader&, const char *Data);
	int CheckPacketRead(int use_blocking_read=0) ;
	int ReadPacket() ;
	void block();
	void no_block();
};


inline int ImplementPipeCommunication_::Read(char * Buf,int NBytes)
{
	tmp = read(the_file_desc,Buf,NBytes);
	return tmp < 0 ? 0:tmp ;
}

inline int ImplementPipeCommunication_::Write(const char * Buf,int NBytes)
{
	tmp = write(the_file_desc,Buf, NBytes);
	return tmp < 0 ? 0:tmp ;
}

class CommControl {
	int CommActive ;
	class NotifyCommActiveList * ToNotify ;
	friend void CheckForQueuedMessages() ;
public:
	CommControl();
	~CommControl();
	void SetCommActive() ;
	int IsCommActive(NotifyCommActive Notify = 0) ;
};

static CommControl * comm_control = 0 ;

class QueuedPacket {
	PacketHeader Head;
	char * Text ;
public:
	QueuedPacket (PacketHeader& head, const char * Msg);
	~QueuedPacket () {delete Text;}
	PacketHeader& head() {return Head;}
	const char * text() const {return Text;}
} ;

QueuedPacket::QueuedPacket(PacketHeader& head, const char * Msg)
{
	if (head.DataSize) {
		Text = new char[head.DataSize];
		memcpy(Text,Msg,head.DataSize);
	} else Text = 0;
	Head = head ;
}

class QueuedPacketList:public SingleList {
public:
	ErrCode Insert(QueuedPacket *nt) {return SingleList::Insert(nt);}
	ErrCode Append(QueuedPacket *nt) {return SingleList::Append(nt);}
	QueuedPacket * Get()   {return (QueuedPacket *) SingleList::Get();}
	QueuedPacket * Pop() {return (QueuedPacket *) SingleList::Pop();}
	QueuedPacket * GetNFromTop(int N) ;
	QueuedPacket * GetNthEntry(int N) ;
	QueuedPacketList(){}
	int Size(){return SingleList::Size();}
} ;

class QueuedPacketListIterator:public SingleListIterator {
public:
	QueuedPacketListIterator(QueuedPacketList& df):
		SingleListIterator((SingleList&) df){}
	QueuedPacket * operator()()
		{return (QueuedPacket *) Next();}
};




PipeCommunication::PipeCommunication(int file_desc, int write_flag):
	pipe_com(new ImplementPipeCommunication_(file_desc,write_flag))
{
}

PipeCommunication::~PipeCommunication()
{
}

void PipeCommunication::no_block()
{
	pipe_com->no_block();
}	

void PipeCommunication::block()
{
	pipe_com->block();
}	

void PipeCommunication::WritePacket(PacketHeader& head, const char * data)
{
	pipe_com->WritePacket(head,data);
}

int PipeCommunication::CheckPacketRead()
{
	return pipe_com->CheckPacketRead();
}

int PipeCommunication::ReadPacket()
{
	return pipe_com->ReadPacket();
}

int PipeCommunication::is_comm_active(NotifyCommActive Notify)
{
	if (!comm_control) comm_control = new CommControl ;
	return comm_control->IsCommActive(Notify);
}

void PipeCommunication::set_comm_active()
{
	if (!comm_control) comm_control = new CommControl ;
	comm_control->SetCommActive();
}

extern PipeCommunication * ReadSeg ;
extern PipeCommunication * WriteSeg ;

static void LocCheckRead()
{
	if (ReadSeg) ReadSeg->CheckPacketRead();
}



PipeCommunication * PipeCommunication::read_segment()
{
	return ReadSeg ;
}

PipeCommunication * PipeCommunication::write_segment()
{
	return WriteSeg ;
}

ImplementPipeCommunication_::ImplementPipeCommunication_(int file_desc,
	int write_flag):
	the_file_desc(file_desc),
	the_write_flag(write_flag),
	WritingInProgress(0),
	DeQueuingInProgress(0),
	Queue(0),
	BufPtr(0),
	CurrentStatus(NoReadInProgress),
	BytesToRead(0),
	block_read_state(0),
	no_block_read_state(0)
{
/*
 *	cerr << "InplementPipeCommunication_::ctor(" << file_desc << ", " <<
 *		the_write_flag << ")\n" ;
 */
	if (the_write_flag) {
		Queue = new QueuedPacketList ;
		return ;
	}
#ifndef __NT_VC__
	block_read_state = fcntl(the_file_desc,F_GETFL,O_NONBLOCK);
	if (no_block_read_state < 0) WriteErrorMessage(
		"InplementPipeCommunication_::ctor","cannot get pipe mode");
	block_read_state &= ~O_NONBLOCK ;
	no_block_read_state = block_read_state | O_NONBLOCK ;
	// cerr << "no_block = 0x" << hex << no_block_read_state << dec << "\n" ;
	// cerr << "block = 0x" << hex << block_read_state << dec << "\n" ;
	no_block();
#else
	block_read_state = 0 ;
#endif
}


void ImplementPipeCommunication_::block()
{
#ifndef __NT_VC__
	int do_f = fcntl(the_file_desc,F_SETFL,block_read_state);
	if (do_f) WriteErrorMessage("ImplementPipeCommunication_::block",
		"cannot");
#endif
}

void ImplementPipeCommunication_::no_block()
{
#ifndef __NT_VC__
	int do_f = fcntl(the_file_desc,F_SETFL,no_block_read_state);
	if (do_f) WriteErrorMessage("ImplementPipeCommunication_::no_block",
		"cannot");
#endif
}



void ImplementPipeCommunication_::WritePacket( PacketHeader& InHead,
	const char * InData)
{
/*
 *	LogOut << ProgramName << ":WritePacket(0x" << hex << (int)
 *		InHead.ThePacketType <<
 *		", 0x" << (int) InHead.DataSize  << ", 0x" << InHead.Identifier 
 *		<< dec << ")\n" ;
 */
	if (WritingInProgress) {
		Queue->Append(new QueuedPacket(InHead,InData));
		int QueuedSize = Queue->Size() ;
		// LogOut << "WritePacket returning in progress\n" ;
		return ;
	}
	PacketHeader Head = InHead ;
	char * Data = 0;
	if (Head.DataSize) Data = (char *) memcpy(new char [Head.DataSize],
		InData, Head.DataSize) ;
	WritingInProgress = 1 ;
	int Bytes  = sizeof(Head);
	int Written ;
	const char * Bptr = (char *) &Head ;
	int ReTry = 0;
	for (;;) {
		Written = Write(Bptr,Bytes);
/*
 *		LogOut << ProgramName << ":Written = " << Written << ", Bytes = "
 *			<< Bytes << "\n" ;
 */
		Bytes -= Written ;
		if (Bytes < 1) break;
		Bptr+=Written;
		LocCheckRead();
	}
	Bytes  = Head.DataSize ;
	Bptr = Data ;
	if (Bytes) for (;;) {
		Written = Write(Bptr,Bytes);
/*
 *		LogOut << ProgramName << ":Written = " << Written << ", Bytes = "
 *			<< Bytes << "\n" ;
 */
		Bytes -= Written ;
		if (Bytes < 1) break;
		Bptr+=Written;
		LocCheckRead();
	}
	WritingInProgress = 0;
	delete Data ;
	CheckForQueuedMessages() ;
	// LogOut << "WritePacket done\n" ;
}

// Bit 1 is set if any bytes are written and 2 if any routine called
int ImplementPipeCommunication_::CheckPacketRead(
	int use_blocking_read)
{
/*
 *	LogOut << ProgramName << ":CheckPacketRead CurrentStatus = " <<
 *		CurrentStatus << ", ToRead = " << BytesToRead << "\n" ;
 */
	if (use_blocking_read) block();
	else no_block();
	int HaveRead = 0 ;
	int Ret = 0 ;
	int ForceExit = 0 ;
	for (;;) switch(CurrentStatus) {
case NoReadInProgress:
		BufPtr = (char *) &TempHead ;
		BytesToRead = sizeof(TempHead) ;
case HeaderReadInProgress:
		HaveRead = Read(BufPtr,BytesToRead);
		// LogOut << ProgramName << ":HaveRead = " << HaveRead << "\n" ;

		if (!HaveRead) return Ret ;
		CurrentStatus = HeaderReadInProgress;
		Ret |= 1;
		BufPtr+= HaveRead ;
		BytesToRead -= HaveRead ;
		if (BytesToRead) return Ret ;
		BufPtr = LocalBuf ;
		BytesToRead = abs(TempHead.DataSize) ;
case DataReadInProgress:
		CurrentStatus = DataReadInProgress;
		if (ForceExit) return Ret ;
		if (!BytesToRead) goto DoneSendPacket ;
		HaveRead = Read(BufPtr,BytesToRead);
		// LogOut << ProgramName << ":HaveRead = " << HaveRead << "\n" ;
		if (!HaveRead) return Ret ;
		Ret |= 1 ;
		BufPtr+= HaveRead ;
		BytesToRead -= HaveRead ;
		if (BytesToRead) return Ret ; 
DoneSendPacket:
		CurrentStatus = NoReadInProgress ;
		ReturnHead = TempHead ;
		// LogOut << "Calling ProcessPacket\n" ;
		ProcessPacket(ReturnHead,LocalBuf,
			HaveRead == BytesToRead ? &TempHead:0);
		Ret |= 2 ;
		if (use_blocking_read) {
			CheckPacketRead();
			return Ret ;
		}
	}
	return Ret ;
}

int ImplementPipeCommunication_::ReadPacket()
{
	return CheckPacketRead(1);
}

void ImplementPipeCommunication_::CheckForQueuedMessages()
{
    if (DeQueuingInProgress||WritingInProgress) return ;
    DeQueuingInProgress = 1 ;
    QueuedPacket * Packet ;
    int QueuedSize = Queue->Size();
    while (Packet = Queue->Get()) {
        WritePacket(Packet->head(),Packet->text());
        delete Packet ;
        if (WritingInProgress) break ;
    }
    DeQueuingInProgress = 0 ;
}

class NotifyCommActiveList: public SingleList {
public:
	ErrCode Insert(NotifyCommActive nt)
		{return SingleList::Insert((Entity)nt);}
	ErrCode Append(NotifyCommActive nt)
		{return SingleList::Append((Entity)nt);}
	void AddNotification(NotifyCommActive nt) ;
	int IsInList(NotifyCommActive nt);
	NotifyCommActive Get() {return (NotifyCommActive)
		SingleList::Get();}
	NotifyCommActive Pop() {return (NotifyCommActive)
		SingleList::Pop();}
	NotifyCommActive GetNFromTop(int N) ;
	NotifyCommActive GetNthEntry(int N) ;
	NotifyCommActiveList(){;}
	int Size(){return SingleList::Size();}
} ;

class NotifyCommActiveListIterator:private SingleListIterator {
public:
	NotifyCommActiveListIterator(NotifyCommActiveList& df):
		SingleListIterator((SingleList&) df){}
	NotifyCommActive operator()()
		{return (NotifyCommActive) Next();}
};


void NotifyCommActiveList::AddNotification(NotifyCommActive nt)
{
	if (IsInList(nt)) return ;
	// LogMsg("New entry - adding to list");
	Append(nt);
}

int NotifyCommActiveList::IsInList(NotifyCommActive nt)
{
	NotifyCommActiveListIterator Next(*this);
	NotifyCommActive Check ;
	while (Check = Next()) if (Check == nt) return 1;
	return 0;
}
CommControl::CommControl()
{
	CommActive = 0 ;
	ToNotify = new NotifyCommActiveList ;
}


CommControl::~CommControl()
{
	ToNotify->Clear();
	delete ToNotify ;
}


int CommControl::IsCommActive(NotifyCommActive Notify) 
{
	// LogMsg("In CommControl::IsCommActive");
	if (!CommActive) if (Notify) {
		ToNotify->AddNotification(Notify);
		// LogMsg("Added notify");
	}
	// LogForm("Returning %d",CommActive);
	return CommActive ;
}

void CommControl::SetCommActive()
{
	// LogForm("SetCommActive have %d notifications", ToNotify->Size());
	CommActive = 1;
	NotifyCommActive Notify ;
	while (Notify = ToNotify->Get()) {
		(*Notify)();
		// LogMsg("Did a notification");
	}
}

