module Level where

import Control.Monad
import Data.Array
import Data.List

import Vector

data Level = Level
             { levelName :: String
             , levelSize :: (Int,Int)
             , legalMoves :: Array (Int,Int) [Direction]
             , doors :: [(Direction,Int)]
             , entrances :: [(Direction,Int,Int)]
             } deriving Show

data Direction = North | East | South | West
               deriving (Eq, Enum, Show)

fieldSize :: Int
fieldSize = 16

fieldMid :: Int
fieldMid = fieldSize `div` 2

fieldPos :: Vec -> (Int,Int)
fieldPos (V x y) = ((x+fieldMid) `div` fieldSize, (y+fieldMid) `div` fieldSize)

fieldSub :: Vec -> (Int,Int)
fieldSub (V x y) = (mkSub x, mkSub y)
  where mkSub c = let s = c `mod` fieldSize in if s >= fieldMid then s-fieldSize else s

atFieldMid :: (Int,Int) -> Direction -> Bool
atFieldMid (sx,sy) dir = 0 == if isVertical dir then sx else sy

halfway :: (Int,Int) -> Direction -> Bool
halfway (sx,sy) dir = 0 /= if isVertical dir then sy else sx

dirFromInt :: Int -> Direction
dirFromInt = toEnum . (`mod` 4)

turnRight :: Direction -> Direction
turnRight = dirFromInt . (+1) . fromEnum

turnBack :: Direction -> Direction
turnBack = dirFromInt . (+2) . fromEnum

turnLeft :: Direction -> Direction
turnLeft = dirFromInt . (+3) . fromEnum

isVertical :: Direction -> Bool
isVertical North = True
isVertical South = True
isVertical _     = False

parseLevel :: [String] -> Maybe (Level,[String])
parseLevel lines = do
  let lines' = dropWhile null lines
      nameLine = head lines'
      (mazeLines,rest) = span (not . null) (tail lines')
      width = length (head mazeLines) `div` 2
      height = length mazeLines `div` 2

      mkEntrance (North,x) = (South,-1,x)
      mkEntrance (South,x) = (North,height,x)
      mkEntrance (East,y) = (West,y,width)
      mkEntrance (West,y) = (East,y,-1)

      makeMoves lines = concat . odds $ zipWith3 f lines (tail lines) (tail (tail lines))
        where f n c s = odds $ zipWith4 f' (tail n) (tail s) c (tail (tail c))
              f' n s w e = (if n == ' ' then [North] else []) ++
                           (if s == ' ' then [South] else []) ++
                           (if w == ' ' then [West] else []) ++
                           (if e == ' ' then [East] else [])

      odds (x:_:xs) = x : odds xs
      odds xs = xs

      chars `onEdgeOf` lines =
        [(d,i `div` 2) |
         (d,f) <- [(North,head),(South,last),(West,map head),(East,map last)],
         i <- findIndices (`elem` chars) (f lines)]

  guard $ not (null lines')
  guard $ length mazeLines > 2
  guard $ head nameLine == '"'

  return (Level { levelName = takeWhile (/= '"') (tail nameLine)
                , levelSize = (width,height)
                , legalMoves = listArray ((0,0),(height-1,width-1)) (makeMoves mazeLines)
                , doors = "<>^v" `onEdgeOf` mazeLines
                , entrances = map mkEntrance ("*" `onEdgeOf` mazeLines)
                }, rest)
