/*
 * AXFChrono.cpp
 */

#include <iomanip>
#include <string.h>
#include <stdlib.h>

#include "AXFChrono.hpp"

#ifdef	UNUSE_CHRONO	// baba Chronoは時間・Tick変換関数のみ使用
#include "FreeRTOS.h"

AXFChrono::AXFChrono() {
}

AXFChrono::~AXFChrono() {
}

ax::INT32_t AXFChrono::convertNsecToTick(ax::INT32_t nsec) {
  if ((nsec < 0) || (999999999 < nsec)) {
    return -1;
  }

  ax::INT32_t tick = 0;
  if (nsec == 0) {
    return tick;
  }
	
  // ナノ秒(1〜999999999)をtickに変換する
	// 精度はTICKの周波数により変化する
  // 四捨五入して TICKの周波数(1 sec)となる場合のみ、
  // 最大値 TICKの周波数-1 に変換する
	// ns * Hz / 1000000000 (tick = ns / 1000000000[ns->s] * Hz)
	tick = (INT32_t)((double)nsec * (double)configTICK_RATE_HZ / 1000000000.0 + 0.5);
  if (configTICK_RATE_HZ <= tick) {
    tick = configTICK_RATE_HZ - 1;
  }
  return tick;
}

ax::INT32_t AXFChrono::convertTickToNsec(ax::INT32_t tick) {
  if ((tick < 0) || (configTICK_RATE_HZ <= tick)) {
    return -1;
  }

  ax::INT32_t nsec = 0;
  if (tick == 0) {
    return nsec;
  }

  // tickをナノ秒(1〜999999999)に変換する
	// 精度はTICKの周波数により変化する
	// tick * 1000000000 / Hz (ns = tick / Hz * 1000000000[s->ns])
  ax::UINT64_t tonsec = tick;
	nsec = tonsec * 1000000000 / configTICK_RATE_HZ;
  return nsec;
}
#else	/* UNUSE_CHRONO */
const ax::INT32_t BASE_YEAR      = 1985; // 起源となる年
const ax::INT32_t BASE_MONTH     = 1;    // 起源となる月
const ax::INT32_t BASE_DAY       = 1;    // 起源となる日

const ax::INT32_t DATE_TIME_LEN  = 25;   // 日時文字列の長さ(yyyy/MM/dd/hh/mm/ss/ttttt)※区切り文字含む

// 分割開始位置
const ax::INT32_t YEAR_GET_POS   = 0;
const ax::INT32_t MONTH_GET_POS  = 5;
const ax::INT32_t DAY_GET_POS    = 8;
const ax::INT32_t HOUR_GET_POS   = 11;
const ax::INT32_t MINUTE_GET_POS = 14;
const ax::INT32_t SEC_GET_POS    = 17;
const ax::INT32_t TICK_GET_POS   = 20;

// 分割する文字数
const ax::INT32_t YEAR_GET_NUM   = 4;
const ax::INT32_t MONTH_GET_NUM  = 2;
const ax::INT32_t DAY_GET_NUM    = 2;
const ax::INT32_t HOUR_GET_NUM   = 2;
const ax::INT32_t MINUTE_GET_NUM = 2;
const ax::INT32_t SEC_GET_NUM    = 2;
const ax::INT32_t TICK_GET_NUM   = 5;

const ax::INT32_t TICK_PER_SEC   = 60000;  // 1秒 = 60000tick

#define SIGNED_INT_MIN(type)	( ((type)1) << (sizeof(type) * 8 - 1) )
#define SIGNED_INT_MAX(type)	( ~SIGNED_INT_MIN(type) )

AXFChrono::AXFChrono()
    : m_sec(0),
      m_tick(0) {
}

AXFChrono::AXFChrono(INT64_t sec, INT32_t tick)
    : m_sec(0),
      m_tick(0) {
  set(sec, tick);
}

AXFChrono::AXFChrono(TimeSpec time)
    : m_sec(0),
      m_tick(0) {
  set(time);
}

AXFChrono::~AXFChrono() {
}

AXFChrono::ChronoStatus AXFChrono::set(std::string& date) {
  ChronoStatus ret = CHRONO_ERROR;

  if ((ax::INT32_t) date.length() != DATE_TIME_LEN) {
    return ret;
  }

  // yyyy/MM/dd/hh/mm/ss/ttttt の形式から年月日時分秒を取得する
  ax::INT32_t year, month, day, hour, minute, sec, tick;
  year = convertStringToInt(date.substr(YEAR_GET_POS, YEAR_GET_NUM));
  if (year < BASE_YEAR) {
    return ret;
  }

  month = convertStringToInt(date.substr(MONTH_GET_POS, MONTH_GET_NUM));
  if (month < 1 || 12 < month) {
    return ret;
  }

  day = convertStringToInt(date.substr(DAY_GET_POS, DAY_GET_NUM));
  if (day < 1 || getMaxDayOfMonth(year, month) < day) {
    return ret;
  }

  hour = convertStringToInt(date.substr(HOUR_GET_POS, HOUR_GET_NUM));
  if (hour < 0 || 23 < hour) {
    return ret;
  }

  minute = convertStringToInt(date.substr(MINUTE_GET_POS, MINUTE_GET_NUM));
  if (minute < 0 || 59 < minute) {
    return ret;
  }

  sec = convertStringToInt(date.substr(SEC_GET_POS, SEC_GET_NUM));
  if (sec < 0 || 59 < sec) {
    return ret;
  }

  tick = convertStringToInt(date.substr(TICK_GET_POS, TICK_GET_NUM));
  if (tick < 0 || TICK_PER_SEC <= tick) {
    return ret;
  }

  INT64_t sec64 = convertDateToSec(year, month, day, hour, minute, sec);
  if (sec64 == -1) {
    return ret;
  }

  set(sec64, tick);
  return CHRONO_SUCCESS;
}

AXFChrono::ChronoStatus AXFChrono::setSystemTime() {
  if (setTime(m_sec, convertTickToNsec(m_tick)) != 0) {
    return CHRONO_ERROR;
  }
  return CHRONO_SUCCESS;
}

AXFChrono::ChronoStatus AXFChrono::getSystemTime() {
  ax::INT64_t sec;
  ax::INT32_t nsec;
  getTime(&sec, &nsec);

  if (sec == 0 && nsec == 0) {
    // error ?
    // return CHRONO_ERROR;
  }

  set(sec, convertNsecToTick(nsec));
  return CHRONO_SUCCESS;
}

void AXFChrono::setCounter() {
  getSystemTime();
  // エラーは返さない
}

void AXFChrono::setCounter(TimeSpec time) {
  set(time);
}

void AXFChrono::set(TimeSpec time) {
  set(time.sec, time.tick);
}

void AXFChrono::set(INT64_t sec, INT32_t tick) {
  set((INT64_t)tick);
  m_sec += sec;
}

void AXFChrono::set(INT64_t tick) {
  if (TICK_PER_SEC <= tick) {
    m_sec = tick / TICK_PER_SEC;
    m_tick = tick % TICK_PER_SEC;
  } else if (tick < 0) {
    m_sec = -( ( -(tick + 1) ) / TICK_PER_SEC ) - 1;
    m_tick = (TICK_PER_SEC - 1) - ( -(tick + 1) ) % TICK_PER_SEC;
  } else {
    m_sec = 0;
    m_tick = tick;
  }
}

void AXFChrono::upCounter(ax::INT64_t tick) {
  // 加算分のtickが負の場合を記録
  bool nega = tick < 0;

  AXFChrono add;
  add.set(tick);

  // 加算前の状態を記録
  AXFChrono bak = *this;

  // 加算
  *this = *this + add;

  // 加算した結果、飽和したかどうか
  bool satu = *this < bak;
  if (nega) {
    // 加算分が負の場合
    satu = !satu;
  }

  if (!satu) {
    // 飽和なし
    return;
  }

  if (nega) {
    // 飽和したら最小値を設定する
    set(SIGNED_INT_MIN(INT64_t), 0);
  } else {
    // 飽和したら最大値を設定する
    set(SIGNED_INT_MAX(INT64_t), TICK_PER_SEC - 1);
  }
}

void AXFChrono::downCounter(ax::INT64_t tick) {

  if (tick == SIGNED_INT_MIN(INT64_t)) {
    // 64ビット整数の最小値の場合
    upCounter(SIGNED_INT_MAX(INT64_t));
    upCounter(1);
    return;
  }

  upCounter(-tick);
}

void AXFChrono::get(INT64_t* ret_sec, INT32_t* ret_tick) {
  if (ret_sec) {
    *ret_sec = m_sec;
  }
  if (ret_tick) {
    *ret_tick = m_tick;
  }
}

AXFChrono::TimeSpec AXFChrono::getCounter() {
  TimeSpec time;
  get(&time.sec, &time.tick);
  return time;
}

INT64_t AXFChrono::getTick() {
  TimeSpec time = getCounter();
  return (time.sec * TICK_PER_SEC + time.tick);
}

std::string AXFChrono::getString() {
  std::stringstream date;
  std::stringstream time;
  std::string strtime = "";

  ax::INT32_t year, mon, day, hour, min, sec;

  calcDate(&year, &mon, &day);
  calcTime(&hour, &min, &sec);

  date << std::setw(4) << std::setfill('0') << year << "/" << std::setw(2)
       << std::setfill('0') << mon << "/" << std::setw(2) << std::setfill('0')
       << day;
  time << std::setw(2) << std::setfill('0') << hour << ":" << std::setw(2)
       << std::setfill('0') << min << ":" << std::setw(2) << std::setfill('0')
       << sec << "." << std::setw(5) << std::setfill('0') << m_tick;
  strtime = date.str() + " " + time.str();
  return strtime;
}

bool AXFChrono::operator ==(const AXFChrono& chrono) const {
  return m_sec == chrono.m_sec && m_tick == chrono.m_tick;
}

bool AXFChrono::operator !=(const AXFChrono& chrono) const {
  return !operator==(chrono);
}

bool AXFChrono::operator <(const AXFChrono& chrono) const {
  return m_sec < chrono.m_sec
    || (m_sec == chrono.m_sec && m_tick < chrono.m_tick);
}

bool AXFChrono::operator <=(const AXFChrono& chrono) const {
  return operator<(chrono) || operator==(chrono);
}

bool AXFChrono::operator >(const AXFChrono& chrono) const {
  return !operator<=(chrono);
}

bool AXFChrono::operator >=(const AXFChrono& chrono) const {
  return !operator<(chrono);
}

AXFChrono AXFChrono::operator -(const AXFChrono& chrono) const {
  INT64_t sec = m_sec - chrono.m_sec;
  INT32_t tick = m_tick - chrono.m_tick;

  if (tick < 0) {
    sec--;
    tick += TICK_PER_SEC;
  }

  return AXFChrono(sec, tick);
}

AXFChrono AXFChrono::operator +(const AXFChrono& chrono) const {
  INT64_t sec = m_sec + chrono.m_sec;
  INT32_t tick = m_tick + chrono.m_tick;

  if (tick >= TICK_PER_SEC) {
    sec++;
    tick -= TICK_PER_SEC;
  }

  return AXFChrono(sec, tick);
}

ax::INT32_t AXFChrono::convertNsecToTick(ax::INT32_t nsec) {
  if ((nsec < 0) || (999999999 < nsec)) {
    return -1;
  }

  ax::INT32_t tick = 0;
  if (nsec == 0) {
    return tick;
  }

  // ナノ秒(1〜999999999)をtick(1〜59999)に変換する
  // 変換誤差を少なくするため小数点第一位で四捨五入する
  // 999983333 ナノ秒を超える値を指定し、
  // 四捨五入して 60000 tick(1 sec)となる場合のみ、
  // 最大値 59999 tick に変換する
  tick = ((double) nsec * 6 / 100000) + 0.5;
  if (TICK_PER_SEC <= tick) {
    tick -= 1;
  }
  return tick;
}

ax::INT32_t AXFChrono::convertTickToNsec(ax::INT32_t tick) {
  if ((tick < 0) || (TICK_PER_SEC <= tick)) {
    return -1;
  }

  ax::INT32_t nsec = 0;
  if (tick == 0) {
    return nsec;
  }

  // tick(1〜59999)をナノ秒(1〜999999999)に変換する
  // 1 tick 16666.66… nsec として変換を行い、四捨五入しない
  // 計算途中で値が大きくなる(最大5999900000)ため、
  // 算出時 UINT64_t 変数へ格納している
  // 変換の最大値は(999983333)となる
  ax::UINT64_t tonsec = tick;
  nsec = tonsec * 100000 / 6;
  return nsec;
}

void AXFChrono::calcDate(ax::INT32_t* ret_year, ax::INT32_t* ret_mon, ax::INT32_t* ret_day) {
  ax::INT32_t year = BASE_YEAR;
  ax::INT32_t mon = BASE_MONTH;
  ax::INT32_t day = BASE_DAY;

  if (m_sec > 0) {
    ax::INT32_t addDay = (m_sec / 3600) / 24;  // 1日 = 3600秒 * 24時間
    ax::INT32_t maxday = getMaxDayOfMonth(year, mon);

    day += addDay;
    while (maxday < day) {
      day -= maxday;
      mon++;
      if (12 < mon) {
	mon = 1;
	year++;
      }
      maxday = getMaxDayOfMonth(year, mon);
    }
  }

  if (ret_year) {
    *ret_year = year;
  }
  if (ret_mon) {
    *ret_mon = mon;
  }
  if (ret_day) {
    *ret_day = day;
  }
}

void AXFChrono::calcTime(ax::INT32_t* ret_hour, ax::INT32_t* ret_min, ax::INT32_t* ret_sec) {
  ax::INT32_t hour = 0;
  ax::INT32_t min = 0;
  ax::INT32_t sec = 0;

  if (m_sec > 0) {
    hour = (m_sec / 3600) % 24;  // 1時間 = 3600秒
    min = (m_sec / 60) % 60;    // 1分   = 60秒
    sec = m_sec % 60;
  }

  if (ret_hour) {
    *ret_hour = hour;
  }
  if (ret_min) {
    *ret_min = min;
  }
  if (ret_sec) {
    *ret_sec = sec;
  }
}

ax::INT32_t AXFChrono::getMaxDayOfMonth(ax::INT32_t year, ax::INT32_t month) {
  ax::INT32_t maxday = 0;
  switch (month) {
    case 2:   // 2月 は 28日か29日(閏年)まで
      // 西暦が4で割り切れる年は閏年
      // ただし、4で割りきれても100で割り切れる年は閏年ではない
      // ただし、100で割り切れても400で割り切れる年は閏年
      if ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))) {
        maxday = 29; /* 閏年 */
      } else {
        maxday = 28;
      }
      break;
    case 4:   // 4月 は30日まで
    case 6:   // 6月 は30日まで
    case 9:   // 9月 は30日まで
    case 11:  // 11月 は30日まで
      maxday = 30;
      break;
    default:  // 1,3,5,7,8,10,12月 は31日まで
      maxday = 31;
      break;
  }
  return maxday;
}

ax::INT32_t AXFChrono::convertStringToInt(std::string date) {
  ax::INT32_t ret = -1;

  if (isInteger(date)) {
    ret = atoi(date.c_str());
  }
  return ret;
}

bool AXFChrono::isInteger(std::string& date) {
  if (date.find_first_not_of("0123456789") != std::string::npos) {
    return false;
  }
  return true;
}
#endif	/* UNUSE_CHRONO */
