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

/* Kompiler: gcc -Wall -lsocket -lnsl route1.c -o route1*/

              /**************************************/
              /* -------- DEKLARASJONER ----------- */
              /**************************************/

/* M1-M5 er maskinene i nettverket*/
#define M1 "grotte.ifi.uio.no\0"
#define M2 "dromi.ifi.uio.no\0"
#define M3 "gram.ifi.uio.no\0"
#define M4 "naglfar.ifi.uio.no\0"
#define M5 "hridil.ifi.uio.no\0"

/*#define DEBUG*/
#define PORTNR 1024
#define MAX_PENDING 5
#define MAX_LINE 32


/* En struktur som inneholder socket-identifikasjon på en forbindelse */
typedef struct {
  int sock;
  struct sockaddr_in dest_addr;
  int  sekvinn;
  int  sekvut;

}linksession;

/*Denne struckten innholder informasjon om alle maskiner på nettverket*/
typedef struct{
  int akt_open[6];
  int address[6];
  int via[6];
  char *machine[6];
  linksession *lls[6];
}participants;


/*En struktur som bygger lenkende lister.*/
/* Den skal inneholde pekere til alle aktive linksessioner*/
struct aktive_sessions{
  linksession *lls;
  struct aktive_sessions *next;
};


/*en linket liste som inneholder pekere til alle linksessioner*/
struct aktive_sessions *akt_sess;

/*en peker til en participants struktur*/
participants *nett;

/*en global variable som identifiserer maskinen du sitter på*/
int maskin;

/*en global variabel for socketen, som gjør en passiv_open*/
int req_sock;

/* En global variabel , som brukes av avsenderen for å se om acken er mottat */
char* venterAck;

/*oppretter en participants struct*/
participants* NewParticipantsStruct();


/*Når maskinen starter, bygges det opp en rutingtabell, ved hjelp av */
/*participants* NewParticipantsStruct() og AddParticipant*/

/*legger inn opplysninger om nye maskiner nettet*/
void AddParticipant( participants *p, char *machine,int i, int activeopen, int via );

/*Tar en peker til participants som parameter og adressen til ruteren*/
int Ropen( participants *p, int ident );

/*Rnewlink kalles når det gjøres en activopen mot oss*/
int Rnewlink( void );

/*parametrene participants* vil her inneholde ruting tabellen*/
/* til variabelen int i som er maskinen programmet kjøres på*/
participants* init_nett(participants *p, int i);


/*Oppretter tjenestene til  serveren. Til dette benyttes portnummeret*/
int Lopenenable(const int portnummer);

/*Mottar socket fra Lopenenable som en parameter.*/
linksession* Lopendone(int sock);


/*oppretter tjenestene til clienten,sender med portnummer*/
/*som forteller hvilke port det skal kommuniseres igjennom og*/
/*maskine som forteller hvilke maskiner det skal være kontakt*/
/*mellom*/ 
linksession* Lopen(const int portnummer, const char* maskin);

ssize_t uswrite(int fildes, const void *buf, size_t nbyte);

/*Finner ut hvilken adresse meldingen i buf[] skal til og*/
/* skriver inn sin maskins adresse*/
void Rpush( char buf[] );


/*Rdemux videresender meldinge- char *buf[] til Rpop*/
void Rdemux( char *buf, linksession *lls );


/* Får en melding - char *buf[] - fra Rdemux. */
/* hvis meldingen er en addressert hit sendes den til skjerm*/
/*hvis ikke sendes den til Rpush*/
void Rpop( char *buf,linksession *lls );

/*Lpush mottar data fra tastaturet, og bestemmer størrelsen på utbufferet,*/
/* sender så bufferet parameteret destination forteller hvilken link*/
/* det er  som skal motta meldingen*/
void Lpush(char buf[], linksession* destination);

/*Mottar data fra nettverket,sender innbufferet til Rdemux*/
/*parameteret sender forteller hvilken link det er som har send meldingen*/
void Lpop(char buf[], linksession* sender, int length);

/* Ldemux finner hvilken linksession sock tilhører og sender denne linksession til Lpop*/
void Ldemux( char buf[], int sock, int length );

/*ctoi oversetter et char till til et int tall*/
int ctoi( char t );

/*oversetter fra int til char*/
char itoc( int t );



                  /***********************************/
                  /* ------- HOVEDPROGRAMMET ------- */
                  /***********************************/




int
main(int argc, char *argv[]){
  int bufLen;
  fd_set mask;
  static struct timeval timeout = { 1,0 };
  char buf[MAX_LINE];
  char buf1[256]; /* leser inn alt som brukeren taster inn */
  venterAck = NULL;


   if (argc == 2)
     {
       maskin = atoi(argv[1]);
       nett = NewParticipantsStruct();
       init_nett(nett,maskin);
       
       akt_sess = NULL;
       req_sock = Ropen(nett, maskin);
       
       
       /* Programmet går i uendelig løkke , avsluttes ved å taste C-x C-c */
       for (;;) 
	 {
	   
	   struct aktive_sessions *sessions;
	   
	   FD_ZERO(&mask);
	   FD_SET(req_sock, &mask); 
	   FD_SET(fileno(stdin), &mask);
	   
	   for ( sessions = akt_sess; sessions; sessions = sessions->next )
	     {
	       FD_SET( sessions->lls->sock, &mask );
	     }
	   /* Sjekker om det er kommet noe inn */
	   select(FD_SETSIZE, &mask, (fd_set *)0, (fd_set *)0, &timeout);
	   
	   /* Noen gjør active open mot maskinen */
	   if ( FD_ISSET( req_sock, &mask ) )
	     {
		   Rnewlink( );
	     }
	   
	   /* Data fra tastatur */
	   if(FD_ISSET(fileno(stdin), &mask)) 
	     {
	       
	       bufLen = read(fileno(stdin),buf1, 255); 
	       buf1[bufLen] = 0;
	       bufLen++;
	       sprintf(buf,"%c%c%s", 'm','0', buf1);
	       Rpush(buf);

	       bufLen = 0; 
	      
	       
	       
	     } /* end of if FD_ISSET (tastatur) */
	       
	   for ( sessions = akt_sess; sessions; sessions = sessions->next )
	     {
	       /* Data fra nettverk (enten ACK eller ny melding) */
	       if ( FD_ISSET( sessions->lls->sock, &mask ) )
		 {
		   bufLen = read(sessions->lls->sock, buf, MAX_LINE);
		   Ldemux( buf, sessions->lls->sock,bufLen );
		 } /* end of if FD_ISSET (nettverk) */
	     }
	 } /* end of uendelig løkke */ 
     } /* end of if (argc == 2) */ 
   else 
     {
       fprintf(stderr, "Usage: route maskinNr\n");
       exit(1);
     }
   
return 0;    
   
}/* end of main */





             /**************************************/
             /*  ----------- NETT-LAGET ---------- */
             /**************************************/


/*NewParticipantsstruct oppretter en participants struct*/


participants*
NewParticipantsStruct(){
  int i;
  participants *new;
  new = malloc(sizeof(participants));
  for (i = 1; i <= 5; i++){
    new->akt_open[i]=0;
    new->address[i]='0';
    new->via[i] = '0';

  }/* for */
  return new;

}/* end of NewParticipantsStruct() */



/*ADDPARTICIPANT: */
/*tar med en peker til participants som parameter for oppdatere */
/*variablene i participants strukten. Machine er navnet på maskinen som skal*/
/*legges inn, variblen i brukes som adressen som er et tegn som identifiserer*/
/*maskinen. Via forteller hvilke maskiner den om innom for å sende pakkene*/
/*denne maskinen. Parameteret activopen inneholder informasjon om det skal */
/*gjøres en activopen på en link til denne maskinen*/

void 
AddParticipant( participants *p, char *machine, 
                int i, int activeopen, int via ){

  p->machine[i] = malloc( strlen( machine ) );
  strcpy( p->machine[i],machine );
  p->akt_open[i] = activeopen;
  p->address[i] = i;
  p->via[i] = via;

}/* end of AddParticipant() */




/*Ropen kaller Lopen med parameter portnr og maskinnavn*/
/*Ropen får en liksession fra Lopen, som tar en peker til paricipants som*/
/*parameter, og informasjone om denne ruteren sin adress*/

int 
Ropen( participants *p, int ident ){
  int id;
  linksession *ls;
  struct aktive_sessions *tmp_sessions;
  

  for ( id = 1; id <= 5; id++ ){
    ls = NULL;
    if ( nett->akt_open[id] ){

      
      while ( ls == NULL ){ ls = Lopen( PORTNR,p->machine[id] ); }
      tmp_sessions = malloc( sizeof( struct aktive_sessions ) );
      tmp_sessions->lls = ls;
      
      if ( !akt_sess ){
        akt_sess = tmp_sessions;
        akt_sess->next = NULL;
      }
      else {
        tmp_sessions->next = akt_sess;
        akt_sess = tmp_sessions;
      }

    }

    p->lls[id] = ls;

  }
  
  
  return  ( req_sock > 0 ) ? req_sock : Lopenenable( PORTNR );

}/* end of Ropen() */



/*Rnewlink kalles nå det gjøres ative opens mot oss.*/
/*Den oppretter en ny linksession ved hjelp av LopenDone*/
/*Den legger linksessionen inn i akt_sess og den riktige participanten*/


int 
Rnewlink( void ){

  /* linksession *ls er den nye linken. */
  linksession *ls;

  /* 
   * new_sock blir den socketen til den nye linken
   * id er indexen i nettverks topologien denne nye linken vil ha.
   * Rnewlink returnerer id
   */
  int new_sock, i,id;

  struct hostent *remote;

  /*
   * hjelpevariable som sørger for å oppdatere akt_sess
   */
  struct aktive_sessions *tmp_sessions;


  new_sock = req_sock;
  ls = Lopendone( new_sock );

  remote = gethostbyaddr( ( char* )&ls->dest_addr.sin_addr,
                          sizeof( struct in_addr ), AF_INET );
  printf(" **** kontakt fra : %s ****\n",remote->h_name );

  id = 0;
  for ( i = 1; i <= 5 ; i++ ){
    if ( ! strcmp( remote->h_name, nett->machine[i] ) ){
      
      id = i;
    }
  }
  
  tmp_sessions = malloc( sizeof( struct aktive_sessions ) );
  tmp_sessions->lls = ls;
  
  if ( !akt_sess ){
    akt_sess = tmp_sessions;
    akt_sess->next = NULL;
  }
  else {
    tmp_sessions->next = akt_sess;
    akt_sess = tmp_sessions;
  }
  
  nett->lls[id] = ls;
  return id;
  
} /* end of Rnewlink () */




/*Rpush finner ut hvilken adresse meldingen i buf[] skal til*/
/*I tillegg skriver Rpush inn sin maskins adresse*/
/*Meldingen sendes videre til Lpush*/
/*Skal passe på å sende videre på riktig link*/
void 
Rpush( char buf[] ){

  int i, via;
  i = 1;

#ifdef DEBUG

  printf("Sender: %s", buf);

#endif

  /* Sjekker addressen til mottakeren */
  while( ( ctoi(buf[2]) != nett->address[i] ) && i <= 5 ){ i++;}

  via = nett->via[i];

  if ( ( i > 0 ) && ( i < 6) && ( i != maskin ) ) { 
    if ( nett->lls[via] != NULL ) 
      {

#ifdef DEBUG

  printf("via: %d\n", via);

#endif
	Lpush( buf, nett->lls[via] ); 
      }
    else
      {
	printf( "%s er ikke på nett!\n",nett->machine[via] ); 
	buf = '\0';
      }
  }
  else { buf = '\0'; }


} /* end of Rpush() */



/*Rdemux videresender meldinge- char *buf[] til Rpop*/

void 
Rdemux( char *buf, linksession *lls ){

  Rpop( buf,lls );

} /* end of Rdemux() */




/* Rpop får en melding - char *buf[] - fra Rdemux. */
/* hvis meldingen er en addressert hit sendes den til skjerm*/
/*hvis ikke sendes den til Rpush*/

void 
Rpop( char *buf,linksession *lls ){  
  if( ctoi(buf[2]) == maskin ) { printf( "%c:%s\n",(char)buf[2],&buf[3] ); }
  else { Rpush( buf ); }

} /* end of Rpop()*/
		   



/*init_network kaller addparticipant med parametre avhengig av hvilke maskin*/
/*programmet kjøres på. *p vil inneholde rutetabellen for denn maskinen*/
	    

participants*
init_nett(participants *p, int i){

   if ( i == 1 ){
    AddParticipant( p, M1, 1 , 0 , 1 );
    AddParticipant( p, M2, 2 , 1 , 2 );
    AddParticipant( p, M3, 3 , 1 , 3 );
    AddParticipant( p, M4, 4 , 0 , 3 );
    AddParticipant( p, M5, 5 , 0 , 3 );
  }

  else if ( i == 2 ){
    AddParticipant( p, M1, 1 , 0 , 1 );
    AddParticipant( p, M2, 2 , 0 , 2 );
    AddParticipant( p, M3, 3 , 1 , 3 );
    AddParticipant( p, M4, 4 , 0 , 3 );
    AddParticipant( p, M5, 5 , 0 , 3 );
  }
  else if ( i == 3 ){
    AddParticipant( p, M1, 1 , 0 , 1 );
    AddParticipant( p, M2, 2 , 0 , 2 );
    AddParticipant( p, M3, 3 , 0 , 3 );
    AddParticipant( p, M4, 4 , 1 , 4 );
    AddParticipant( p, M5, 5 , 0 , 4 );
  }
  else if( i == 4 ){
    AddParticipant( p, M1, 1 , 0 , 3 );
    AddParticipant( p, M2, 2 , 0 , 3 );
    AddParticipant( p, M3, 3 , 0 , 3 );
    AddParticipant( p, M4, 4 , 0 , 4 );
    AddParticipant( p, M5, 5 , 1 , 5 );
    }
  else if( i == 5 ){
    AddParticipant( p, M1, 1 , 0 , 4 );
    AddParticipant( p, M2, 2 , 0 , 4 );
    AddParticipant( p, M3, 3 , 0 , 4 );
    AddParticipant( p, M4, 4 , 0 , 4 );
    AddParticipant( p, M5, 5 , 0 , 5 );
  }

  return p;

}/* end of init_nett(participants *p, int maskin) */


                  /**************************/
                  /*------ LINK-LAGET ------*/
                  /**************************/


/*oppretter tjenestene til clienten,(utfører en activ open)*/ 
/*sender med portnummer som forteller hvilken port det skal kommuniseres*/ 
/*mellon og maskin navn som sier hvilken maskin det skal være kontakt med*/
/* som parametre.*/


linksession*
Lopen(const int portnummer, const char *maskin)
{
  linksession* session_to_return;
  struct hostent *hp;
  struct sockaddr_in sin;
  

  hp=gethostbyname(maskin);
  if(!hp){
    fprintf(stderr, "simlpex-talk: unknown host: %s\n",maskin );
    exit(1);
  }
  
  
  bzero((char *)&sin, sizeof(sin));
  sin.sin_family = AF_INET;
  bcopy(hp->h_addr, (char *)&sin.sin_addr, hp->h_length);
  sin.sin_port = portnummer;
  
  
  session_to_return = (linksession*)malloc(sizeof(linksession));
   session_to_return->sock = socket(PF_INET, SOCK_STREAM, 0);
   session_to_return->sekvinn = 0; /* Den første pakken vi mottar skal ha sekvnr 0 */
   session_to_return->sekvut = 0  ; /* Den første pakken vi sender skal ha sekvnr 0 */

  if (session_to_return->sock < 0){
    perror("client: socket");
    exit(1);
  }
  
  
  if(connect(session_to_return->sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
    perror("client: connect");
    close(session_to_return->sock);
    exit(1);
  }
  
  return session_to_return;
}/* end of Lopen-method */




/*Oppretter tjenestene til  serveren. Serveren er passiv.*/ 
/*Den lytter etter innkommende oppkoblinger.*/
/* Til dette benyttes portnummeret. */
/*Portnummeret forteller hvilke port serveren er villig til å lytte på.*/
/* Returnerer socketen*/

int
Lopenenable(const int portnummer)
{
  struct sockaddr_in sin;
  int ret_socket;
  
  bzero((char *)&sin, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = INADDR_ANY;
  sin.sin_port = portnummer;
  
  if ((ret_socket = socket(PF_INET, SOCK_STREAM, 0)) <0) {
    perror("server: socket");
    exit(1);
  }
  
  if ((bind(ret_socket, (struct sockaddr *)&sin, sizeof(sin)))< 0){
    perror("server: bind");
    exit(1);
  }
  
  listen(ret_socket, MAX_PENDING);
  
  return ret_socket;
}/*end of Lopenenable */




/*Mottar socket fra Lopenenable som en parameter.*/
/*Lopendone oppretter serveren og sørger for å sende */ 
/*en bekrftelse til clienten. Binder forbindelsen mellom */
/*client og server,ved å fortelle hvilke socket som blir benyttet.*/

linksession*
Lopendone(int sock)
{
  linksession *ret_session;
  struct sockaddr_in remote; /* addresse til en klient som tar kontakt */
  int len;
  
  ret_session = (linksession*)malloc(sizeof(linksession));
  len = sizeof( remote );
  ret_session->sock = accept(sock,(struct sockaddr*)&ret_session->dest_addr, &len);
  
  return ret_session;
}/* end of Lopendone*/
 



/*Lpush mottar data fra tastaturet, og bestemmer størrelsen på utbufferet,*/
/* sender så bufferet parameteret destination forteller hvilken link*/
/* det er  som skal motta meldingen*/

void 
Lpush(char buf[], linksession* destination){
  char *buf1 = (char *)malloc(MAX_LINE);/* Setter av minne til bufferet */

  /* Setter sekvut i header, for å vise at dette er en melding */
  sprintf(buf1,"%s", buf);
  buf1[1] = itoc(destination->sekvut);
  
  buf1[31] = 0;

  /* Sender ramma med uswrite */
  uswrite(destination->sock, buf1, MAX_LINE);

#ifdef DEBUG

  printf("Sender: %s", buf1);

#endif

  venterAck = buf1;
  
  /* Man kan ikke sende noe , hvis man venter en ACK */
	       
	       while(venterAck)
		 {
		   fd_set mask;
		   static struct timeval timeout = { 1,0 };
		   timeout.tv_sec = 1;
		   timeout.tv_usec = 0; /* 1 sekund */
		   FD_ZERO(&mask);
		   FD_SET(destination->sock, &mask); 
		       
		   /* Sjekker om det er kommet noe fra nettverk */
		   /* mens vi venter på Ack. Det kan enten være Ack */
		   /* eller en ny pakke */
		   
		   select(FD_SETSIZE, &mask, (fd_set *)0, (fd_set *)0, &timeout); 
		   
		   /* Dersom vi har fått noe fra nettverk */
		   if (FD_ISSET(destination->sock,&mask)) 
		     {
		       read(destination->sock, buf, MAX_LINE);
		       Lpop(buf,destination,MAX_LINE);
		     }
		   /* time-out, sender pakken på nytt */
		   else 
		     {
		       uswrite(destination->sock,venterAck, MAX_LINE);
		       
#ifdef DEBUG
		       printf("Sender pakken en gang til.\n");
		       
#endif
		       
		     }/* end of if-else */
		       
		 }/* end of while */


  return; 

}/* end of Lpush */




/*Mottar data fra nettverket,sender innbufferet til Rdemux*/
/*parameteret sender forteller hvilken link det er som har send meldingen*/
  
void 
Lpop(char buf[], linksession* sender, int length){
  /*char ack [MAX_LINE]; */
  
  /* Sjekker om dette var en Ack - Ja*/
  if (buf[0] == 'a' && venterAck != NULL)
    {

#ifdef DEBUG

      printf("Acken er mottat\n");
      
      printf("buf[1] = %d\n", ctoi(buf[1]));
      printf("buf[1] = %d\n", ctoi(buf[1]));
#endif
      if (ctoi(buf[1]) == sender->sekvut)
	{
	  /* Endrer sekvut fra 0 til 1 eller omvendt*/

	  sender->sekvut = sender->sekvut ^ (char)1;  
	  free(venterAck);
	  venterAck = NULL;	  
	}/* end of if (buf[1] == sekvut) */
      else
	{
	  free(venterAck);
	  venterAck = NULL;	
	}
      
    }/* end of if (buf[0] == 'a' &...) */
  
  
  /* Dette var ikke Acken...Det var meldingen. Må sende Ack tilbake.*/
  else
    {
      buf[0] = 'a';
      /*sprintf (ack,"a%c", buf[0]);*/
      uswrite(sender->sock, buf, MAX_LINE);

      /* Sjekker om vi har mottat pakken fra før */
      if(ctoi(buf[1]) == sender->sekvinn)
	{
	  Rdemux(buf, sender);

	  sender->sekvinn = sender->sekvinn ^ (char)1;
	}
      else
	{
	  /* Kaster pakken */
	}

#ifdef DEBUG

      printf("Acken er sendt\n");

#endif
    }/* end of else */

  return;
  
}/* end of Lpop */

/*Ldemux tar imot bufferet og bufferets lengde som parameter, og den tar imot*/
/* informasjon om hvilke socket dataen kom inn på*/
/* Ldemux finner hvilken linksession sock tilhører og sender denne linksession til Lpop*/

void
Ldemux( char buf[], int sock, int length ){
  
  struct aktive_sessions *sess; 

  sess = akt_sess;
  while ( sess->lls->sock != sock ){ sess = sess->next; }
  
  Lpop( buf,sess->lls, length );
}/* end of Ldemux */



ssize_t
uswrite(int fildes, const void *buf, size_t nbyte)
{
  if( rand()%2)
    {
      write(fildes, buf, nbyte);
    }
  return 1;
}/* end of uswrite */



/*ctoi oversetter char til et int tall*/

int 
ctoi( char t ){
  int i;
  
  if ( t == '1' ) i = 1;
  else if ( t == '2' ) i = 2;
  else if ( t == '3' ) i = 3;
  else if ( t == '4' ) i = 4;
  else if ( t == '5' ) i = 5;
  else if ( t == '0' ) i = 0;
  else i = -1;
  return i;
}/* end of ctoi() */




 /*itoc oversetter int til et char*/
char 
itoc( int t ){
  char i;
  
  if ( t == 1 ) i = '1';
  else if ( t == 0 ) i = '0';
  return i;
}/* end of itoc() */













