{-# OPTIONS -Wall #-}

--------------------------------------------------------------------------------
-- |
-- Module      :  ZMidi.Core.Internal.SimpleFormat
-- Copyright   :  (c) Stephen Tetley 2010-2012
-- License     :  BSD3
--
-- Maintainer  :  Stephen Tetley <stephen.tetley@gmail.com>
-- Stability   :  unstable
-- Portability :  As per dependencies.
--
-- Simple line-oriented formatting combinators.
--
--------------------------------------------------------------------------------

module ZMidi.Core.Internal.SimpleFormat
  (

    Doc
  , width
  , output

  , cat
  , sep
  , char
  , text
  , repeatChar

  , padl
  , padr
  , hex2  
  , hex4
  , integral

  ) where

import Data.Monoid
import Data.Word
import Numeric

-- | Strings are represented as Hughes lists 
--
-- ShowS is a Hughes list representation specialized to Strings
--
type H a = [a] -> [a]


type HString = H Char

-- | Make a HString of spaces.
--
spaceH :: Int -> HString 
spaceH n = showString $ replicate n ' '


fromH :: H a -> [a]
fromH = ($ [])

-- | Docs represent a single line - they should not contain 
-- newlines.
--
data Doc = Doc { 
    -- | Width of the doc.
    width :: !Int, 

    -- | Internal representation.
    doch :: HString }

-- | Make a literal Doc from a String.
--
doc :: String -> Doc
doc s = Doc (length s) (showString s) 

-- | Unwrap a Doc making a String.
--
output :: Doc -> String
output = fromH . doch



instance Monoid Doc where
  mempty                        = Doc 0 id
  Doc i1 f1 `mappend` Doc i2 f2 = Doc (i1+i2) (f1 . f2)


infixr 6 `cat`

-- | Concatenate - no space.
--
cat :: Doc -> Doc -> Doc
cat = mappend

infixr 6 `sep`

-- | Concatenate - with space.
--
sep :: Doc -> Doc -> Doc
sep (Doc i1 f1) (Doc i2 f2) = Doc (1+i1+i2) (f1 . (' ':) . f2)


-- | Make a Doc from a Char.
--
char :: Char -> Doc 
char c = Doc 1 (c:)

-- | Make a Doc from a String.
--
text :: String -> Doc
text = doc 

-- | Repeat the Char /n/ times to make a Doc.
--
repeatChar :: Int -> Char -> Doc
repeatChar n c = Doc n (showString $ replicate n c)


-- | Pad the left with space.
--
padl :: Int -> Doc -> Doc
padl i d@(Doc n f) | i > n     = Doc i (spaceH (i-n) . f)
                   | otherwise = d

-- | Pad the right with space.
--
padr :: Int -> Doc -> Doc
padr i d@(Doc n f) | i > n     = Doc i (f . spaceH (i-n))
                   | otherwise = d

-- | Show as a two digit hex number.
--
hex2 :: Word8 -> Doc
hex2 n | n < 0x10  = Doc 2 (('0' :) . showHex n)
       | otherwise = Doc 2 (showHex n)  

-- | Show as a four digit hex number.
--
hex4 :: Word16 -> Doc
hex4 n | n < 0x10   = Doc 4 (('0':) . ('0':) . ('0':) . showHex n)
       | n < 0x100  = Doc 4 (('0':) . ('0':) . showHex n)
       | n < 0x1000 = Doc 4 (('0':) . showHex n)
       | otherwise = Doc 4 (showHex n)  

-- | Show an Integral value as a base 10 number.
--
integral :: (Show a, Integral a) => a -> Doc
integral = doc . show