/*
 * Copyright 2012 Steven Gribble.  This program is part of the cse333
 * course sequence.
 */

#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <iostream>
#include <list>
#include <sstream>
#include <string>

#include "./libhw3/QueryProcessor.h"
#include "./SocketLineReader.h"
#include "./SocketUtils.h"

// Prints out an error message describing how to use the program
// and then calls exit(EXIT_FAILURE).
void Usage(char *progname);

// Process queries arriving over the client_fd.
bool HandleClient(int c_fd, struct sockaddr *addr, socklen_t addrlen,
                  int sock_family, hw3::QueryProcessor *cp);

int main(int argc, char **argv) {
  // We expect at least a portnumber and one index file as arguments.
  if (argc < 3) {
    Usage(argv[0]);
  }

  // Create a queryprocessor, validating the indices.
  std::list<std::string> indexlist;
  for (int i = 2; i < argc; i++) {
    indexlist.push_back(argv[i]);
  }
  hw3::QueryProcessor qp(indexlist, true);

  // Create the listening socket on port argv[1].
  int listen_addr_family;
  int listen_fd = Listen(argv[1], &listen_addr_family);
  if (listen_fd == -1) {
    // We failed to bind/listen to the socket.  Quit with failure.
    std::cerr << "Couldn't bind/listen to any addresses." << std::endl;
    return EXIT_FAILURE;
  }

  // Loop forever, accepting a connection from a client and processing
  // queries arriving over it.
  while (1) {
    struct sockaddr_storage caddr;
    socklen_t caddr_len = sizeof(caddr);
    int client_fd = accept(listen_fd,
                           reinterpret_cast<struct sockaddr *>(&caddr),
                           &caddr_len);
    if (client_fd < 0) {
      // Check for "try again" vs. a real error.
      if ((errno == EAGAIN) || (errno == EINTR))
        continue;
      std::cerr << "Failure on accept: " << strerror(errno) << std::endl;
      break;
    }

    // Do the double-fork here.
    pid_t pid = fork();
    if (pid > 0) {
      // I'm the parent, pid is the child.  Wait for the child to exit().
      while (1) {
        int stat_loc;
        pid_t res = wait(&stat_loc);
        if ((res == -1) && (errno == EINTR))
          continue;
        if (res == -1) {
          std::cerr << "main process failed on wait(): ";
          std::cerr << strerror(errno) << std::endl;
          exit(EXIT_FAILURE);
        }
        // The child exited and our wait succeeded.  Break back
        // out to the main accept loop after closing the client fd.
        assert(res == pid);
        close(client_fd);
        break;
      }
    } else if (pid == 0) {
      // I'm the child. Fork a grandchild and exit.
      pid = fork();
      if (pid > 0) {
        // I'm the child.  Exit!
        exit(EXIT_SUCCESS);
      } else if (pid == 0) {
        // I'm the grandchild.  Handle the client connection.
        HandleClient(client_fd,
                     reinterpret_cast<struct sockaddr *>(&caddr),
                     caddr_len,
                     listen_addr_family,
                     &qp);

        // Break out of the main while loop when I'm done with
        // this client, so that the grandchild exits.
        break;
      } else {
        // Error in child's fork.
        std::cerr << "child process coudln't fork(): ";
        std::cerr << strerror(errno) << std::endl;
        exit(EXIT_FAILURE);
      }
    } else {
      // error in parent's fork.
      std::cerr << "main process couldn't fork(): ";
      std::cerr << strerror(errno) << std::endl;
      exit(EXIT_FAILURE);
    }
  }

  // Clean up and exit.
  close(listen_fd);
  return EXIT_SUCCESS;
}

void Usage(char *progname) {
  std::cerr << "Usage: " << progname << " portnumber index+"
            << std::endl;
  exit(EXIT_FAILURE);
}

bool HandleClient(int c_fd, struct sockaddr *addr, socklen_t addrlen,
                  int sock_family, hw3::QueryProcessor *qp) {
  bool terminate = false;

  // Print out information about the client.
  std::cout << std::endl;
  std::cout << "New client connection" << std::endl;
  PrintOut(c_fd, addr, addrlen);
  PrintReverseDNS(addr, addrlen);
  PrintServerSide(c_fd, sock_family);

  // Loop, reading queries and writing query results, until
  // error or client disconnects.
  SocketLineReader slr(c_fd);
  while (1) {
    // Get next "\n" terminated line from the client socket
    std::string line;
    if (!slr.GetNextLine(&line))
      break;

    // Split the line into words
    std::vector<std::string> words = slr.SplitIntoWords(line);
    if (words.size() == 0)
      continue;

    // See if our magic "break" word is spotted.
    if (words[0].compare("secretbreakword") == 0) {
      terminate = true;
      break;
    }

    // Process the query against the words
    std::vector<hw3::QueryProcessor::QueryResult> results =
      qp->ProcessQuery(words);

    // Build up a query result string
    std::stringstream ret;
    ret << "Results:\r\n";
    for (const hw3::QueryProcessor::QueryResult &res : results) {
      ret << " " << res.document_name << " (" << res.rank << ")";
      ret << "\r\n";
    }
    std::string retstr = ret.str();
    int retlen = retstr.size();

    // Write the query result string over the client socket
    int res = WrappedWrite(c_fd, (unsigned char *) retstr.c_str(), retlen);
    if (res != retlen)
      break;
  }

  // Clean up shop.
  std::cout << "Closing client [" << c_fd << "]" << std::endl;
  close(c_fd);
  return terminate;
}