#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>

#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>

#include "../src/game/server/statistics_types.hpp"

using namespace statistics;

void usage(const char* self) {
 const char* c = self;
 while (*c) {
  if (*c == '/') self = c;
  ++c;
 };

 std::cout << 
  "This program reads and converts to human-readable format "
  "teeworlds statistics file\n"
  "Usage: [-n] [-c] [-i] [-s]" << self << " file\n"
  "Where\n"
  " -n: show player names (default)\n"
  " -i: show player ips\n"
  " -s: show player skins\n"
  " -c: show player client ids (default)\n"
  "You may combine these options\n";
};

int id_flags = 0;

struct player {
 std::string name;
 std::string skin;
 int cid;
 char ip[20];
};

std::ostream& operator<<(std::ostream& s, const player& p) {
 if (id_flags & 8) s << '\t' << p.cid;
 if (id_flags & 1) s << '\t' << p.name;
 if (id_flags & 2) s << '\t' << p.ip;
 if (id_flags & 4) s << '\t' << p.skin;
 return s;
};

const char* team_name(char team) {
 switch (team) {
  case -1:
   return "spectators";
  case 0:
   return "red";
  case 1:
   return "blue";
  default:
   return "undefined team";
 };
};

template <typename T> std::istream& operator%(std::istream& f, T& x) {
 return f.read((char*)&x, sizeof(x));
};

template <> std::istream& operator%(std::istream& f, std::string& x) {
 char c;
 x.clear();
 // It is likely there is a better way...
 while (f.read(&c, 1) && c) x.push_back(c);
 return f;
};

int main(int argc, char** argv) {
 try {
  int option;

  const char* optstring = "nisch";
  while ((option = getopt(argc, argv, optstring)) != -1) {
   if (option == 'h') {
    usage(argv[0]);
    return 0;
   };

   for (int i = 0; i < 4; ++i)
    if (option == optstring[i]) {
     id_flags |= 1 << i;
     goto found;
    };

   return 1;

 found:;
  };

  if (id_flags == 0) id_flags = 1;

  if (optind >= argc) {
   usage(argv[0]);
   return 1;
  };

  std::fstream file(argv[optind], std::ios::in | std::ios::binary);
  file.exceptions(std::ios::badbit);

  player players[16];
  for (int i = 0; i < sizeof(players)/sizeof(player); ++i)
   players[i].cid = i;

  int start_tick = 0;

  while (true) {
   int time;
   header action;
   file % time % action;
   time -= start_tick;
   if (!file) break;
   switch (action) {
    case START: {
     time_t t;
     std::string map;
     file % start_tick % t % map;
     char* ct = ctime(&t);
     char* c = ct;
     while (*c && *c != '\n') ++c;
     if (*c == '\n') *c = 0;
     std::cout << ct << ": a new match starts on " << map << '\n';
     break;
    };
    case END:
     std::cout << time << ": match ends\n";
     break;
    case JOIN: {
     char cid;
     file % cid;
     if (cid < 0 || cid >= sizeof(players)) {
      std::cerr << "Bad cid joins: " << (int)cid << '\n';
      return 1;
     };

     char team;
     bool ipv4;
     char ip[16];
     file % team % players[cid].name % players[cid].skin % ipv4;
     file.read(ip, sizeof(ip));
     if (ipv4) {
      int32_t x = *(int32_t*)ip;
      sprintf(
        players[cid].ip, "%d.%d.%d.%d", 
        x & 0xff, (x >> 8) & 0xff, (x >> 16) & 0xff, (x >> 24) & 0xff
      );
     } else memcpy(players[cid].ip, ip, sizeof(ip));

     std::cout << time << ": " 
      << (int)cid 
      << '\t' << players[cid].name 
      << '\t' << players[cid].skin 
      << '\t' << players[cid].ip
      << " enters and joins " << team_name(team) << '\n';
     break;
    };
    case LEAVE: {
     char cid;
     file % cid;
     std::cout << time << ": " << players[cid] << " leaves the game\n";
     break;
    };
    case SWITCH: {
     char cid, team;
     file % cid % team;
     std::cout << time << ": " 
      << players[cid] << " switches to " << team_name(team) << '\n';
     break;
    };
    case SHOOT: {
     char cid;
     file % cid;
     std::cout << time << ": " << players[cid] << " shoots\n";
     break;
    };
    case KILL: {
     char killer, victim;
     file % killer % victim;
     std::cout << time << ": " 
      << players[killer] << " kills " << players[victim] << '\n';
     break;
    };
    case CHANGE_NAME: {
     char cid;
     std::string name;
     file % cid % name;
     std::cout << time << ": " 
      << players[cid] << " changes name to " << name << '\n';
     players[cid].name = name;
     break;
    };
    case CHANGE_SKIN: {
     char cid;
     std::string skin;
     file % cid % skin;
     std::cout << time << ": "
      << players[cid] << " changes skin to " << skin << '\n';
     players[cid].skin = skin;
     break;
    };
    default:
     std::cerr << "Unknown action: " << (int)action << '\n';
     return 1;
   };
  };

  return 0;
 } catch (std::exception& e) {
  std::cerr << e.what() << '\n';
  return 1;
 };
};
