{-# LANGUAGE CPP               #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE UndecidableInstances #-}
module Commonmark.Extensions.Smart
  ( HasQuoted(..)
  , smartPunctuationSpec )
where

import Commonmark.Types
import Commonmark.Syntax
import Commonmark.Inlines
import Commonmark.Html
import Commonmark.SourceMap
import Commonmark.TokParsers (symbol)
import Text.Parsec
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid (Monoid)
import Data.Semigroup (Semigroup, (<>))
#endif

class IsInline il => HasQuoted il where
  singleQuoted :: il -> il
  doubleQuoted :: il -> il

instance Rangeable (Html a) => HasQuoted (Html a) where
  singleQuoted x = htmlText "‘" <> x <> htmlText "’"
  doubleQuoted x = htmlText "“" <> x <> htmlText "”"

instance (HasQuoted i, Monoid i, Semigroup i)
        => HasQuoted (WithSourceMap i) where
  singleQuoted x = (singleQuoted <$> x) <* addName "singleQuoted"
  doubleQuoted x = (doubleQuoted <$> x) <* addName "doubleQuoted"

smartPunctuationSpec :: (Monad m, IsBlock il bl, IsInline il, HasQuoted il)
                     => SyntaxSpec m il bl
smartPunctuationSpec = mempty
  { syntaxFormattingSpecs = [singleQuotedSpec, doubleQuotedSpec]
  , syntaxInlineParsers = [pEllipses, pDash]
  }

singleQuotedSpec :: (IsInline il, HasQuoted il) => FormattingSpec il
singleQuotedSpec = FormattingSpec '\'' False False (Just singleQuoted) Nothing '’'

doubleQuotedSpec :: (IsInline il, HasQuoted il) => FormattingSpec il
doubleQuotedSpec = FormattingSpec '"' False False (Just doubleQuoted) Nothing '“'

pEllipses :: (Monad m, IsInline a) => InlineParser m a
pEllipses = try $ do
  count 3 (symbol '.')
  return $! str "…"

pDash :: (Monad m, IsInline a) => InlineParser m a
pDash = try $ do
  symbol '-'
  numhyphens <- (+1) . length <$> many1 (symbol '-')
  let (emcount, encount) =
        case numhyphens of
             n | n `mod` 3 == 0 -> (n `div` 3, 0)
               | n `mod` 2 == 0 -> (0, n `div` 2)
               | n `mod` 3 == 2 -> ((n - 2) `div` 3, 1)
               | otherwise      -> ((n - 4) `div` 3, 2)
  return $! mconcat $
    replicate emcount (str "—") <>
    replicate encount (str "–")