namespace hr {

namespace ads_game {

enum eObjType { oRock, oMissile, oParticle, oResource };

struct ads_object {
  eObjType type;
  eResourceType resource;
  cell *owner;
  ads_matrix at;
  color_t col;
  vector<ld>* shape;
  
  ld life_start, life_end;
  cross_result pt_main;
  vector<cross_result> pts;
  
  ads_object(eObjType t, cell *_owner, const ads_matrix& T, color_t _col) : type(t), owner(_owner), at(T), col(_col) { 
    life_start = -HUGE_VAL;
    life_end = HUGE_VAL;
    }
  };

enum eWalltype { wtNone, wtDestructible, wtSolid, wtGate };

struct cellinfo {
  int mpd_terrain; /* 0 = fully generated terrain */
  int rock_dist; /* rocks generated in this radius */
  vector<std::unique_ptr<ads_object>> rocks;
  eWalltype type;
  cellinfo() {
    mpd_terrain = 4;
    rock_dist = -1;
    type = wtNone;
    }
  };

std::unordered_map<cell*, cellinfo> ci_at;

using worldline_visitor = std::function<bool(cell*, ld)>;

void compute_life(cell *c, transmatrix S1, const worldline_visitor& wv) {
  ld t = 0;

  int iter = 0;
  cell *cur_c = c;
  auto cur_w = hybrid::get_where(c);
  while(t < 2 * M_PI) {
    iter++;
    auto last_w = cur_w;
    auto next_w = cur_w;
    transmatrix next_S1;
    ld next_t;
    ld last_time = t;
    cell *next_c = nullptr;
    binsearch(t, t+M_PI/2, [&] (ld t1) { 
      S1 = S1 * chg_shift(t1 - last_time);
      last_time = t1;
      virtualRebase(cur_c, S1);
      cur_w = hybrid::get_where(cur_c);
      if(cur_w.first != last_w.first) {
        next_c = cur_c;
        next_w = cur_w;
        next_S1 = S1;
        next_t = t1;
        return true;
        }
      return false;
      }, 20);
    if(!next_c) return;
    S1 = next_S1;
    cur_w = next_w;
    t = next_t;
    cur_c = next_c;
    if(iter > 1000) {
      println(hlog, "compute_life c=", cur_c, " w=", cur_w, "t=", t, " S1=", S1);
      fixmatrix_ads(S1);
      }
    if(iter > 1100) break;
    if(wv(cur_w.first, t)) break;
    }
  }

map<int, int> genstats;

int gen_budget;

void gen_terrain(cell *c, cellinfo& ci, int level = 0) {
  if(level >= ci.mpd_terrain) return;
  if(ci.mpd_terrain > level + 1) gen_terrain(c, ci, level+1);
  forCellCM(c1, c) gen_terrain(c1, ci_at[c1], level+1);
  genstats[level]++;
  
  if(level == 2) {
    int r = hrand(100);
    if(r < 3) {
      forCellCM(c1, c) if(hrand(100) < 50)
        forCellCM(c2, c1)  if(hrand(100) < 50)
          if(ci_at[c2].type == wtNone) ci_at[c2].type = wtDestructible;
      }
    else if(r < 6) {
      forCellCM(c1, c) if(hrand(100) < 50)
        forCellCM(c2, c1)  if(hrand(100) < 50)
          if(ci_at[c2].type < wtSolid)
            ci_at[c2].type = wtSolid;
      }
    else if(r < 8)
      ci_at[c].type = wtGate;
    }
  ci.mpd_terrain = level;
  }

void gen_rocks(cell *c, cellinfo& ci, int radius) {
  if(radius <= ci.rock_dist) return;
  if(ci.rock_dist < radius - 1) gen_rocks(c, ci, radius-1);
  forCellCM(c1, c) gen_rocks(c1, ci_at[c1], radius-1);
  if(geometry != gNormal) { println(hlog, "wrong geometry detected in gen_rocks 1!");  exit(1); }

  if(radius == 0) {
    hybrid::in_actual([&] {
      int q = rpoisson(.25);
      
      auto add_rock = [&] (ads_matrix T) {
        eResourceType rt = eResourceType(rand() % 6);
        auto r = std::make_unique<ads_object> (oRock, c, T, rock_color[rt]);
        r->resource = rt;
        r->shape = &(rand() % 2 ? shape_rock2 : shape_rock);
        if(geometry != gRotSpace) { println(hlog, "wrong geometry detected in gen_rocks 2!");  exit(1); }
        compute_life(hybrid::get_at(c, 0), unshift(r->at), [&] (cell *c, ld t) {
          auto& ci = ci_at[c];
          hybrid::in_underlying_geometry([&] { gen_terrain(c, ci); });
          ci.type = wtNone;
          return false;
          });
        ci.rocks.emplace_back(std::move(r));
        };
      
      for(int i=0; i<q; i++) {
        int kind = hrand(100);
        if(kind < 50) 
          add_rock(ads_matrix(rots::uxpush(randd() * .6 - .3) * rots::uypush(randd() * .6 - .3)));
        else
          add_rock(ads_matrix(rots::uypush(randd() * .6 - .3) * lorentz(0, 3, 0.5 + randd() * 1)));
        }        
      });
    }
  ci.rock_dist = radius;
  }

void gen_particles(int qty, cell *c, shiftmatrix from, color_t col, ld t, ld spread = 1) {
  auto& ro = ci_at[c].rocks;
  for(int i=0; i<qty; i++) {
    auto r = std::make_unique<ads_object>(oParticle, c, from * spin(randd() * TAU * spread) * lorentz(0, 2, 1 + randd()), col );
    r->shape = &shape_particle;
    r->life_end = randd() * t;
    r->life_start = 0;
    ro.emplace_back(std::move(r));
    }
  }

void gen_resource(cell *c, shiftmatrix from, eResourceType rsrc) {
  if(!rsrc) return;
  auto r = std::make_unique<ads_object>(oResource, c, from, rsrc_color[rsrc]);
  r->shape = rsrc_shape[rsrc];
  r->life_end = HUGE_VAL;
  r->life_start = 0;
  r->resource = rsrc;
  ci_at[c].rocks.emplace_back(std::move(r));
  }

bool pointcrash(hyperpoint h, const vector<cross_result>& vf) {
  int winding = 0;
  vector<hyperpoint> kleins;
  for(auto& p: vf) kleins.push_back(kleinize(p.h) - h);
  auto take = [&] (hyperpoint& a, hyperpoint& b) {
    if(asign(a[1], b[1]) && xcross(b[0], b[1], a[0], a[1]) < 1e-6)
      winding++;
    };
  for(int i=1; i<isize(kleins); i++) take(kleins[i-1], kleins[i]);
  take(kleins.back(), kleins[0]);
  return winding & 1;
  }

void crash_ship() {
  if(ship_pt < invincibility_pt) return;
  invincibility_pt = ship_pt + how_much_invincibility;
  pdata.hitpoints--;
  if(pdata.hitpoints <= 0) game_over = true;
  hybrid::in_actual([&] {
    cell *c = hybrid::get_where(vctr).first;
    gen_particles(16, c, ads_inverse(current * vctrV) * spin(ang*degree), rsrc_color[rtHull], 0.5);
    });
  }

void handle_crashes() {
  vector<ads_object*> missiles;
  vector<ads_object*> rocks;
  vector<ads_object*> resources;
  for(auto m: displayed) {
    if(m->type == oMissile)
      missiles.push_back(m);
    if(m->type == oRock)
      rocks.push_back(m);
    if(m->type == oResource)
      resources.push_back(m);
    }
  hybrid::in_underlying_geometry([&] {
    for(auto m: missiles) {
      hyperpoint h = kleinize(m->pt_main.h);
      for(auto r: rocks) {
        if(pointcrash(h, r->pts)) {
          m->life_end = m->pt_main.shift;
          r->life_end = r->pt_main.shift;
          hybrid::in_actual([&] {
            gen_particles(8, m->owner, m->at * ads_matrix(Id, m->life_end), missile_color, 0.1);
            gen_particles(8, r->owner, r->at * ads_matrix(Id, r->life_end), r->col, 0.5);
            gen_resource(r->owner, r->at * ads_matrix(Id, r->life_end), r->resource);
            });
          }
        }
      }
    if(!game_over) for(int i=0; i<isize(shape_ship); i+=2) {
      hyperpoint h = spin(ang*degree) * hpxyz(shape_ship[i], shape_ship[i+1], 1);
      for(auto r: rocks) {
        if(pointcrash(h, r->pts)) crash_ship();
        }
      for(auto r: resources) {
        if(pointcrash(h, r->pts)) {
          r->life_end = r->pt_main.shift;
          gain_resource(r->resource);
          }
        }

      hyperpoint h1 = normalize(h);
      swap(h1[2], h1[3]);
      bool crashed = false;
      hybrid::in_actual([&] {
        ads_point rel = ads_inverse(current * vctrV) * ads_point(h1, 0);
        cell *c = vctr;
        virtualRebase(c, rel.h);
        optimize_shift(rel);
        auto w = hybrid::get_where(c);
        auto& ci = ci_at[w.first];
        ld t = rel.shift + w.second * cgi.plevel;
        if(ci.type == wtDestructible || ci.type == wtSolid || (ci.type == wtGate && (int(floor(t)) & 3) == 0)) {
          if(!crashed && ship_pt > invincibility_pt) println(hlog, "crashed at t = ", t / TAU, " shift = ", rel.shift/TAU, " sec = ", w.second*cgi.plevel/TAU);
          crashed = true;
          }
        });

      if(crashed) crash_ship();
      }
    });
  }

}}