// HyperRogue -- expansion_analyzer
// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details

namespace hr {

int subtype(cell *c) {
  return patterns::getpatterninfo(c, patterns::PAT_NONE, 0).id;
  }

bignum& bignum::operator +=(const bignum& b) {
  int K = isize(b.digits);
  if(K > isize(digits)) digits.resize(K);
  int carry = 0;
  for(int i=0; i<K || carry; i++) {
    if(i >= isize(digits)) digits.push_back(0);
    digits[i] += carry;
    if(i < K) digits[i] += b.digits[i];
    if(digits[i] >= BASE) {
      digits[i] -= BASE;
      carry = 1;
      }
    else carry = 0;
    }
  return *this;
  }

string bignum::get_str(int max_length) {
  if(digits.empty()) return "0";
  string ret = its(digits.back());
  for(int i=isize(digits)-2; i>=0; i--) {
    if(isize(ret) > max_length && i) {
      ret += XLAT(" (%1 more digits)", its(9 * (i+1)));
      return ret;
      }

    ret += " ";
    string val = its(digits[i]);
    while(isize(val) < 9) val = "0" + val;
    ret += val;
    }
  return ret;
  }

vector<int> expansion_analyzer::gettype(cell *c) {
  vector<int> res;
  res.push_back(subtype(c) * 4 + 2);
  int d = celldist(c);
  for(int i=0; i<c->type; i++) {
    cell *c1 = c->cmove(i);
    res.push_back(subtype(c1) * 4 + celldist(c1) - d);
    }
  return res;
  }

int expansion_analyzer::sample_id(cell *c) {
  auto t = gettype(c);
  if(codeid.count(t)) return codeid[t];
  auto &cit = codeid[t];
  cit = isize(samples);
  samples.push_back(c);
  return cit;
  }
  
void expansion_analyzer::preliminary_grouping() {
  samples.clear();
  codeid.clear();
  children.clear();
  sample_id(currentmap->gamestart());
  for(int i=0; i<isize(samples); i++) {
    children.emplace_back();
    auto c = samples[i];
    int d = celldist(c);
    for(int k=0; k<c->type; k++) {
      cell *c1 = c->cmove(k);
      if(celldist(c1) != d+1) continue;
      cell *c2 = c->cmove((k+1) % c->type);
      if(celldist(c2) != d+1) continue;
      children.back().push_back(sample_id(c1));
      }
    }
  N = isize(samples);
  rootid = 0;
  }

void expansion_analyzer::reduce_grouping() {
  vector<int> grouping;
  grouping.resize(N);
  int nogroups = 1;
  for(int i=0; i<N; i++) grouping[i] = 0;
  while(true) {
    vector< pair<vector<int>, int > > childgroups(N);
    for(int i=0; i<N; i++) {
      childgroups[i].second = i;
      for(int j: children[i])
        childgroups[i].first.push_back(grouping[j]);
      sort(childgroups[i].first.begin(), childgroups[i].first.end());
      }
    sort(childgroups.begin(), childgroups.end());
    int newgroups = 0;
    for(int i=0; i<N; i++) {
      if(i == 0 || childgroups[i].first != childgroups[i-1].first) newgroups++;
      grouping[childgroups[i].second] = newgroups - 1;
      }
    if(nogroups == newgroups) break;
    nogroups = newgroups;
    }
  
  vector<int> groupsample(nogroups);
  for(int i=0; i<N; i++) groupsample[grouping[i]] = i;
  vector<vector<int>> newchildren(nogroups);
  for(int i=0; i<nogroups; i++) 
    for(int j: children[groupsample[i]])
      newchildren[i].push_back(grouping[j]);
  children = move(newchildren);
  for(auto& p: codeid) p.second = grouping[p.second];
  N = nogroups;
  rootid = grouping[rootid];
  diskid = N;
  children.push_back(children[rootid]);
  children[diskid].push_back(diskid);
  N++;
  }

template<class T> int size_upto(vector<T>& v, int s) {
  int res = isize(v);
  if(res < s) v.resize(s);
  return res;
  }

bignum& expansion_analyzer::get_descendants(int level) {
  if(!N) preliminary_grouping(), reduce_grouping();
  return get_descendants(level, rootid);
  }

bignum& expansion_analyzer::get_descendants(int level, int type) {
  auto& pd = descendants;
  size_upto(pd, level+1);
  for(int d=0; d<=level; d++)
  for(int i=size_upto(pd[d], N); i<N; i++)
    if(d == 0) pd[d][i].be(1);
    else for(int j: children[i]) 
      pd[d][i] += pd[d-1][j];
  return pd[level][type];
  }

bool expansion_analyzer::verify(int id) {
  if(id < isize(coef)) return false;
  long long res = 0;
  for(int t=0; t<isize(coef); t++)
    res += coef[t] * get_descendants(id-t-1).approx_ll();
  return res == get_descendants(id).approx_ll();
  }

int expansion_analyzer::valid(int v, int step) {
  if(step < 0) return 0;
  if(get_descendants(step+v+v+5).approx_int() >= bignum::BASE) return 0;
  ld matrix[100][128];
  for(int i=0; i<v; i++)
  for(int j=0; j<v+1; j++)
    matrix[i][j] = get_descendants(step+i+j).approx();
  
  for(int k=0; k<v; k++) {
    int nextrow = k;
    while(nextrow < v && std::abs(matrix[nextrow][k]) < 1e-6)
      nextrow++;
    if(nextrow == v) return 1;
    if(nextrow != k) {
      // printf("swap %d %d\n", k, nextrow);
      for(int l=0; l<=v; l++) swap(matrix[k][l], matrix[nextrow][l]);
      // display();
      }
    ld divv = 1. / matrix[k][k];
    for(int k1=k; k1<=v; k1++) matrix[k][k1] *= divv;
    // printf("divide %d\n", k);
    // display();
    for(int k1=k+1; k1<v; k1++) if(matrix[k1][k] != 0) {
      ld coef = -matrix[k1][k];
      for(int k2=k; k2<=v; k2++) matrix[k1][k2] += matrix[k][k2] * coef;
      }
    // printf("zeros below %d\n", k);
    // display();
    }
  
  for(int k=v-1; k>=0; k--)
  for(int l=k-1; l>=0; l--)
    if(matrix[l][k]) matrix[l][v] -= matrix[l][k] * matrix[k][v];
  
  coef.resize(v);
  for(int i=0; i<v; i++) coef[i] = int(floor(matrix[v-1-i][v] + .5));
    
  for(int t=step+v; t<step+v+v+5; t++) if(!verify(t)) return 2;
  tested_to = step+v+v+5;
  while(tested_to < step+v+v+100 && get_descendants(tested_to).approx_ll() < bignum::BASE2) {
    if(!verify(tested_to)) return 2;
    tested_to++;
    }
  
  valid_from = step+v;
  return 3;
  }

void expansion_analyzer::find_coefficients() {
  if(coefficients_known) return;
  if(!N) preliminary_grouping(), reduce_grouping();
  for(int v=1; v<25; v++) 
  for(int step=0; step<1000; step++) { 
    int val = valid(v, step);
    if(val == 0) break;
    if(val == 3) { coefficients_known = 2; return; }
    }
  coefficients_known = 1;
  }

ld growth;

ld expansion_analyzer::get_growth() {
  if(growth >= 1) return growth;
  if(!N) preliminary_grouping(), reduce_grouping();
  vector<ld> eigen(N, 1);
  ld total;
  for(int iter=0; iter<100000; iter++) {
    total = 0;
    vector<ld> neweigen(N, 0);
    for(int i=0; i<N; i++) {
      for(int j: children[i]) neweigen[i] += eigen[j];
      total += neweigen[i];
      }
    for(int i=0; i<N; i++) eigen[i] = .1 * eigen[i] + .9 * neweigen[i] / total;
    // for(int i=0; i<N; i++) printf("%lf ", eigen[i]); printf("total = %lf\n", total);
    }
  return growth = total;
  }

void expansion_analyzer::reset() {
  N = 0;
  growth = 0;
  coefficients_known = 0;
  samples.clear();
  codeid.clear();
  children.clear();
  coef.clear();
  descendants.clear();
  }

bool sizes_known() {
  if(bounded) return false;
  // Castle Anthrax is infinite
  if(binarytiling) return false;
  // not implemented
  if(archimedean) return false;
  return true;  
  }

string expansion_analyzer::approximate_descendants(int d, int max_length) {
  auto t = SDL_GetTicks();
  while(isize(descendants) <= d && SDL_GetTicks() < t + 100)
    get_descendants(isize(descendants));
  if(isize(descendants) > d) 
    return get_descendants(d).get_str(max_length);
  int v = isize(descendants) - 1;
  bignum& b = get_descendants(v);
  if(b.digits.empty()) return "0";
  ld log_10 = log(b.digits.back()) / log(10) + 9 * (isize(b.digits) - 1) + (d - v) * log(get_growth()) / log(10);
  int more_digits = int(log_10);
  return XLAT("about ") + fts(pow(10, log_10 - more_digits)) + "E" + its(more_digits);
  }

void expansion_analyzer::view_distances_dialog() {
  distcolors[0] = forecolor;
  dialog::init("");
  long long qty[64];
  vector<cell*>& ac = currentmap->allcells();
  for(int i=0; i<64; i++) qty[i] = 0;
  for(int i=0; i<isize(ac); i++) {
    int d = celldistance(ac[i], cwt.at);
    if(d >= 0 && d < 64) qty[d]++;
    }
  
  if(sizes_known()) {
    find_coefficients();
    if(gamerange() >= valid_from && coefficients_known == 2) {
      for(int i=gamerange()+1; i<64; i++) {
        qty[i] = 0;
        for(int j=0; j<isize(coef); j++)
          qty[i] += qty[i-1-j] * coef[j];
        }
      }
    }

  for(int i=0; i<(bounded ? 64 : 16); i++) if(qty[i])
    dialog::addInfo(its(qty[i]), distcolors[i&7]);
  
  if(binarytiling) {
    dialog::addBreak(300);
    dialog::addInfo("a(d) ~ 2ᵈ");
    }
  else if(sizes_known()) {
    if(euclid) {
      dialog::addBreak(300);
      dialog::addInfo("a(d) = " + its(get_descendants(10).approx_int() - get_descendants(9).approx_int()) + "d", forecolor);
      }
    else {
      dialog::addBreak(200);
      
      if(coefficients_known == 2) {
        string fmt = "a(d+" + its(isize(coef)) + ") = ";
        bool first = true;
        for(int i=0; i<isize(coef); i++) if(coef[i]) {
          if(first && coef[i] == 1) ;
          else if(first) fmt += its(coef[i]);
          else if(coef[i] == 1) fmt += " + ";
          else if(coef[i] == -1) fmt += " - ";
          else if(coef[i] > 1) fmt += " + " + its(coef[i]);
          else if(coef[i] < -1) fmt += " - " + its(-coef[i]);
          fmt += "a(d";
          if(i != isize(coef) - 1)
            fmt += "+" + its(isize(coef) - 1 - i);
          fmt += ")";
          first = false;
          }
        dialog::addHelp(fmt);
        }
      else dialog::addBreak(100);
      
      char buf[20];
      snprintf(buf, 20, "%.8lf", (double) get_growth());
      dialog::addInfo("a(d) ~ " + string(buf) + "ᵈ", forecolor);
      }
    }
  dialog::display();
  }

#if !CAP_MINI
void compute_coefficients() {
  printf("%s %s\n", gp::operation_name().c_str(), ginf[geometry].name);
  start_game();
  
    printf("  sizes:");
    for(int i=0; i<10; i++) printf(" %d", expansion.get_descendants(i).approx_int());
    
    printf("  N = %d\n", expansion.N);

  expansion.find_coefficients();      
  if(expansion.coefficients_known == 2) {
    printf("  coefficients:"); for(int x: expansion.coef) printf(" %d", x);
    printf(" (tested on %d to %d)\n", expansion.valid_from, expansion.tested_to);
    }
  }

int readArgs() {
  using namespace arg;
           
  if(0) ;
  else if(argis("-vap")) { 
    PHASEFROM(2); 
    start_game();
    while(true) {
      string s = expansion.approximate_descendants(10000, 100);
      printf("s = %s\n", s.c_str());
      if(isize(expansion.descendants) >= 10000) break;
      }
    }
  else if(argis("-csizes")) { 
    PHASEFROM(2); 
    start_game();
    expansion.get_growth();
    for(int i=0; i<30; i++)
      printf("%s / %s\n", expansion.get_descendants(i).get_str(1000).c_str(), expansion.get_descendants(i, expansion.diskid).get_str(1000).c_str());  
    }
  else if(argis("-csolve")) { 
    PHASEFROM(2); 
    start_game();
    printf("preliminary_grouping...\n");
    expansion.preliminary_grouping();
    printf("N = %d\n", expansion.N);
    printf("reduce_grouping...\n");
    expansion.reduce_grouping();
    printf("N = %d\n", expansion.N);
    printf("growth = %lf\n", (double) expansion.get_growth());
    expansion.find_coefficients();      
    if(expansion.coefficients_known == 2) {
      printf("coefficients:"); for(int x: expansion.coef) printf(" %d", x);
      printf(", valid from %d to %d\n", expansion.valid_from, expansion.tested_to);
      }
    }
  else if(argis("-csolve_tab")) {
    for(eGeometry geo: {gNormal, gOctagon, g45, g46, g47}) {
      set_geometry(geo);
      set_variation(eVariation::pure);
      compute_coefficients();
      set_variation(eVariation::bitruncated);
      compute_coefficients();
      for(int x=1; x<9; x++)
      for(int y=0; y<=x; y++) {
        if(x == 1 && y == 0) continue;
        if(x == 1 && y == 1 && S3 == 3) continue;
        if(x+y > 10) continue;
        stop_game();
        gp::param = gp::loc(x, y);
        need_reset_geometry = true;
        set_variation(eVariation::goldberg);
        compute_coefficients();
        }
      }
    }
  else return 1;
  return 0;
  }

auto ea_hook = addHook(hooks_args, 100, readArgs);
#endif

expansion_analyzer expansion;

}