import random, time

# by hand likely with typos, probably goes in mtt

# Time 11
# #########################################
# # World 0 (modeled blind to any others) #
#  data = 275
#  agents = ['B']
# ~~~~ Bing! A appears ~~~~
# #########################################

class BigThing:
  def __init__(self, **attrs):
    self._attrs = attrs
    for name, val in attrs.items():
      setattr(self, name, val)
  def __str__(self):
    return str(next(iter(self._attrs.values())))

class P:
  def stability(self):
    return max([prob for val, prob in self])
  def sample(self):
    return random.choices([val for val, prob in self], [prob for val, prob in self])[0]
class PConst(P):
  def __init__(self, val):
    self.value = val
  def __iter__(self):
    yield [self.value, 1.0]
class PSimple(P):
  def __init__(self, probs_by_val):
    self.choices = list(probs_by_val.keys())
    self.weights = list(probs_by_val.values())
    self.size = len(probs_by_val)
  def __iter__(self):
    for x in range(self.size):
      yield [self.choices[x], self.weights[x]] # simplify
class PCombined(PSimple):
  # for flattening multiple possible resulting events
  def __init__(self, probs_by_val_by_prob):
    probs_by_val = {}
    for prob, dist in probs_by_val_by_prob.items():
      for val, subprob in dist.items():
        probs_by_val[val] = probs_by_val.get(val, 0) + subprob * prob
    super().__init__(probs_by_val)

class FakeTimeline: # change to TeleportationNegotiation or such
  def __init__(self, known_point):
    self.anchor = known_point
  # def expand(self,

# All probability content above. Have not turned
# universe into tree of possibilities. Just emotionally
# challenging.

class Region(BigThing):
  def __init__(self, universe, real=False, **attrs):
    super().__init__(**attrs)
    self.universe = universe
    self.universe.regions.append(self)
    self.real = real
    self.agents = set()
  def tick(self, time):
    pass
  @property
  def other_regions(self):
    return [region for region in self.universe.regions if region is not self]

class Miniverse:
  def __init__(self):
    self.regions = []
    self.time = 0
  def tick(self):
    self.time += 1
    for region in self.regions:
      region.tick(self.time)
    # tick all agents after all regions for fair&acc order
    for agent in [agent
                    for region in self.regions
                      for agent in region.agents]:
      agent.tick(agent.region, self.time)

class Agent(BigThing):
  def __init__(self, region, **attrs):
    super().__init__(**attrs)
    self.region = region
    self.region.agents.add(self)
  def teleport(self, region):
    if who.region.real:
      print('~~~~ Pop! {self} disappears ~~~~')
    elif region.real:
      print('~~~~ Bing! {self} appears ~~~~')
    region.agents.add(self)
    self.region.agents.discard(self)
    self.region = region
  def clone(self, region):
    return Agent(region, **self._attrs)

def create_model(idx, total):
  class SimpleRegion(Region):
    def __init__(self, universe, seed=None, **attrs):
      super().__init__(universe, **attrs):
      self.rand = random.Random(x=seed)
      self.tick(0)
    def tick(self, time):
      self.data = self.rand.randint(0, 256)
      super().tick(time)
    def clone(self):
      region = SimpleRegion(None, **self._attrs)
      region.agents = set([agent.clone for agent in self.agents])
      region.rand.setstate(self.rand.getstate())
      return region
  miniverse = Miniverse()
  for region_idx in range(total):
    SimpleRegion(miniverse, seed=region_idx, real=idx==region_idx)

  A = Agent(miniverse.regions[0], name='A')
  A.tick = lambda region, time: A.teleport(region.other_regions[0]) if region.data % 4 == 2 else None

  B = Agent(miniverse.regions[1], name='B')
  B.tick = lambda region, time: B.teleport(region.other_regions[0]) if region.data % 8 == 4 else None

  return miniverse.regions[idx]

def main():
  worlds = [create_model(idx, 2) for idx in range(2)]

  # needs uncertainty of data or tick, make trees

  while True:
    print('Time', worlds[0].universe.time)
    for world in worlds:
      print('#######'+ '###'     +'###############################')
      print('# World', world.idx, '(modeled blind to any others) #')
      print(' data =', world.data)
      print(' agents =', [agent.name for agent in world.agents])
      world.universe.tick() # Bing! A appears
      assert world.universe.time == worlds[0].universe.time
      print('#######'+ '###'     +'###############################')
      print()
    print()
    time.sleep(2)

if __name__ == '__main__':
  main()