module Nove.Verse where

import Zero ( total )
import Data.Tuple ( swap )
import Data.IntMap as IntMap ( IntMap, fromDistinctAscList, (!), adjust, insert )
import Data.Map as Map ( Map, fromList )

{- Index coordinates

       + + + +              z z z x x x y y y
      z z z x +            - - - - - - - - -
     z z z x x +          x x x y y y z z z
    z z z x x x +   ->   x x x y y y z z z
     x x y y y +        x x x y y y z z z
      x y y y +        - - - - - - - - -
       y y y +        y y y z z z x x x


       + + + +            + + + +               6 7 8 0 1 2 3 4 5      0 0 0 0 0 0 0 0 0
      6 7 8 0 +          2 2 2 2 +             - - - - - - - - -      - - - - - - - - -
     6 7 8 0 1 +        1 1 1 1 1 +           0 1 2 3 4 5 6 7 8      2 2 2 2 2 2 2 2 2
    6 7 8 0 1 2 +      0 0 0 0 0 0 +         0 1 2 3 4 5 6 7 8      1 1 1 1 1 1 1 1 1
     1 2 3 4 5 +        2 2 2 2 2 +         0 1 2 3 4 5 6 7 8      0 0 0 0 0 0 0 0 0
      2 3 4 5 +          1 1 1 1 +         - - - - - - - - -      - - - - - - - - -
       3 4 5 +            0 0 0 +         3 4 5 6 7 8 0 1 2      2 2 2 2 2 2 2 2 2

         x                  y                     x                      y

Performance should be predictable: time over space -}

-- | Verse

class Atom a where
   void :: a  -- empty cel
   move :: a -> Maybe Dir  -- wants to move
   lock :: a -> Bool

data Verse a = Verse
   { radius :: Int
   , nodes :: IntMap a
   }

verse :: Atom a => Int -> Verse a
verse r = v
   where
   v = Verse
      { radius = r
      , nodes = IntMap.fromDistinctAscList $ zip [0..] (replicate (3 * r * r) void)
      }

-- | Navigation

coordToIndex :: Verse a -> (Int,Int) -> Int
coordToIndex v (x,y) = y * (radius v * 3) + x

indexToCoord :: Verse a -> Int -> (Int,Int)
indexToCoord v = swap . flip divMod (radius v * 3)

distance :: Int -> Int -> Int
distance _ _ = undefined

data Dir = I | L | M | N | H | U
   deriving ( Eq, Ord, Enum, Bounded, Show )

opposite :: Dir -> Dir
opposite I = N
opposite L = H
opposite M = U
opposite N = I
opposite H = L
opposite U = M

shift :: Verse a -> Int -> Dir -> Int -> Int
shift v n d i
   | L <- d = coordToIndex v $ f (mod (x + n) (r * 3) , y)
   | I <- d = coordToIndex v $ f (x , y + n)
   | U <- d = shift v n I . shift v n H $ i
   | H <- d = shift v (negate n) L i
   | N <- d = shift v (negate n) I i
   | M <- d = shift v (negate n) U i
   where
   (x,y) = indexToCoord v i
   r = radius v
   f (x',y')
      | y' >= r || y' < 0 = (mod (x' + t * 2 * r) (r * 3) , mod y' r)
      | otherwise = (x' , y')
      where
      t = div y' r  -- outbound multiplier

node :: Atom a => Verse a -> Int -> a
node v i = nodes v IntMap.! i

adjacents :: Atom a => Verse a -> Int -> Map Dir a
adjacents v i = Map.fromList [(dir , node v (shift v 1 dir i)) | dir <- total]

-- | Manipulation

upd :: Atom a => (a -> a) -> Int -> Verse a -> Verse a
upd f i v = v { nodes = IntMap.adjust f i $ nodes v }

set :: Atom a => a -> Int -> Verse a -> Verse a
set a i v = v { nodes = IntMap.insert i a $ nodes v }