{-# LINE 1 "DB/HSQL/Type/Time.hsc" #-}
{-# LANGUAGE CPP,ForeignFunctionInterface #-}
{-# LINE 2 "DB/HSQL/Type/Time.hsc" #-}
module DB.HSQL.Type.Time where

import Control.Monad(mplus)
import System.IO.Unsafe(unsafePerformIO)
import System.Time(ClockTime(..),CalendarTime(..)
                  ,getClockTime,toCalendarTime,toUTCTime)
import Text.ParserCombinators.ReadP(ReadP,char,skipSpaces,readP_to_S)
import Text.Read.Lex(readDecP)
import Foreign(Ptr,allocaBytes,pokeByteOff)
import Foreign.C(CTime,CInt)

import DB.HSQL.Type
    (SqlType(SqlTimeTZ,SqlTime,SqlDate,SqlDateTimeTZ,SqlDateTime
            ,SqlTimeStamp,SqlText))
import Database.HSQL.Types(SqlBind(..))


{-# LINE 19 "DB/HSQL/Type/Time.hsc" #-}

-- |
mkClockTime :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> ClockTime
mkClockTime year mon mday hour min sec tz =
    unsafePerformIO $ do
      allocaBytes (44) $ \p_tm -> do
{-# LINE 25 "DB/HSQL/Type/Time.hsc" #-}
	((\hsc_ptr -> pokeByteOff hsc_ptr 0)) p_tm	(fromIntegral sec  :: CInt)
{-# LINE 26 "DB/HSQL/Type/Time.hsc" #-}
        ((\hsc_ptr -> pokeByteOff hsc_ptr 4)) p_tm	(fromIntegral min  :: CInt)
{-# LINE 27 "DB/HSQL/Type/Time.hsc" #-}
        ((\hsc_ptr -> pokeByteOff hsc_ptr 8)) p_tm	(fromIntegral hour :: CInt)
{-# LINE 28 "DB/HSQL/Type/Time.hsc" #-}
        ((\hsc_ptr -> pokeByteOff hsc_ptr 12)) p_tm	(fromIntegral mday :: CInt)
{-# LINE 29 "DB/HSQL/Type/Time.hsc" #-}
        ((\hsc_ptr -> pokeByteOff hsc_ptr 16)) p_tm	(fromIntegral (mon-1) :: CInt)
{-# LINE 30 "DB/HSQL/Type/Time.hsc" #-}
        ((\hsc_ptr -> pokeByteOff hsc_ptr 20)) p_tm	(fromIntegral (year-1900) :: CInt)
{-# LINE 31 "DB/HSQL/Type/Time.hsc" #-}
        ((\hsc_ptr -> pokeByteOff hsc_ptr 32)) p_tm	(-1 :: CInt)
{-# LINE 32 "DB/HSQL/Type/Time.hsc" #-}
	t <- mktime p_tm
        let t'=

{-# LINE 35 "DB/HSQL/Type/Time.hsc" #-}
                fromEnum t

{-# LINE 39 "DB/HSQL/Type/Time.hsc" #-}
        return (TOD (fromIntegral t' + fromIntegral (tz-currTZ)) 0)

foreign import ccall unsafe 
  mktime :: Ptr () -> IO CTime

-- |
{-# NOINLINE currTZ #-}
currTZ :: Int
currTZ = 
    ctTZ (unsafePerformIO (getClockTime >>= toCalendarTime)) -- Hack

-- |
parseTZ :: ReadP Int
parseTZ =  
    (char '+' >> readDecP) `mplus` (char '-' >> fmap negate readDecP)

-- |
f_read :: ReadP a -> String -> Maybe a
f_read f s = case readP_to_S f s of 
               [(x,_)] -> Just x

-- |
readHMS :: ReadP (Int, Int, Int)
readHMS = do
  hour  <- readDecP
  char ':'
  minutes <- readDecP
  char ':'
  seconds <- readDecP
  return (hour, minutes, seconds)

-- |
readYMD :: ReadP (Int, Int, Int)
readYMD = do
  year  <- readDecP
  char '-'
  month <- readDecP
  char '-'
  day   <- readDecP
  return (year, month, day)

-- |
readDateTime :: ReadP (Int, Int, Int, Int, Int, Int)
readDateTime = do
  (year, month, day) <- readYMD
  skipSpaces
  (hour, minutes, seconds) <- readHMS
  return (year, month, day, hour, minutes, seconds)


-- |
instance SqlBind ClockTime where
    fromSqlValue SqlTimeTZ s = f_read getTimeTZ s
	where getTimeTZ :: ReadP ClockTime
	      getTimeTZ = do
		(hour, minutes, seconds) <- readHMS
		(char '.' >> readDecP) `mplus` (return 0)
		tz <- parseTZ
		return (mkClockTime 1970 1 1 hour minutes seconds (tz*3600))
    fromSqlValue SqlTime s = f_read getTime s
	where getTime :: ReadP ClockTime
	      getTime = do
		(hour, minutes, seconds) <- readHMS
		return (mkClockTime 1970 1 1 hour minutes seconds currTZ)
    fromSqlValue SqlDate s = f_read getDate s
	where getDate :: ReadP ClockTime
	      getDate = do
		(year, month, day) <- readYMD
		return (mkClockTime year month day 0 0 0 currTZ)
    fromSqlValue SqlDateTimeTZ s = f_read getDateTimeTZ s
	where getDateTimeTZ :: ReadP ClockTime
	      getDateTimeTZ = do
	        (year, month, day, hour, minutes, seconds) <- readDateTime
		char '.' >> readDecP -- ) `mplus` (return 0)
                tz <- parseTZ
		return (mkClockTime year month day 
                                    hour minutes seconds 
                                    (tz*3600))
    -- The only driver which seems to report the type as SqlTimeStamp seems
    -- to be the MySQL driver. MySQL (at least 4.1) uses the same format for
    -- datetime and timestamp columns.
    -- Allow SqlText to support SQLite, which reports everything as SqlText
    fromSqlValue t s 
        | t == SqlDateTime || t == SqlTimeStamp || t == SqlText = 
            f_read getDateTime s
                where getDateTime :: ReadP ClockTime
		      getDateTime = do
			(year, month, day, hour, minutes, seconds) <- 
                            readDateTime
			return (mkClockTime year month day 
                                            hour minutes seconds 
                                            currTZ)
    fromSqlValue _ _ = Nothing

    toSqlValue ct = 
        '\'' : (shows (ctYear t) . score .
                shows (ctMonth t) . score .
                shows (ctDay t) . space .
                shows (ctHour t) . colon .
                shows (ctMin t) . colon .
                shows (ctSec t)) "'"
        where t = toUTCTime ct
              score = showChar '-'
              space = showChar ' '
              colon = showChar ':'