{-# LANGUAGE FlexibleInstances #-}
module PathFindingCore.PathingMap(findDirection, getTerrain, insertPath, markAsGoal, neighborsOf, PrintablePathingGrid(..), step) where

import Control.Arrow((>>>))
import Data.Array.IArray((!), (//), assocs, bounds)
import Data.Foldable(fold)
import Data.List(sortBy)
import Data.List.Split(chunksOf)
import Data.Maybe(fromMaybe)
import Text.Printf(printf)

import PathFindingCore.PathingMap.Coordinate(Coordinate(..))
import PathFindingCore.PathingMap.Direction(Direction(East, North, South, West), directions)
import PathFindingCore.PathingMap.Interpreter(PathingGrid)
import PathFindingCore.PathingMap.Terrain(isPassable, Terrain(Goal, Path, Query, Self), terrainToChar)

data PrintablePathingGrid
  = PPG {
      pathingGrid :: PathingGrid
    }

a |> f = f a

getTerrain :: Coordinate -> PathingGrid -> Maybe Terrain
getTerrain coord@(Coord x y) grid = if isInBounds then Just $ grid ! coord else Nothing
  where
    (Coord x1 y1, Coord x2 y2) = bounds grid
    isInBounds                 = and [x >= x1, x <= x2, y >= y1, y <= y2]

neighborsOf :: Coordinate -> PathingGrid -> [Coordinate]
neighborsOf coordinate grid = directions |> ((fmap $ findNeighborCoord coordinate) >>> (filter canTravelTo))
  where
    canTravelTo = (flip getTerrain) grid >>> (fmap isPassable) >>> (fromMaybe False)

step :: Coordinate -> Coordinate -> PathingGrid -> PathingGrid
step prev new grid = grid // [(prev, Query), (new, Self)]

markAsGoal :: Coordinate -> PathingGrid -> PathingGrid
markAsGoal coord grid = grid // [(coord, Goal)]

insertPath :: [Coordinate] -> PathingGrid -> PathingGrid
insertPath coords grid = grid // (fmap f coords)
  where
    f coord = (coord, Path)

findNeighborCoord :: Coordinate -> Direction -> Coordinate
findNeighborCoord (Coord x y) North = Coord  x     (y + 1)
findNeighborCoord (Coord x y) South = Coord  x     (y - 1)
findNeighborCoord (Coord x y) East  = Coord (x + 1) y
findNeighborCoord (Coord x y) West  = Coord (x - 1) y

findDirection :: Coordinate -> Coordinate -> Direction
findDirection startCoord@(Coord x1 y1) endCoord@(Coord x2 y2)
    | y2 == y1 + 1 = North
    | y2 == y1 - 1 = South
    | x2 == x1 + 1 = East
    | x2 == x1 - 1 = West
    | otherwise    = error $ printf "Cannot find direction to non-adjacent coordinates (start: %s, end: %s)" (show startCoord) (show endCoord)

instance Show PrintablePathingGrid where
  show (PPG grid) = fold lines
    where
      maxX   = grid |> (bounds >>> snd >>> x >>> (+1))
      str    = grid |> (assocs >>> (sortBy sillySort) >>> (fmap $ snd >>> terrainToChar))
      lines  = str  |> ((chunksOf maxX) >>> reverse >>> (makeLinesPretty maxX))

makeLinesPretty :: Int -> [String] -> [String]
makeLinesPretty maxX lines = concat [[topB], linesB, [botB]]
  where
    linesB = fmap (\x -> "|" ++ x ++ "|\n") lines
    border = replicate maxX '-'
    topB   = concat ["+", border, "+", "\n"]
    botB   = concat ["+", border, "+"]

sillySort :: (Coordinate, Terrain) -> (Coordinate, Terrain) -> Ordering
sillySort (Coord x1 y1, _) (Coord x2 y2, _) =
  if y1 < y2 then LT else if y1 > y2 then GT
     else if x1 < x2 then LT else if x1 > x2 then GT
       else EQ