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

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

#include "system.h"
#include "libro.h"
#include "message.h"
#include "socket.h"
#include "support.h"
#include "hstdio.h"

#define LINE_LEN 80

typedef      enum RcHandle_tag
  {
	 RC_Continue      =  1,     /* continua operazioni su socket */
	 RC_CloseSocket   =  0,     /* fine operazioni su socket -> close */
	 RC_Error         = -1        /* errore su trattamento socket -> close */

  } RcHandle_t;

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

#define     HOST_MAX                         3
#define     HOST_BIBLIO_MAX                 20
#define     HOST_NAME_MAX                   40

static Biblio_t ServerBiblio="LocalBiblio";
static int rc;
static int ListenSocket=0;   /* Su questa aspetto il client */

/*
************************************************************************
*
*   funzioni trattamento condizione di completamento scrittura
*
************************************************************************
*/

int HandleWrite(int Socket)
{
  SocketInfo_t* Info              =  RetrieveSockInfo(Socket);
  ResetWriteToken(Socket);        /* Intanto mi tiro via, se necessario mi ci rimetto     */
  /* Nota: Fatto per motivi di pulizia                  */
  switch (Info->Type)
	 {
	 case SockTypeClient:
		{
		  switch(Info->Control)
			 {
			 case SockCtrlLocalServer:        /* stato iniziale dopo accept */
				Info->Offset = SearchLibro(&Info->Libro,Info->Offset);

				if (Info->Offset > 0)
				  {
					 PutMsgReply(Socket,RetrieveHostInfo(0)->Biblio,
									 RetrieveLibro());

					 SetWriteToken(Socket);
					 return RC_Continue;
				  }
				else
				  {
					 if (Info->NoForward == FALSE)
						{
						  Info->Control = SockCtrlNextServer;
						  PutMsgNil(Socket);
						  SetWriteToken(Socket);
						  return RC_Continue;
						}
					 else
						return RC_CloseSocket;
				  }

			 case SockCtrlNextServer:                      /* inizializzazione               */
				/* Info->Request = -1   */
				for (++Info->Request; Info->Request < GetHostCount();Info->Request++)
				  {
					 int newServer;
					 SocketInfo_t* newInfo;

					 HostInfo_t* HostInfo =  RetrieveHostInfo(Info->Request);
					 if (HostInfo->Inactive)      /* salta host disconnessi */
						continue;

					 newServer = CreateConnectSocket();
					 newInfo   = RetrieveSockInfo(newServer);

					 newInfo->Type = SockTypeServer;

					 newInfo->Linked = Socket;
					 Info->Linked = newServer;

					 rc =  connect(newServer,(struct sockaddr*) &HostInfo->Peer,
										sizeof(struct sockaddr_in));
					 if ((rc == ERROR) && (errno != EWOULDBLOCK)) /*come gestire condizioni */
						/*di errore successive (ETIMEOUT)?*/
						{
						  Info->Linked = 0;
						  DestroySocket(newServer); /* A chiudere col server ci penso quando      */
						  continue;                 /*    becco eof su socket control                             */
						}

					 PutMsgQuery(newServer, TRUE, &Info->Libro); /*no forward = TRUE */
					 SetWriteToken(newServer);
					 Info->Control = SockCtrlContinue;       /* stato passacarte da s2 a client*/
					 return RC_Continue;
				  }
				return RC_CloseSocket;                                          /* devo chiudere per fine richieste*/


			 case SockCtrlContinue:  /* stato passacarte da s2 a client*/
				{
				  SetReadToken(Info->Linked);
				  return RC_Continue;
				}
			 }               /* Info->Control */
		}  /* SockTypeClient */

	 case SockTypeServer:                    /* dopo connect e write request a server */
		SetReadToken(Socket);    /* imposta attesa risposta da server */
		return RC_Continue;

	 case SockTypeControl:
		/* ho scritto il mio indirizzo e aspetto ormai solo eof su sconnessione */
		SetReadToken(Socket);
		return RC_Continue;
	 }
  return RC_Error; /* Non ci dovrei arrivare */
}



/*
************************************************************************
*
*   funzioni trattamento messaggio in lettura
*
************************************************************************
*/

int HandleRead(int Socket, Message_t* Msg)
{
  SocketInfo_t* Info              =  RetrieveSockInfo(Socket);
  ResetReadToken(Socket);              /* comporta FD_CLR in read  */

  switch (Info->Type)
	 {
	 case SockTypeUnknown:             /* direttamente da accept */

		switch(Msg->Type)
		  {
		  case MsgTypeControl:            /* registrazione host remoto */
			 {
				Info->Type = SockTypeControl;
				AddAcceptedPeer(Socket, &Msg->Body.Control.Sin);
				SetReadToken(Socket); /* per riconoscere caduta server*/
				return RC_Continue;
			 }
			 break;

		  case MsgTypeQuery:
			 {

				Info->Type = SockTypeClient;

				/* imposta prossima ricerca locale */

				Info->Control           = SockCtrlLocalServer;

				/* reset progressivo server  */

				Info->Request           = 0;

				/* offset ricerca locale*/
				Info->Offset            = 0;

				/* da server locale, senza successivo inoltro */
				Info->NoForward         = Msg->Body.Query.NoForward;

				/* salvataggio dati query */
				Info->Libro             = Msg->Body.Query.Libro;

				/* mette a zero ToWrite e Len           */
				PutMsgNil(Socket);      /*      per i cicli nulli di write      */
				SetWriteToken(Socket);  /*      (quelli usati per protocollo) */
				/*      comporta FD_SET in write                */
				return RC_Continue;
			 }

		  case MsgTypeError:
		  case MsgTypeEof:
		  default:

			 return RC_Error;

		  }
		break;  /* SockTypeUnknown: */



	 case SockTypeServer:

		switch(Msg->Type)
		  {
		  case MsgTypeReply:
			 {
				PutMsgReply(Info->Linked, Msg->Body.Reply.Biblio,
								&Msg->Body.Reply.Libro);
				SetWriteToken(Info->Linked);

				return RC_Continue;
			 }

		  case MsgTypeError:
		  case MsgTypeEof:

			 {
				SocketInfo_t* Client = RetrieveSockInfo(Info->Linked);

				/* end of server -> ricerca successivo*/
				Client->Control = SockCtrlNextServer;
				Client->ToWrite = 0;
				/* ritorna controllo a client con FD_SET(write)*/
				SetWriteToken(Info->Linked);

				return RC_CloseSocket;     /* close server socket */
			 }

		  }
		break;  /* SockTypeServer: */


		/* teoricamente non viene eseguita FD_SET per read client */
	 case SockTypeClient:
		return RC_Error;

		/*
		  Qui return RC_CloseSocket; fa si' che venga chiusa la socket di controllo.
		  Le eventuali socket in read sul server che e' andato giu' si chiudono
		  automaticamente da sole alla ricezione di EOF
		*/
	 case SockTypeControl:
		RetrieveHostBySocket(Socket)->Inactive = TRUE;
		return RC_CloseSocket;

	 };
  return RC_Error;        /* se sono qui qualcosa s'e' impallato */
}

/*
************************************************************************
*
*   inizializzazione server con open listen e connect a server da args
*
************************************************************************
*/

int InitServer(Biblio_t BiblioName )
{
  int ContServ=0;
  HostInfo_t * hostinfo = NULL;
  struct sockaddr_in Sin;
  int  sockaddr_in_size=sizeof(Sin);

  strcpy(RetrieveHostInfo(0)->Biblio, BiblioName);

  /*---(Listen Socket)-------------------------------------------*/
  ListenSocket=PassiveSocket(0, 5);

  FD_SET(0,                               GetActiveReadFds());    /* stdin */
  FD_SET(ListenSocket, GetActiveReadFds());

  /*---(Avviso gli altri server)----------------------------------*/

  hostinfo = RetrieveHostInfo(0); /* mi serve per i MsgControl */
  for (ContServ = 1 ; ContServ < GetHostCount() ; ContServ++)
	 {
		int newServer;
		SocketInfo_t* newInfo;

		hostinfo =  RetrieveHostInfo(ContServ);

		newServer = CreateConnectSocket();
		newInfo   = RetrieveSockInfo(newServer);
		hostinfo->Socket = newServer;

		newInfo->Type = SockTypeControl;
		newInfo->State = SockStateWriting;

		rc =  connect(newServer,(struct sockaddr*) &hostinfo->Peer,
						  sizeof(struct sockaddr_in));
		if ((rc == ERROR) && (errno != EWOULDBLOCK)) /*come gestire condizioni */
		  /*di errore successive (ETIMEOUT)?*/
		  {
			 DestroySocket(newServer); /* A chiudere col server ci penso quando      */
			 continue;                 /*    becco eof su socket control                             */
		  }

		PutMsgControl(newServer, &RetrieveHostInfo(0)->Peer);
		SetWriteToken(newServer);
	 }

  printf("Server \"%s\" Listening on Port:%d\n",RetrieveHostInfo(0)->Biblio,
			GetSocketPort(ListenSocket));
  return 0;
}

/*
************************************************************************
*
*   chiusura server
*
************************************************************************
*/

int ExitServer(void)
{
  return 0;
}

/*
************************************************************************
*
*   server
*
************************************************************************
*/

void Server(void)
{
  int ExitRequest=FALSE;  /* Richiesta terminazione del server */
  int Socket=0;

  struct sockaddr_in Sin;
  int  sockaddr_in_size=sizeof(Sin);

  while(!ExitRequest)
	 {
		/* BCOPY(src,dest,n) */

		LoadSelectFds();

		rc = select(GetFdSetSize(),FDS(GetSelectReadFds()), FDS(GetSelectWriteFds()),
						FDS((struct fd_set*)0),  (struct timeval*)0 );
		if ( rc == ERROR )
		  ErrExit("Main:Errore nella select");


		/*---(Listen Socket)-------------------------------------------*/


		if ( FD_ISSET(ListenSocket, GetSelectReadFds())) /* Richiesta di connessione */
		  {
			 int AcceptedSocket=accept(ListenSocket, (struct sockaddr *)&Sin,
												&sockaddr_in_size);
			 if (AcceptedSocket == ERROR)
				ErrExit("Main: Errore nell'accept");

			 CreateAcceptedSocket(AcceptedSocket);
			 SetReadToken(AcceptedSocket);
		  }


		/*---(Standard Input)-------------------------------------------*/


		if (FD_ISSET(0, GetSelectReadFds()))    /* stdin */
		  {
			 if ( HandleStdin() == -1 )
				{
				  ExitRequest = TRUE;
				  break;
				}
		  }

		/*---(Select Socket)-------------------------------------------*/


		for(Socket=0; Socket < GetFdSetSize(); Socket++)
		  {
			 if(Socket == 0)                                                 continue;       /* stdin */
			 if(Socket == ListenSocket)                      continue;


			 /*** READ ***/

			 if (FD_ISSET(Socket,GetSelectReadFds()))
				{
				  Message_t Msg;

				  if (ReadMessage(Socket,&Msg) == 0)
					 continue;

				  switch (HandleRead(Socket,&Msg))
					 {
					 case RC_Error:
					 case RC_CloseSocket:
						DestroySocket(Socket);
						continue;

					 default:
						continue;
					 };
				}

			 /*** WRITE ***/
			 if (FD_ISSET(Socket,GetSelectWriteFds()))
				{

				  if (WriteMessage(Socket) == 0)
					 continue;

				  switch (HandleWrite(Socket))
					 {
					 case RC_Error:
					 case RC_CloseSocket:
						DestroySocket(Socket);
						continue;

					 default:
						continue;
					 };
				}

		  }

	 }  /*!ExitRequested*/
}