{-# LANGUAGE OverloadedStrings #-}
{-| This module provides functions for creating and encoding messages for use
within Pushover requests.

Pushover messages contain a very limited subset of HTML. Users can insert bold,
italic, underlined, and colored text, and URLs within messages. The 'Message'
type represents all of these possible formatting options.

Constructing a 'Message' is done through the use of the 'message', 'bold',
'italic', 'underline', 'color', 'link', and 'text' functions. The 'message'
function takes a list of parts created using the other functions, and
concatenates these into a single message. Different types of formatting can
be nested within each other by simply calling the functions on the results of
other function calls. For example:-

@
  bold 
    [ italic 
        [ text "This is bold & italic" 
        ]
    , text "This is bold"
    , underline
        [ text "This is bold & underlined"
        ]
    ]
@

will create a message with the text values formatted as described.

-}
module Network.Pushover.Message 
  ( -- * Creating a Message
    Message
  , message
  , bold
  , italic
  , underline
  , color
  , link
  , text
    -- * Encoding for inclusion in request
  , encodeMessage
    -- * Attributes
  , ColorCode
  , makeColorCode
  , Url
  ) where

import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Numeric (showHex)

-- | Represents a message sent to the Pushover API.
--
-- A Pushover message can be constructed with a very small subset of HTML.
-- This type represents the available HTML formatting for a message.
data Message
  = Parts [Message]
  | Bold [Message]
  | Italic [Message]
  | Underline [Message]
  | Color ColorCode [Message]
  | Link Url [Message]
  | MessageText Text
  deriving (Show, Eq)

-- | Represents an HTML color code.
--
-- A ColorCode consists of a red, a green and a blue element, each of which
-- must have a value of between 0 and 255. This type cannot enforce this
-- constraint, but see 'makeColorCode' which does.
data ColorCode = ColorCode
  { red   :: Integer
  , green :: Integer
  , blue  :: Integer
  } deriving (Show, Eq)

type Url
  = Text

-- | Make a message from a list of message parts.
message :: [Message] -> Message
message =
  Parts

-- | Make a bold message.
bold :: [Message] -> Message
bold =
  Bold

-- | Make an italic message.
italic :: [Message] -> Message
italic =
  Italic

-- | Make an underlined message.
underline :: [Message] -> Message
underline =
  Underline

-- | Make a message with colored text.
--
-- Accepts three integer arguments for red, green and blue color elements,
-- respectively.
color :: Integer -> Integer -> Integer -> [Message] -> Message
color r g b =
  Color (makeColorCode r g b)

-- | Make a url message.
link :: Url -> [Message] -> Message
link =
  Link

-- | Make a textual message.
text :: Text -> Message
text =
  MessageText

-- | Construct a 'ColorCode' value.
--
-- A 'ColorCode' requires a red, a green and a blue value for construction.
-- This function takes these as arguments and returns a constructed 
-- 'ColorCode'.
--
-- This function checks that each element is within the required 0-255 range.
-- Any element which is not is rounded to the nearest extrema (0 for negative
-- values; 255 for values larger than that number).
makeColorCode :: Integer -> Integer -> Integer -> ColorCode
makeColorCode r g b =
  ColorCode
    { red   = f r
    , green = f g
    , blue  = f b
    }

  where
        f =
          max 0 . min 255 
  
-- | Encode a 'Message' into a bytestring.
--
-- This function is intended to convert a 'Message' into a form useable within
-- a 'Request'. It generates a bytestring containing the HTML for the message.
encodeMessage :: Message -> ByteString
encodeMessage =
  enc

  where 
        enc msg =
          case msg of
               Parts msg ->
                 encInner msg

               Bold msg ->
                 wrapTag "b" "" msg

               Italic msg ->
                 wrapTag "i" "" msg

               Underline msg ->
                 wrapTag "u" "" msg

               Color colorCode msg ->
                 wrapTag "font" (colorAttr colorCode) msg

               Link url msg ->
                 wrapTag "a" (linkAttr url) msg

               MessageText txt ->
                 T.encodeUtf8 txt

        encInner =
          B.concat . map enc

        wrapTag tagName attr inner =
          B.concat [ openTag tagName attr
                   , encInner inner
                   , closeTag tagName
                   ]

        openTag tagName attr =
          B.concat [ "<"
                   , tagName
                   , attr
                   , ">"
                   ]

        closeTag tagName =
          B.concat [ "</"
                   , tagName
                   , ">"
                   ]

        colorAttr =
          B.append " color=" . encodeColorCode

        linkAttr =
          B.append " href=" . T.encodeUtf8

-- | Encode a 'ColorCode' into a bytestring.
--
-- This converts a 'ColorCode' value into its corresponding hexadecimal 
-- representation.
encodeColorCode :: ColorCode -> ByteString
encodeColorCode (ColorCode r g b) =
  B.pack $ '#':hexCode

  where 
        hexCode =
          padShow r . padShow g . padShow b $ ""

        padShow =
          (\hex rest -> replicate (2 - length (hex "")) '0' ++ hex rest) 
            <$> showHex