-- |
-- Module      :  Web.Telegram.Bot.Story
-- Copyright   :  Alexander Krupenkin 2016
-- License     :  BSD3
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  portable
--
-- Story is a dialog like abstraction for processing data from
-- user sparsed by messages.
--
-- @
--      hello :: Monad m => Text -> Text -> Int -> m BotMessage
--      hello name surname age = toMessage $
--          "Hello, " <> name <> " " <> surname <> "!\n"
--       <> "You lost " <> (pack $ show age) <> " years =)"
--
--      helloStory :: Story
--      helloStory _ = hello <$> question "How your name?"
--                           <*> question "How your surname?"
--                           <*> question "How old are you?"
-- @
--
module Web.Telegram.Bot.Story where

import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
import Web.Telegram.API.Bot (Message, Chat, User, text)
import Data.Text.Read (signed, decimal, double)
import Control.Monad.IO.Class (MonadIO)
import Web.Telegram.Bot.Types (Bot)
import Pipes (Pipe, await, yield)
import qualified Data.Text as T
import Data.Text (Text, pack)

-- | Story is a pipe from user message to bot message
-- and result is a final message bot.
type Story a = (User, Chat) -> StoryT (Bot a) BotMessage
type StoryT = Pipe Message BotMessage

-- | Bot message data.
data BotMessage
  = BotTyping
  | BotText Text
  | BotKeyboard Text [[Text]]
  deriving Show

-- | Bot message typeclass for conversion.
class ToBotMessage a where
    toMessage :: a -> BotMessage

-- | Idenity instance
instance ToBotMessage BotMessage where
    toMessage = id

-- | Simple text question send text message from bot
instance ToBotMessage Text where
    toMessage = BotText

-- | The value can be passed to story handler function.
class Answer a where
    parse :: MonadIO m => Message -> ExceptT Text m a

-- | Simple text answer, pass any text message
instance Answer Text where
    parse x =
        case text x of
            Just t  -> return t
            Nothing -> throwE "Please send text message."

instance Answer Double where
    parse x = do
        t <- parse x
        case signed double t of
            Left e -> throwE (pack e)
            Right (v, "") -> return v
            Right _       -> throwE "Please use only 0-9 and '.' chars."

instance Answer Integer where
    parse x = do
        t <- parse x
        case signed decimal t of
            Left e -> throwE (pack e)
            Right (v, x) -> if T.null x
                            then return v
                            else throwE "Please use only 0-9 chars."

instance Answer Int where
    parse x = do
        v <- parse x
        return (fromIntegral (v :: Integer))

instance Answer Word where
    parse x = do
        v <- parse x
        return (fromIntegral (v :: Integer))

-- | Reply keyboard selection
select :: (MonadIO m, Answer a) => Text -> [[Text]] -> StoryT m a
{-# INLINE select #-}
select q = replica . BotKeyboard q

-- | Bot text question.
question :: (MonadIO m, Answer a) => Text -> StoryT m a
{-# INLINE question #-}
question = replica

-- | Generalized story maker.
-- The replica send to user, when answer isn't parsed
-- the error send to user and waiting for correct answer.
replica :: (ToBotMessage a, MonadIO m, Answer b) => a -> StoryT m b
replica msg = do
    yield (toMessage msg)
    res <- runExceptT . parse =<< await
    yield BotTyping
    case res of
        Left e  -> replica e
        Right a -> return a