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

import Data.Array.IArray((!), (//), assocs, bounds)
import Data.List(filter, reverse, sortBy)
import Data.Text(chunksOf, replicate)

import Text.Printf(printf)

import PathFindingCore.PathingMap.Coordinate(Coordinate(Coord, x))
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)

newtype PrintablePathingGrid = PPG PathingGrid

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 (, Path) coords)

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 $ asText $ printf "Cannot find direction to non-adjacent coordinates (start: %s, end: %s)" (show startCoord) (show endCoord)

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

makeLinesPretty :: Int -> [Text] -> [Text]
makeLinesPretty maxX lines = concat [[topB], linesB, [botB]]
  where
    linesB = fmap (\x -> "|" <> x <> "|\n") lines
    border = replicate maxX "-"
    topB   = "+" <> border <> "+" <> "\n"
    botB   = "+" <> 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