#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

#define BRANCH_MAX 0x03
#define TIME_MAX   0x0FFFF


static
unsigned int ON_VersionNumberTime(
  unsigned int year,
  unsigned int month,
  unsigned int day_of_month
  )
{
  unsigned int time;
  unsigned int day_of_year;
  
  day_of_year = (year < 2100 && year >= 2000)
    ? ON_DayOfGregorianYear(year,month,day_of_month)
    : 0;

  if ( day_of_year > 0 )
  {
    time = (year - 2000)*367 + day_of_year;
  }
  else
  {
    time = 0;
  }

  return time;
}

static
bool ON_VersionNumberTimeDeconstruct(
  unsigned int time,
  unsigned int* time_year,
  unsigned int* time_month,
  unsigned int* time_day_of_month
  )
{
  unsigned int year = 0;
  unsigned int month = 0;
  unsigned int day_of_month = 0;
  unsigned int u = time;

  if (u > 0)
  {
    unsigned int day_of_year = (u%367);
    u /= 367;

    if (day_of_year > 0 && u < 100)
    {
      year = u + 2000;
      if ( !ON_GetGregorianMonthAndDayOfMonth(year,day_of_year,&month,&day_of_month) )
      {
        year = 0;
        month = 0;
        day_of_month = 0;
      }
    }
  }

  if ( 0!= time_year)
    *time_year = year;
  if ( 0!= time_month)
    *time_month = month;
  if ( 0!= time_day_of_month)
    *time_day_of_month = day_of_month;

  return (0 != month);
}

unsigned int ON_VersionNumberConstruct(
  unsigned int major_version,
  unsigned int minor_version,
  unsigned int year,
  unsigned int month,
  unsigned int day_of_month,
  unsigned int platform_branch
  )
{
  unsigned int version_number = 0;
  unsigned int version_time = ON_VersionNumberTime(year,month,day_of_month);

  if (version_time > TIME_MAX)
    version_time = TIME_MAX;

  // platform
  //   0 = developer
  //   1 = Windows Commercial/Beta/WIP
  //   2 = Mac OS Commercial/Beta/WIP
  unsigned int platform
    = (platform_branch > 0)
    ? (2 - (platform_branch % 2) )
    : 0;
  if ( platform > BRANCH_MAX )
    platform = 0; // all invalid branch numbers get mapped to "developer"
  if ( minor_version > ON::VersionMinorMaximum())
    minor_version = ON::VersionMinorMaximum();
  if ( major_version > ON::VersionMajorMaximum())
    major_version = ON::VersionMajorMaximum();
  version_number = ((major_version*(ON::VersionMinorMaximum()+1) + minor_version)*(TIME_MAX+1) + version_time)*(BRANCH_MAX+1) + platform;
  version_number |= 0x80000000;
  return version_number;
}

unsigned int ON_VersionNumberFromYearMonthDateFormat(
  unsigned int version_number_major,
  unsigned int YYYYMMDDn_version_number
  )
{
  unsigned int version_number = 0;
  if ( ON_VersionNumberIsYearMonthDateFormat(version_number_major,YYYYMMDDn_version_number) )
  {
    // convert old format to new format
    unsigned int n = (YYYYMMDDn_version_number % 10);
    unsigned int day_of_month = (YYYYMMDDn_version_number/10) % 100;
    unsigned int month = (YYYYMMDDn_version_number/1000) % 100;
    unsigned int year = (YYYYMMDDn_version_number/100000);

    unsigned int major_version = (version_number_major > 0 )
                               ? version_number_major
                               : ((n > 0 && n < 9) ? n : 5);
    unsigned int minor_version = 0;
    unsigned int branch = 0; // 0 = developer
    version_number = ON_VersionNumberConstruct(
      major_version,
      minor_version,
      year,
      month,
      day_of_month,
      branch
      );
  }
  return version_number;
}

unsigned int ON_VersionNumberMinimum(unsigned int major_version)
{
  unsigned int version_number_minimum = ON_VersionNumberConstruct(
    major_version,0,
    2000,1,1,
    0
    );
  return version_number_minimum;
}

bool ON_VersionNumberIsYearMonthDateFormat(
  unsigned int archive_3dm_version,
  unsigned int version_number
  )
{
  // Note: 
  //   When new versions of opennurbs save V2,V3,V4 or V5 files,
  //   they have to write old yyyymmddn verison numbers so the
  //   old code will read the file.  In this case, n = 6,7,8,9.
  //   (n=9 meant debug prior to 2014);
  //
  //   200012210 is the earliest known opennurbs version number
  bool rc = false;
  unsigned int min_old_format_year = 2000;
  unsigned int max_old_format_year = 2100;
  if (    version_number >= min_old_format_year*100000 + 1010 
       && version_number < max_old_format_year*100000 
     )
  {
    unsigned int day_of_month = (version_number/10) % 100;
    unsigned int month = (version_number/1000) % 100;
    unsigned int year = (version_number/100000);
    if (    year >= min_old_format_year 
         && year < max_old_format_year
         && month >= 1 && month <= 12
         && day_of_month >= 1 && day_of_month <= ON_DaysInMonthOfGregorianYear(year,month)
       )
    {
      unsigned int version_major = (archive_3dm_version >= 50 && 0 == (archive_3dm_version%10)) ? (archive_3dm_version/10) : archive_3dm_version;
      // valid yyyy-mm-dd - check n
      unsigned int n = version_number%10;

      if (0 == n && 200612060 == version_number)
        n = 5; // bug in 32-bit Rhino 5 SR12 SDK version number (2006 is also a bogus year for V5!)
      else if ( 9 == n && year <= 2016 )
      {
        // 9 was used by debug builds in V2, V3, V4 and V5.
        n = version_major;
      }

      if (    (0 != n && n == version_major)
           || (n <= 4 && year <= 2011)
           || (5==n && year >= 2006)
           || (6==n && year >= 2012)
           || (n>=7 && year >= 2018)
         )
      {
        rc = true;
      }
    }
  }
  return rc;
}




bool ON_VersionNumberIsValid(
  unsigned int version_number
  )
{
  bool rc = ( version_number >= ON_VersionNumberMinimum(1) );
  if (rc)
  {
    unsigned int version_major = 0;
    unsigned int version_minor = 0;
    unsigned int version_branch = 0;
    unsigned int version_year = 0;
    unsigned int version_month = 0;
    unsigned int version_day_of_month = 0;
    rc = ON_VersionNumberParse(
      version_number,
      &version_major,
      &version_minor,
      &version_year,
      &version_month,
      &version_day_of_month,
      &version_branch
      );
    if (rc)
    {
      const unsigned int v = ON_VersionNumberConstruct(version_major,version_minor,version_year,version_month,version_day_of_month,version_branch);
      if ( v != version_number )
        rc = false;
    }
  }

  return rc;
}

bool ON_VersionNumberParse(
  unsigned int version_number,
  unsigned int* version_major,
  unsigned int* version_minor,
  unsigned int* version_year,
  unsigned int* version_month,
  unsigned int* version_day_of_month,
  unsigned int* version_branch
  )
{
  unsigned int u;
  unsigned int version_time;
  unsigned int max;
  bool rc;

  u = version_number;

  if ( ON_VersionNumberIsYearMonthDateFormat(0,u) )
  {
    unsigned int n = u%10;
    u /= 10;
    unsigned int dd = u % 100;
    u /= 100;
    unsigned int mm = u %100;
    u /= 100;
    if (0 == n && 200612060 == version_number)
      n = 5;  // 32-bit Rhino V5 SR12 version number bug
    else if (0 == n)
      n = 2;
    else if ( 9 == n && u < 2017 )
      n = 5; // debug maps to 5
    u = ON_VersionNumberConstruct(n,0,u,mm,dd,0);
  }

  if ( 0 == (0x80000000 & u) )
  {
    // This number was not created by ON_VersionNumberConstruct()
    u = 0;
  }
  else
  {
    // Clear the bit that makes new version numbers
    // created ON_VersionNumberConstruct()
    // bigger than old yyyymmddn version numbers.
    u &= 0x7FFFFFFF;
  }

  max = BRANCH_MAX+1;
  if ( 0 != version_branch )
    *version_branch = u % max;
  u /= max;

  max = TIME_MAX+1;
  version_time = u % max;
  u /= max;

  rc = ON_VersionNumberTimeDeconstruct(
    version_time,
    version_year,
    version_month,
    version_day_of_month);

  max = ON::VersionMinorMaximum()+1;
  if ( 0 != version_minor )
    *version_minor = u % max;
  u /= max;

  if ( 0 != version_major )
    *version_major = u;

  return rc;
}

const ON_String ON_VersionNumberToString(
  unsigned int version_number,
  bool bUnsignedFormat,
  bool bDateFormat
)
{
  unsigned int version_major = 0;
  unsigned int version_minor = 0;
  unsigned int version_year = 0;
  unsigned int version_month = 0;
  unsigned int version_day_of_month = 0;
  unsigned int version_branch = 0;

  if (false == ON_VersionNumberIsValid(version_number))
  {
    bUnsignedFormat = false;
    bDateFormat = false;
  }
  else if (bDateFormat)
  {
    bDateFormat = ON_VersionNumberParse(
      version_number,
      &version_major,
      &version_minor,
      &version_year,
      &version_month,
      &version_day_of_month,
      &version_branch
    );
  }

  ON_String str_version;
  if (bUnsignedFormat)
  {
    str_version = ON_String::FormatToString("%u", version_number);
    if (bDateFormat)
      str_version += " (";
  }

  if (bDateFormat)
  {
    str_version += ON_String::FormatToString(
      "%u.%u %04u-%02u-%02u:%u", 
    version_major,
    version_minor, 
    version_year, 
    version_month, 
    version_day_of_month,
    version_branch
    );
    if (bUnsignedFormat)
      str_version += ")";
  }

  if (str_version.IsEmpty())
  {
    str_version =
      (0 != version_number)
      ? ON_String::FormatToString("0x%08X", version_number)
      : "0";
  }

  return str_version;
}

const ON_wString ON_VersionNumberToWideString(
  unsigned int version_number,
  bool bUnsignedFormat,
  bool bDateFormat
)
{
  return ON_wString(ON_VersionNumberToString(version_number, bUnsignedFormat, bDateFormat));
}

const ON_String ON_SdkVersionNumberToString(
  unsigned int sdk_version_number,
  unsigned int sdk_service_release_number
)
{
  const bool bValidVersionNumber = ON_VersionNumberIsValid(sdk_version_number);
  const bool bValidServiceReleaseNumber = ON_VersionNumberIsValid(sdk_service_release_number);
  ON_String str_version = ON_VersionNumberToString(sdk_version_number, true, false);
  str_version += ".";
  str_version += ON_VersionNumberToString(sdk_service_release_number, true, false);
  if (bValidVersionNumber || bValidServiceReleaseNumber)
  {
    str_version += " (";
    if (bValidVersionNumber != bValidServiceReleaseNumber || sdk_version_number == sdk_service_release_number )
    {
      str_version += ON_VersionNumberToString(bValidVersionNumber ? sdk_version_number : sdk_service_release_number, false, true);
    }
    else
    {
      str_version += ON_VersionNumberToString(sdk_version_number, false, true);
      str_version += ", ";
      str_version += ON_VersionNumberToString(sdk_service_release_number, false, true);
    }
    str_version += ")";
  }
  return str_version;
}

const ON_wString ON_SdkVersionNumberToWideString(
  unsigned int sdk_version_number,
  unsigned int sdk_service_release_number
)
{
  return ON_wString(ON_SdkVersionNumberToString(sdk_version_number, sdk_service_release_number));
}


static
unsigned int AppendValueToString(
  unsigned int u,
  size_t string_buffer_capacity,
  char* string_buffer
  )
{
  unsigned int digit_count = 0;
  unsigned int i;

  if ( string_buffer_capacity > 0 && 0 != string_buffer )
  {
    i = u;
    for(;;)
    {
      digit_count++;
      i /= 10;
      if ( i < 1 )
        break;
    }

    if ( digit_count >= string_buffer_capacity )
      digit_count = 0;
    else
    {
      i = digit_count;
      string_buffer[i] = 0;
      for (;;)
      {
        i--;
        string_buffer[i] = (u % 10) + '0';
        u /= 10;
        if ( u < 1 )
          break;
      }
    }
  }

  return digit_count;
}

unsigned int ON_GetVersionQuartet(
  unsigned int major_version,
  unsigned int minor_version,
  unsigned int year,
  unsigned int month,
  unsigned int day_of_month,
  unsigned int hour,
  unsigned int minute,
  unsigned int branch,
  unsigned short quartet_values[4]
  )
{
  unsigned int i;
  unsigned int item_value;
  unsigned int version_number = ON_VersionNumberConstruct(
                                      major_version,
                                      minor_version,
                                      year,
                                      month,
                                      day_of_month,
                                      branch
                                      );

  if ( hour >= 24 || minute >= 60 )
  {
    if ( 24 == hour && 0 == minute )
    {
      // leap second
      hour = 23;
      minute = 60;
    }
    if ( 23 != hour || 60 != minute )
    {
      hour = 0;
      minute = 0;
    }
  }

  for(i = 0; i < 4; i++ )
  {
    switch(i)
    {
    case 0:
      item_value = major_version;
      break;
    case 1:
      item_value = minor_version;
      break;
    case 2:
      item_value = (year-2000)*1000 + ON_DayOfGregorianYear(year,month,day_of_month);
      break;
    case 3:
      item_value = (hour*100 + minute)*10 + branch;
      break;
    default:
      continue;
      break;
    }

    if ( item_value > 0xFFFF )
    {
      version_number = 0;
      item_value = 0;
    }

    quartet_values[i] = (unsigned short)item_value;
  }

  return version_number;
}

unsigned int ON_GetVersionString(
  unsigned int major_version,
  unsigned int minor_version,
  unsigned int year,
  unsigned int month,
  unsigned int day_of_month,
  unsigned int hour,
  unsigned int minute,
  unsigned int branch,
  size_t string_buffer_capacity,
  char* string_buffer
  )
{
  char s[24] = {0};
  const size_t s_capacity = sizeof(s)/sizeof(s[0]);
  unsigned int string_length = 0;
  unsigned int i, j;
  unsigned short quartet_values[4] = {0};
  unsigned int version_number = ON_GetVersionQuartet(
                                      major_version,
                                      minor_version,
                                      year,
                                      month,
                                      day_of_month,
                                      hour,
                                      minute,
                                      branch,
                                      quartet_values
                                      );

  if (string_buffer_capacity <= 0 || nullptr == string_buffer)
  {
    string_buffer_capacity = s_capacity;
    string_buffer = s;
  }

  if ( version_number > 0 )
  {
    for(i = 0; i < 4 && string_length < string_buffer_capacity; i++ )
    {
      if ( i > 0 )
        string_buffer[string_length++] = '.';

      j = AppendValueToString(
        quartet_values[i],
        string_buffer_capacity - string_length,
        string_buffer + string_length
        );

      if ( j <= 0 )
      {
        string_length = 0;
        break;
      }

      string_length += j;

    }
  }

  if ( string_length > string_buffer_capacity )
    string_length = 0;

  for ( i = string_length; i < string_buffer_capacity; i++ )
    string_buffer[i] = 0;

  return string_length;
}

unsigned int ON_GetVersionWideString(
  unsigned int major_version,
  unsigned int minor_version,
  unsigned int year,
  unsigned int month,
  unsigned int day_of_month,
  unsigned int hour,
  unsigned int minute,
  unsigned int branch,
  size_t string_buffer_capacity,
  wchar_t* string_buffer
  )
{
  char s[24] = {0};
  const size_t s_capacity = sizeof(s)/sizeof(s[0]);
  unsigned int string_length;
  unsigned int i;

  string_length = ON_GetVersionString(
                    major_version,
                    minor_version,
                    year,
                    month,
                    day_of_month,
                    hour,
                    minute,
                    branch,
                    s_capacity, s
                    );
  
  if (string_buffer_capacity > 0 && nullptr != string_buffer)
  {
    if (((size_t)string_length) <= string_buffer_capacity)
    {
      for (i = 0; i < string_length && 0 != s[i]; i++)
      {
        string_buffer[i] = s[i];
      }
    }
    else
    {
      string_length = 0;
      i = 0;
    }

    while (i < string_buffer_capacity)
      string_buffer[i++] = 0;
  }
  
  return string_length;
}






