-------------------------------------------------------------------------------
-- |
-- Module      :  Text.SmallCaps.PrintableParser
-- Copyright   :  (c) Stefan Berthold 2014-2015
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  stefan.berthold@gmx.net
-- Stability   :  unstable
-- Portability :  GHC
--
-- This modules specifies parsers on printable 'Text'.
--
-------------------------------------------------------------------------------

module Text.SmallCaps.PrintableParser where

import            Prelude      hiding ( head, tail, null )

import            Text.Parsec         ( runParser, try, oneOf, anyChar, many, many1, lower, upper, string, getState, modifyState )
import qualified  Text.Parsec    as P ( space, newline )
import            Text.Parsec.Text    ( GenParser )
import            Data.Text           ( Text, singleton, pack, unpack, intercalate )
import            Control.Monad       ( msum )

import            Text.SmallCaps.Config ( Config (..), StopState (..), ParserState (..), SubParser, PatternReplace (..) )

type Parser = GenParser ParserState

runPrintableWith :: SubParser Text
runPrintableWith state = either (Left . show) Right . runParser (printable >>= \a -> fmap ((,) a) getState) state ""

-- ** Parsers

printable :: Parser Text
printable = fmap (intercalate (pack "")) $ many $ printableElement

printableElement :: Parser Text
printableElement = msum
  [ excepts
  , lowers
  , uppers
  , period
  , newline
  , space
  , misc
  ] 

excepts :: Parser Text
excepts = msum =<< fmap (map toParser . exceptions . config) getState
  where toParser x = try (string (unpack $ pattern x)) >> pass reset (replacement x)

lowers :: Parser Text
lowers =  pass reset . pack =<< many1 lower

uppers :: Parser Text
uppers = do
  text <- fmap pack $ many1 upper
  state <- getState
  if ignore state || not (replaceFilter (config state) text)
  then pass reset text
  else pass reset $ replace (config state) (stop state) text

period :: Parser Text
period = do
  ps <- fmap (periodChars . config) getState
  pass set . singleton =<< oneOf ps

space :: Parser Text
space =  pass sticky . singleton =<< P.space

newline :: Parser Text
newline = pass inc . singleton =<< P.newline

misc :: Parser Text
misc = pass reset . singleton =<< anyChar

-- ** State modification

pass :: Parser b -> a -> Parser a
pass m a = m >> return a

reset :: Parser ()
reset = modifyState (\state -> state { stop = None })

set :: Parser ()
set = modifyState (\state -> state { stop = Stop })

inc :: Parser ()
inc = modifyState (\state -> state { stop = inc' (stop state) }) where
  inc' None = NewLine
  inc' _    = NewSentence

sticky :: Parser ()
sticky = modifyState (\state -> state { stop = inc' (stop state) }) where
  inc' None     = None
  inc' NewLine  = NewLine
  inc' _        = NewSentence

-- vim: ft=haskell:sts=2:sw=2:et:nu:ai
