/** @file
 * Standard include file: time.h
 *
 * Copyright (C) 2009, Bernhard Kauer <bk@vmmon.org>
 * Economic rights: Technische Universitaet Dresden (Germany)
 *
 * Copyright (C) 2013 Jacek Galowicz, Intel Corporation.
 *
 * This file is part of Vancouver.
 *
 * Vancouver is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Vancouver is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details.
 */

#pragma once

#include "math.h"

/**
 * An appreviated broken down time.
 */
struct tm_simple {
  timevalue sec;
  timevalue min;
  timevalue hour;
  timevalue mday;
  timevalue mon;
  timevalue year;
  timevalue wday;
  timevalue yday;

  tm_simple() : sec(0), min(0), hour(0), mday(0), mon(0), year(0), wday(0), yday(0) {};
  tm_simple(int _year, int _mon, int _mday, int _hour, int _min, int _sec) : sec(_sec), min(_min), hour(_hour), mday(_mday), mon(_mon), year(_year), wday(0), yday(0) {}
};

static inline bool is_leap(timevalue year) { return (!(year & 3) && ((year % 100) != 0)) || (year % 400 == 0); }

static inline timevalue mktime(struct tm_simple *tm)
{
  bool before_march = tm->mon < 3;
  timevalue days = ((tm->mon + 10)*367)/12 + 2*before_march - 719866 + tm->mday - before_march * is_leap(tm->year) + tm->year*365;
  days += tm->year / 4 - tm->year / 100 + tm->year / 400;
  return ((days*24+tm->hour)*60+tm->min)*60 + tm->sec;
}

static inline timevalue moddiv(timevalue &value, unsigned divider)
{
  auto const mod = Math::moddiv<timevalue>(value, divider);
  auto const   d = value;
  value = mod;
  return d;
}

static inline void gmtime(timevalue seconds, struct tm_simple *tm)
{
  // move from 1970 to 1 as epoch, to be able to use division with positive values
  seconds       = seconds + (719528ull - 366ull)*86400ull;
  tm->sec       = Math::moddiv<timevalue>(seconds, 60);
  tm->min       = Math::moddiv<timevalue>(seconds, 60);
  tm->hour      = Math::moddiv<timevalue>(seconds, 24);
  timevalue days= seconds++;
  tm->wday      = Math::moddiv<timevalue>(seconds, 7);
  auto years400 = moddiv(days, 4*36524+1);
  auto years100 = moddiv(days, 36524);
  // overflow on a 400year boundary?
  if (years100 == 4) years100--, days += 36524;

  auto years4 = moddiv(days, 1461);
  auto years  = moddiv(days, 365);
  // overflow on the 4year boundary?
  if (years == 4)  years -= 1, days += 365;



  // move back to our timebase
  tm->year = ((((years400 << 2) + years100)*25 + years4) << 2) + years + 1;
  tm->yday  = days + 1;

  // get month,day from day_of_year
  static unsigned leap[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
  static unsigned noleap[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
  unsigned *l = is_leap(tm->year) ? leap : noleap;

  // heuristic to (under-)estimate the month
  unsigned m = static_cast<unsigned>(days) / 31;
  days -= l[m];
  // did we made it?
  if (days >= (l[m+1] - l[m]))
    {
      days -= l[m+1] - l[m];
      m += 1;
    }
  tm->mon  = m + 1;
  tm->mday = days + 1;
}

class StopWatch
{
private:
    Clock *_clock;
    unsigned _frequency;
    timevalue _tic, _toc;

public:
    void start()      { _tic = _clock->clock(_frequency); }
    timevalue stop()  { _toc = _clock->clock(_frequency); return delta(); }
    timevalue delta() { return _toc - _tic; }

    timevalue abs_start() { return _tic; }
    timevalue abs_stop()  { return _toc; }

    // Returns B/ms, which is actually kB/s (if using default frequency)
    timevalue rate(timevalue bytes) {
        if (delta()) return bytes / delta();
        else return 0;
    }

    StopWatch(Clock *clock, unsigned frequency = 1000 /* ms */)
        : _clock(clock), _frequency(frequency), _tic(0), _toc(0)
    {}
};
