/************************************************************/
/*                                                          */
/*  The cpp file of receiver program                        */
/*  For CS 145 (Fall 2003) Lab 3                            */
/*  Author: Xiaoliang (David) Wei                           */
/*  Date:   Dec 05, 2002                                    */
/*                                                          */
/************************************************************/


/*
Command Line format:
sender <port1> <IP> <port2> <Filename>

The code use a fixed window of W=10 packets, and a fixed timeout of RTO=300ms, rolling back window when there is a loss.


*/


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

#include "udptcp.h"






/* The IP information of the receiver*/
unsigned short int udpport;
int udpsock;



/* The IP information of sender*/
unsigned short int nextPORT;
int nextSock;
struct sockaddr_in nextServer;

FILE *outfile; //Input file. 

u_int32_t expecting;
int termed;

struct timeval TermEnd;

/*Initialization of the sending part*/
int sendInit(unsigned short int nextPort,char* nextHost)
{
  struct hostent *hp;

  /* Create a socket for sending */  
  nextSock=socket(AF_INET,SOCK_DGRAM,0);
  if (nextSock<0)
  {
	printf("ERROR:opening stream socket\n");
	return 1;
  }
  nextServer.sin_family=AF_INET;
    
  hp=gethostbyname(nextHost);
  if (hp==0)
  {
	printf("ERROR: %s:unknown host\n",nextHost);
	return 2;
  }
  memcpy((char *)&nextServer.sin_addr,(char *)hp->h_addr,hp->h_length);
  nextServer.sin_port=htons(nextPort);
  return 0;
}


/* Initialization of the feedback channel */
int listen_init(unsigned short int port)
{
    int length;
    struct sockaddr_in server;   	
    
    /* Create a socket for receiving feedback */
    udpsock=socket(AF_INET,SOCK_DGRAM,0);
    if (udpsock<0)
    {
 	 printf("ERROR: opening stream socket!\n");
   	exit(1);
    }

    server.sin_family=AF_INET;
    server.sin_addr.s_addr=INADDR_ANY;
    server.sin_port=htons(port);
    
    /*Bind the socket to the listening port */
    if (bind(udpsock,(struct sockaddr *)&server,sizeof server)<0)
    {
    	printf("UDP Binding error, maybe port number occupied by others. Change another port");
   	exit(1);
    };
    length=sizeof server;
    if (getsockname(udpsock,(struct sockaddr *)&server,(socklen_t *)&length)<0)
    {
	 printf("ERROR: getting socket name\n");
   	exit(1);
    }
    printf("UDP Socket port # %d\n",ntohs(server.sin_port)); 
    return 0;
};


/* get an incoming packet */
void get_packet()
{
	Packet* pkt=new Packet(udpsock);
	if (pkt->getHeaderPtr()->type&TYPE_DATA)
	{
		if (pkt->getHeaderPtr()->seq==expecting)
		{
			pkt->output(outfile);
			expecting++;
			if (pkt->getHeaderPtr()->type&TYPE_TERM)
			{
				termed=1;
			}
		}
		Packet* ackpkt=new Packet();
		ackpkt->getHeaderPtr()->type|=TYPE_ACK;
		if (termed) ackpkt->getHeaderPtr()->type|=TYPE_TERM;
		ackpkt->getHeaderPtr()->ackseq=expecting;
		ackpkt->send(nextSock, (struct sockaddr*) &nextServer, sizeof nextServer);
	}
};


int main (int argc,char *argv[])
{
	/* port nextIP nextPort filename*/
	fd_set setForSelect;

	udpport=atoi(argv[1]);
	if (listen_init(udpport)!=0) { printf("UDP Socket establish failure!\n"); return 1;};
	if (sendInit(atoi(argv[3]),argv[2])!=0) return 1;

	outfile=fopen(argv[4],"w");
	if (outfile==NULL)
	{
		printf("Erorr when reading file!");
		return 1;
	}

	expecting=0;
	termed=0;
	
	while (!termed)
	{ 
		FD_ZERO(&setForSelect);
		FD_SET(udpsock,&setForSelect);   	 
		select(udpsock+1,&setForSelect,0,0,NULL);
		if (FD_ISSET(udpsock,&setForSelect))
		{
			printf("get packet\n");
			get_packet();
		}
	};
	fclose(outfile);
	
	
	printf("Closing...\n");
	gettimeofday(&TermEnd,NULL);	
	TermEnd.tv_sec+=(TermEnd.tv_usec+TERM)/1000000;
	TermEnd.tv_usec=(TermEnd.tv_usec+TERM)%1000000;
	struct timeval temp;
	
	do
	{
		gettimeofday(&temp, NULL);
		if (TermEnd.tv_usec<temp.tv_usec)
		{
			if (TermEnd.tv_sec<=temp.tv_sec)
			{
				temp.tv_sec=0;temp.tv_usec=0;
			}
			else
			{
				temp.tv_sec=TermEnd.tv_sec-temp.tv_sec-1;
				temp.tv_usec=1000000-temp.tv_usec+TermEnd.tv_usec;
			}
		}
		else
		{
			if (TermEnd.tv_sec<temp.tv_sec)
			{
				temp.tv_sec=0;temp.tv_usec=0;
			}
			else
			{
				temp.tv_sec=TermEnd.tv_sec-temp.tv_sec;
				temp.tv_usec=TermEnd.tv_usec-temp.tv_usec;
			}			
		}		
		
		FD_ZERO(&setForSelect);
		FD_SET(udpsock,&setForSelect);  
		printf("Timeout after: %d:%d\n",temp.tv_sec, temp.tv_usec);
		select(udpsock+1,&setForSelect,0,0,&temp);
		if (FD_ISSET(udpsock,&setForSelect))
		{
			Packet* pkt=new Packet(udpsock);
			pkt->getHeaderPtr()->type&=(~TYPE_DATA);
			pkt->getHeaderPtr()->type|=TYPE_ACK;
			pkt->getHeaderPtr()->ackseq=pkt->getHeaderPtr()->seq;
			pkt->getHeaderPtr()->ackseq++;
			pkt->send(nextSock, (struct sockaddr*) &nextServer, sizeof nextServer);
		}
		else
			return 0;
	}while ((temp.tv_sec!=0)||(temp.tv_usec!=0));
};


