

universe_devmode <- false


function make_world(x, y, color)
{
  local scheme="generic"
  // local scheme=choose_scheme(frnd())
  // local mods=[]
  // foreach (m in modifiers)
  //   if (frnd()<m.chance) mods.append(m.name)

  return {
    ux=x
    uy=y
    seed=irnd()
    scheme=scheme
    // mods=mods
    hidden=true
    recent=false
    activated=false
    visited=false
    color1=color
    // color2=color
    color2=tocolor(hsv2rgba(frnd()*360, rndf(0.5, 1), rndf(0.4, 1), 1))
  }
}


function make_link(a, b, links)
{
  if (a<0 || b<0) return;
  links.append({ from=a, to=b, type="bi" })
}


class UniCluster
{
  id=0
  color=0
  edge=null
  touches=null
  linked=false

  constructor(x, y, i)
  {
    id=i
    color=tocolor(hsv2rgba(frnd()*360, rndf(0.5, 1), rndf(0.4, 0.7), 1))
    edge=[]
    touches=[]
    addEdge(x-1, y)
    addEdge(x+1, y)
    addEdge(x, y-1)
    addEdge(x, y+1)
  }

  function addEdge(x, y)
  {
    local i=make_map_index(x, y)
    foreach (e in edge) if (e==i) return;
    edge.append(i)
  }

  function addTouched(c)
  {
    if (c==id) return;
    foreach (t in touches) if (t==c) return;
    touches.append(c)
  }

  function markLinked(clusters)
  {
    if (linked) return;
    linked=true
    foreach (t in touches) clusters[t].markLinked(clusters)
  }

  function expand(map)
  {
    for (local i=edge.len()-1; i>=0; --i)
    {
      local c=map[edge[i]]
      if (c==0) continue;
      addTouched(c)
      edge.remove(i)
    }

    if (edge.len()==0) return false;

    local i=irnd()%edge.len()
    local e=edge[i]
    edge.remove(i)
    map[e]=id
    local x=map_index_x(e)
    local y=map_index_z(e)
    addEdge(x-1, y)
    addEdge(x+1, y)
    addEdge(x, y-1)
    addEdge(x, y+1)
    return true;
  }
}


function generate_universe()
{
  local map=IntMap(0)

  local maxClusters=25
  local seedingRange=10
  local maxWorlds=250

  if (DEMO)
  {
    maxClusters=2
    seedingRange=2
    maxWorlds=10
  }

  // seed clusters
  local clusters=[null]

  local numWorlds=0

  for (local ci=1; ci<=maxClusters; )
  {
    local p=make_dir_xz(frnd())*sqrt(frnd())*seedingRange
      +vec4f(HMAP_SIZE/2, 0, HMAP_SIZE/2, 0)
    local x=getx(p).tointeger()
    local y=getz(p).tointeger()
    if (map.get(x, y)!=0) continue;
    map.set(x, y, ci)
    clusters.append(UniCluster(x, y, ci))
    ++ci
    ++numWorlds
  }

  // expand clusters
  for (;;)
  {
    foreach (c in clusters)
    {
      if (c==null) continue;
      if (c.expand(map)) ++numWorlds
      c.linked=false
    }

    clusters[1].markLinked(clusters)

    local ok=true
    foreach (c in clusters)
      if (c!=null && !c.linked) { ok=false; break; }

    if (ok && numWorlds>=maxWorlds) break;
  }

  print(numWorlds+" worlds after all clusters linked\n")

  // create worlds
  local worlds=[]
  local wmap=IntMap(-1)

  local gridStep=40, gridRnd=0.9
  for (local y=0; y<HMAP_SIZE; ++y)
    for (local x=0; x<HMAP_SIZE; ++x)
    {
      local ci=map.get(x, y)
      if (ci==0) continue;
      local w=make_world(
        (x+frnd()*gridRnd)*gridStep,
        (y+frnd()*gridRnd)*gridStep,
        clusters[ci].color)
      local id=worlds.len()
      w.id<-id
      wmap.set(x, y, id)
      worlds.append(w)
    }

  // create links
  local links=[]

  for (local y=0; y<HMAP_SIZE; ++y)
    for (local x=0; x<HMAP_SIZE; ++x)
    {
      local cc=map.get(x, y)
      if (cc==0) continue;

      local cw=wmap.get(x, y)

      local lc=map.get(x-1, y)
      local dc=map.get(x, y-1)

      if (lc!=0) make_link(cw, wmap.get(x-1, y), links)
      if (dc!=0) make_link(cw, wmap.get(x, y-1), links)
    }

  // unhide starting world
  local startId=irnd()%worlds.len()
  worlds[startId].hidden=false

  // adjust world positions
  universe=
  {
    worlds=worlds,
    links=links,
    focusWorld=startId,
    enteredWorld=-1,
  }

  for (local n=10; n; --n) relax_worlds_pos()

  return universe
}


function get_world_tooltip(w)
{
  local s=""

  if (universe_devmode)
  {
    s+="World #"+w.id+"\n"
    s+=w.scheme+" "+w.seed
    s+="\n---\n"
  }

  if (!w.visited) s+="Unvisited.\n"
  if (w.activated) s+="Portal is active.\n"
  if (w.recent) s+="Recently discovered.\n"

  return s;
}


function relax_worlds_pos()
{
  local old=array(universe.worlds.len())
  for (local i=0; i<old.len(); ++i) old[i]=clone universe.worlds[i]

  local rk=0.1

  foreach (w in universe.worlds)
  {
    local cx=w.ux, cy=w.uy
    local bx=0, by=0, bd=60
    bd*=bd
    foreach (ow in old)
    {
      if (ow.id==w.id) continue;
      local dx=ow.ux-cx, dy=ow.uy-cy
      local d=dx*dx+dy*dy
      if (d>bd) continue;
      bd=d; bx=dx; by=dy
    }

    w.ux-=bx*rk
    w.uy-=by*rk
  }
}


function unhide_world(w)
{
  if (!w.hidden) return;
  w.hidden=false
  w.recent=true
}


function activate_world(w)
{
  if (w.id<0) return;

  universe.focusWorld=w.id
  w.hidden=false
  w.recent=false

  if (w.activated) return;
  w.activated=true

  foreach (w in universe.worlds) w.recent=false

  foreach (l in universe.links)
  {
    if (l.type=="bi")
    {
      if (l.from==w.id || l.to==w.id)
      {
        unhide_world(universe.worlds[l.from])
        unhide_world(universe.worlds[l.to  ])
      }
    }
  }
}


function activate_current_world()
{
  if (!cur_world || cur_world.id<0) return;

  activate_world(cur_world)
  // universe.enteredWorld=-1
  save_game()
}


function get_world_ctx_menu(w)
{
  if (!universe_devmode) return null;

  local menu=[]
  menu.append("World #"+w.id)
  menu.append(["Activate", function() { activate_world(w); save_game() }])
  // menu.append(["relax", relax_worlds_pos])
  return menu;
}
