/*********************************************************/
/*    Autori:                                            */
/*            Pelosi Giovanni, 346787, pelosi            */
/*            Pesce  Agatino,  456868, pesce             */
/*                                                       */
/*  Progetto: Sistemi 2: biblioteca                      */
/*  Data Pr.: 9/2/96                                     */
/*      File: socket.c                                   */
/*                                                       */
/*********************************************************/

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#include "system.h"
#include "socket.h"
#include "support.h"

/*
************************************************************************
*
* Array di puntatori a descrittori dinamici di socket SocketInfo_t
*
************************************************************************
*/

static SocketInfo_t*  SockInfo[FD_MAX];

/*
************************************************************************
*
* Array statico di descrittori dei server HostInfo_t
*
************************************************************************
*/

#define      HOST_MAX                20
static       HostInfo_t      HostInfo[HOST_MAX];
static       int             HostCount=1;    /* server locale HostInfo[0] */

/*
************************************************************************
*
* Array fd_set utilizzati per select()
*
************************************************************************
*/

static fd_set    ActiveReadFds;
static fd_set    SelectReadFds;
static fd_set    ActiveWriteFds;
static fd_set    SelectWriteFds;
static int       NumFds=FD_MAX;


/*
************************************************************************
*
*   Alloca e inizializza una SocketInfo_t
*
************************************************************************
*/

int CreateSocketInfo(int Socket)
{
  SocketInfo_t*   Info;
  Info = (SocketInfo_t*) malloc(sizeof(SocketInfo_t));
  memset(Info,0,sizeof(SocketInfo_t));

  Info->Type              = SockTypeUnknown;
  Info->State             = SockStateIdle;
  Info->Linked    = -1;

  SockInfo[Socket] = Info;

  return Socket;

}

/*
************************************************************************
*
*   Crea Socket per Connect e relativo Descrittore
*
************************************************************************
*/

int CreateConnectSocket(void)
{
  int                             Socket;

  Socket = socket(PF_INET, SOCK_STREAM, 0);
  if(Socket == ERROR)
	 ErrExit("Richiesta socket");

#if defined(__HPUX_D__) || defined(_LINUS_D_)
  if (fcntl(Socket,F_SETFL,fcntl(Socket,F_GETFL) | O_NONBLOCK) == ERROR)
	 ErrExit("Errore fcntl");
#endif

  CreateSocketInfo(Socket);
  return Socket;

}

/*
************************************************************************
*
*   Crea Descrittore per Socket Accettata
*
************************************************************************
*/

int CreateAcceptedSocket(int Socket)
{

#if defined(__HPUX_D__) || defined(_LINUS_D_)
  if (fcntl(Socket,F_SETFL,fcntl(Socket,F_GETFL) | O_NONBLOCK) == ERROR)
	 ErrExit("Errore fcntl");
#endif

  CreateSocketInfo(Socket);
  return Socket;

}

/*
************************************************************************
*
*   Rilascia Descrittore Socket e chiude socket
*
************************************************************************
*/

void DestroySocket(int Socket)
{
  SocketInfo_t*   Info = SockInfo[Socket];

  if(Info == 0)
	 return;

  if ( Info->Linked > 0 )
	 SockInfo[Info->Linked]->Linked = 0;

  free(Info);

  SockInfo[Socket] = 0;

  FD_CLR(Socket, &ActiveReadFds);
  FD_CLR(Socket, &ActiveWriteFds);

  close(Socket);
}

/*
************************************************************************
*
*   ritorna contatore server
*
************************************************************************
*/

int GetHostCount(void)
{
  return HostCount;
}

/*
************************************************************************
*
*   ritorna desctittore relativo a socket
*
************************************************************************
*/

SocketInfo_t* RetrieveSockInfo(int Socket)
{
  return SockInfo[Socket];
}


/*
************************************************************************
*
*   Converte host,port in struttura sockaddr_in
*
************************************************************************
*/


static int GetRemoteHost(struct sockaddr_in* Sin,char* Hostname, char* Hostport)
{
  struct hostent  * pHE;
  struct servent  * pSE;
  int Port;

  /* 1 Cerca ed imposta la porta da usare */

  Port = atoi(Hostport);
  if (Port>0)     /*Se viene specificata la porta usa quella*/
	 Sin->sin_port=htons(Port);
  else            /*Se no sceglila in base al servizio ed al protocollo*/
	 if((pSE = getservbyname(Hostport, "tcp")) != 0 )
		Sin->sin_port=pSE->s_port;
	 else
		{
		  ErrMess("Non trovo la porta per il servizio con tcp");
		  return -1;
		}

  /* 2 Cerca ed imposta il nome del server */
  if( (pHE=gethostbyname(Hostname)) != 0) /*Nome Simbolico*/
	 {
		memcpy((char*)&Sin->sin_addr, pHE->h_addr, pHE->h_length);
		printf("Host: %s\n", pHE->h_name);
	 }
  else                            /*Dotted Decimal*/
	 if ((Sin->sin_addr.s_addr=inet_addr(Hostname)) == INADDR_NONE)
		{
		  ErrMess("GetRemoteHost: gethostbyname");
		  return -1;
		}
  Sin->sin_family=AF_INET;
  return 0;
}

/*
************************************************************************
*
*   Restituisce Port associato a Socket
*
************************************************************************
*/

int GetSocketPort(int Sock)
{
  struct sockaddr_in Sin;
  int sockaddr_in_size=sizeof(Sin);

  if(getsockname(Sock, (struct sockaddr*)&Sin,
					  &sockaddr_in_size) < 0)
	 ErrExit("GetSocketPort: getsockname Fallita");
  return ntohs(Sin.sin_port);
}

/*
************************************************************************
*
*   aggiunge host a tabella server
*
************************************************************************
*/

int AddRemoteHost(char* Hostname, char* Hostport)
{
  int h = HostCount;
  if (h >= sizeof(HostInfo)/sizeof(HostInfo[0]))
	 return -1;
  if (GetRemoteHost(&HostInfo[h].Peer,Hostname,Hostport))
	 return -1;

  HostInfo[h].State = HostStateConnectable;
  HostInfo[h].Inactive = FALSE;
  HostInfo[h].Socket = -1;

  HostCount++;
  return 0;
}

/*
************************************************************************
*
*   aggiunge host a tabella host dopo accept messaggio registrazione
*
************************************************************************
*/

int AddAcceptedPeer(int Socket, struct sockaddr_in * Sin)
{
  int h;
  for(h=1; h < HostCount ; h++)
	 if (HostInfo[h].Inactive == TRUE)
		break;
  if (h >= sizeof(HostInfo)/sizeof(HostInfo[0]))
	 return -1;
  HostInfo[h].State = HostStateAccepted;
  HostInfo[h].Inactive = FALSE;
  HostInfo[h].Peer = * Sin;
  HostInfo[h].Peer = * Sin;
  HostInfo[h].Peer.sin_family = AF_INET;
  HostInfo[h].Socket = Socket;

  if (h == HostCount)
	 HostCount++;
  return 0;
}

/*
************************************************************************
*
*   prepara una socket in listen
*
************************************************************************
*/

int PassiveSocket(int Port, int Qlen)
{
  struct sockaddr_in Sin;
  int sockaddr_in_size=sizeof(Sin);
  int Socket;

  memset( (char*)&Sin,0, sizeof(Sin) );

  /* 1 Imposta l'indirizzo a zero
	* vuol dire che se la macchina ha 2 o piu' schede di rete
	* accetto richieste su tutte
	*/
  Sin.sin_addr.s_addr=INADDR_ANY; /* 0 */

  /* 2 Imposta la porta voluta */
  if (Port>0)     /*Se viene specificata la porta usala*/
	 Sin.sin_port=htons(Port);

  /* 4 Apri una socket */
  Socket=socket(PF_INET, SOCK_STREAM, 0);
  if(Socket<0)
	 ErrExit("Passive Socket: Apertura Socket fallita");

  /* 5 Connetti la socket alla porta */

  /*
	 if ((sp = getservbyname ("example", "tcp")) != NULL)
	 Sin.sin_port = sp->s_port;
  */

  if( bind(Socket, (struct sockaddr*)&Sin, sizeof(Sin)) < 0)
	 ErrExit("Passive Socket: Fallita la Bind");

  /* 6 Se e' stata la bind a scegliere la porta la stampo a video */

  /* 7 Prepara la porta in ricezione */
  if( listen(Socket, Qlen) < 0)
	 ErrExit("Passive Socket: Fallita la Listen");

  /* 8 Initializza l'entry 0 in HostInfo */
  {
	 if(getsockname(Socket, (struct sockaddr*)&HostInfo[0].Peer,
						 &sockaddr_in_size) < 0)
		ErrExit("PassiveSocket: getsocknamr Fallita");
  }

  /* 8 Ora la socket e' pronta */
  return Socket;
}



/*
************************************************************************
*
*   prepara e connette una socket
*
************************************************************************
*/

int ActiveSocket(char* Host, char * Port)
{
  struct hostent  * pHE;
  /*        struct servent  * pSE;*/
  struct sockaddr_in Sin;

  int Socket;
  int nPort=0;

  memset( (char*) &Sin, 0, sizeof(Sin) );

  /* 1 Cerca ed imposta la porta da usare */

  nPort=atoi(Port);

  if (nPort > 0)                       /*Se viene specificata la porta usa quella*/
	 Sin.sin_port=htons(atoi(Port));
  else
	 {
		ErrMess("ActiveSocket: Porta = 0");
		return -1;
	 }

  printf("Sin.sin_port: %d\n", ntohs(Sin.sin_port));

  /* 2 Cerca ed imposta il nome del server */
  if( (pHE=gethostbyname(Host)) != 0)     /*Nome Simbolico*/
	 {
		memcpy(&Sin.sin_addr, pHE->h_addr, pHE->h_length);
	 }
  else                                                       /*Dotted Decimal*/
	 if ((Sin.sin_addr.s_addr=inet_addr(Host)) == INADDR_NONE)
		{
		  ErrMess("Host sconosciuto");
		  return -1;
		}

  /* 3 Imposta la famiglia */
  Sin.sin_family=AF_INET;

  /* 4 Apri una socket */
  Socket=socket(PF_INET, SOCK_STREAM, 0);
  if(Socket<0)
    {
		ErrMess("Richiesta socket");
		return -1;
	 }

  /* 5 Connetti la socket alla porta, con famiglia e host scelti */
  if ( connect(Socket, (struct sockaddr*)&Sin, sizeof(Sin)) < 0)
	 {
		ErrMess("Richiesta connect");
		return -1;
	 }

  /* 6 Ora la socket e' pronta */
  return Socket;
}

/*
************************************************************************
*
*   inizializza/rilascia strutture statiche
*
************************************************************************
*/

int InitSocket(void)
{
  memset(SockInfo,0,sizeof(SockInfo));
  memset(&ActiveReadFds,0,sizeof(fd_set));
  memset(&ActiveWriteFds,0,sizeof(fd_set));

  return 0;
}

int ExitSocket(void)
{
  int Socket=0;

  for(Socket=0; Socket < GetFdSetSize(); Socket++)
	 DestroySocket(Socket);  /* se Socket non c'era non fa nulla */
}

/*
************************************************************************
*
*   copia active in select per fd_set
*
************************************************************************
*/

void LoadSelectFds(void)
{
  memcpy(&SelectReadFds,  &ActiveReadFds, sizeof(fd_set));
  memcpy(&SelectWriteFds, &ActiveWriteFds,        sizeof(fd_set));
}

int GetFdSetSize(void)
{
  return NumFds;
}


/*
************************************************************************
*
*   accesso a array fd_set
*
************************************************************************
*/

fd_set* GetActiveWriteFds(void)
{
  return &ActiveWriteFds;
}

fd_set* GetActiveReadFds(void)
{
  return &ActiveReadFds;
}

fd_set* GetSelectWriteFds(void)
{
  return &SelectWriteFds;
}

fd_set* GetSelectReadFds(void)
{
  return &SelectReadFds;
}


/*
************************************************************************
*
*   impostazione token
*
************************************************************************
*/


void SetWriteToken(int Socket)
{
  SocketInfo_t * Info = RetrieveSockInfo(Socket);
  Info->State =SockStateWriting;
  FD_SET(Socket,GetActiveWriteFds());
}

void ResetWriteToken(int Socket)
{
  SocketInfo_t * Info = RetrieveSockInfo(Socket);
  Info->State =SockStateIdle;
  FD_CLR(Socket,GetActiveWriteFds());
}

void SetReadToken(int Socket)
{
  SocketInfo_t * Info = RetrieveSockInfo(Socket);

  Info->State =SockStateWaitRead;
  Info->ToRead = Info->Len = 1;
  FD_SET(Socket,GetActiveReadFds());
}

void ResetReadToken(int Socket)
{
  SocketInfo_t * Info = RetrieveSockInfo(Socket);
  Info->State =SockStateIdle;
  Info->ToRead = Info->Len = 0;
  FD_CLR(Socket,GetActiveReadFds());
}


/*
************************************************************************
*
*   accesso tabella server
*
************************************************************************
*/

HostInfo_t * RetrieveHostInfo(int Host)
{
  return &HostInfo[Host];
}

HostInfo_t * RetrieveHostBySocket(int Socket)
{
  int i;
  for ( i = 0; i < HostCount; i++ )
	 if (HostInfo[i].Inactive == FALSE)
		if (HostInfo[i].Socket == Socket)
		  return &HostInfo[i];
  return NULL;
}