#ifndef _cgid
#define _cgid

/*

Copyright (C) 2011.  All rights reserved.
This software is the intellectual property of Matthew William Coan.

CGI-D (Common Gateway Interface Daemon)...

Author: Matthew William Coan
Date: Thu Sep 29 03:47:58 EDT 2011

*/

#include <iostream>

#include <cstdio>
#include <cstdlib>

//#define OS_UNIX
#define OS_WINDOWS

#ifdef OS_UNIX
#include <signal.h>
#endif

#include "tcp_stream.h"
#include "tcp_server.h"
#include "thread.h"
#include "dll_file.h"
//#include "sql.h"
#include "csv.h"

#include "cgilet.h"

using namespace std;
using namespace thread_tools;
using namespace net_tools;
using namespace csv_reader;
//using namespace sql_client;

typedef Queue< int > fd_queue_type;

fd_queue_type * p_fd_queue = 0;

tcp_server * p_tcp_server = 0;

#ifdef OS_UNIX
void
on_SIGNAL(int arg)
{
   switch(arg) {
   case SIGINT:
   case SIGTERM:
   case SIGHUP:
      if(p_fd_queue && p_tcp_server) {
         cout << "shutdown next..." << endl << flush;

         p_fd_queue->shutdown();

         p_tcp_server->close();

         cout << "done shutdown..." << endl << flush;
      }
      else {
         exit(0);
      }
   }
}
#endif

char
x2c(const char ch1, const char ch2)
{
   int ret = 0;

   char digit;

   digit = (ch1 >= 'A' ? ((ch1 & 0xdf) - 'A')+10:(ch1 - '0'));

   digit *= 16;

   digit += (ch2 >= 'A' ? ((ch2 & 0xdf) - 'A')+10:(ch2 - '0'));

   return digit;

   return ret;
}

string
x2c(const string & str) 
{
   string ret;

   for(size_t i = 0; i < str.size(); i++) {
      if(str[i] == '+')
         ret += " ";
      else if(str[i] == '%') {
         i++;
         if(i+1 < str.size()) {
            ret += x2c(str[i], str[i+1]);
            i++;
         }
      }
      else {
         ret += str[i];
      }
   }

   return ret;
}

namespace cgid {
using namespace std;
using namespace net_tools;
using namespace thread_tools;

typedef Queue< int > fd_queue_type;

enum { MAX_THREAD = 10 };

class CGIContext {
public:
   string name;
   dll_file dll;
   CGILet * cgi_ptr;
   string db;

   CGIContext(const string & n, const string & filen, const string & fn, const string & the_db)
   :name(n), dll(filen.c_str()), db(the_db) {
      typedef void* (*pf_t)();
      pf_t create_cgi = dll.get_symbol(fn.c_str());
      cgi_ptr = (CGILet*)create_cgi();
   }

   ~CGIContext() { delete cgi_ptr; dll.close(); }
};

typedef vector< CGIContext* > cgi_context_vector_type;

class CGIThread: public Thread {
   fd_queue_type * p_queue;
   dll_file * p_dll;
   cgi_context_vector_type * p_context;
   int id;
   //Connection * connection;

public:
   CGIThread(fd_queue_type * q, const int the_id, cgi_context_vector_type * ctx) {
      p_queue = q;
      id = the_id;
      p_dll = 0;
      p_context = ctx;
      //connection = 0;
   }

   ~CGIThread() {

   }

   CGIContext * find_cgi(const string & name) {
      for(size_t i = 0; i < p_context->size(); i++) {
         if(name == (*p_context)[i]->name) {
            return (*p_context)[i];
         }
      }
      return 0;
   }

   void process_request(int fd) {
      cout << "process request...\n" << flush;

      tcp_stream stream(fd);

      if(stream) {
         string form_data;

         form_data = stream.read_line();

         FormData the_form_data;

         char * buffer = new char [form_data.size()+1];

         memset(buffer, 0, form_data.size()+1);

         strcpy(buffer, form_data.c_str());

         char * ptr = strtok(buffer, "&");

         while(ptr != NULL) {
            char * name = ptr;

            char * value = strchr(ptr, '=');

            *value = '\0';

            value++;
 
            the_form_data.put(x2c(name), x2c(value));

            ptr = strtok(NULL, "&");
         }

         delete [] buffer;

         string name = the_form_data.get_value("cgilet");

         if(name.size() == 0)
            name = "hello";

         cout << "cgilet=\"" << name << "\"" << endl << flush;

         CGIContext * pContext = find_cgi(name);

         if(pContext) {
            CGILet * pCGILet = pContext->cgi_ptr;

            /*if(*connection && pContext->db != "none") {
               connection->set_catalog(pContext->db);

               pCGILet->process_request(stream, the_form_data, connection);
            }
            else {*/
               pCGILet->process_request(stream, the_form_data);
            //}
         }

         if(p_dll) {
            delete p_dll;
            p_dll = 0;
         }

         stream.close();
      }
   }

   void run() {
      int fd;

      cout << "thread #" << id << " start..." << endl << flush;

      //Connection conn("localhost", "root", "l0vey0u", "test");

      //connection = &conn;

      while(p_queue->is_running()) {
         try {
            fd = p_queue->get();

            cout << "id: " << id << flush;
            cout << " got: " << fd << endl << flush;

            process_request(fd);
         }
         catch(...) {
            cout << "CGI error thread #" << id << "..." << endl << flush;
         }
      }

      //conn.close();

      cout << "done thread #" << id << "..." << endl << flush; 
   }
};

class CGID {
   tcp_server svr;
   fd_queue_type queue;
   bool daemon_flag;
   cgi_context_vector_type context;

public:
   CGID(const char * host = "127.0.0.1", const int port = 1995, bool daemon_flag = false)
   :svr(host, port) {
      this->daemon_flag = daemon_flag;
   }

   void run() {
      ifstream cgi_file("cgi.csv", ios::in);
      if(cgi_file) {
         csv_row_type row;
         while(cgi_file >> row) {
            if(row.size() >= 4) {
               context.push_back(new CGIContext(row[0], row[1], row[2], row[3]));
            }
         }
         cgi_file.close();
      }

      int fd;
      CGIThread * p_thread;
      list< CGIThread * > thread_list;

      p_tcp_server = &svr;
 
      p_fd_queue = &queue;

#ifdef OS_UNIX
      if(daemon_flag)
         daemon(0, 0);

      signal(SIGINT, on_SIGNAL);
      signal(SIGTERM, on_SIGNAL);
      signal(SIGHUP, on_SIGNAL);
#endif

      for(size_t i = 0; i < MAX_THREAD; i++) {
         p_thread = new CGIThread(&queue, 100 + i, &context);

         thread_list.push_back(p_thread); 

         p_thread->start();
      }

      while(queue.is_running()) {
         fd = svr.accept();

         if(fd <= 0)
            break;
       
         cout << "fd: " << fd << endl << flush;

         queue.put(fd);
      }

      for(list< CGIThread* >::iterator p = thread_list.begin(); p != thread_list.end(); p++) {
         (*p)->join();
      }

      for(list< CGIThread* >::iterator p = thread_list.begin(); p != thread_list.end(); p++) {
         delete (*p);
      }

      thread_list.clear();

      for(size_t i = 0; i < context.size(); i++) {
         delete context[i];
      }
  
      context.clear();
   }
};

}

#endif /* _cgid */
