// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.

#include "PosBookie.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/THttpServer.h>

#include "Pos.h"
#include <thrift/protocol/TJSONProtocol.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>

#include "salt_store.h"
#include "sha1.h"
#include <stdio.h>

#include "ConfigFile.h"

using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;

using boost::shared_ptr;

using namespace  ::posbookie;
using namespace  ::pos;

class PosBookieHandler : virtual public PosBookieIf {

  shared_ptr<TTransport> socket;
  shared_ptr<TTransport> transport;
  shared_ptr<TProtocol> protocol;
  PosClient * client;

  SaltStore * saltStore;

  std::string getAuthString() {
    std::string s;
    this->client->getSalt(s);
    return SHA1Hash(s+"PSK").toHex();
  }

 public:
  PosBookieHandler(char* serverHost, int serverPort, SaltStore* saltStore)
  {
    socket = shared_ptr<TTransport>(new TSocket(serverHost, serverPort));
    transport = shared_ptr<TTransport>(new TFramedTransport(socket));
    protocol = shared_ptr<TProtocol>(new TBinaryProtocol(transport));
    client = new PosClient(protocol);
	this->saltStore = saltStore;

    try {
	  transport->open();
      this->client->ping();
    } catch(...) {
      fprintf(stderr, "Failed to contact server '%s:%d'!\n", serverHost, serverPort);
      abort();
    }
  }

  void ping() { }

  void getSalt(std::string& _return) {
    _return = this->saltStore->getSalt();
  }

  E_PURCHASE_STATUS::type purchaseItems(const std::string& auth, const std::string& dataToHash, const std::vector<int64_t> & upcs) {
    if(saltStore->consumeKey(auth)) {
      try {
        int32_t total_price = 0;
        std::vector<int64_t>::const_iterator i;
        for(i = upcs.begin(); i != upcs.end(); i++) {
          total_price += this->getUPCPrice(*i);
        }

        int32_t accountBalance = this->client->getHashAccountBalance(dataToHash);
        if(accountBalance > total_price) {
          this->client->doTransactionOnHash(this->getAuthString(), dataToHash, total_price * -1);
          return E_PURCHASE_STATUS::EPS_SUCCESS;
        }

        return E_PURCHASE_STATUS::EPS_NOMONEY;
      } catch(...) {
        return E_PURCHASE_STATUS::EPS_FAIL;
      }
    }

    return E_PURCHASE_STATUS::EPS_FAIL;
  }

  int32_t getUPCPrice(const int64_t upc) {
    return this->client->getUPCPrice(upc);
  }

  int32_t getStock(const int64_t upc) {
    return this->client->getStock(upc);
  }

};

int main(int argc, char **argv) {
  if(argc != 2) {
    printf("usage: posbookie <configFile>\n");
    exit(1);
  }

  ConfigFile conf(argv[1]);

  const char* server_host;
  int server_port;
  int listen_port;
  SaltStore * saltStore;

  try {
    server_host = conf.read<std::string>("server_host", "localhost").c_str();
    server_port = conf.read<int>("server_port", 9090);
    listen_port = conf.read<int>("listen_port", 9091);
    saltStore = new SaltStore(
      conf.read<uint64_t>("key_lifetime", 120),
      conf.read<size_t>("salt_size", 256),
      conf.read<std::string>("secret")
      );
  } catch(ConfigFile::file_not_found & e) {
    fprintf(stderr, "Could not open configuration file '%s'!\n", e.filename.c_str());
    abort();
  } catch(ConfigFile::key_not_found & e) {
    fprintf(stderr, "Could not find required configuration key '%s'!\n", e.key.c_str());
    abort();
  }
  
  shared_ptr<PosBookieHandler> handler(new PosBookieHandler((char*)server_host, server_port, saltStore));
  shared_ptr<TProcessor> processor(new PosBookieProcessor(handler));
  shared_ptr<TServerTransport> serverTransport(new TServerSocket(listen_port));
  shared_ptr<TTransportFactory> transportFactory(new THttpServerTransportFactory());
  shared_ptr<TProtocolFactory> protocolFactory(new TJSONProtocolFactory());

  TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);

  server.serve();

  delete saltStore;
  return 0;
}

