{-# LANGUAGE LambdaCase #-}
module Lab12b where

import Data.Functor
import Data.Char
import Control.Applicative

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

data Maze = M [[Block]]

instance Show Maze where
  show (M rows) = unlines $ showRow <$> rows
    where
      showRow blocks = showBlock <$> blocks
      showBlock Wall = '#'
      showBlock Free = ' '
      showBlock Star = '*'

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]
    ]

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

safePut :: Int -> a -> [a] -> Maybe [a]
safePut n x xs
  | 0 <= n && n < length xs = Just $ take n xs ++ (x : drop (n+1) xs)
  | otherwise = Nothing

setBlock :: Block -> Pos -> Maze -> Maybe Maze
setBlock b (x, y) (M rows) = do
  row <- safeGet y rows
  newRow <- safePut x b row
  newMaze <- safePut y newRow rows
  pure (M newMaze)

-- | X, Y
type Pos = (Int, Int)

type Path = [Pos]

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


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 s@(c:cs) = do
  char c
  string cs
  pure s

digit :: Parser Char
digit = sat isDigit

numberS :: Parser String
numberS = some digit

toInt :: String -> Int
toInt = read

number :: Parser Int
number = toInt <$> numberS

separator :: Parser Char
separator = char ' '

spaces :: Parser String
spaces = many separator

token :: Parser a -> Parser a
token' p = do
  spaces
  output <- p
  spaces
  pure output

token p = spaces *> p <* spaces

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

pos = char '(' *>
        ((,)
          <$> (token number <* char ',')
          <*> (token number))
      <* char ')'

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

block :: Parser Block
block = wall <|> freeSpace

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

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

row :: Parser [Block]
row = many block <* char '\n'

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

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

processInput :: String -> String
processInput input = case parse task input of
  Nothing -> "Syntax error in input"
  Just (t, "") -> "Searching path"
  Just (_, rest) -> "Syntax error, unexpected characters: " ++ rest

main :: IO ()
main = interact processInput
