{-# LANGUAGE LambdaCase #-}
module Main where

import Data.List
import Data.List.Split
import Data.List.Utils
import Data.Char
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Data.Maybe
import Helpers
import Linear.V2
import Debug.Trace

type DungeonMap = M.Map (V2 Int) Char
data Distance = Reachable Int | Unreachable deriving (Show, Eq)

instance Num Distance where
    Reachable x + Reachable y = Reachable (x+y)
    Unreachable + _ = Unreachable
    _ + Unreachable = Unreachable

instance Ord Distance where
    Reachable x <= Reachable y = x<=y
    Unreachable <= _ = False
    _ <= Unreachable = True

main :: IO ()
main = do
  dMap <- updateScreenBuffer M.empty (0, 0) <$> readFile "testinput1"
  putStrLn $ drawMap dMap
  print $ distanceFromTo dMap '@' 'f'
  print $ distanceFromTo dMap '@' 'a'
  print $ distanceFromTo dMap '@' 'A'
  print $ tls1 dMap (S.fromList "abcdef") 'f'

updateScreenBuffer :: DungeonMap -> (Int, Int) -> String -> DungeonMap
updateScreenBuffer buf _ [] = buf
updateScreenBuffer buf (x, y) (a:as) = updateScreenBuffer bufNew coord as
  where
    bufNew =
      if a /= '\n'
        then M.insert (V2 x y) a buf
        else buf
    coord =
      if a /= '\n'
        then (x + 1, y)
        else (0, y + 1)

getNeighbors :: DungeonMap -> V2 Int -> DungeonMap
getNeighbors m coord =
  M.fromList
    [ (u + coord, M.findWithDefault '#' (u + coord) m)
    | u <- unitVecs
    , let c = M.findWithDefault '#' (u + coord) m  in c `elem` ".@⌂" || isLower c
    ]

getNeighborsMap :: DungeonMap -> DungeonMap -> DungeonMap
getNeighborsMap m n = M.unions [getNeighbors m x | (x, _) <- M.toList n]

distanceFromTo :: DungeonMap -> Char -> Char -> Distance
distanceFromTo dMap schar echar =
  distanceFromTo' dMap (M.singleton spos schar) spos epos 1
  where
    spos = findPos schar dMap
    epos = findPos echar dMap
    distanceFromTo' mMap nMap start end n
      | M.member end newNMap = Reachable n
      | newNMap == nMap = Unreachable
      | otherwise = distanceFromTo' mMap newNMap start end (n + 1)
      where
        newNMap = M.union nMap (getNeighborsMap mMap nMap)
        --todo: maek tail recursive

findPos :: Char -> DungeonMap -> V2 Int
findPos c = head . M.keys . M.filter (`elem` [c])

tls1 :: DungeonMap -> S.Set Char -> Char -> Distance
tls1 dMap nodeSet goal
  | S.size nodeSet == 1 = distanceFromTo dMap '@' (head $ S.toList nodeSet)
  -- ^ S = {c}, different 1 element sets possible?
  | otherwise =
    minimum
      [tls1 newDMap sMinusC x + distanceFromTo newDMap x goal | x <- S.toList sMinusC,
       let newDMap = M.insert door '⌂' dMap
           door = findPos (toUpper (traceShowId x)) dMap]
  where
    sMinusC = S.delete goal nodeSet
