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

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define ERR(a) fprintf(stderr, "Error: %s\n", a)

uint32_t fib(uint32_t n)
{
   if (n <= 2)
      return 1;
   else
      return fib(n-1) + fib(n-2);
}

int main()
{
   // server variables
   int server;
   struct addrinfo hints, *info;

   // client variables
   int client;
   struct sockaddr_in client_addr;
   socklen_t addr_size;

   // calculation variables
   uint32_t n, fib_n;


   // Get the address & some socket information
   memset((void *) &hints, 0, sizeof(hints));      // zero out the structure
   hints.ai_family = AF_INET;          // use IPv4
   hints.ai_socktype = SOCK_STREAM;    // give me a "stream socket" rather than a "datagram socket"
   hints.ai_flags = AI_PASSIVE;        // fill in my IP for me

   getaddrinfo(NULL, "4000", &hints, &info);    // Get me the information for that address, and let the port be 4000

   // Get an actual network socket
   server = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
   if (server < 0)
   {
      ERR("could not get a socket!");
      return -1;
   }

   // Bind the socket to the desired address to be able to listen for incoming connections
   if (bind(server, info->ai_addr, info->ai_addrlen) < 0)
   {
      ERR("could not bind to socket!");
      return -1;
   }

   // Start listening for incoming connections and allow 5 connections at a time
   // in the backlog
   if (listen(server, 5) < 0)
   {
      ERR("problem in listening!");
      return -1;
   }

   // Set things up for the incoming client
   addr_size = sizeof(client_addr);

   // Accept a single client
   client = accept(server, (struct sockaddr *) &client_addr, &addr_size);
   if (client < 0)
   {
      ERR("could not accept client socket!");
      return -1;
   }

   do
   {
      recv(client, (void *) &n, sizeof(uint32_t), 0);    // receive a message from the client
      // NOTE: you should check the reutrn result of recv to see how many bytes
      // were received or if there was an error. But we will skip that for now

      // NOTE: usually the data received is in "network order" and should be
      // changed to "host order". This will be explained in the next lecture,
      // and is skipped for now

      printf("Received %d from the client\n", n);

      fib_n = fib(n);
      send(client, (void *) &fib_n, sizeof(uint32_t), 0);   // send a message to the client
      // NOTE: as before, you should check the return of send to see how many
      // bytes were sent and if there were any errors. But we will skip that now

      printf("Sent fib(%d) = %d to the client\n", n, fib_n);
   } while(n > 0);

   // We are done! Close things up
   close(client);
   close(server);

   return 0;
}