/************************************************************/
/*                                                          */
/*  The cpp file of sender program                          */
/*  For CS 145 (Fall 2002) Lab 4                            */
/*  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 sender*/
unsigned short int udpport;
int udpsock;



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

FILE *infile; //Input file. 

struct timeval lastAck;

SendQueue * queue;

/*Initialize the transmission/retransmission queue */
void initQueue()
{
	queue=new SendQueue();
}


/*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;
};


/* Reset the Retransmission timer */
void resetTimer()
{
	gettimeofday(&lastAck,NULL);
}

//roll back if you have a loss
void retransmit()
{
	queue->retransmit();
	resetTimer();
};


void calculateRTO(struct timeval *to)
{
	struct timeval temp;
	gettimeofday(&temp, NULL);
	to->tv_usec=(lastAck.tv_usec+RTO)%1000000;
	to->tv_sec=lastAck.tv_sec+(lastAck.tv_usec+RTO)/1000000;
	if (to->tv_usec<temp.tv_usec)
	{
		to->tv_usec+=1000000;
		to->tv_sec--;
	};
	if (to->tv_sec<temp.tv_sec)
	{
		to->tv_sec=0;
		to->tv_usec=0;
	}
	else
	{
		to->tv_sec-=temp.tv_sec;
		to->tv_usec-=temp.tv_usec;
	}
}

/* When we get an ack...*/
void ackProcess(u_int32_t ackseq)
{
	queue->cleanAcked(ackseq);
	resetTimer();
};


/* get an incoming packet */
void get_packet()
{
	Packet* pkt=new Packet(udpsock);
	if (pkt->getHeaderPtr()->type&TYPE_ACK)
	{
		ackProcess(pkt->getHeaderPtr()->ackseq);
	}
};

int finished()
{
	return (((infile==NULL) || (feof(infile))) && (queue->isEmpty()));
};

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;

	infile=fopen(argv[4],"r");
	if (infile==NULL)
	{
		printf("Erorr when reading file!");
		return 1;
	}
	printf("Init queue\n");
	initQueue();
	printf("resetting timer\n");
	resetTimer();
	
	//sendto(nextSock,"abc",3,0,(struct sockaddr *)&nextServer,sizeof nextServer);
        /*printf("%d sent\n",rval);*/


	
	while (!finished())
	{ 
		printf("Filling queue\n");
   	 	queue->tryToFill(infile);
   	 	printf("sending\n");
   	 	queue->tryToSend(nextSock, (struct sockaddr*) &nextServer, sizeof nextServer);
   	 	
		struct timeval to;
		calculateRTO(&to);
		printf("RTO: %d %d\n",to.tv_sec,to.tv_usec);
		
		FD_ZERO(&setForSelect);
		FD_SET(udpsock,&setForSelect);   	 
		select(udpsock+1,&setForSelect,0,0,&to);
		if (FD_ISSET(udpsock,&setForSelect))
		{
			get_packet();
		}
		else
		//RTO
		{
			retransmit();
		}
	};
	fclose(infile);
	printf("Closing...\n");
	Packet* pkt=new Packet();
	pkt->getHeaderPtr()->type=TYPE_TERM2;
	pkt->send(nextSock, (struct sockaddr*) &nextServer, sizeof nextServer);
};


