/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            server.cc
 *
 *  Wed Aug 22 12:16:03 CEST 2007
 *  Copyright 2007 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of Pracro.
 *
 *  Pracro is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  Pracro is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Pracro; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "server.h"

#include "tcpsocket.h"
#include <errno.h>

#include <stdlib.h>

// For fork
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#include "configuration.h"
#include "transaction.h"
#include "transactionparser.h"
#include "templateparser.h"
#include "macroparser.h"

#include "queryhandler.h"
#include "queryhandlerpracro.h"
#include "queryhandlerpentominos.h"

#include "queryparser.h"
#include "luaquerymapper.h"
#include "database.h"
#include "widgetgenerator.h"
#include "resumeparser.h"
#include "journal_commit.h"
#include "xml_encode_decode.h"

static std::string error_box(std::string message)
{
  std::string errorbox =
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    "<pracro version=\"1.0\">\n"
    "  <course name=\"error\">\n"
    "    <macro name=\"error\" static=\"true\">\n"
    "      <window caption=\"ERROR!\" layout=\"vbox\" name=\"error\">\n"
    "        <textedit name=\"errorlabel\" value=\"" + message + "\"/>\n"
    "      </window>\n"
    "    </macro>\n"
    "  </course>\n"
    "</pracro>\n";
  return errorbox;
}

class NotFoundException : public Exception {
public:
  NotFoundException(Request &r)
  : Exception("Macro " + r.macro + " not found in course " + r.course) {}
};

static std::string handleTransaction(Transaction *transaction,
                                     TCPSocket *pentominos_socket,
                                     Database *db)
{
  std::string answer;
  answer += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  answer += "<pracro version=\"1.0\">\n";

  try {
    //
    // Handle commits
    //
    {
      Commits::iterator i = transaction->commits.begin();
      while(i != transaction->commits.end()) {
        Commit &commit = *i;
        
        MacroParser mp(commit.macro);
        mp.parse();
        Macro *macro = mp.getMacro();

        std::string resume = resume_parser(macro->resume, commit);
	commit.fields["journal.resume"] = resume;
        db->commitTransaction(transaction->user, transaction->cpr, *macro, commit.fields);
        

        bool store_in_journal = true;
        // We always need to store in journal!
        // macro->resume.attributes.find("store_in_journal") != macro->resume.attributes.end() &&
        // macro->resume.attributes["store_in_journal"] == "true";

        if(resume != "" && store_in_journal) {
          journal_commit(transaction->cpr.c_str(), transaction->user.c_str(),
                         Conf::journal_commit_addr.c_str(), Conf::journal_commit_port,
                         resume.c_str(), resume.length());
        }

        i++;
      }
    }
        
    //
    // Handle requests
    //
    Requests::iterator i = transaction->requests.begin();
    while(i != transaction->requests.end()) {
      Request &request = *i;

      PRACRO_DEBUG(server, "Handling request - macro: %s, course: %s\n",
             request.macro.c_str(), request.course.c_str());

      // Read and parse the template file.
      TemplateParser tp(request.course);
      tp.parse();
      
      Template *templ = tp.getTemplate();

      answer += "  <course name=\"";
      answer += templ->course.attributes["name"];
      answer += "\">\n";
      
      bool foundmacro = false;

      // Generate the macro and return it to the client
      std::vector< Macro >::iterator mi2 = templ->course.macroes.begin();
      while(mi2 != templ->course.macroes.end()) {
        Macro &macro = (*mi2);

        // FIXME: This is to be made in some other way in a later version.
        if(macro.attributes.find("header") != macro.attributes.end()) {
          // Macro is a special headline macro
          // Simply output a headline, and ignore the rest of it.
          answer += "    <macro header=\"" + macro.attributes["header"] + "\"></macro>\n";
          mi2++;
          continue;
        }

        bool completed = db->checkMacro(transaction->cpr, macro.attributes["name"]);
        
        answer += "    <macro completed=";
        if(completed) answer += "\"true\"";
        else answer += "\"false\"";

        std::map< std::string, std::string >::iterator ai = macro.attributes.begin();
        while(ai != macro.attributes.end()) {
          std::string name = ai->first;
          std::string value = ai->second;
          answer += " "+name+"=\"" + value + "\"";
          ai++;
        }

        if(macro.attributes["name"] == request.macro || 
           (macro.attributes.find("static") != macro.attributes.end() && macro.attributes["static"] == "true")
           ) {
          foundmacro = true;

          MacroParser mp(macro.attributes["name"]);
          mp.parse();
          Macro *m = mp.getMacro();
          answer += " caption=\"" + m->window.attributes["caption"] + "\"";
          answer += ">\n";

          LUAQueryMapper lqm;

          ////////////////////////
          std::vector< Query >::iterator qi = m->queries.begin();
          while(qi != m->queries.end()) {

            Query &query = *qi;
            std::string service = query.attributes["service"];

            if(service == "pentominos") {
              // Send the queries to Pentominos (if any)
              QueryHandlerPentominos qh(pentominos_socket, transaction->cpr);

              QueryResult queryresult = qh.exec(*qi);
              lqm.addQueryResult(queryresult);
            }

            if(service == "pracro") {
              // Send the queries to Pentominos (if any)
              QueryHandlerPracro qh(db, transaction->cpr);

              QueryResult queryresult = qh.exec(*qi);
              lqm.addQueryResult(queryresult);
            }

            qi++;
          }

          // Handle scripts
          if(m->scripts.size()) {
            answer += "      <scripts>\n";
            
            std::vector< Script >::iterator spi = m->scripts.begin();
            while(spi != m->scripts.end()) {
              answer += "        <script language=\"" + spi->attributes["language"] 
                + "\" name=\"" + spi->attributes["name"] + "\">\n";
              answer += xml_encode(spi->attributes["code"]);
              answer += "\n        </script>\n";
              spi++;
            }
            answer += "      </scripts>\n";
          }

          answer += widgetgenerator(transaction->cpr, *m, lqm, db);
        } else {
          // only find macro title
          MacroParser mp(macro.attributes["name"]);
          mp.parse();
          Macro *m = mp.getMacro();
          answer += " caption=\"" + m->window.attributes["caption"] + "\"";
          answer += ">\n";

        }

        if(completed) {
          answer += "      <resume>";
          answer += db->getResume(transaction->cpr, macro, time(NULL) - Conf::db_max_ttl);
          answer += "</resume>\n";
        }

        answer += "    </macro>\n";
        mi2++;

      }
      
      if(foundmacro == false && request.macro != "")
        throw NotFoundException(request);

      answer += "  </course>\n";

      i++;
    }

    answer += "</pracro>\n";

  } catch( std::exception &e ) {
    answer = error_box(xml_encode(e.what()));
  }

  PRACRO_DEBUG(server, "Done handling transaction\n");
  PRACRO_DEBUG(serverxml, "%s\n", answer.c_str());
  return answer;
}


static void handleConnection(TCPSocket *socket)
{
  TCPSocket pentominos_socket;
#ifndef WITHOUT_PENTOMINOS
  pentominos_socket.connect(Conf::pentominos_addr, Conf::pentominos_port);
#endif/*WITHOUT_PENTOMINOS*/

  Database *db = new Database("pgsql", Conf::database_addr, "", Conf::database_user, Conf::database_passwd, "");

  ssize_t size;
  char buf[4096];
  
  Transaction *transaction = NULL;
  TransactionParser *parser = NULL;
  
  //  while( (size = socket->read(buf, sizeof(buf))) != -1) {
  while( (size = socket->read(buf, sizeof(buf))) > 0) {

    PRACRO_DEBUG(server, "Read %d bytes from network\n", size);
   
    while(size) {

      if(transaction == NULL) {
        transaction = new Transaction();
      }

      if(parser == NULL) {
        parser = new TransactionParser(transaction);
      }

      PRACRO_DEBUG(server, "Got %d bytes in read loop\n", size);
      if(parser->parse(buf, size)) {
        PRACRO_DEBUG(server, "Got complete XML document %d bytes used, %d bytes in current buffer.\n", parser->usedBytes(), size);

        socket->write(handleTransaction(transaction, &pentominos_socket, db));
        size = size - parser->usedBytes();
        
        delete transaction; transaction = NULL;
        delete parser; parser = NULL;
      } else {
        size = size - parser->usedBytes();
      }

      if(size > 0) {
        strcpy(buf, buf + size);
        PRACRO_DEBUG(server, "Replaying %d bytes.\n", size);
      }
    }
  }

  if(transaction) {
    delete transaction;
    transaction = NULL;
  }

  if(parser) {
    delete parser;
    parser = NULL;
  }

  PRACRO_DEBUG(server, "Out of read loop!\n");
}

//#define NON_FORKING
#include <sys/socket.h>
extern bool pracro_is_running;
void server()
{
  port_t port = Conf::server_port;
  TCPSocket *socket = NULL;
  
  try {
    socket = new TCPSocket("Listen socket");
    socket->listen(port);
  } catch (Exception &e) {
    PRACRO_ERR_LOG(server, "Error during parsing:\n%s\n",
            e.what());
    delete socket;
    socket = NULL;
    return;
  }

  while(pracro_is_running && socket->connected()) {

    { // Reload if new port is assigned.
      int old_port = port;
      port = Conf::server_port;

      if(port != old_port) {
        // Start listening on the new port
        delete socket;
        socket = new TCPSocket("Listen socket (reloaded)");
        socket->listen(port);
      }
    }

    TCPSocket *child = socket->accept();
    if(child) {

#ifndef NON_FORKING
      switch(fork()) {
      case -1: // error
        PRACRO_ERR_LOG(server, "Could not fork: %s\n", strerror(errno));
        break;
        
      case 0: // child
        delete socket;
#endif/*NON_FORKING*/
        handleConnection(child);
        delete child;
#ifndef NON_FORKING
        return;
        
      default: // parent
        delete child;
        break;
      }
#endif/*NON_FORKING*/

    }
  }

  //socket->shutdown();
  delete socket;

  PRACRO_DEBUG(server, "Server gracefully shut down.\n");
}


#ifdef TEST_SERVER

char request[] = 
  "<?xml version='1.0' encoding='UTF-8'?>\n"
  "<pracro cpr=\"2003791613\" version=\"1.0\">\n"
  "  <request macro=\"example\" course=\"example\"/>\n"
  "</pracro>\n";

int main()
{
  Configuration conf("../etc/pracrod.conf");
  initConfig(&conf);

  switch(fork()) {
  case -1: // error
    return 1;
    
  case 0: // child
    server();
    return 0;
    
  default: // parent
    {
      TCPSocket socket;
      int port = config()->lookup("port");
      socket.connect("localhost", port);
      
      socket.write(request);
      char buf[32];
      memset(buf, 0, sizeof(buf));
      while(socket.read(buf, 31)) {
        printf(buf); fflush(stdout);
        memset(buf, 0, sizeof(buf));
      }
    }
    return 0;
  }
  
  return 1;
}

#endif/*TEST_SERVER*/