#Various high level routines and tools for building quick
#NPC AIs. Returns only action arguments where possible. More
#complex routines return (action, args) pairs where required.

import numpy as np
from queue import Queue
from forge.blade.lib import enums, utils

def turfSearchAndDestroy(world, entity, whitelist):
   for e in sortedByl1(world.env.ent, world.size, entity):
     if e.entityIndex == enums.Entity.NEURAL.value.data:
         if isAdjacent(entity.pos, e.pos):
            tree = Actions.ActionTree(world, entity, rootVersion=Actions.MeleeV2) 
            tree.decideArgs(e)
            return tree.actionArgPair()
         move = routePath(entity.pos, e.pos)
         if inWhitelist(world.env.tiles, entity.pos, move, whitelist):
            tree = Actions.ActionTree(world, entity, rootVersion=Actions.MoveV2) 
            tree.decideArgs(move)
            return tree.actionArgPair()
   return randomOnTurf(world, entity, whitelist)

def randomMove(world, entity):
   tree = Actions.ActionTree(world, entity, rootVersion=Actions.MoveV2) 
   tree.randomArgs()
   return tree.actionArgPair()

def randomOnTurf(world, entity, whitelist):
   delMatPairs = adjacencyDelMatPairs(world.env, entity.pos)
   moves = whitelistByBlock(delMatPairs, whitelist)
   if len(moves) == 0: return Actions.Pass(), Actions.EmptyArgs()
   ind = np.random.randint(0, len(moves))

   tree = Actions.ActionTree(world, entity, rootVersion=Actions.MoveV2) 
   tree.decideArgs(moves[ind])
   return tree.actionArgPair()

def inWhitelist(env, pos, delta, whitelist):
   r, c = posSum(pos, delta)
   return env[r, c] in whitelist

def whitelistByBlock(delMatPairs, whitelist):
   ret = []
   for deli, mati in delMatPairs:
      if mati in whitelist:
         ret += [deli]
   return ret

#Adjacency functions
def adjacentDeltas():
   return [(-1, 0), (1, 0), (0, 1), (0, -1)]

def l1Deltas(s):
   rets = []
   for r in range(-s, s+1):
      for c in range(-s, s+1):
         rets.append((r, c))
   return rets

def adjacentPos(pos):
   return [posSum(pos, delta) for delta in adjacentDeltas()]

def adjacentEmptyPos(env, pos):
   return [p for p in adjacentPos(pos) 
         if utils.inBounds(*p, env.size)]

def adjacentTiles(env, pos):
   return [env.tiles[p] for p in adjacentPos(pos) 
         if utils.inBounds(*p, env.size)]

def adjacentMats(env, pos):
   return [type(env.tiles[p].mat) for p in adjacentPos(pos) 
         if utils.inBounds(*p, env.shape)]

def adjacencyDelMatPairs(env, pos):
   return zip(adjacentDeltas(), adjacentMats(env, pos))
###End###

def l1(pos1, pos2):
   r1, c1 = pos1
   r2, c2 = pos2
   return abs(r1-r2) + abs(c1-c2)

def sortedByl1(ent, sz, startEnt):
   targs = l1Range(ent, sz, startEnt.pos, startEnt.searchRange)
   targs = sorted(targs, key=lambda targ: l1(startEnt.pos, targ.pos))
   return targs

def l1Range(ent, sz, start, rng):
   R, C = sz, sz
   rs, cs = start
   rt = max(0, rs-rng)
   rb = min(R, rs+rng)
   cl = max(0, cs-rng)
   cr = min(C, cs+rng)
   ret = []
   for r in range(rt, rb):
      for c in range(cl, cr):
         if len(ent[r, c]) > 0:
            ret += ent[r, c]
   return ret

def isAdjacent(pos1, pos2):
   rs, re = pos1
   es, ee = pos2
   return np.logical_xor(abs(re - rs) == 1, abs(ee - es) == 1) 

def posSum(pos1, pos2):
   return pos1[0] + pos2[0], pos1[1] + pos2[1]

def routePath(start, end):
   sr, sc = start
   er, ec = end
   if abs(sc - ec) > abs(sr - er):
      return (0, np.sign(ec - sc))
   return (np.sign(er - sr), 0)

def inRange(env, start, targ, rng):
   R, C = env.shape
   rs, cs = start
   rt = max(0, rs-rng)
   rb = min(R, rs+rng)
   cl = max(0, cs-rng)
   cr = min(C, cs+rng)
   return targ in env[rt:rb, cl:cr]

#Fix this
def findNearest(env, start, targ, rng=4 ):
   #Quick check
   rs, ts = start
   if not inRange(env, start, targ, rng):
      return

   #Expensive search
   cur = Queue()
   visited = {}
   cur.push(start)
   while not cur.empty:
      r, c = cur.pop()
      if (r, c) in visited:
         continue

      if env[r, c] == targ:
         return (r, c)

      visited[(r, c)] = 1
      if rs - r < targ:
         cur.push(r+1,  c)
      if cs - c < targ:
         cur.push(r,  c+1)
      if r - rs < targ:
         cur.push(r-1, c) 
      if c - cs < targ:
         cur.push(r, c-1)

   return None
   
class RageClock:
   def __init__(self, ticks):
      self.ticks = ticks

   def tick(self):
      self.ticks -= 1

   def isActive(self):
      return self.ticks > 0

