{-# LANGUAGE CPP #-}
{-
	Copyright (C) 2018 Dr. Alistair Ward

	This file is part of BishBosh.

	BishBosh is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	BishBosh is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with BishBosh.  If not, see <http://www.gnu.org/licenses/>.
-}
{- |
 [@AUTHOR@]	Dr. Alistair Ward

 [@DESCRIPTION@]	Implements a parser for PGN-comments.
-}

module BishBosh.ContextualNotation.PGNComment(
-- * Types
-- ** Data-types
        PGNComment(..),
-- * Constants
--	blockCommentStart,
        blockCommentEnd,
--	lineCommentStart,
        lineCommentEnd,
-- * Functions
        blockCommentParser,
--	lineCommentParser,
        parser,
-- ** Accessors
        getString
) where

import                  Control.Applicative((<|>))
import qualified        Control.Applicative

#ifdef USE_POLYPARSE
import qualified        BishBosh.Text.Poly                      as Text.Poly
#if USE_POLYPARSE == 1
import qualified        Text.ParserCombinators.Poly.Lazy        as Poly
#else /* Plain */
import qualified        Text.ParserCombinators.Poly.Plain       as Poly
#endif
#else /* Parsec */
import qualified        Control.Monad
import qualified        Text.ParserCombinators.Parsec           as Parsec
import                  Text.ParserCombinators.Parsec((<?>))
#endif

-- | Constant comment start-delimiter.
blockCommentStart :: Char
blockCommentStart       = '{'

-- | Constant comment end-delimiter.
blockCommentEnd :: Char
blockCommentEnd         = '}'

-- | Constant comment end-delimiter.
lineCommentStart :: Char
lineCommentStart        = ';'

-- | Constant comment end-delimiter.
lineCommentEnd :: Char
lineCommentEnd          = '\n'

-- | Represents a comment in PGN.
data PGNComment = BlockComment String | LineComment String

instance Show PGNComment where
        showsPrec _ (BlockComment s)    = showChar blockCommentStart . showString s . showChar blockCommentEnd
        showsPrec _ (LineComment s)     = showChar lineCommentStart . showString s . showChar lineCommentEnd

-- | Accessor.
getString :: PGNComment -> String
getString (BlockComment s)      = s
getString (LineComment s)       = s

-- | Parses PGN block-comments.
blockCommentParser ::
#ifdef USE_POLYPARSE
        Text.Poly.TextParser PGNComment
blockCommentParser      = Text.Poly.spaces >> Poly.bracket (
        Text.Poly.char blockCommentStart
 ) (
        Text.Poly.char blockCommentEnd  -- CAVEAT: 'Poly.commit' here fails ?!
 ) (
        BlockComment `fmap` Control.Applicative.many (Poly.satisfyMsg (/= blockCommentEnd) "Block-comment")
 )
#else /* Parsec */
        Parsec.Parser PGNComment
blockCommentParser      = Parsec.try (
        Parsec.spaces >> Parsec.between (
                Parsec.char blockCommentStart   <?> "Block-comment start"
        ) (
                Parsec.char blockCommentEnd     <?> "Block-comment end"
        ) (
                BlockComment `fmap` Control.Applicative.many (Parsec.satisfy (/= blockCommentEnd)) <?> "Block-comment text"
        ) <?> "Block-comment"
 )
#endif

-- | Parses PGN line-comments.
lineCommentParser ::
#ifdef USE_POLYPARSE
        Text.Poly.TextParser PGNComment
lineCommentParser       = Text.Poly.spaces >> Poly.bracket (
        Text.Poly.char lineCommentStart
 ) (
        Poly.commit $ Text.Poly.char lineCommentEnd <|> Poly.eof
 ) (
        LineComment `fmap` Control.Applicative.many (Poly.satisfyMsg (/= lineCommentEnd) "Line-comment text")
 )
#else /* Parsec */
        Parsec.Parser PGNComment
lineCommentParser       = Parsec.try (
        Parsec.spaces >> Parsec.between (
                Parsec.char lineCommentStart    <?> "Line-comment start"
        ) (
                Control.Monad.void (Parsec.char lineCommentEnd <?> "EOLN") <|> (Parsec.eof <?> "EOF")
        ) (
                LineComment `fmap` Control.Applicative.many (Parsec.satisfy (/= lineCommentEnd)) <?> "Line-comment text"
        ) <?> "Line-comment"
 )
#endif

-- | Parses PGN-comments.
parser ::
#ifdef USE_POLYPARSE
        Text.Poly.TextParser String
#else /* Parsec */
        Parsec.Parser String
#endif
parser  = fmap getString $ blockCommentParser <|> lineCommentParser