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

#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.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);

// When we spawn a new thread, we want to pass it a bunch of arguments
// to get its work done.  But, pthread_create only allows us to pass a
// single (void *) as an argument.  So, we'll dynamically allocate one
// of these "thr_arg" structures, fill it in with all the arguments we
// want to pass in, and pass a pointer to the structure cast to a
// (void *).
typedef struct thr_arg_st {
  int c_fd;
  struct sockaddr_storage addr;
  socklen_t addrlen;
  int sock_family;
  std::list<std::string> indexlist;
} thr_arg;

// This function is where newly spawned threads begin their life.
// We will arrange to pass a (thr_arg *) as the "arg" pointer.
void *thr_fn(void *arg);

// 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]);
  }

  // Prepare the list of indices, so that we can pass it in to
  // newly created threads.  This way, each thread can create its
  // own QueryProcessor.
  std::list<std::string> indexlist;
  for (int i = 2; i < argc; i++) {
    indexlist.push_back(argv[i]);
  }

  // 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) {
    // Allocate and prepare a new "thr_arg" structure that we will
    // pass in to thr_fn() when we create the new thread.
    thr_arg *arg = new thr_arg;
    arg->addrlen = sizeof(arg->addr);
    arg->sock_family = listen_addr_family;
    arg->indexlist = indexlist;

    // Accept the next connection from a client
    arg->c_fd = accept(listen_fd,
                       reinterpret_cast<struct sockaddr *>(&(arg->addr)),
                       &(arg->addrlen));
    if (arg->c_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;
    }

    // Create the new thread, passing in the pointer to the thr_arg
    // we prepared.
    pthread_t thr;
    if (pthread_create(&thr, NULL, thr_fn,
                       reinterpret_cast<void *>(arg)) != 0) {
      std::cerr << "Failure calling pthread_create." << std::endl;
      exit(EXIT_FAILURE);
    }

    // Detach the thread so that we don't have to pthread_join to it.
    if (pthread_detach(thr) != 0) {
      std::cerr << "Failure calling pthread_detach." << 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);
}

void *thr_fn(void *arg) {
  // Recover the "thr_arg" from arg.
  thr_arg *ta = reinterpret_cast<thr_arg *>(arg);

  // Create a new QueryProcessor for this thread.
  hw3::QueryProcessor qp(ta->indexlist, false);

  // Call HandleClient to process the query.
  HandleClient(ta->c_fd,
               reinterpret_cast<struct sockaddr *>(&(ta->addr)),
               ta->addrlen,
               ta->sock_family,
               &qp);

  // We're all done with the "thr_arg", so remember to delete it.
  delete ta;

  // Pthreads allows a thread to return information back to the
  // parent that spawned it.  We don't have anything to return
  // here, so we just return NULL.
  return NULL;
}

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;
}