/*************************************************************************/
/*                                                                       */
/*  The cpp file of the operation of a single connection (Class Host)    */
/*  For CS 145 (Fall 2002) Lab 3                                         */
/*  Author: Xiaoliang (David) Wei                                        */
/*  Last Modification: Dec 12, 2002                                      */
/*  Original Date:     Dec 20, 2000                                      */
/*                                                                       */
/*************************************************************************/



#ifndef HOST_CLASS
#define HOST_CLASS
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

extern fd_set readfds;
extern fd_set writefds;
//extern FILE *publicFile;
extern int myclock;
extern int CHECK_INT;	//the frequency of the selection check (ms)
extern int TIME_OUT;	//the time out threshold of communicaition (ms)
extern int UPDATE_INT;	//the frequency of update (clock==CHECK_INT ms)

#define DATA_SIZE 4077  //4096-19

void iptoip4(long unsigned int ip, int *ip1,int * ip2, int *ip3, int *ip4)
{
	long unsigned int tmp=ip;
	(*ip1)=tmp % 256;	tmp=tmp /256;
	(*ip2)=tmp % 256;	tmp=tmp /256;
	(*ip3)=tmp % 256;	tmp=tmp /256;
	(*ip4)=tmp;	
}

long unsigned int ip4toip(int ip1,int ip2,int ip3,int ip4)
{
	char buffer[1024];	
	struct hostent *hp/*,*gethostbyname()*/;
	sprintf(buffer,"%d.%d.%d.%d",ip1,ip2,ip3,ip4);
	hp=gethostbyname(buffer);
	if (hp==0)
	{
		fprintf(stderr,"%s:unknown host\n",buffer);
		exit(2);
	}
	long unsigned int temp;	
	memcpy((char *)&temp,(char *)hp->h_addr,hp->h_length);	
	return temp;
}

struct packet_struct
{
	char marker[16];
	char length1;
	char length2;
	char type;
	char data[DATA_SIZE];	
	int length;
};

#define HOST_SERVER 0
#define HOST_CLIENT 1
#define HOST_LISTEN 2

#define STATE_ERROR 0
#define STATE_ACTIVE 1
#define STATE_CONNECT 2
#define STATE_OPEN_SENT 3
#define STATE_OPEN_CONFIRMED 4
#define STATE_ESTABLISHED 5

#define PACKET_OPEN 1
#define PACKET_UPDATE 2
#define PACKET_NOTIFICATION 3
#define PACKET_KEEPALIVE 4
#define PACKET_NULL 5


#define ERROR_NONE 0
#define ERROR_INVALID_TYPE 1
#define ERROR_MISMATCH_TYPE 2
#define ERROR_LENGTH 3
#define ERROR_TIMEOUT 4
#define ERROR_HEADER 5
#define ERROR_IO 6



class Host
{
	//static int number;	
	
	//FILE* file;
	
	int waittime;
	int left;
	
	int rwSize;	//
	char * rwPtr;
	

	

	struct packet_struct packet;
	
	int macroStep;
	
	
	int packetExpected;
	
	
	int errorType;
	
	int ip1,ip2,ip3,ip4;
	
	void printID();
	void suicide(char * reason);
	
	
public:	
	//int ID;
	
	int macroState;
	
	long unsigned int ip;
	
	
	Host(int sock, int type, long unsigned int ip);
	~Host();
	void work();
	int checkPacket();
	void turnToError();	
	void setWritePacket();
	void setReadPacket(int type);
	void clearWait();
	void createPacket(int type);
	void replyPacket(int type);
	void nextMacroState();
	void nextMacroStep();
	void getPacket();
	
	void addNextNode(Host *newNode);

	Host* next;
	int alive;
	int sock;
	int rwState;	//1:read header 2:read body 3: write head 4: write body 5: finished	
};
//int Host::number=0;


Host::~Host()
{
	close(sock);
	
};
Host::Host(int sock,int type, long unsigned int ip)
{	
	this->sock=sock;      
	
	this->ip=ip;
	
	iptoip4(ip,&ip1,&ip2,&ip3,&ip4);
	//file=publicFile;	
	
	
	alive=1;
	waittime=0;
	
	rwState=0;
	rwSize=0;
	
	
	macroStep=0;	
	if (type==HOST_SERVER) macroState=STATE_ACTIVE; else macroState=STATE_CONNECT;
	
	//ID=number;
	//number++;
	
	
	
	next=NULL;
	
	printID();
	printf("newly Created\n");
};

void Host::addNextNode(Host *newNode)
{
	newNode->next=next;
	next=newNode;
}
void Host::clearWait()
{
	waittime=0;
}

int Host::checkPacket()
{	
	errorType=ERROR_NONE;
	for (int i=0;i<16;i++) if (packet.marker[i]!=char(255)) 
	{
		errorType=ERROR_HEADER;		
		return 0;
	}
	
	packet.length=packet.length1*256+packet.length2;	
	if (packet.length<19) 
	{
		errorType=ERROR_LENGTH;
		return 0;
	};
	if ((packet.type<1)||(packet.type>4))
	{
		errorType=ERROR_INVALID_TYPE;
		return 0;
	};
	if (packet.type!=packetExpected)
	{
		errorType=ERROR_MISMATCH_TYPE;
		return 0;
	}
	printf("Got packet:%d\n", packet.type);
	return 1;
};

void Host::setWritePacket()
{		
	rwState=3;
	rwSize=packet.length;
	packet.length1=packet.length /256;
	packet.length2=packet.length %256;
	rwPtr=(char *)&packet;
	left=1;
}

void Host::setReadPacket(int type)
{	
	memset(packet.data,0,DATA_SIZE);
	packet.type=PACKET_NULL;
	packet.length=0;
	packetExpected=type;
	rwState=1;
	rwSize=19;
	rwPtr=(char *)&packet;	
	left=1;
}
void Host::nextMacroStep()
{
	macroStep++;	
}
void Host::nextMacroState()
{
	int oldState=macroState;
	
	macroStep=0;
	switch (macroState)	
	{
		case STATE_ACTIVE:
			macroState=STATE_OPEN_SENT;
		break;
		case STATE_OPEN_SENT:
			macroState=STATE_OPEN_CONFIRMED;
		break;
		case STATE_CONNECT:
			macroState=STATE_OPEN_CONFIRMED;
		break;
		case STATE_OPEN_CONFIRMED:
			macroState=STATE_ESTABLISHED;
		break;		
		default:		
			printf("Internal Error for nextMacroState!\n");
			exit(1);
		break;
	}
	
	printID();
	printf("---changing state---");
	printID();
	printf("\n");
	
}


void Host::work()
{
	printID();
	printf("working on step %d\n", macroStep);
	printf("rwState:%d   rwSize:%d\n",rwState,rwSize);
	if
	( 
		(rwSize!=0) 
		&&	
		( 
	   	  (((rwState==1)||(rwState==2)) && (!(FD_ISSET(sock,&readfds))))
	 	||( (rwState==3)  && (!(FD_ISSET(sock,&writefds))))	 
		)	   
	)//still blocked
	{
		//check time out
		waittime++;
		if (waittime>TIME_OUT) 
		{
			suicide("Blocked Time Out");
			
		}		
	}	
	else
	{		
		left=0;
		while ((left==0)&&(alive))
		{
			clearWait();
			if (rwState==1)
			{
				
				if (rwSize)
				{
					int tmpSize=read(sock,rwPtr,rwSize);
					if (tmpSize==0) suicide("Connect failed");;
					rwSize-=tmpSize;
					rwPtr+=tmpSize;				
				};
				left=1;
				if (rwSize==0)				
				{
					if (checkPacket()==0)
					{																		
						turnToError();
					}	
					else
					{				
						rwSize+=packet.length-19;
						//read data
						rwState=2;
					}					
				}				
			}
			else
			if (rwState==2)
			{
				if (rwSize)
				{
					int tmpSize=read(sock,rwPtr,rwSize);
					if (tmpSize==0) suicide("Connect Failed");;
					rwSize-=tmpSize;
					rwPtr+=tmpSize;		
				};				
				if (rwSize) left=1; else 
				{
					rwState=0;
					getPacket();
				}				
			}
			else
			if (rwState==3)
			{
				if (rwSize)
				{
					int tmpSize=write(sock,rwPtr,rwSize);
					if (tmpSize==0) suicide("Connect Failed");;
					rwSize-=tmpSize;
					rwPtr+=tmpSize;		
				};				
				if (rwSize) left=1; else rwState=0;
			}
			else
			//no read/write  rwState==0
			if (macroState==STATE_ERROR)
			{
				if (macroStep==0)				
				{
					createPacket(PACKET_NOTIFICATION);
					setWritePacket();
					nextMacroStep();					
				}
				else				
				{
					suicide("Error State");
				};		
					
			}
			else
			if (macroState==STATE_ACTIVE)
			//for server
			{
				if (macroStep==0)
				{
					createPacket(PACKET_OPEN);
					setWritePacket();					
					nextMacroStep();				
				}
				else				
					nextMacroState();
			}
			else
			if (macroState==STATE_OPEN_SENT)
			{
				if (macroStep==0)
				{
					setReadPacket(PACKET_OPEN);
					nextMacroStep();
				}
				else
				if (macroStep==1)
				{					
					replyPacket(PACKET_KEEPALIVE);
					setWritePacket();
					nextMacroStep();
				}
				else
					nextMacroState();
				
			}
			else
			if (macroState==STATE_CONNECT)
			{
				if (macroStep==0)
				{
					setReadPacket(PACKET_OPEN);
					nextMacroStep();
				}
				else
				if (macroStep==1)
				{					
					replyPacket(PACKET_OPEN);
					setWritePacket();
					nextMacroStep();					
				}
				else
				if (macroStep==2)
				{
					replyPacket(PACKET_KEEPALIVE);
					setWritePacket();
					nextMacroStep();					
				}
				else
					nextMacroState();
			}
			else
			if (macroState==STATE_OPEN_CONFIRMED)
			{
				if (macroStep==0)
				{
					setReadPacket(PACKET_KEEPALIVE);
					nextMacroStep();
				}
				else
				{					
					nextMacroState();
				}				
			}
			else
			if (macroState==STATE_ESTABLISHED)
			{
				if (macroStep==0)
				{
					//waiting for update
					if (FD_ISSET(sock,&readfds))
					{
						printf("Got an packet...Expecting an update\n");
						setReadPacket(PACKET_UPDATE);
						macroStep=1;
					}
					else					
					{
						macroStep=2;
					}
					
				}
				else
				if (macroStep==1)
				{
					//I've got an update					
					//suggestion: print it?
					
					macroStep=0;
					left=1;		
				}				
				else 
				if (macroStep==2)
				{
					//for client, sent update or wait
					if ((myclock%UPDATE_INT)==0)
					{
						createPacket(PACKET_UPDATE);
						setWritePacket();
						macroStep=0;
						left=1;
					}
					else		
					{			
						macroStep=0;					
						left=1;
					}					
				};
			}
		}
	}	
	
};

void Host::turnToError()
//1.make return State
//2.do I need to make NOTIFICATION packet? Y: macroStep=0 else macroStep=1;
//3.change macroState to STATE_ERROR
{
	printID();
	printf("Error Occured:   ");
	switch (errorType)
	{
		case ERROR_NONE:
			printf("None error but turn to error occured!!!\n");exit(1);
		break;
		case ERROR_INVALID_TYPE:
			printf("Invalid Type:%d !!!",packet.type);
		break;
		case ERROR_MISMATCH_TYPE:
			printf("Unexpected type: %d!!!", packet.type);
		break;
		case ERROR_LENGTH:
			printf("Length Error:%d+%d,%d!!!",packet.length1,packet.length2,packet.length);
		break;
		case ERROR_TIMEOUT:
			printf("Timeout!!!");
		break;
		case ERROR_HEADER:
			printf("Bad Header!!!");
		break;
		case ERROR_IO:
			printf("Socket IO Error!!!");
		break;
	}
	if (packet.type==PACKET_NOTIFICATION)
	{		
		macroStep=1;
		printf("get a notification packet\n");
	}
	else
	if ((macroState==STATE_CONNECT) || (macroState==STATE_ACTIVE))
	{
		macroStep=1;
		printf(" return to INIT\n");
		
	}
	else
	{
		macroStep=0;
		printf(" send notification and return to init\n");
	}
	macroState=STATE_ERROR;
	rwState=0;
	rwSize=0;
};
void Host::createPacket(int type)
//create a open packet for server
{	
	char *s;
	packet.length=0;
	switch (type)
	{		
		case PACKET_OPEN:
			
		break;
		case PACKET_KEEPALIVE:
			
		break;
		case PACKET_UPDATE:
			
		break;
		case PACKET_NOTIFICATION:
			switch (errorType)
			{			
				case ERROR_INVALID_TYPE:
					s="Type Error!";
				break;								
				case ERROR_MISMATCH_TYPE:
					s="Type Mismatch!";					
				break;
				case ERROR_LENGTH:
					s="Length Error!";					
				break;
				case ERROR_TIMEOUT:
					s="Time Out!";
				break;
				case ERROR_HEADER:
					s="Header Error!";
				break;
				default:
					printf("No such error type to handler: Create Packet \n");
					exit(1);
				break;										
			}			
			memcpy(packet.data,s,strlen(s));
			packet.length+=strlen(s);
		break;
		default:
			printf("No such packet type to create: Create Packet \n");
			exit(1);
		break;
	}
	for (int i=0;i<16;i++)
		packet.marker[i]=255;	
	packet.length+=19;
	packet.type=type;
};
void Host::replyPacket(int type)
{
	switch (type)
	{		
		case PACKET_OPEN:
			
		break;
		case PACKET_KEEPALIVE:
			
		break;
		case PACKET_UPDATE:
			
		break;
		default:
			printf("No such packet type to create: Reply Packet \n");
			exit(1);
		break;
	}
	packet.type=type;
};
void Host::getPacket()
{
	printID();
	printf("get a packet: type:%d ",packet.type);
	if (packet.length>19) printf("%s",packet.data);
	printf("\n");
};
void Host::printID()
{
	char *buffer;
	printf("(%d.%d.%d.%d),",ip1,ip2,ip3,ip4);
	switch (macroState)	
	{
		case STATE_ACTIVE:
			buffer="ACTIVE";
		break;
		case STATE_OPEN_SENT:
			buffer="OPEN_SENT";
		break;
		case STATE_CONNECT:
			buffer="CONNECT";
		break;
		case STATE_OPEN_CONFIRMED:
			buffer="OPEN CONFIRMED";
		break;
		case STATE_ESTABLISHED:
			buffer="ESTABLISHED";
		break;
		case STATE_ERROR:
			buffer="ERROR";
		break;
		default:	
			printf("Internal Error for nextMacroState!\n");
			exit(1);			
		break;		
	}
	printf("(%s):",buffer);
	
}
void Host::suicide(char *reason)
{
	alive=0;
	printID();
	printf(" Suiciding...reason: %s :\n",reason);
};

#endif

