// E3 -> H2xE -> Solv:
// -run -debug-portal -1 -canvas-random 100 -geo beti -intra-add -intra-bxe 4 4 3 -intra-sol 4 4 3 -ray-do  -noplayer

// S2xE->S3:
// -debug-portal -1 -run -canvas-random 0 -intra-120 -intra-add -intra-1440 -intra-start -ray-do

#include "../rogueviz/rogueviz.h"

namespace hr {

namespace intraf {

using namespace intra;

void set_wall(cell *c, color_t col) {
  c->wall = waWaxWall;
  c->landparam = col;
  }

map<cellwalker, int> plane;

cellwalker flatspin(cellwalker cw, int i) {
  if(hybri && cw.spin < cw.at->type - 2)
    cw.spin = gmod(cw.spin + (cw.mirrored ? -i : i), cw.at->type - 2);
  return cw;
  }

cellwalker gstrafe(cellwalker cw, int i) {
  if(reg3::in()) return currentmap->strafe(cw, i);
  if(prod) {
    if(i == cw.at->type-2 || i == cw.at->type-1)
      return cellwalker(cw.at->move(i), cw.spin);
    else for(int k: {-1, 1})
      if(i == flatspin(cw, k).spin)
        return cellwalker(cw.at->move(i), flatspin(flatspin(cw, k) + wstep, +k).spin);      
    }
  if(euc::in())
    return cellwalker(cw.at->move(i), cw.spin);
  throw hr_exception("unknown gstrafe");
  }

void make_plane(cellwalker cw, int d) {
  if(plane.count(cw)) return;
  plane[cw] = d;
  auto& ss = currentmap->get_cellshape(cw.at);
  for(int i=0; i<cw.at->type; i++)
    if(ss.dirdist[i][cw.spin] == 1)
      make_plane(gstrafe(cw, i), d+1);
  }

void build_wall(cellwalker cw, color_t a, color_t b) {
  println(hlog, "trying to build wall in ", full_geometry_name());
  cw += wstep;
  plane.clear();
  make_plane(cw, 0);
  for(auto p: plane)
    set_wall(p.first.at, p.  second & 1 ? a : b);
  println(hlog, "success, ", isize(plane));
  }

void check_shape() {
  start_game();
  auto sh = currentmap->get_cellshape(cwt.at);
  int j = 0;
  for(auto f: sh.faces) {
    j++;
    for(int i=0; i<isize(f); i++) {
      ld d = hdist(f[i], f[(1+i)%isize(f)]);
      println(hlog, tie(j,i), " : ", fts_fixed(d, 20));
      }
    }
  ld edgelen = 2 * edge_of_triangle_with_angles(M_PI/S7, M_PI / S3, M_PI/2);

  println(hlog, "crossf = ", cgi.crossf);
  println(hlog, "hcrossf = ", cgi.hcrossf);
  println(hlog, "edgelen = ", edgelen);
  println(hlog, sh.faces);  
  }

void create_intra_floors() {
  if(intra::in) intra::become();
  else stop_game();

  set_geometry(gSpace534);
  field_quotient_3d(5, 0x72414D0C);
  start_game();
  build_wall(cwt, 0xFFFF40, 0xD0D000);
  become();

  hybrid::csteps = 6;
  set_geometry(gBring);
  set_variation(eVariation::pure);
  check_cgi();
  cgi.require_basics();
  vid.plevel_factor = cgi.edgelen / cgi.scalefactor;
  check_cgi();
  cgi.require_basics();
  set_geometry(gProduct);
  start_game();
  check_shape();
  
  cwt.spin = 0;
  build_wall(cwt, 0xC0FFC0, 0x80C080);
  become();
  
  auto& T0 = euc::eu_input.user_axes;
  for(int i=0; i<3; i++)
  for(int j=0; j<3; j++)
    T0[i][j] = i==j ? 6 : 0;
  euc::eu_input.twisted = 0;
  euc::build_torus3();
  set_geometry(gCubeTiling);
  start_game();
  build_wall(cwt, 0xC0FFFF, 0x40FFFF);
  become();

  hybrid::csteps = 6;
  set_geometry(gOctahedron);
  check_cgi();
  cgi.require_basics();
  vid.plevel_factor = cgi.edgelen / cgi.scalefactor;
  check_cgi();
  cgi.require_basics();
  set_variation(eVariation::pure);
  set_geometry(gProduct);
  start_game();
  cwt.spin = 0;
  build_wall(cwt, 0x8080FF, 0x0000FF);
  become();

  set_geometry(gCell16);
  start_game();
  build_wall(cwt, 0xFF80FF, 0xFF00FF);
  become();

  start();

  // rogueviz::rv_hook(hooks_handleKey, 150, intra_key);
  }

void create_intra_solv() {
  if(intra::in) intra::become();
  else stop_game();

  arg::shift(); int x = arg::argi();
  arg::shift(); int y = arg::argi();
  
  println(hlog, "x=", x, " y=", y);
  
  vector<vector<cell*> > h(y), s(y);
  
  for(int i=0; i<y; i++) h[i].resize(x), s[i].resize(x);  

  stop_game();

  set_geometry(gBinary3);
  start_game();
  h[0][0] = cwt.at;
  for(int i=1; i<y; i++) h[i][0] = h[i-1][0]->cmove(6);
  for(int i=0; i<y; i++) for(int j=1; j<x; j++)
    h[i][j] = h[i][j-1]->cmove(4);

  for(int i=0; i<y; i++)
  for(int j=0; j<x; j++) {
    setdist(h[i][j], 7, nullptr);
    h[i][j]->wall = waNone;
    forCellCM(c1, h[i][j]) {}
    set_wall(h[i][j]->cmove(0), 0xFF00FF ^ (hrand(0x1000000) & 0x1F1F1F));
    set_wall(h[i][j]->cmove(1), 0xC000FF ^ (hrand(0x1000000) & 0x1F1F1F));
    set_wall(h[i][j]->cmove(2), 0xFF00C0 ^ (hrand(0x1000000) & 0x1F1F1F));
    set_wall(h[i][j]->cmove(3), 0xC000C0 ^ (hrand(0x1000000) & 0x1F1F1F));
    set_wall(h[i][j]->cmove(8), 0xFF40FF ^ (hrand(0x1000000) & 0x1F1F1F));
    }

  become();

  set_geometry(gSol);
  start_game();
  s[0][0] = cwt.at;
  for(int i=1; i<y; i++) s[i][0] = s[i-1][0]->cmove(5);
  for(int i=0; i<y; i++) for(int j=1; j<x;j++)
    s[i][j] = s[i][j-1]->cmove(4);
  
  for(int i=0; i<y; i++)
  for(int j=0; j<x; j++) {
    setdist(s[i][j], 7, nullptr);
    s[i][j]->wall = waNone;
    forCellCM(c1, s[i][j]) {}
    set_wall(s[i][j]->cmove(2), 0xFFFFFF ^ (hrand(0x1000000) & 0x1F1F1F));
    set_wall(s[i][j]->cmove(3), 0xC0FFFF ^ (hrand(0x1000000) & 0x1F1F1F));
    set_wall(s[i][j]->cmove(6), 0xFFC0FF ^ (hrand(0x1000000) & 0x1F1F1F));
    set_wall(s[i][j]->cmove(7), 0xFFFFC0 ^ (hrand(0x1000000) & 0x1F1F1F));
    }
  become();

  start();
  
  println(hlog, "Started");

  for(int i=0; i<y; i++)
    connect_portal(cellwalker(h[i][0], 5), cellwalker(s[i][x-1], 4), 1);
  
  for(int i=0; i<y; i++)
    connect_portal(cellwalker(s[i][0], 0), cellwalker(h[i][x-1], 4), 1);

  for(int j=0; j<x; j++)
    connect_portal(cellwalker(h[0][j], 7), cellwalker(s[y-1][x-1-j], 5), 0);

  for(int j=0; j<x; j++)
    connect_portal(cellwalker(s[0][j], 1), cellwalker(h[y-1][x-1-j], 6), 0);
  
  set_wall(s[hrand(y)][hrand(x)], 0xA02020);
  set_wall(h[hrand(y)][hrand(x)], 0x2020A0);

  // rogueviz::rv_hook(hooks_handleKey, 150, intra_key);
  }

void create_intra_120() {
  patterns::whichCanvas = 'r';
  patterns::rwalls = 0;
  if(intra::in) intra::become();
  else stop_game();
  arcm::current.parse("8,4,6");
  set_geometry(gArchimedean);
//  set_variation(eVariation::pure);
  set_variation(eVariation::dual);
  be_ratio_edge(0);
  for(cell *c: currentmap->allcells()) {
    println(hlog, currentmap->full_shvid(c), " : ", currentmap->get_corner(c, 0), currentmap->get_corner(c, 1), currentmap->get_corner(c, 2));
    }
  hybrid::csteps = 10;
  set_geometry(gProduct);
  start_game();
  vector<cell*> bound;
  PIU( [&] {
    cell *s = currentmap->gamestart();
    hyperpoint h = currentmap->get_corner(s, 1);
    for(cell *c: currentmap->allcells()) {
      hyperpoint j = currentmap->relative_matrix(c, s, C0) * C0;
      if(hdist(h, j) > M_PI/2) c->wall = waPalace;
      }
    for(cell *c: currentmap->allcells()) if(c->wall == waPalace) {
      int nei = 0;
      forCellCM(c1, c) if(c1->wall != waPalace) nei++;
      if(nei == 1) bound.push_back(c);
      }
    auto ang = [&] (cell *c) {
      hyperpoint j = currentmap->relative_matrix(c, s, C0) * C0;
      j = gpushxto0(h) * j;
      return atan2(j[0], j[1]);
      };
    for(cell *c: bound) println(hlog, "j= ", ang(c));
    sort(bound.begin(), bound.end(), [&] (cell *a, cell *b) { return ang(a) < ang(b); });
    int id = 0;
    for(auto b: bound) set_wall(b, ((id++)&1) ? 0xFFFFFF : 0x202020);
    } ());
  for(int i=0; i<isize(bound); i++)
  for(int j=0; j<hybrid::csteps-2; j++)
    set_wall(hybrid::get_at(bound[i], j), (i+j)&1 ? 0x8080FF : 0x00000FF);
  }

void create_intra_1440() {
  patterns::whichCanvas = 'r';
  patterns::rwalls = 0;
  if(intra::in) intra::become();
  else stop_game();
  set_geometry(gCell8);
//  set_variation(eVariation::pure);
  set_variation(eVariation::coxeter);
  reg3::coxeter_param = 7;
  start_game();

  cell *s = currentmap->gamestart();
  auto vs = currentmap->get_cellshape(s).vertices_only_local;
  println(hlog, vs);
  hyperpoint h = vs[1];
  for(cell *c: currentmap->allcells()) {
    hyperpoint j = currentmap->relative_matrix(c, s, C0) * C0;
    // hyperpoint j = inverse(currentmap->relative_matrix(cwt.at, c, C0)) * C0;
    if(hdist(h, j) > M_PI/2) 
      set_wall(c, (celldistance(c, s)&1) ? 0xFF80FF : 0xFF00FF);
    }
  }

void recurse(int r, cell *c, int i, int j) {
  setdist(c, 7, nullptr);
  forCellCM(c1, c) setdist(c1, 7, nullptr);
  c->wall = waNone;
  if(r == 1) {
    if(sol) {
      int flip = (j&2)>>1;
      set_wall(c->cmove(6^flip), 0xC000C0);
      set_wall(c->cmove(7^flip), 0x800080);
      }
    else {
      set_wall(c->cmove((j&1)), 0x64BF95);
      set_wall(c->cmove(1-(j&1)), 0x449F75);
      }
    }
  else {
    if(sol) {
      recurse(r-1, c->cmove(6), 2*i, j>>1);
      recurse(r-1, c->cmove(7), 2*i+1, j>>1);
      }
    else {
      recurse(r-1, c->cmove(0), i, j);
      recurse(r-1, c->cmove(1), i, j);
      }
    }
  }

void connect_portal_x(cellwalker cw1, cellwalker cw2, int spin) {
  setdist(cw1.cpeek(), 7, nullptr);
  setdist(cw2.cpeek(), 7, nullptr);
  set_wall(cw1.cpeek(), 0xFF0000);
  set_wall(cw2.cpeek(), 0xFF0000);
  connect_portal(cw1, cw2, spin);
  }

void recurse_portal(int r, cell *cl, cell *cr) {
  connect_portal_x(cellwalker(cl, 6), cellwalker(cr, 5), 3);
  if(r > 1) {
    recurse_portal(r-1, cl->cmove(0), cr->cmove(0));
    recurse_portal(r-1, cl->cmove(1), cr->cmove(1));
    }
  }

vector<reaction_t> portals;

void create_intra_bxe() {
  println(hlog, "called create_intra_bxe");
  patterns::whichCanvas = 'r';
  patterns::rwalls = 100;
  if(intra::in) intra::become();
  else stop_game();
  hybrid::csteps = 0;

  arg::shift(); int x = arg::argi();
  arg::shift(); int y = arg::argi();
  arg::shift(); int z = arg::argi();
  
  vector<vector<cell*> > h(y);
  
  for(int i=0; i<y; i++) h[i].resize(x);

  stop_game();

  set_geometry(gBinary4);
  vid.plevel_factor = log(2);
  set_geometry(gProduct);
  start_game();
  h[0][0] = cwt.at;
  
  for(int i=1; i<y; i++) h[i][0] = h[i-1][0]->cmove(2);
  for(int i=0; i<y; i++) for(int j=1; j<x; j++)
    h[i][j] = h[i][j-1]->cmove(5);
  
  println(hlog, h);

  for(int i=0; i<y; i++)
  for(int j=0; j<x; j++) {
    setdist(h[i][j], 7, nullptr);
    }

  for(int i=0; i<y; i++)
  for(int j=0; j<x; j++) {
    set_wall(h[i][j]->cmove(3), ((j+(i>>1))&1) ? 0xA4FFD5 : 0x64BF95);
    recurse(z, h[i][j], i, j);
    }

  become();
  start(isize(intra::data)-1);
  
  for(int i=0; i<y; i++)
    recurse_portal(z, h[i][0], h[i][x-1]);

  for(int i=0; i<x; i++) {
    cell *cl = h[0][i];
    cell *cr = h[y-1][i];
    for(int j=0; j<z; j++) {
      connect_portal_x(cellwalker(cl, 4), cellwalker(cr, 2), 2);
      cl = cl->move(0);
      cr = cr->move(1);
      }
    }
    
  }
  
void recurse_portal_solv1(int r, cell *cl, cell *cr) {
  connect_portal_x(cellwalker(cl, 0), cellwalker(cr, 4), 0);
  if(r > 1) {
    recurse_portal_solv1(r-1, cl->cmove(6), cr->cmove(6));
    recurse_portal_solv1(r-1, cl->cmove(7), cr->cmove(7));
    }
  }

void recurse_portal_solv2(int r, cell *cl, cell *cr) {
  connect_portal_x(cellwalker(cl, 1), cellwalker(cr, 5), 0);
  if(r > 1) {
    recurse_portal_solv2(r-1, cl->cmove(7), cr->cmove(6));
    }
  }

void create_intra_sol() {
  println(hlog, "called create_intra_sol");
  patterns::whichCanvas = 'r';
  patterns::rwalls = 100;
  if(intra::in) intra::become();
  else stop_game();

  arg::shift(); int x = arg::argi();
  arg::shift(); int y = arg::argi();
  arg::shift(); int z = arg::argi();
  
  vector<vector<cell*> > h(y);
  
  for(int i=0; i<y; i++) h[i].resize(x);

  stop_game();

  set_geometry(gSol);
  start_game();
  h[0][0] = cwt.at;

  for(int i=0; i<x-1; i++) h[0][0] = h[0][0]->cmove(0);
  for(int i=0; i<y-1; i++) h[0][0] = h[0][0]->cmove(1);
  
  for(int i=1; i<y; i++) h[i][0] = h[i-1][0]->cmove(5);
  for(int i=0; i<y; i++) for(int j=1; j<x; j++)
    h[i][j] = h[i][j-1]->cmove(4);

  for(int i=0; i<y; i++)
  for(int j=0; j<x; j++) {
    setdist(h[i][j], 7, nullptr);
    }

  for(int i=0; i<y; i++)
  for(int j=0; j<x; j++) {
    set_wall(h[i][j]->cmove(2^((i>>1)&1)), 0xFFFDD0);
    set_wall(h[i][j]->cmove(3^((i>>1)&1)), 0xFFE080);
    recurse(z, h[i][j], i, j);
    }

  become();
  start(isize(intra::data)-1);
  
  for(int i=0; i<y; i++)
    recurse_portal_solv1(z, h[i][0], h[i][x-1]);

  for(int i=0; i<x; i++)
    recurse_portal_solv2(z, h[0][i], h[y-1][i]);

  println(hlog, "finished create_intra_sol");
  }

// all generators will add to the current scene

auto hooks = 
  // generate scene with H3, H2xE, E3, S2xE (8x6), S3 (16-cell) with floors; runs automatically
  arg::add3("-intra-floors", create_intra_floors)
+ arg::add3("-check-shape", check_shape)
  // generate scene with H3 and Solv landscape, need X Y parameters, runs automatically
+ arg::add3("-intra-solv", create_intra_solv)
  // generate S2xE with triangles (-intra-start to run it)
+ arg::add3("-intra-120", create_intra_120)
  // generate S3 with triangles (-intra-start to run it)
+ arg::add3("-intra-1440", create_intra_1440)
  // generate binary-tiling H2xE with floors to the current scene, runs automatically
+ arg::add3("-intra-bxe", create_intra_bxe)
  // generate Sol with floors to the current scene, runs autimatically
+ arg::add3("-intra-sol", create_intra_sol)
//+ arg::add3("-intra-more", create_intra_more);
+ arg::add3("-intra-demo-floors", [] {
  walking::colors_of_floors = {
    0xFFFF40, 0xD0D000,
    0xC0FFC0, 0x80C080,
    0xC0FFFF, 0x40FFFF,
    0x8080FF, 0x0000FF,
    0xFF80FF, 0xFF00FF,
    0x64BF95, 0xA4FFD5,
    0xFFFDD0, 0xFFD080
    };
  })
+ addHook_rvslides(10, ([] (string s, vector<tour::slide>& v) {
    println(hlog, "called with s='", s, "'");
    if(s != "portal") return;

    using namespace tour;
    auto load = [] (string s, ld x, int y) {
      return [s, x, y] {
        slide_backup(vid.cells_drawn_limit, 100);
        slide_backup(smooth_scrolling, true);
        slide_backup(ray::max_cells, 999999);
        slide_backup(walking::on, true);
        slide_backup(walking::eye_level, x);
        mapstream::loadMap(s);
        slide_backup(ray::fixed_map, true);
        slide_backup(ray::max_iter_intra, y);
        slide_backup(mapeditor::drawplayer, false);
        slide_backup(playermoved, false);
        #if CAP_VR
        slide_backup(vrhr::hsm, vrhr::eHeadset::holonomy);
        slide_backup(vrhr::eyes, vrhr::eEyes::truesim);
        slide_backup(vrhr::cscr, vrhr::eCompScreen::eyes);
        #endif
        };
      };

    auto add = [&] (string s, string desc, string youtube, string twitter, reaction_t loader) {
      v.push_back(tour::slide{
        s, 10, tour::LEGAL::NONE | tour::QUICKSKIP | tour::QUICKGEO, desc,
        [=] (tour::presmode mode) {
          setCanvas(mode, '0');
          if(youtube != "")
            slide_url(mode, 'y', "YouTube link", youtube);
          if(twitter != "")
            slide_url(mode, 't', "Twitter link", twitter);
          slide_action(mode, 'r', "run this visualization", loader);
          slidecommand = "portal options";
          if(mode == tour::pmKey) pushScreen(intra::show_portals);
          }
        });
      };

    add("inter-geometric portals",
      "In this world we can find portals between six different geometries. The camera is in 'walking mode' i.e. restricted to keep close to the floor (this can be disabled with '5').",
      "https://youtu.be/yqUv2JO2BCs", "https://twitter.com/ZenoRogue/status/1496867204419452935",
      load("portalscene3.lev", 0.2174492, 600)
      );
    add("curved landscape",
      "Here we create portals between Solv and H3 geometries, resulting in a scene looking a bit like a curved landscape.",
      "", "https://twitter.com/ZenoRogue/status/1446127100516130826",
      load("solv-h3-scene.lev", 0.05, 3000));
    }));

}

}
