import Control.Applicative (Alternative (..), asum)
import Control.Monad (ap, liftM, void)
import Data.Char (isDigit, isSpace, ord)

stringToInt1 :: String -> Int
stringToInt1 s = loop 0 s
 where
  loop acc [] = acc
  loop acc (c : cs) =
    let c' = ord c - ord '0'
     in loop (acc * 10 + c') cs

stringToInt2 :: String -> Maybe Int
stringToInt2 s = loop 0 s
 where
  loop acc [] = Just acc
  look acc (c : cs) =
    if isDigit c
      then
        let c' = ord c - ord '0'
         in loop (acc * 10 + c') cs
      else Nothing

-- where did stringToInt3 go???

stringToInt4 :: String -> Maybe (Int, String)
stringToInt4 "" = Nothing
stringToInt4 (c : cs) = if isDigit c then loop 0 (c : cs) else Nothing
 where
  loop acc "" = Just (acc, "")
  loop acc (c : cs) = if isDigit c then loop ((ord c - ord '0') + acc * 10) cs else Just (acc, c : cs)

hasLeadingSpace :: String -> Maybe String
hasLeadingSpace (c : cs) = if c == ' ' then Just cs else Nothing

stringToInts :: String -> Maybe ((Int, Int), String)
stringToInts s =
  case stringToInt4 s of
    Nothing -> Nothing
    Just (x, s') -> case hasLeadingSpace s' of
      Just s'' -> case stringToInt4 s'' of
        Just (y, s''') -> Just ((x, y), s''')
        Nothing -> Nothing
      _ -> Nothing

stringToInts2 :: String -> Maybe ((Int, Int), String)
stringToInts2 s = do
  (x, s') <- stringToInt4 s
  s'' <- hasLeadingSpace s'
  (y, s''') <- stringToInt4 s''
  pure ((x, y), s''')

data Parser a = Parser ((Int, String) -> Either (Int, String) (a, (Int, String)))

instance Functor Parser where
  fmap :: (a -> b) -> Parser a -> Parser b
  fmap = liftM

instance Applicative Parser where
  pure a = Parser $ \s -> Right (a, s)
  (<*>) = ap

instance Monad Parser where
  Parser a >>= f = Parser $ \s -> do
    (a', s') <- a s
    let (Parser b) = f a'
    b s'

runParser :: Parser a -> String -> Either String a
runParser (Parser f) s = case f (0, s) of
  Left (pos, err) -> Left ("Parse error at " ++ show pos ++ ": " ++ err)
  Right (a, _) -> pure a

satisfy :: (Char -> Bool) -> Parser Char
satisfy p = Parser $ \(pos, s) ->
  case s of
    "" -> Left (pos, "Empty input")
    c : (cs) ->
      if p c
        then
          pure (c, (pos + 1, cs))
        else Left (pos, "Invalid character: " ++ [c])

threeDigits :: Parser (Char, Char, Char)
threeDigits = let s = satisfy isDigit in (,,) <$> s <*> s <*> s

instance Alternative Parser where
  empty = Parser $ \(pos, s) -> Left (pos, "All branches exhausted")
  (Parser p1) <|> (Parser p2) = Parser $ \s -> case p1 s of
    Left err -> p2 s
    Right a -> pure a

choice :: [Parser a] -> Parser a
choice = asum

(<?>) :: Parser a -> String -> Parser a
Parser p <?> e = Parser $ \s -> case p s of
  Left (o, _) -> Left (o, e)
  a -> a

try :: Parser a -> Parser (Maybe a)
try (Parser p) = Parser $ \(pos, s) -> case p (pos, s) of
  Right (a, (pos', s')) -> Right $ (Just a, (pos', s'))
  Left e -> Right $ (Nothing, (pos, s))

mymany :: Parser a -> Parser [a]
mymany p =
  choice
    [ do
        x <- p
        xs' <- mymany p
        pure (x : xs')
    , pure []
    ]

parseDigit :: Parser Int
parseDigit = (\c -> ord c - ord '0') <$> satisfy isDigit

parseInt :: Parser Int
-- parseInt = do
--   xs <- some parseDigit
--   pure $ foldl' (\acc x -> acc * 10 + x) 0 xs
parseInt = foldl' (\acc x -> acc * 10 + x) 0 <$> some parseDigit

skipSpaces :: Parser ()
skipSpaces = void $ many (satisfy isSpace)

parseTwoInts :: Parser (Int, Int)
parseTwoInts = do
  x <- parseInt
  skipSpaces
  y <- parseInt
  pure (x, y)

getTwoInts :: String -> Either String (Int, Int)
getTwoInts s = runParser (skipSpaces *> parseTwoInts) s

-- I guess it is halloween
-- We are getting sugars thrown at us at random <*
-- 🫳🍬🍬🍬

postfix :: Parser a -> Parser b -> Parser a
postfix p1 p2 = p1 <* p2

parseInts :: Parser [Int]
parseInts = many parseIntThenSpace
 where
  parseIntThenSpace = do
    x <- parseInt
    skipSpaces
    pure x

eof :: Parser ()
eof = Parser end
 where
  end :: (Int, String) -> Either (Int, String) ((), (Int, String))
  end s@(_, "") = pure ((), s)
  end (pos, _) = Left (pos, "EOF expected")

instance MonadFail Parser where
  fail s = Parser (\(pos, _) -> Left (pos, s))

-- I don't provide invalid data to a program so I'm unfamiliar with the situation (thug life)

-- Spaaace
