-- | W3C Date and Time parser combinator for "Text.Parsec".
module Data.Time.W3C.Parser.Parsec
    ( w3cDateTime
    )
    where

import Control.Monad
import Data.Time
import Data.Time.W3C.Types
import Text.Parsec

-- | This is a parser combinator for "Text.Parsec".
w3cDateTime :: Stream s m Char => ParsecT s u m W3CDateTime
w3cDateTime = read4 >>= mdhmst
    where
      mdhmst year
          = ( char '-' >> read2 >>= dhmst year )
            <|>
            return W3CDateTime {
                         w3cYear     = year
                       , w3cMonth    = Nothing
                       , w3cDay      = Nothing
                       , w3cHour     = Nothing
                       , w3cMinute   = Nothing
                       , w3cSecond   = Nothing
                       , w3cTimeZone = Nothing
                       }
      dhmst year month
          = ( char '-' >> read2 >>= hmst year month )
            <|>
            return W3CDateTime {
                         w3cYear     = year
                       , w3cMonth    = Just month
                       , w3cDay      = Nothing
                       , w3cHour     = Nothing
                       , w3cMinute   = Nothing
                       , w3cSecond   = Nothing
                       , w3cTimeZone = Nothing
                       }
      hmst year month day
          = ( do _ <- char 'T'
                 h <- read2
                 _ <- char ':'
                 m <- read2
                 st year month day h m
            )
            <|>
            return W3CDateTime {
                         w3cYear     = year
                       , w3cMonth    = Just month
                       , w3cDay      = Just day
                       , w3cHour     = Nothing
                       , w3cMinute   = Nothing
                       , w3cSecond   = Nothing
                       , w3cTimeZone = Nothing
                       }
      st year month day hour minute
          = ( do _ <- char ':'
                 s <- second
                 t <- timezone
                 return W3CDateTime {
                              w3cYear     = year
                            , w3cMonth    = Just month
                            , w3cDay      = Just day
                            , w3cHour     = Just hour
                            , w3cMinute   = Just minute
                            , w3cSecond   = Just s
                            , w3cTimeZone = Just t
                            }
            )
            <|>
            ( do t <- timezone
                 return W3CDateTime {
                              w3cYear     = year
                            , w3cMonth    = Just month
                            , w3cDay      = Just day
                            , w3cHour     = Just hour
                            , w3cMinute   = Just minute
                            , w3cSecond   = Nothing
                            , w3cTimeZone = Just t
                            }
            )

      second = do int  <- read2
                  frac <- option 0 (char '.' >> liftM parseFrac (many1 digit))
                  return (int + frac)

      timezone = liftM minutesToTimeZone
                 ( ( char 'Z' >> return 0 )
                   <|>
                   do sign <- ( char '+' >> return 1 )
                              <|>
                              ( char '-' >> return (-1) )
                      h    <- read2
                      _    <- char ':'
                      m    <- read2
                      return (sign * (h * 60 + m))
                 )

{- 0.152 => 2,5,1 -->
   * (0    / 10) + 0.2 = 0.2
   * (0.2  / 10) + 0.5 = 0.52
   * (0.52 / 10) + 0.1 = 0.152  *done*
 -}
parseFrac :: RealFrac r => String -> r
parseFrac = parseFrac' 0 . reverse . map fromC
    where
      parseFrac' r []     = r
      parseFrac' r (d:ds) = parseFrac' (r / 10 + d / 10) ds

read4 :: (Stream s m Char, Num n) => ParsecT s u m n
read4 = do n1 <- digit'
           n2 <- digit'
           n3 <- digit'
           n4 <- digit'
           return (n1 * 1000 + n2 * 100 + n3 * 10 + n4)

read2 :: (Stream s m Char, Num n) => ParsecT s u m n
read2 = do n1 <- digit'
           n2 <- digit'
           return (n1 * 10 + n2)

digit' :: (Stream s m Char, Num n) => ParsecT s u m n
digit' = liftM fromC digit

fromC :: Num n => Char -> n
fromC '0' = 0
fromC '1' = 1
fromC '2' = 2
fromC '3' = 3
fromC '4' = 4
fromC '5' = 5
fromC '6' = 6
fromC '7' = 7
fromC '8' = 8
fromC '9' = 9
fromC _   = undefined