-- | Payee parsers. There are two types of payee parsers:
--
-- Quoted payees. These allow the most latitude in the characters
-- allowed. They are surrounded by @<@ and @>@.
--
-- Unquoted payees. These are not surrounded by @<@ and @>@. Their
-- first character must be a letter or number.

module Penny.Copper.Payees (
  -- * Parse any payee
  payee
  
  -- * Quoted payees
  , quotedChar
  , quotedPayee
    
    -- * Unquoted payees
  , unquotedFirstChar
  , unquotedRestChars
  , unquotedPayee
    
    -- * Rendering
  , smartRender
  , quoteRender
  ) where

import Control.Applicative ((<$>), (<*>), (<|>))
import Data.Text (pack, Text, snoc, cons)
import qualified Data.Text as X
import Text.Parsec (char, satisfy, many, between, (<?>))
import Text.Parsec.Text ( Parser )

import Penny.Copper.Util (rangeLettersToSymbols, rangeLetters)
import qualified Penny.Lincoln.Bits as B
import Penny.Lincoln.TextNonEmpty as TNE

quotedChar :: Char -> Bool
quotedChar c = allowed && not banned where
  allowed = rangeLettersToSymbols c || c == ' '
  banned = c == '>'

payee :: Parser B.Payee
payee = quotedPayee <|> unquotedPayee

quotedPayee :: Parser B.Payee
quotedPayee = between (char '<') (char '>') p <?> "quoted payee" where
  p = (\c cs -> B.Payee (TextNonEmpty c (pack cs)))
      <$> satisfy quotedChar
      <*> many (satisfy quotedChar)

unquotedFirstChar :: Char -> Bool
unquotedFirstChar = rangeLetters

unquotedRestChars :: Char -> Bool
unquotedRestChars = quotedChar

unquotedPayee :: Parser B.Payee
unquotedPayee = let
  p c cs = B.Payee (TextNonEmpty c (pack cs))
  in p
     <$> satisfy unquotedFirstChar
     <*> many (satisfy unquotedRestChars)
     <?> "unquoted payee"

-- | Render a payee with a minimum of quoting. Fails if cannot be
-- rendered at all.
smartRender :: B.Payee -> Maybe Text
smartRender (B.Payee p) = let
  TextNonEmpty f r = p
  noQuoteNeeded = unquotedFirstChar f
                  && X.all unquotedRestChars r
  renderable = TNE.all quotedChar p
  quoted = '<' `cons` TNE.toText p `snoc` '>'
  makeText
    | noQuoteNeeded = Just $ TNE.toText p
    | renderable = Just quoted
    | otherwise = Nothing
  in makeText

-- | Renders with quotes, whether the payee needs it or not.
quoteRender :: B.Payee -> Maybe Text
quoteRender (B.Payee p) = let
  renderable = TNE.all quotedChar p
  quoted = '<' `cons` TNE.toText p `snoc` '>'
  in if renderable
     then Just quoted
     else Nothing