{-# LANGUAGE FlexibleContexts, TypeFamilies, TemplateHaskell, QuasiQuotes, DeriveDataTypeable, ScopedTypeVariables, MultiParamTypeClasses,
    FlexibleInstances, TypeSynonymInstances, UndecidableInstances, LambdaCase #-}
{-# OPTIONS_HADDOCK prune #-}
{-|
  Module      : Language.Pads.BaseTypes
  Description : Base types provided by Pads
  Copyright   : (c) 2011
                Kathleen Fisher <kathleen.fisher@gmail.com>
                John Launchbury <john.launchbury@gmail.com>
  License     : MIT
  Maintainer  : Karl Cronburg <karl@cs.tufts.edu>
  Stability   : experimental

	Some useful Pads types (parsers) implemented by the code generator in lieu of
	writing them by hand.

-}

module Language.Pads.BaseTypes where

import Language.Pads.Source
import Language.Pads.Errors
import Language.Pads.Generic
import Language.Pads.MetaData
import Language.Pads.CoreBaseTypes
import Language.Pads.Quote
import Language.Pads.RegExp
import Language.Pads.PadsPrinter
import Language.Pads.Generation
import Data.Time
--import System.Locale as Locale
import Text.PrettyPrint.Mainland (text)
import Text.PrettyPrint.Mainland.Class

import qualified Data.Char as C
import qualified Data.List as L
import Data.Data
import qualified Data.ByteString as B

[pads|
-- string that stops in a newline
type StringEOR = [Char] terminator EOR
type Line a   = (a, EOR)
type StringLn = [Char] terminator (Try EOR)
type StringLnP (p :: String -> Bool) = constrain s :: StringLn where <| p s |>
type StringESCLn (p :: (Char, [Char])) = StringPESC <|(True, p)|>
type StringESC   (p :: (Char, [Char])) = StringPESC <|(False, p)|>

data PMaybe a = PJust a
              | PNothing Void
obtain Maybe a from PMaybe a using <|(pm2m,m2pm)|>

|]

-- | Pads maybe to Haskell maybe
pm2m :: Span -> (PMaybe a, PMaybe_md a_md) -> (Maybe a, Maybe_md a_md)
pm2m p (PJust x, md) = (Just x, md)
pm2m p (PNothing,md) = (Nothing,md)

-- | Haskell maybe to Pads maybe
m2pm :: (Maybe a, Maybe_md a_md) -> (PMaybe a, PMaybe_md a_md)
m2pm (Just x, md) = (PJust x, md)
m2pm (Nothing,md) = (PNothing,md)

maybe_genM :: PadsGen st a -> PadsGen st (Maybe a)
maybe_genM x = pMaybe_genM x >>= (\case PJust a  -> return $ Just a
                                        PNothing -> return $ Nothing)

[pads|
type Lit   (x::String) = (Void, x)
type LitRE (x::RE)     = (Void, x)
|]

[pads| obtain Bool from Bits8 1 using <| (bits8ToBool, boolToBits8) |> generator bitBool_genM |]

bits8ToBool :: Span -> (Bits8, Bits8_md) -> (Bool, Bool_md)
bits8ToBool _ (b, md) = (b == 1, md)

boolToBits8 :: (Bool, Bool_md) -> (Bits8, Bits8_md)
boolToBits8 (b, md) = ((fromIntegral . fromEnum) b, md)


[pads| type DateFSE (fmt :: String, se :: RE) = obtain UTCTime from StringSE se using <| (strToUTC fmt, utcToStr fmt) |>
       type DateFC (fmt::String, c::Char) = DateFSE <|(fmt, RE ("[" ++ [c] ++  "]")) |> |]

-- | Coordinated universal time Pads metadata type
type UTCTime_md = Base_md
instance Pretty UTCTime where
  ppr utc = text (show utc)

-- | UTC parser from a string based on Haskell builtin UTC parser.
strToUTC :: String -> Span -> (StringSE, Base_md) -> (UTCTime, Base_md)
strToUTC fmt pos (input, input_bmd) =
  case parseTimeM True Data.Time.defaultTimeLocale fmt input of
       Nothing -> (gdef, mergeBaseMDs [errPD, input_bmd])
       Just t  -> (t, input_bmd)
  where
    errPD = mkErrBasePD (TransformToDstFail "DateFSE" input " (conversion failed)") (Just pos)

-- | Default time of: 0h Nov 17, 1858
uTCTime_def = UTCTime (ModifiedJulianDay 0) (secondsToDiffTime 0)

-- | Format a UTC instance as a string.
utcToStr :: String -> (UTCTime, Base_md) -> (StringSE, Base_md)
utcToStr fmt (utcTime, bmd) = (formatTime Data.Time.defaultTimeLocale fmt utcTime, bmd)


[pads| type TimeZoneSE (se :: RE) = obtain TimeZone from StringSE se using <| (strToTz, tzToStr) |>
       type TimeZoneC (c::Char) = TimeZoneSE <|RE ("[" ++ [c] ++  "]") |> |]

type TimeZone_md = Base_md
instance Pretty TimeZone where
  ppr tz = text (show tz)

-- | Timezone parser
strToTz :: Span -> (StringSE, Base_md) -> (TimeZone, Base_md)
strToTz pos (input, input_bmd) =
  case parseTimeM True Data.Time.defaultTimeLocale "%z" input of
       Nothing -> (gdef,  mergeBaseMDs [mkErrBasePD (TransformToDstFail "TimeZoneSE" input " (conversion failed)") (Just pos), input_bmd])
       Just t  -> (t, input_bmd)

-- | Timezone formatter
tzToStr ::  (TimeZone, Base_md) -> (StringSE, Base_md)
tzToStr (tz, bmd) = (h ++ ":" ++ m, bmd)
           where (h,m) = splitAt 3 (show tz)

timeZone_def = utc

[pads| type Phex32FW (size :: Int) = obtain Int from StringFW size using <| (hexStr2Int,int2HexStr size) |> |]

-- | Transform a hexadecimal string to an int
hexStr2Int :: Span -> (StringFW, Base_md) -> (Int, Base_md)
hexStr2Int src_pos (s,md) = if good then (intList2Int ints 0, md)
                                      else (0, mkErrBasePD  (TransformToDstFail "StrHex" s " (non-hex digit)") (Just src_pos))
  where
    hc2int c = if C.isHexDigit c then (C.digitToInt c,True) else (0,False)
    (ints,bools) = unzip (map hc2int s)
    good = (L.and bools) && (length ints > 0)
    intList2Int digits a = case digits of
        []     -> a
        (d:ds) -> intList2Int ds ((16 * a) + d)

-- | Transform an int into a hexadecimal string
int2HexStr :: Int -> (Int, Base_md) -> (StringFW, Base_md)
int2HexStr size (x,md)
  | length result == size && wasPos = (result, md)
  | not wasPos = (Prelude.take size result,
                  mkErrBasePD (TransformToSrcFail "StrHex" (show x) (" (Expected positive number)")) Nothing)
  | otherwise  = (Prelude.take size result,
                  mkErrBasePD (TransformToSrcFail "StrHex" (show x) (" (too big to fit in "++ (show size) ++" characters)")) Nothing)
  where
   cvt rest a = if rest < 16 then {- reverse $ -} (C.intToDigit rest) : a
                else cvt (rest `div` 16) (C.intToDigit (rest `mod` 16) : a)
   (wasPos,x') = if x < 0 then (False, -x) else (True, x)
   temp = cvt x' []
   padding = size - (length temp)
   stutter c n = if n <= 0 then [] else c : (stutter c (n-1))
   result = (stutter '0' padding) ++ temp