-- |
-- Module      : Data.Hourglass.Local
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Local time = global time + timezone
--
{-# LANGUAGE FlexibleInstances #-}
module Data.Hourglass.Local
    (
    -- * Local time
    -- ** Local time type
      LocalTime
    -- ** Local time creation and manipulation
    , localTime
    , localTimeUnwrap
    , localTimeToGlobal
    , localTimeFromGlobal
    , localTimeGetTimezone
    , localTimeSetTimezone
    , localTimeConvert
    ) where

import Data.Hourglass.Types
import Data.Hourglass.Time
import Data.Hourglass.Diff

-- | Local time representation
--
-- this is a time representation augmented by a timezone
-- to get back to a global time, the timezoneOffset needed to be added to the local time.
--
data LocalTime t = LocalTime
    { localTimeUnwrap      :: t              -- ^ unwrap the LocalTime value. the time value is local.
    , localTimeGetTimezone :: TimezoneOffset -- ^ get the timezone associated with LocalTime
    }

-- FIXME add instance Read too.

instance Show t => Show (LocalTime t) where
    show (LocalTime t tz) = show t ++ show tz
instance Eq t => Eq (LocalTime t) where
    LocalTime t1 tz1 == LocalTime t2 tz2 = tz1 == tz2 && t1 == t2

instance (Ord t, Time t) => Ord (LocalTime t) where
    compare l1@(LocalTime g1 tz1) l2@(LocalTime g2 tz2) =
        case compare tz1 tz2 of
            EQ -> compare g1 g2
            _  -> let t1 = localTimeToGlobal l1
                      t2 = localTimeToGlobal l2
                   in compare t1 t2

instance Functor LocalTime where
    fmap f (LocalTime t tz) = LocalTime (f t) tz

-- | Create a local time type from a timezone and a time type.
--
-- The time value is assumed to be local to the timezone offset set,
-- so no transformation is done.
localTime :: Time t => TimezoneOffset -> t -> LocalTime t
localTime tz t = LocalTime t tz

-- | Get back a global time value
localTimeToGlobal :: Time t => LocalTime t -> t
localTimeToGlobal (LocalTime local tz)
    | tz == TimezoneOffset 0 = local
    | otherwise              = timeConvert $ elapsedTimeAddSecondsP (timeGetElapsedP local) tzSecs
  where tzSecs = negate $ timezoneOffsetToSeconds tz

-- | create a local time value from a global one
localTimeFromGlobal :: Time t => t -> LocalTime t
localTimeFromGlobal = localTime (TimezoneOffset 0)

-- | Change the timezone, and adjust the local value to represent the new local value.
localTimeSetTimezone :: Time t => TimezoneOffset -> LocalTime t -> LocalTime t
localTimeSetTimezone tz currentLocal@(LocalTime t currentTz)
    | diffTz == 0 = currentLocal
    | otherwise   = LocalTime (timeConvert t') tz
  where t'        = elapsedTimeAddSecondsP (timeGetElapsedP t) diffTz
        diffTz    = timezoneOffsetToSeconds tz - timezoneOffsetToSeconds currentTz

-- | convert the local time representation to another time representation determined by context.
localTimeConvert :: (Time t1, Time t2) => LocalTime t1 -> LocalTime t2
localTimeConvert = fmap timeConvert
