// show the fundamental domain for quotient spaces
// Copyright (C) 2018 Zeno and Tehora Rogue, see 'hyper.cpp' for details

#include "rogueviz.h"

namespace hr {

namespace fundamental {

color_t color1, color2;

map<cell*, int> same;
map<cell*, shiftmatrix> gm;

bool is_connected(cellwalker cw) {
  return same[cw.at] & (1<<cw.spin);
  }

void be_connected(cellwalker cw) {
  // transmatrix T = gm[cw.at];
  same[cw.at] |= (1<<cw.spin);
  cw += wstep;
  same[cw.at] |= (1<<cw.spin);
  /* printf("%s", display(T * C0)); 
  printf(" %s\n", display(gm[cw.at] * C0)); */
  // queueline(T * C0, gm[cw.at] * C0, 0xFF0000FF, 3);
  }

int funmode = 0;

shiftpoint corner(cellwalker cw) {
  shiftmatrix T = gm[cw.at];
  if(funmode == 2) {
    while(cw.at->type != S7) { 
      cw++; 
      T = T * currentmap->adj(cw.at, cw.spin);
      cw += wstep; 
      }
    return T * C0;
    }
  return gm[cw.at] * get_corner_position(cw.at, cw.spin+(cw.mirrored?0:1), 3);
  }

transmatrix rel(cellwalker cw) {
  return currentmap->adj(cw.at, cw.spin);
  }

ld label_dist = .3;

shiftmatrix labelpos(shiftpoint h1, shiftpoint h2) {
  shiftpoint h = mid(h1, h2);
  shiftmatrix T = rgpushxto0(h);
  hyperpoint hx = inverse_shift(T, h2);
  ld alpha = atan2(-hx[1], hx[0]);
  return T * xspinpush(alpha + M_PI/2, label_dist);
  }
 
ld widthfactor = 5;
ld label_scale = 1;

void fundamental_marker() {
  if(!funmode || !quotient) return;
  same.clear();
  gm.clear();
  
  same[cwt.at] = 0;
  gm[cwt.at] = ggmatrix(cwt.at);
  
  vector<cell*> cells;
  cells.push_back(cwt.at);
  
  int tree_edges = 0;
  int face_edges = 0;
  
  for(int k=0; k<isize(cells); k++) {
    cell *c = cells[k];
    for(int i=0; i<c->type; i++) {
      cellwalker cw(c, i);
      cell *c2 = cw.cpeek();
      if(gm.count(c2)) continue;
      gm[c2] = gm[c] * rel(cw);
      // queueline(gm[c2] * C0, gm[c2] * xspinpush0(ticks, 0.2), 0xFFFFFFFF, 3);
      be_connected(cw);
      tree_edges++;
      cells.push_back(c2);
      }
    }
  
  while(true) {
    int f = face_edges;
    for(int k=0; k<isize(cells); k++) {
      cell *c = cells[k];
      for(int i=0; i<c->type; i++) {
        cellwalker cw(c, i);
        if(is_connected(cw) && is_connected(cw+1) && !is_connected(cw+wstep-1)) {
          face_edges++;
          be_connected(cw+wstep-1);
          }
        }
      }
    if(f == face_edges) break;
    }
  
  cellwalker cw;
  
  int corners = 0;

  for(int k=0; k<isize(cells); k++) {
    cell *c = cells[k];
    for(int i=0; i<c->type; i++) {
      cellwalker cw0(c, i);
      if(!is_connected(cw0) && !is_connected(cw0+1) && !is_connected(cw0+wstep-1))
        corners++, cw = cw0;
      }
    }
  
  // printf("tree edges = %d, face edges = %d, corners = %d\n", tree_edges, face_edges, corners);
  
  map<cellwalker, cellwalker> next_corner;
  map<cellwalker, cellwalker> prev_corner;
  
  for(int ci=0; ci<corners; ci++) {
    cellwalker cw0 = cw;
        
    while(true) {
      cw++;
      if(is_connected(cw)) {
        cw += wstep;
        cw++;
        }
      if(!is_connected(cw+1) && !is_connected(cw+wstep-1))
        break;
      }
    
    next_corner[cw0] = cw;
    prev_corner[cw] = cw0;
    }

  vector<pair<shiftmatrix, shiftmatrix>> nearm;

  for(int ci=0; ci<corners; ci++) {
    for(int u=0; u<1; u++) {
      cellwalker cw1 = cw+u+wstep+(u-1);
      /* printf("%p/%d %p/%d ", cw.at, cw.spin, cw1.at, cw1.spin);
      printf("[%d %d %d] ", is_connected(cw), is_connected(cw+1), is_connected(cw+wstep-1));
      printf("[%d %d %d] ", is_connected(cw1), is_connected(cw1+1), is_connected(cw1+wstep-1));
      printf("%d %d;\n", !!next_corner.count(cw1), !!next_corner.count(cw1+wmirror-1)); */
      shiftmatrix T_here = gm[cw.at] * rel(cw+u);
      shiftmatrix T_there = gm[cw1.at];
      nearm.emplace_back(T_here, T_there);
      }
    cw = next_corner[cw];
    }
  
  vid.linewidth *= widthfactor;

  for(int ci=0; ci<corners; ci++) {

    shiftpoint h = corner(cw);
    cw = next_corner[cw];
    shiftpoint h2 = corner(cw);
    
    for(auto& n: nearm) queueline(n.first * inverse_shift(n.second, h), n.first * inverse_shift(n.second, h2), color1, 3);
    }
    
  for(int ci=0; ci<corners; ci++) {

    shiftpoint h = corner(cw);
    cw = next_corner[cw];
    shiftpoint h2 = corner(cw);
    
    queueline(h, h2, color2, 3);
    }
  
  if(0) for(int k=0; k<isize(cells); k++) {
    cell *c = cells[k];
    for(int i=0; i<c->type; i++) {
      cellwalker cw0(c, i);
      if(!is_connected(cw0)) continue;
      int v = 0;
      for(auto& n: nearm) {
        queueline(n.first * inverse_shift(n.second, gm[cw0.at]) * xspinpush0(v, .05), n.first * inverse_shift(n.second, gm[cw0.cpeek()]) * xspinpush0(v, .05), 0xFF8000FF, 0);
        v++;
        }
      queueline(gm[cw0.at] * C0, gm[cw0.cpeek()] * C0, 0xFF0000FF, 0);
      }
    }

  set<cellwalker> visited;
  
  int id = 0;
  
  for(int ci=0; ci<corners; ci++) {
    
    cellwalker cw1 = (cw+1+wstep);
    bool mirrored = false;
    if(!next_corner.count(cw1)) cw1 = cw1 + wmirror - 1, mirrored = true;

    // visited.insert(next_corner[cw]);
    // cellwalker cw2 = next_corner[cw];
    if(next_corner[cw] < (mirrored ? next_corner[cw1] : cw1)) {
    
      int mc = (mirrored ? color1 : color2) >> 8;
      if(hdist(corner(cw), corner(next_corner[cw])) > 1e-3) {
        queuestr(labelpos(corner(cw), corner(next_corner[cw])), label_scale/cgi.scalefactor, its(id), mc);
        if(mirrored)
          queuestr(labelpos(corner(cw1), corner(next_corner[cw1])), label_scale/cgi.scalefactor, its(id), mc);
        else
          queuestr(labelpos(corner(prev_corner[cw1]), corner(cw1)), label_scale/cgi.scalefactor, its(id), mc);
        id++;
        }      
      }
    cw = next_corner[cw];
    }

  vid.linewidth /= widthfactor;
  }

int readArgs() {
  using namespace arg;
           
  if(0) ;
  else if(argis("-fundamental")) {
    shift(); funmode = argi();
    shift(); color1 = arghex();
    shift(); color2 = arghex();
    shift_arg_formula(widthfactor);
    shift_arg_formula(label_scale);
    shift_arg_formula(label_dist);
    }
  else return 1;
  return 0;
  }

auto fundamentalhook = addHook(hooks_args, 100, readArgs) + addHook(hooks_frame, 100, fundamental_marker) + addHook(hooks_clearmemory, 100, [] { same.clear(); gm.clear(); });

}

}