--------------------------------------------------------------------------------
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE TemplateHaskell            #-}
module Patat.Presentation.SpeakerNotes
    ( SpeakerNotes
    , parse
    , toText
    , remove
    , split
    , partition

    , Settings
    , Handle
    , with
    , write
    ) where


--------------------------------------------------------------------------------
import           Control.Exception      (bracket)
import           Control.Monad          (when)
import qualified Data.Aeson.TH.Extended as A
import qualified Data.IORef             as IORef
import           Data.List              (intersperse)
import qualified Data.Text              as T
import qualified Data.Text.IO           as T
import           Patat.EncodingFallback (EncodingFallback)
import qualified Patat.EncodingFallback as EncodingFallback
import           System.Directory       (removeFile)
import qualified System.IO              as IO
import qualified Text.Pandoc            as Pandoc


--------------------------------------------------------------------------------
newtype SpeakerNotes = SpeakerNotes [T.Text]
    deriving (SpeakerNotes -> SpeakerNotes -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: SpeakerNotes -> SpeakerNotes -> Bool
$c/= :: SpeakerNotes -> SpeakerNotes -> Bool
== :: SpeakerNotes -> SpeakerNotes -> Bool
$c== :: SpeakerNotes -> SpeakerNotes -> Bool
Eq, Semigroup SpeakerNotes
SpeakerNotes
[SpeakerNotes] -> SpeakerNotes
SpeakerNotes -> SpeakerNotes -> SpeakerNotes
forall a.
Semigroup a -> a -> (a -> a -> a) -> ([a] -> a) -> Monoid a
mconcat :: [SpeakerNotes] -> SpeakerNotes
$cmconcat :: [SpeakerNotes] -> SpeakerNotes
mappend :: SpeakerNotes -> SpeakerNotes -> SpeakerNotes
$cmappend :: SpeakerNotes -> SpeakerNotes -> SpeakerNotes
mempty :: SpeakerNotes
$cmempty :: SpeakerNotes
Monoid, NonEmpty SpeakerNotes -> SpeakerNotes
SpeakerNotes -> SpeakerNotes -> SpeakerNotes
forall b. Integral b => b -> SpeakerNotes -> SpeakerNotes
forall a.
(a -> a -> a)
-> (NonEmpty a -> a)
-> (forall b. Integral b => b -> a -> a)
-> Semigroup a
stimes :: forall b. Integral b => b -> SpeakerNotes -> SpeakerNotes
$cstimes :: forall b. Integral b => b -> SpeakerNotes -> SpeakerNotes
sconcat :: NonEmpty SpeakerNotes -> SpeakerNotes
$csconcat :: NonEmpty SpeakerNotes -> SpeakerNotes
<> :: SpeakerNotes -> SpeakerNotes -> SpeakerNotes
$c<> :: SpeakerNotes -> SpeakerNotes -> SpeakerNotes
Semigroup, Int -> SpeakerNotes -> ShowS
[SpeakerNotes] -> ShowS
SpeakerNotes -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SpeakerNotes] -> ShowS
$cshowList :: [SpeakerNotes] -> ShowS
show :: SpeakerNotes -> String
$cshow :: SpeakerNotes -> String
showsPrec :: Int -> SpeakerNotes -> ShowS
$cshowsPrec :: Int -> SpeakerNotes -> ShowS
Show)


--------------------------------------------------------------------------------
parse :: Pandoc.Block -> Maybe SpeakerNotes
parse :: Block -> Maybe SpeakerNotes
parse (Pandoc.RawBlock Format
"html" Text
t0) = do
    Text
t1 <- Text -> Text -> Maybe Text
T.stripPrefix Text
"<!--" Text
t0
    Text
t2 <- Text -> Text -> Maybe Text
T.stripSuffix Text
"-->" Text
t1
    forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ [Text] -> SpeakerNotes
SpeakerNotes [Text -> Text
T.strip Text
t2]
parse Block
_ = forall a. Maybe a
Nothing


--------------------------------------------------------------------------------
toText :: SpeakerNotes -> T.Text
toText :: SpeakerNotes -> Text
toText (SpeakerNotes [Text]
sn) = [Text] -> Text
T.unlines forall a b. (a -> b) -> a -> b
$ forall a. a -> [a] -> [a]
intersperse forall a. Monoid a => a
mempty [Text]
sn


--------------------------------------------------------------------------------
remove :: [Pandoc.Block] -> [Pandoc.Block]
remove :: [Block] -> [Block]
remove = forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Block] -> (SpeakerNotes, [Block])
partition


--------------------------------------------------------------------------------
-- | Take all speaker notes from the front of the list.  Return those and the
-- remaining blocks.
split :: [Pandoc.Block] -> (SpeakerNotes, [Pandoc.Block])
split :: [Block] -> (SpeakerNotes, [Block])
split = [SpeakerNotes] -> [Block] -> (SpeakerNotes, [Block])
go []
  where
    go :: [SpeakerNotes] -> [Block] -> (SpeakerNotes, [Block])
go [SpeakerNotes]
sn []                           = (forall a. Monoid a => [a] -> a
mconcat (forall a. [a] -> [a]
reverse [SpeakerNotes]
sn), [])
    go [SpeakerNotes]
sn (Block
x : [Block]
xs) | Just SpeakerNotes
s <- Block -> Maybe SpeakerNotes
parse Block
x = [SpeakerNotes] -> [Block] -> (SpeakerNotes, [Block])
go (SpeakerNotes
s forall a. a -> [a] -> [a]
: [SpeakerNotes]
sn) [Block]
xs
    go [SpeakerNotes]
sn [Block]
xs                           = (forall a. Monoid a => [a] -> a
mconcat (forall a. [a] -> [a]
reverse [SpeakerNotes]
sn), [Block]
xs)


--------------------------------------------------------------------------------
-- | Partition the list into speaker notes and other blocks.
partition :: [Pandoc.Block] -> (SpeakerNotes, [Pandoc.Block])
partition :: [Block] -> (SpeakerNotes, [Block])
partition = [SpeakerNotes] -> [Block] -> [Block] -> (SpeakerNotes, [Block])
go [] []
  where
    go :: [SpeakerNotes] -> [Block] -> [Block] -> (SpeakerNotes, [Block])
go [SpeakerNotes]
sn [Block]
bs []                           = (forall a. Monoid a => [a] -> a
mconcat (forall a. [a] -> [a]
reverse [SpeakerNotes]
sn), forall a. [a] -> [a]
reverse [Block]
bs)
    go [SpeakerNotes]
sn [Block]
bs (Block
x : [Block]
xs) | Just SpeakerNotes
s <- Block -> Maybe SpeakerNotes
parse Block
x = [SpeakerNotes] -> [Block] -> [Block] -> (SpeakerNotes, [Block])
go (SpeakerNotes
s forall a. a -> [a] -> [a]
: [SpeakerNotes]
sn) [Block]
bs [Block]
xs
    go [SpeakerNotes]
sn [Block]
bs (Block
x : [Block]
xs)                     = [SpeakerNotes] -> [Block] -> [Block] -> (SpeakerNotes, [Block])
go [SpeakerNotes]
sn (Block
x forall a. a -> [a] -> [a]
: [Block]
bs) [Block]
xs


--------------------------------------------------------------------------------
data Settings = Settings
    { Settings -> String
sFile :: !FilePath
    } deriving (Int -> Settings -> ShowS
[Settings] -> ShowS
Settings -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Settings] -> ShowS
$cshowList :: [Settings] -> ShowS
show :: Settings -> String
$cshow :: Settings -> String
showsPrec :: Int -> Settings -> ShowS
$cshowsPrec :: Int -> Settings -> ShowS
Show)


--------------------------------------------------------------------------------
data Handle = Handle
    { Handle -> Settings
hSettings :: !Settings
    , Handle -> IORef SpeakerNotes
hActive   :: !(IORef.IORef SpeakerNotes)
    }


--------------------------------------------------------------------------------
with :: Settings -> (Handle -> IO a) -> IO a
with :: forall a. Settings -> (Handle -> IO a) -> IO a
with Settings
settings = forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket
    (Settings -> IORef SpeakerNotes -> Handle
Handle Settings
settings forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. a -> IO (IORef a)
IORef.newIORef forall a. Monoid a => a
mempty)
    (\Handle
_ -> String -> IO ()
removeFile (Settings -> String
sFile Settings
settings))


--------------------------------------------------------------------------------
write :: Handle -> EncodingFallback -> SpeakerNotes -> IO ()
write :: Handle -> EncodingFallback -> SpeakerNotes -> IO ()
write Handle
h EncodingFallback
encodingFallback SpeakerNotes
sn = do
    Bool
change <- forall a b. IORef a -> (a -> (a, b)) -> IO b
IORef.atomicModifyIORef' (Handle -> IORef SpeakerNotes
hActive Handle
h) forall a b. (a -> b) -> a -> b
$ \SpeakerNotes
old -> (SpeakerNotes
sn, SpeakerNotes
old forall a. Eq a => a -> a -> Bool
/= SpeakerNotes
sn)
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
change forall a b. (a -> b) -> a -> b
$ forall r. String -> IOMode -> (Handle -> IO r) -> IO r
IO.withFile (Settings -> String
sFile forall a b. (a -> b) -> a -> b
$ Handle -> Settings
hSettings Handle
h) IOMode
IO.WriteMode forall a b. (a -> b) -> a -> b
$ \Handle
ioh ->
        forall a. Handle -> EncodingFallback -> IO a -> IO a
EncodingFallback.withHandle Handle
ioh EncodingFallback
encodingFallback forall a b. (a -> b) -> a -> b
$
        Handle -> Text -> IO ()
T.hPutStr Handle
ioh forall a b. (a -> b) -> a -> b
$ SpeakerNotes -> Text
toText SpeakerNotes
sn


--------------------------------------------------------------------------------
$(A.deriveFromJSON A.dropPrefixOptions ''Settings)