#include "log.h"
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <sstream>

Log::Log(const char * fn)
	: log_name(fn), serial(0)
{
	FILE * fh = fopen(fn, "r");
	if(!fh) {
		fprintf(stderr, "Log: Could not open log file '%s' for reading! Creating an empty log.\n", fn);
		return;
	}

	LogEntry l;
	while(1 == fread(&l, sizeof(LogEntry), 1, fh))
		entries.push_back(l);

	serial = l.serial;

	fclose(fh);
}

uint64_t Log::writeEntry(LogEntry ent)
{
	FILE * fh = fopen(log_name.c_str(), "a");
	if(!fh) {
		fprintf(stderr, "Log: FATAL: Could not open log file '%s' for append. Aborting.\n", log_name.c_str());
		abort();
	}

	flockfile(fh);

	fseek(fh, 0, SEEK_END);
	ent.serial = ++serial;
	if(1 != fwrite(&ent, sizeof(LogEntry), 1, fh)) {
		fprintf(stderr, "Log: FATAL: Could not write entry to log file '%s'. Aborting.\n", log_name.c_str());
		abort();
	}
	fflush(fh);

	funlockfile(fh);
	fclose(fh);

	entries.push_back(ent);
	return ent.serial;
}

std::list<LogEntry>::const_iterator Log::begin()
{
	return entries.begin();
}

std::list<LogEntry>::const_iterator Log::end()
{
	return entries.end();
}

uint64_t Log::nextSerial()
{
	return serial + 1;
}

std::string logTypeToString (E_OBJ_TYPE type) {
  switch(type) {
    case ET_TRANS:  return "TRANS";
    case ET_HASH:   return "HASH";
    case ET_SALE:   return "SALE";
    case ET_PRICE:  return "PRICE";
    case ET_ADDUPC: return "ADDUPC";
    case ET_REVERT: return "REVERT";
    default: fprintf(stderr, "logTypeToString: unhandled case in switch.\n"); abort();
  }
}

template<typename T>
std::string ThingToString (T num) {
  std::stringstream ss;
  ss << num;
  return ss.str();
}

std::vector<std::string> Log::toString()
{
  assert(sizeof(time_t) == sizeof(uint64_t));
  std::vector<std::string> ans;
  ans.reserve(entries.size());
  std::list<LogEntry>::iterator it;
  for(it = entries.begin(); it != entries.end(); it++) {
    //char* time = new char[64];
    //struct tm* tms = localtime((time_t*)&it->ts);
    //strftime(time, 64, "%c", tms);
    std::string out = ThingToString<uint64_t>(it->ts) + "\t"
                    //+ ThingToString<uint64_t> (it->serial) + "\t"
                    + logTypeToString(it->type) + "\t";
    //delete[] time;
    switch(it->type) {
      case ET_TRANS:
        out += ns.get_name(it->Transaction.uid) + "\t"
             + ThingToString<int32_t>(it->Transaction.delta);
        break;
      case ET_HASH: {
        SHA1Hash h;
        h.set((unsigned char*)it->HashChange.hash);
        out += h.toHex() + "\t"
             + ns.get_name(it->HashChange.uid) + "\t"
             + ThingToString<bool>(it->HashChange.add);
        break;
      }
      case ET_SALE:
        out += ThingToString<uint64_t>(it->StockChange.upc.l) + ThingToString<uint64_t>(it->StockChange.upc.h) + "\t"
             + ThingToString<int32_t>(it->StockChange.delta);
        break;
      case ET_PRICE:
        out += ThingToString<uint64_t>(it->PriceChange.upc.l) + ThingToString<uint64_t>(it->PriceChange.upc.h) + "\t"
             + ThingToString<int32_t>(it->PriceChange.price);
        break;
      case ET_ADDUPC:
        out += ThingToString<uint64_t>(it->AddUPC.upc.l) + ThingToString<uint64_t>(it->AddUPC.upc.h) + "\t"
             + it->AddUPC.name;
        break;
      case ET_REVERT:
        out += ThingToString<uint64_t>(it->Revert.revert_serial);
        break;
      default: fprintf(stderr, "Log: unhandled toString case.\n"); abort();
    }

    ans.push_back(out);
  }
  return ans;
}

LogEntry Log::newLogEntry(E_OBJ_TYPE type) {
  LogEntry l;
  memset(&l, 0, sizeof(LogEntry));
  l.ts = (uint64_t)time(NULL);
  l.type = type;
  l.serial = nextSerial();
  return l;
}

