(* Copyright (C) 1999-2006 Henry Cejtin, Matthew Fluet, Suresh
 *    Jagannathan, and Stephen Weeks.
 * Copyright (C) 1997-2000 NEC Research Institute.
 *
 * MLton is released under a BSD-style license.
 * See the file MLton-LICENSE for details.
 *)

structure Time: TIME_EXTRA =
struct

structure Prim = PrimitiveFFI.Time

(* A time is represented as a number of nanoseconds. *)
val ticksPerSecond: LargeInt.int = 1000000000

datatype time = T of LargeInt.int

val fromTicks = T

exception Time

val zeroTime = T 0

fun fromReal r =
   T (LargeReal.toLargeInt IEEEReal.TO_NEAREST 
      (LargeReal.* (r, LargeReal.fromLargeInt ticksPerSecond)))
   handle Overflow => raise Time

fun toReal (T i) =
   LargeReal./ (LargeReal.fromLargeInt i, 
                LargeReal.fromLargeInt ticksPerSecond)

local
   fun make ticksPer =
      let
         val d = LargeInt.quot (ticksPerSecond, ticksPer)
      in
         (fn i => T (LargeInt.* (i, d)),
          fn T i => LargeInt.quot (i, d))
      end
in
   val (fromSeconds, toSeconds) = make 1
   val (fromMilliseconds, toMilliseconds) = make 1000
   val (fromMicroseconds, toMicroseconds) = make 1000000
   val (fromNanoseconds, toNanoseconds) = make 1000000000
end

local
   fun make f (T i, T i') = f (i, i')
in
   val compare = make LargeInt.compare
   val op < = make LargeInt.<
   val op <= = make LargeInt.<=
   val op > = make LargeInt.>
   val op >= = make LargeInt.>=
end
local
   fun make f (T i, T i') = T (f (i, i'))
in
   val timeAdd = make LargeInt.+
   val timeSub = make LargeInt.-
end

(* There's a mess here to work around a bug in vmware virtual machines
 * that may return a decreasing(!) sequence of time values.  This will
 * cause some programs to raise Time exceptions where it should be
 * impossible.
 *)
local
   fun getNow (): time =
      let
         val sec = ref (C_Time.castFromFixedInt 0)
         val usec = ref (C_SUSeconds.castFromFixedInt 0)
      in
         if ~1 = Prim.getTimeOfDay (sec, usec)
            then raise Fail "Time.now"
         else timeAdd(fromSeconds (C_Time.toLargeInt (! sec)),
                      fromMicroseconds (C_SUSeconds.toLargeInt (! usec)))
      end
   val prev = ref (getNow ())
in
   fun now (): time =
      let
         val old = !prev
         val t = getNow ()
      in
         case compare (old, t) of
            GREATER => old
          | _ => (prev := t; t)
      end
end

val fmt: int -> time -> string =
   fn n => (LargeReal.fmt (StringCvt.FIX (SOME n))) o toReal

val toString = fmt 3

(* Adapted from the ML Kit 4.1.4; basislib/Time.sml
 * by mfluet@acm.org on 2005-11-10 based on
 * by mfluet@acm.org on 2005-8-10 based on
 *  adaptations from the ML Kit 3 Version; basislib/Time.sml
 *  by sweeks@research.nj.nec.com on 1999-1-3.
 *)
fun scan getc src =
   let
      val charToDigit = StringCvt.charToDigit StringCvt.DEC
      fun pow10 0 = 1
        | pow10 n = 10 * pow10 (n-1)
      fun mkTime sign intv fracv decs =
         let
            val nsec = 
               LargeInt.div (LargeInt.+ (LargeInt.* (Int.toLarge (pow10 (10 - decs)), 
                                                     Int.toLarge fracv),
                                         5), 
                             10)
            val t =
               LargeInt.+ (LargeInt.* (Int.toLarge intv, ticksPerSecond),
                           nsec)
            val t = if sign then t else LargeInt.~ t 
         in
            T t
         end
      fun frac' sign intv fracv decs src =
         if Int.>= (decs, 7)
            then SOME (mkTime sign intv fracv decs, 
                       StringCvt.dropl Char.isDigit getc src)
         else case getc src of
            NONE           => SOME (mkTime sign intv fracv decs, src)
          | SOME (c, rest) =>
               (case charToDigit c of
                   NONE   => SOME (mkTime sign intv fracv decs, src)
                 | SOME d => frac' sign intv (10 * fracv + d) (decs + 1) rest)
      fun frac sign intv src =
         case getc src of
            NONE           => NONE
          | SOME (c, rest) =>
               (case charToDigit c of
                   NONE   => NONE
                 | SOME d => frac' sign intv d 1 rest)
      fun int' sign intv src =
         case getc src of
            NONE              => SOME (mkTime sign intv 0 7, src)
          | SOME (#".", rest) => frac sign intv rest
          | SOME (c, rest)    =>
               (case charToDigit c of
                   NONE   => SOME (mkTime sign intv 0 7, src)
                 | SOME d => int' sign (10 * intv + d) rest)
      fun int sign src =
         case getc src of
            NONE           => NONE
          | SOME (#".", rest) => frac sign 0 rest
          | SOME (c, rest) => 
               (case charToDigit c of
                   NONE   => NONE
                 | SOME d => int' sign d rest)
   in 
      case getc (StringCvt.skipWS getc src) of
         NONE              => NONE
       | SOME (#"+", rest) => int true rest
       | SOME (#"~", rest) => int false rest
       | SOME (#"-", rest) => int false rest
       | SOME (#".", rest) => frac true 0 rest
       | SOME (c, rest)    => 
            (case charToDigit c of
                NONE => NONE
              | SOME d => int' true d rest)
   end
handle Overflow => raise Time

val fromString = StringCvt.scanString scan

val op + = timeAdd
val op - = timeSub

end
