{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.Haskell.Printf.Printers where

import Control.Applicative ((<$>), pure)
import Data.Char
import Data.List
import Data.String (fromString)
import Foreign.Ptr
import GHC.Float (FFFormat(..))
import Language.Haskell.Printf.Geometry
import Language.Haskell.PrintfArg
import NumUtils
import qualified Parser.Types as P

type Printer n = PrintfArg n -> Value

printfString :: Printer String
printfString spec =
    Value
        { valArg =
              case prec spec of
                  Nothing -> spec
                  Just c -> take c <$> spec
        , valPrefix = Nothing
        , valSign = Nothing
        }

printfShow :: Show a => Printer a
printfShow spec = printfString (fromString . show <$> spec)

printfChar :: Printer Char
printfChar spec = Value {valArg = pure <$> spec, valPrefix = Nothing, valSign = Nothing}

printfPtr :: Printer (Ptr a)
printfPtr spec =
    Value
        { valArg =
              PrintfArg
                  { width = width spec
                  , prec = Nothing
                  , flagSet = P.emptyFlagSet {P.prefixed = True}
                  , lengthSpec = Nothing
                  , fieldSpec = 'p'
                  , value = showIntAtBase 16 intToDigit (toInt $ value spec)
                  }
        , valPrefix = Just "0x"
        , valSign = Nothing
        }
  where
    toInt x = x `minusPtr` nullPtr

printfDecimal spec =
    Value
        { valArg = padDecimal spec . showIntAtBase 10 intToDigit . abs <$> spec
        , valPrefix = Nothing
        , valSign = sign' spec
        }

fmtUnsigned ::
       forall a. (Bounded a, Integral a)
    => (Integer -> String)
    -> (PrintfArg a -> Maybe String)
    -> Printer a
fmtUnsigned shower p spec =
    Value
        { valArg = padDecimal spec . shower . clamp <$> spec
        , valPrefix = p spec
        , valSign = Nothing
        }
  where
    lb = minBound :: a
    clamp :: a -> Integer
    clamp x
        | x < 0 = toInteger x + (-2 * toInteger lb)
        | otherwise = toInteger x

printfHex b =
    fmtUnsigned
        showHex
        (prefix
             (if b
                  then "0X"
                  else "0x"))
  where
    showHex =
        showIntAtBase
            16
            ((if b
                  then toUpper
                  else id) .
             intToDigit)

printfUnsigned = fmtUnsigned (showIntAtBase 10 intToDigit) (const Nothing)

printfOctal spec
    | "0" `isPrefixOf` value valArg = v
    | otherwise = v {valPrefix = prefix "0" spec}
  where
    v@Value {..} = fmtUnsigned (showIntAtBase 8 intToDigit) (const Nothing) spec

printfFloating upperFlag spec =
    Value {valArg = showFloat . abs <$> spec, valPrefix = Nothing, valSign = sign' spec}
  where
    precision =
        case prec spec of
            Just n -> Just (fromIntegral n)
            Nothing
                | Just P.ZeroPadded <- adjustment spec -> Just 6
            _ -> Nothing
    showFloat = formatRealFloatAlt FFFixed precision (prefixed spec) upperFlag

printfScientific upperFlag spec =
    Value {valArg = showSci . abs <$> spec, valPrefix = Nothing, valSign = sign' spec}
  where
    showSci =
        formatRealFloatAlt
            FFExponent
            (fromIntegral <$> prec spec)
            (prefixed spec)
            upperFlag

printfGeneric upperFlag spec =
    Value {valArg = showSci . abs <$> spec, valPrefix = Nothing, valSign = sign' spec}
  where
    showSci =
        formatRealFloatAlt
            FFGeneric
            (fromIntegral <$> prec spec)
            (prefixed spec)
            upperFlag

printfFloatHex upperFlag spec =
    Value
        { valArg = showHexFloat . abs <$> spec
        , valPrefix =
              Just
                  (if upperFlag
                       then "0X"
                       else "0x")
        , valSign = sign' spec
        }
  where
    showHexFloat = formatHexFloat (fromIntegral <$> prec spec) (prefixed spec) upperFlag