{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- | Helpers for pretty printing
module Dino.Pretty where

import Prelude

import Data.Hashable (Hashable)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.List (sortOn)
import Data.String (IsString)
import GHC.Generics (Generic)
import Text.PrettyPrint.ANSI.Leijen (Doc, Pretty (..), (<+>))
import qualified Text.PrettyPrint.ANSI.Leijen as PP

data Importance
  = Unimportant
  | Important
  deriving (Eq, Show, Generic)

-- | Returns 'Important' iff. any argument is 'Important'.
instance Semigroup Importance where
  Unimportant <> Unimportant = Unimportant
  _ <> _ = Important

instance Hashable Importance

-- | Marks a part of a value that hasn't changed
unchanged :: Doc
unchanged = PP.magenta $ PP.text "*"

-- | Emphasize when 'Important'
emphasize :: Importance -> Doc -> Doc
emphasize Unimportant = id
emphasize Important = PP.bold . PP.blue

-- | Place a document indented under a header:
--
-- > header
-- >   doc
-- >   doc
-- >   ...
underHeader ::
     Doc -- ^ Header
  -> Doc -- ^ Document to place under the header
  -> Doc
underHeader h d = h PP.<$> PP.space <+> PP.align d

-- | Render a list of documents as follows:
--
-- > [ a
-- > , b
-- > , ...
-- > ]
--
-- where @'['@, @','@ and @']'@ are provided as the first three parameters.
verticalList :: Doc -> Doc -> Doc -> [Doc] -> Doc
verticalList l _ r [] = l <+> r
verticalList l sep r ds =
  PP.vcat [c <+> PP.align d | (c, d) <- zip (l : repeat sep) ds] PP.<$> r

-- | A wrapper for 'String' with a 'Show' instance that omits quotes
--
-- Useful in situations where 'show' is (ab)used to provide conversion to
-- 'String' rather than for displaying values.
newtype Field = Field {unField :: String}
  deriving (Eq, Ord, IsString, Hashable)

instance Show Field where
  show = unField

instance Pretty Field where
  pretty = PP.string . unField

-- | Render a record as follows:
--
-- > { field1 =
-- >     value1
-- > , field2 =
-- >     value2
-- > ,  ...
-- > }
--
-- If @k@ is a 'String'-like type, it will be shown with quotes. Use 'Field' to
-- prevent this.
prettyRecord :: (Show k, Ord k) => Importance -> HashMap k Doc -> Doc
prettyRecord imp =
  verticalList PP.lbrace PP.comma PP.rbrace .
  map prettyField . sortOn fst . HM.toList
  where
    prettyField (f, v) =
      underHeader (emphasize imp (PP.string (show f)) <+> PP.string "=") v