#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/poll.h>

#include <assert.h>

#include "swissdef.h"
#include "swissparse.h"
#include "swisscrypt.h"

#define MAXLISTENPENDING 50
#define LISTENEVENTS POLLIN|POLLPRI


//main loop
int loop(struct pollfd* conlst);
//making new connections
int newconnect(struct pollfd** conlst, int connections);
int delconnect(struct pollfd **clp, int i, int connections);
int clerk(int sd);


/*
  Crypto Notizen:
  * gcry_check_version
  *
 */


int main(int argc, char** argv){
      int lsock = 0;
      struct sockaddr_in my_addr;
      struct pollfd* conlst;
      int v = 1;
     
      lsock = SWISSSOCK;
  
      my_addr.sin_family=AF_INET;
      my_addr.sin_port=htons(SWISSPORT);
      my_addr.sin_addr.s_addr=INADDR_ANY;
      bzero(&(my_addr.sin_zero),8);

      if (setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(int))) {
	    perror("setsockopt: ");
	    return -1;
      }
     
      if ((bind(lsock, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in))) < 0){
	    perror("can't bind to socket");
	    return 1;
      }
      // Port ready for listening
      listen(lsock, MAXLISTENPENDING);

      // preparations for polling
      conlst = (struct pollfd*)malloc(sizeof(struct pollfd));
      conlst[0].fd = lsock;
      conlst[0].events = POLLIN|POLLPRI|POLLERR|POLLHUP;
      
      
      return loop(conlst);
      
      
    
}


//conlist has to be initiated with one socket
int loop(struct pollfd* conlst){
      int connections = 1; // number of open/listening connections 
      int nevents = 0;
      int i = 0;
      
      // main loop

      while(1){
	    
	    nevents = poll(conlst, connections, -1);
	    if(nevents > 0){
		  //eventhandling
		  if(conlst[0].revents != 0){
			// some new connection?
			connections = newconnect(&conlst,connections);
			
			conlst[0].revents = 0;
			nevents--; 
		  }
		  for(i = 1; (i < connections) && (nevents > 0); i++){
			//check all fd, do some banking
			if(conlst[i].revents != 0){
			      if(conlst[i].revents&(POLLERR|POLLHUP)){
				    connections = delconnect(&conlst, i, connections);
			      } else {
				    if(0 != clerk(conlst[i].fd)){
					  connections = 
						delconnect(&conlst, i, connections);
				    } else {
					  conlst[i].revents = 0;
				    }
			      } 
			      nevents--;
			}
			
		  }
		  
	    } 
	    else if(nevents < 0) {
		  // error happened
		  perror("polling error!");
		  exit(-1);
	    }
      }
      
      return 0;
}


int delconnect(struct pollfd **clp, int i, int connections){
      struct pollfd *new;
      close((*clp)[i].fd);
      connections--;
      new = (struct pollfd*)realloc(*clp, connections*sizeof(struct pollfd));
      if(!new){
	    perror("delconnect: failed to realloc conlst");
	    exit(errno);
      }
      *clp = new;
      return connections;
}

int newconnect(struct pollfd** conlst, int connections){
      int newfd = 0;

      if ((*conlst)[0].revents&POLLERR){
	    perror("error with listening socket");
	    return connections;
      } else {
	    if(0 > (newfd = accept((*conlst)[0].fd, 
				   NULL, 
				   NULL))){
		  perror("error accepting new customer");
		  return connections;
	    } else {
		  fprintf(stderr,"newconnect: got new connection %d\n",newfd);
		  *conlst = (struct pollfd*)realloc(*conlst, sizeof(struct pollfd)*(connections+1));
		  (*conlst)[connections].fd = newfd;
		  (*conlst)[connections].events = LISTENEVENTS;
		  connections++;
	    }
      }      
      return connections;
}



/* here comes a swiss clerk
 * it has to do access control and be a proxy to the vault 
 */

// return >0 if success ==0 if failure but connection still valid <0 if failure and connection broken

int clerk(int sd) {
       char mtype = 0;
       char dsizebuf[sizeof(uint32_t)] = {0};
       struct account acc;
       int ret = 0, i = 0;
       char credbuf[sizeof(uint32_t)+SWISS_PWDLEN] = {0};
       char *msgbuf = 0;
       int msgsize = 0;
       int result = 1;
       
       memset(&acc, 0, sizeof(struct account));
       
       //msg-type feststellen
       ret = recv(sd, &mtype, 1, 0);
       if(ret < 1){
	     perror("failed to read msgtype from socket");
	     return -1;
       }
       
       //credentials parsen
       for(ret=i=0; i < (20*sizeof(uint32_t)+SWISS_PWDLEN); i++){
	     ret += read(sd, credbuf+ret, 1);
	     if(ret < 0){
		   perror("failed to read credentials from socket");
		   return -1;
	     } else if(ret >= sizeof(uint32_t)+SWISS_PWDLEN){
		   break;
	     }
	     //usleep(2);
       }
       if(ret < sizeof(uint32_t)+SWISS_PWDLEN){
	        perror("not able to read enough from socket");
		return -1;
       }
       sw_parse_cred(&acc, credbuf);
       
       if(mtype == SW_MSG_STORE){
	     ret = recv(sd, dsizebuf, sizeof(uint32_t), 0);
	     if(ret < sizeof(uint32_t)){
		   fprintf(stderr, "failed to read dsize from socket, read %d\n", ret);
	     }
	     sw_parse_dsize(&acc, dsizebuf);
		     acc.data = (char*)malloc(acc.dsize);
	     for(ret=i=0; i < (20*acc.dsize); i++){
		   ret += read(sd, acc.data+ret, 1);
		   if(ret < 0){
			 perror("failed to read data from socket");
			 return -1;
		   } else if(ret >= acc.dsize){
			 break;
		   }
		   //usleep(2);
	     }
	     if(ret < acc.dsize){
		   perror("not able to read enough data from socket");
		   return -1;
	     }
	     
	     ret = store(&acc);	     
	     
	     //compose answer
	     msgbuf = (char*)malloc(msgsize=1+sizeof(uint32_t));
	     if(ret > 0){
		   //answer SW_MSG_SREP,acc.acc_id
		   *msgbuf = SW_MSG_SREP;
		   *((uint32_t*)(msgbuf+1)) = htonl(acc.acc_id);
	     } else {
		//answer error
		   *msgbuf = SW_MSG_ERR;
		   *(msgbuf+1) = acc.acc_id<=0?SW_ERR_NOACCOUNT:SW_ERR_SRV;
	     }
	     
	     
       } else if(mtype == SW_MSG_RETR){
	     //RETRIEVE
	     
	     ret = retrieve(&acc);
	     if(ret > 0){
		   msgbuf = (char*)malloc(msgsize=1+sizeof(uint32_t)+acc.dsize);
		   *msgbuf = SW_MSG_RREP;
		   sw_marsh_dsize_data(&acc, msgbuf+1);
	     } else {
		   msgbuf = (char*)malloc(msgsize=1+sizeof(uint32_t));
		   *msgbuf = SW_MSG_ERR;
		   *((uint32_t*)msgbuf+1) = ret==(-3)?SW_ERR_AUTH:SW_ERR_SRV;
	     }
       } else {
	     // wrong type of message
	     msgbuf = (char*)malloc(msgsize=1+sizeof(uint32_t));
	     *msgbuf = SW_MSG_ERR;
	     *(msgbuf+1) = SW_ERR_CLIENTMSG;
	     result = -1;
       }
       
       

       //abschicken
       if(msgsize > send(sd, msgbuf, msgsize, 0)) result = (-1);

       if(acc.data) free(acc.data);
       if(msgbuf) free(msgbuf);

       return result;
 }
 
