#ifndef _HEADER_HISTORY_H
#define _HEADER_HISTORY_H

#include "position.h"
#include "hash.h"
#include "sysproc.h"

class THistory
{
#ifndef NO_FILES
public:
  static char FileName[1024];
#endif
public:
  THistory(int id = 0) {if (id >= 0) Hid = NHid++; else Hid = id;}

  int GetId() const {return Hid;}
  static int GetNId() {return NHid;}

  int Start(const Position &pos) const;
  int Move(const Position &pos, const unsigned char mv[], int nmove) const;
  int Play(const PlayWrite &play) const;

#ifndef NO_FILES
  static int InitHFile(char *dname = 0);
  static int HRead(FILE *f, PlayWrite *&play);
protected:
  int Print(const char *str) const;
#endif

  int Hid;

  static int NHid;
protected:
  struct TStr
  {
    TStr(const char *ss = 0) : s(0) {(*this) = ss;}
    TStr(const TStr &ss) : s(0) {(*this) = ss.s;}
    ~TStr() {(*this) = 0;}

    TStr &operator=(const char *ss);
    TStr &operator=(const TStr &ss) {return (*this) = ss.s;}

    operator char*() {return s;}
    operator const char*() const {return s;}
    char &operator*() {return *s;}
    const char &operator*() const {return *s;}
    char &operator[](int i) {return s[i];}
    const char &operator[](int i) const {return s[i];}
    void Extend(int n);

    friend int operator==(const TStr &s1, const TStr &s2)
        {return strcmp(s1, s2) == 0;}
    friend int operator==(const char *s1, const TStr &s2)
        {return strcmp(s1, s2) == 0;}
    friend int operator==(const TStr &s1, const char *s2)
        {return strcmp(s1, s2) == 0;}
    friend int operator!=(const TStr &s1, const TStr &s2)
        {return strcmp(s1, s2) != 0;}
    friend int operator!=(const char *s1, const TStr &s2)
        {return strcmp(s1, s2) != 0;}
    friend int operator!=(const TStr &s1, const char *s2)
        {return strcmp(s1, s2) != 0;}
    friend int operator>=(const TStr &s1, const TStr &s2)
        {return strcmp(s1, s2) >= 0;}
    friend int operator>=(const char *s1, const TStr &s2)
        {return strcmp(s1, s2) >= 0;}
    friend int operator>=(const TStr &s1, const char *s2)
        {return strcmp(s1, s2) >= 0;}
    friend int operator<=(const TStr &s1, const TStr &s2)
        {return strcmp(s1, s2) <= 0;}
    friend int operator<=(const char *s1, const TStr &s2)
        {return strcmp(s1, s2) <= 0;}
    friend int operator<=(const TStr &s1, const char *s2)
        {return strcmp(s1, s2) <= 0;}
    friend int operator>(const TStr &s1, const TStr &s2)
        {return strcmp(s1, s2) > 0;}
    friend int operator>(const char *s1, const TStr &s2)
        {return strcmp(s1, s2) > 0;}
    friend int operator>(const TStr &s1, const char *s2)
        {return strcmp(s1, s2) > 0;}
    friend int operator<(const TStr &s1, const TStr &s2)
        {return strcmp(s1, s2) < 0;}
    friend int operator<(const char *s1, const TStr &s2)
        {return strcmp(s1, s2) < 0;}
    friend int operator<(const TStr &s1, const char *s2)
        {return strcmp(s1, s2) < 0;}

    char *s;
  };

  class THash
  {
  public:
    void init(int _m);
    int operator()(const TStr &str) const;
  protected:
    int m;
    int K[16];
  };

  struct TTableItem
  {
    TTableItem(const char *s = 0, int k = 0) : str(s), k(k) {}
    TTableItem(const TStr &s, int k = 0) : str(s), k(k) {}
    TTableItem(const TTableItem &t) : str(t.str), k(t.k) {}

    operator TStr&() {return str;}
    operator const TStr&() const {return str;}

    TStr str;
    int k;
  };
};

#ifndef NO_FILES
char THistory::FileName[1024] = "history.che";
#endif
int THistory::NHid = 0;

#ifndef NO_FILES
int THistory::Print(const char *str) const
{
  char *line = new char[30 + strlen(str)];
  if (!line) return 0;
  unsigned long pr_id = GetProcessId();
  if (Hid == -1) sprintf(line, "%lu  %s\n", pr_id, str);
  else if (Hid < 0) sprintf(line, "%lu%c  %s\n", pr_id, (char)Hid, str);
  else sprintf(line, "%lu:%d  %s\n", pr_id, Hid, str);
  FILE *f = fopen(FileName, "at");
  if (!f)
  {
    clock_t cc = clock();
    do {f = fopen(FileName, "at");}
    while(!f && (clock() - cc) <= 0.05 * CLOCKS_PER_SEC);
  }
  if (!f) {delete[] line; return 0;}
  fputs(line, f);
  fclose(f);
  delete[] line;
  return 1;
}
#endif

int THistory::Start(const Position &pos) const
{
#ifndef NO_FILES
  char str[20 + NUM_CELL] = "Start ";
  if (!pos.Write(str + strlen(str), 1)) return 0;
  if (!Print(str)) return 0;
#endif
  return 1;
}

int THistory::Move(const Position &pos, const unsigned char mv[], int nmove) const
{
#ifndef NO_FILES
  char *str = new char[15 + pos.GetLenMvEx(mv, 11)];
  if (!str) return 0;
  sprintf(str, "%d.%s ", (nmove + 1) / 2, (nmove % 2 == 0) ? ".." : "");
  pos.WriteMvEx(mv, str + strlen(str), 11);
  if (!Print(str)) {delete[] str; return 0;}
  delete[] str;
#endif
  return 1;
}

int THistory::Play(const PlayWrite &play) const
{
  if (play.GetN() <= 0) return 0;
  Position pos;
  if (play.GetPos(pos, 0) < 0) return 0;
  if (!Start(pos)) return 0;
  int i;
  unsigned char mv[NUM_CELL];
  for (i = 1; i < play.GetN(); i++)
  {
    if (play.GetPos(pos, i - 1) < 0) return 0;
    if (play.GetMove(mv, i) < 0) return 0;
    if (!Move(pos, mv, i)) return 0;
  }
  return 1;
}

#ifndef NO_FILES
int THistory::InitHFile(char *dname)
{
  if (dname && dname[0])
  {
    char fnm[1024];
    strcpy(fnm, dname);
    int i;
    for (i = strlen(fnm) - 1; i >= 0; i--)
    {
      if (fnm[i] == DIR_SEPARATOR) break;
    }
    if (i >= 0)
    {
      strcpy(fnm + i + 1, FileName);
      strcpy(FileName, fnm);
    }
  }
  int e = 1;
  FILE *f = fopen(FileName, "rt");
  if (f) {e = feof(f); fclose(f);}
  if (!e) return 0;
  f = fopen(FileName, "wt");
  if (!f) return -1;
  fputs("checkers-history_1.1\n", f);
  fclose(f);
  return 1;
}
#endif

THistory::TStr &THistory::TStr::operator=(const char *ss)
{
  if (s) delete[] s;
  if (ss)
  {
    s = new char[strlen(ss) + 1];
    strcpy(s, ss);
  }
  else s = 0;
  return *this;
}

void THistory::TStr::Extend(int n)
{
  if (n <= 0) {(*this) = 0; return;}
  char *ss = s;
  s = new char[n+1];
  if (ss)
  {
    strncpy(s, ss, n);
    s[n] = 0;
    delete[] ss;
  }
  else s[0] = 0;
}

void THistory::THash::init(int _m)
{
  m = _m;
  for (int i = 0; i < 16; i++)
  {
    K[i] = (2*random(32767) + 1) & ((1 << m) - 1);
  }
}

int THistory::THash::operator()(const TStr &str) const
{
  int i, r = 0;
  const char *s = str;
  for (i = 0; *s; i = (i+1) & 15) r += *(s++) * K[i];
  r &= (1 << m) - 1;
  return r;
}

#ifndef NO_FILES
int THistory::HRead(FILE *f, PlayWrite *&play)
{
  const int MAX_INP_WORD = 100;
  int nplay = 0, mplay = 10;
  play = new PlayWrite[mplay];
  THashTable<TTableItem, TStr, THash> table;
  TStr word;
  char inp_word[MAX_INP_WORD + 1];
  int r, maxword = 0;
  unsigned char ch;
  int i, k = 0, kind = 0, wasspace = 1, nmove = 0;
  for (;;)
  {
    r = (fread(&ch, 1, 1, f) == 1);
    if (!r || isspace(ch))
    {
      if (!wasspace)
      {
        if (kind == 0) kind = 1;
        else if (kind == 2)
        {
          for (i = 0; inp_word[i]; i++)
          {
            inp_word[i] = (char)tolower((unsigned char)inp_word[i]);
          }
          if (strcmp(inp_word, "start") == 0) kind = 5;
          else kind = -1;
          inp_word[0] = 0; k = 0;
        }
        else if (kind == 3)
        {
          nmove *= 2;
          if (k <= 1) nmove--;
          inp_word[0] = 0;
          k = 0; kind = 4;
        }
        else if (kind == 4)
        {
          TTableItem *n_pl = table.find(word);
          if (!n_pl) kind = -1;
          else if (nmove < 1 || nmove > play[n_pl->k].GetN()) kind = -1;
          else
          {
            PlayWrite::PMv pmv;
            if (play[n_pl->k].GetPos(pmv.pos, nmove - 1) < 0) kind = -1;
            else if (!pmv.pos.ReadMv(pmv.mv, inp_word, 1)) kind = -1;
            else
            {
              play[n_pl->k].ClearFrom(nmove);
              if (play[n_pl->k].Add(pmv.mv) != 0) kind = -1;
              else {k = n_pl->k; kind = 11;}
            }
          }
        }
        else if (kind == 5)
        {
          Position pos;
          pos.Read(inp_word, 1);
          if (pos.IsNull()) kind = -1;
          else
          {
            TTableItem *n_pl = table.find(word);
            if (!n_pl)
            {
              table.push(TTableItem(word, nplay));
              n_pl = table.find(word);
            }
            if (!n_pl) kind = -1;
            else
            {
              if (nplay >= mplay)
              {
                PlayWrite *play0 = play;
                int mplay0 = mplay;
                mplay = 2*nplay + 3;
                play = new PlayWrite[mplay];
                if (play0)
                {
                  for (i = 0; i < mplay0; i++) play[i] = play0[i];
                  delete[] play0;
                }
              }
              n_pl->k = nplay++;
              play[n_pl->k].Add(0, pos);
              k = n_pl->k; kind = 12;
            }
          }
        }
      }
      if (!r || ch == '\n' || ch == '\r')
      {
        k = 0;
        kind = 0;
        if (!r) break;
      }
      wasspace = 1;
    }
    else
    {
      if (kind == 0)
      {
        if (k >= maxword) word.Extend(2*k + 3);
        word[k++] = ch;
        word[k] = 0;
      }
      else if (kind == 1)
      {
        if (isdigit(ch)) {nmove = ch - '0'; k = 0; kind = 3;}
        else
        {
          inp_word[0] = ch;
          inp_word[1] = 0;
          k = 1; kind = 2;
        }
      }
      else if (kind == 2 || kind == 4 || kind == 5)
      {
        if (k < MAX_INP_WORD)
        {
          inp_word[k++] = ch;
          inp_word[k] = 0;
        }
      }
      else if (kind == 3)
      {
        if (k == 0 && isdigit(ch)) nmove = 10 * nmove + ch - '0';
        else if (ch == '.') k++;
        else kind = -1;
      }
      wasspace = 0;
    }
  }
  return nplay;
}
#endif

#endif  //_HEADER_HISTORY_H