{-# LANGUAGE LambdaCase #-}
module Lab12a where
import Control.Applicative
import Data.Char (isDigit)
import Data.Functor

data Block = Wall | Free | Star deriving (Eq, Show)

data Maze = M [[Block]]

mazeExample :: Maze   -- a testing maze
mazeExample = M
    [ [Wall, Wall, Wall, Wall, Wall]
    , [Wall, Free, Wall, Free, Wall]
    , [Wall, Free, Wall, Wall, Wall]
    , [Wall, Free, Free, Free, Wall]
    , [Wall, Wall, Wall, Wall, Wall]
    ]

instance Show Maze where
  show (M rows) = unlines $ fmap (fmap displayCell) rows
    where displayCell Wall = '#'
          displayCell Free = ' '
          displayCell Star = '*'

-- X, Y
type Pos = (Int, Int)
type Path = [Pos]

-- Start, Goal, Maze
type Task = (Pos, Pos, Maze)

safeGet :: Int -> [a] -> Maybe a
safeGet n xs
  | 0 <= n && n < length xs = Just $ xs !! n
  | otherwise = Nothing

newtype Parser a = P { parse :: String -> Maybe (a, String) }

instance Functor Parser where
    -- fmap :: (a -> b) -> Parser a -> Parser b
    fmap f p = P (\inp -> case parse p inp of
                            Nothing -> Nothing
                            Just (v,out) -> Just (f v, out))

instance Applicative Parser where
    -- (<*>) :: Parser (a -> b) -> Parser a -> Parser b
    pg <*> px = P (\inp -> case parse pg inp of
                             Nothing -> Nothing
                             Just (g,out) -> parse (fmap g px) out)
    pure v = P (\inp -> Just (v,inp))

instance Monad Parser where
    -- (>>=) :: Parser a -> (a -> Parser b) -> Parser b
    p >>= f = P (\inp -> case parse p inp of
                           Nothing -> Nothing
                           Just (v,out) -> parse (f v) out)

instance Alternative Parser where
    -- empty :: Parser a
    empty = P (const Nothing)
    -- (<|>) :: Parser a -> Parser a -> Parser a
    p <|> q = P (\inp -> case parse p inp of
                           Nothing -> parse q inp
                           Just (v,out) -> Just (v,out))

item :: Parser Char
item = P (\case
             "" -> Nothing
             (c:cs) -> Just (c, cs))

sat :: (Char -> Bool) -> Parser Char
sat pred = item >>= (\c -> if pred c
                           then pure c
                           else empty)

char :: Char -> Parser Char
char c = sat (== c)

string :: String -> Parser String
string "" = pure ""
string (c:cs) = do
  char c
  string cs
  pure (c:cs)

digit :: Parser Char
digit = sat isDigit

toInt :: String -> Int
toInt = read

number :: Parser Int
number = fmap toInt (some digit)

separator :: Parser String
separator = many (char ' ')

token :: Parser a -> Parser a
token p = separator *> p <* separator

pos :: Parser Pos
-- pos = char '(' *> ((,) <$> (token number <* char ',') <*> (token number)) <* char ')'
pos = do
  char '('
  first <- token number
  char ','
  second <- token number
  char ')'
  pure (first, second)

declaration :: String -> Parser a -> Parser a
declaration name p = string name *> token (char '=') *> p <* char '\n'

start :: Parser Pos
start = declaration "start" pos

goal :: Parser Pos
goal = declaration "goal" pos

maze :: Parser Maze
maze = M <$> many row

row :: Parser [Block]
row = many (wall <|> freeSpace) <* char '\n'

wall :: Parser Block
wall = char '#' $> Wall

freeSpace :: Parser Block
freeSpace = char ' ' $> Free

task :: Parser Task
task = (,,) <$> start <*> goal <*> maze

processInput :: String -> String
processInput inp = case parse task inp of
  Nothing -> "Syntax error in task"
  Just (t, "") -> "Read task " ++ show t
  Just (_, rest) -> "Syntax error: additional input " ++ rest

main :: IO ()
main = interact processInput
