/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2013 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#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

#include "opennurbs_parse.h"


ON_LengthValue::StringFormat ON_LengthValue::LengthStringFormatFromUnsigned(
  unsigned int string_format_as_unsigned
)
{
  switch (string_format_as_unsigned)
  {
    ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::ExactDecimal);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::ExactProperFraction);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::ExactImproperFraction);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::CleanDecimal);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::CleanProperFraction);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_LengthValue::StringFormat::CleanImproperFraction);
  }
  ON_ERROR("Invalid string_format_as_unsigned value.");
  return (ON_LengthValue::Unset.m_string_format);
}


ON_AngleValue::StringFormat ON_AngleValue::AngleStringFormatFromUnsigned(
  unsigned int string_format_as_unsigned
)
{
  switch (string_format_as_unsigned)
  {
    ON_ENUM_FROM_UNSIGNED_CASE(ON_AngleValue::StringFormat::ExactDecimal);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_AngleValue::StringFormat::ExactFraction);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_AngleValue::StringFormat::CleanDecimal);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_AngleValue::StringFormat::CleanFraction);
  }
  ON_ERROR("Invalid string_format_as_unsigned value.");
  return (ON_AngleValue::Unset.m_string_format);
}

const ON_LengthValue ON_LengthValue::RemoveUnitSystem() const
{
  if (
    ON::LengthUnitSystem::Unset == this->LengthUnitSystem().UnitSystem()
    || ON::LengthUnitSystem::None == this->LengthUnitSystem().UnitSystem()
    )
  {
    return *this;
  }

  ON_LengthValue rc(*this);
  rc.m_length_unit_system = ON_UnitSystem::None;

  const ON_ParseSettings parse_settings = rc.LengthStringParseSettings();
  const wchar_t* str = static_cast<const wchar_t*>(rc.m_length_as_string);
  const int str_count = rc.m_length_as_string.Length();
  ON_ParseSettings parse_results;
  double x = ON_DBL_QNAN;
  int str_index = ON_ParseNumberExpression(str, str_count, parse_settings, &parse_results, &x);
  if (str_index > 0 && str_index <= str_count && x == m_length)
  {
    rc.m_length_as_string.SetLength(str_index);
    rc.m_length_as_string.TrimLeftAndRight();
  }
  else
  {
    rc = ON_LengthValue::Create(m_length, ON_LengthUnitName::None, m_string_format);
    if (!(rc.m_length == m_length))
    {
      ON_ERROR("Unable to remove unit system");
      return *this;
    }
  }
  return rc;
}

const ON_LengthValue ON_LengthValue::ChangeLength(
  double length_value
) const
{
  ON_LengthValue rc(*this);
  if (!ON_IsValid(length_value))
  {
    rc.m_length_as_string = ON_wString::EmptyString;
    rc.m_length = ON_LengthValue::Unset.m_length;
  }
  else
  {
    rc = ON_LengthValue::Create(length_value, m_length_unit_system, m_context_locale_id, m_string_format);
  }
  return rc;
}


ON_LengthValue ON_LengthValue::CreateFromString(
  ON_ParseSettings parse_settings,
  const wchar_t* string
)
{
  if ( nullptr == string || 0 == string[0] )
    return ON_LengthValue::Unset;

  const wchar_t* string_end = nullptr;
  const ON_LengthValue length_value = ON_LengthValue::CreateFromSubString(parse_settings, string, -1, &string_end);
  if (nullptr == string_end || !(string < string_end))
  {
    // Calling ON_ERROR here causes .NET trauma when ON_LengthValue::CreateFromString() is used for 
    // input validation.
    //ON_ERROR("Invalid input parameters.");
    return ON_LengthValue::Unset;
  }

  return length_value;
}

ON_LengthValue ON_LengthValue::CreateFromSubString(
    ON_ParseSettings parse_settings,
    const wchar_t* string,
    int string_count,
    const wchar_t** string_end
  )
{
  // All calls to some version of ON_LengthValue::CreateFrom*String(...) end up here.
  if (nullptr != string_end && &string != string_end)
    *string_end = string;

  if (string_count < -1)
  {
    ON_ERROR("Invalid string_count parameter.");
    return ON_LengthValue::Unset;
  }

  if (nullptr == string || 0 == string_count || 0 == string[0] )
  {
    // empty string fails silently.
    return ON_LengthValue::Unset;
  }

  double length_value = ON_DBL_QNAN;
  ON_ParseSettings parse_results;
  ON::LengthUnitSystem string_length_unit_system = ON::LengthUnitSystem::Unset;
  const int parse_count = ON_ParseLengthExpression(
    string,
    string_count,
    parse_settings, 
    &length_value,
    &parse_results,
    &string_length_unit_system
  );

  if ( parse_count <= 0 
    || (parse_count > string_count && string_count >= 0)
    || false == (length_value == length_value) 
    ) 
  {
    // Calling ON_ERROR HERE .NET wrapper trauma when CreateFromSubString() is used for string validation.
    // ON_ERROR("Input string parameter is not valid.");
    return ON_LengthValue::Unset;
  }

  ON_LengthValue rc;

  rc.m_length = length_value;

  if (ON::LengthUnitSystem::Unset != string_length_unit_system && ON::LengthUnitSystem::None != string_length_unit_system)
    rc.m_length_unit_system = ON_UnitSystem(string_length_unit_system);
  else
    rc.m_length_unit_system = ON_UnitSystem(parse_settings.ContextLengthUnitSystem());

  rc.m_context_angle_unit_system = parse_settings.ContextAngleUnitSystem();
  rc.m_context_locale_id = parse_settings.ContextLocaleId();

  rc.m_length_as_string = ON_wString(string, parse_count);
  rc.m_length_as_string.TrimLeftAndRight();

  if (nullptr != string_end)
    *string_end = string + parse_count;

  return rc;
}

static 
double ON_CleanValueTolerance(
  double value,
  double clean_value_tolerance
)
{
  if (clean_value_tolerance > 1.0 / 512.0)
    clean_value_tolerance = 1.0 / 512.0;
  const double a = fabs(value);
  // The relative tolerance 256.0*ON_EPSILON*a is reqired in order for all (1+/- ON_EPSILON)*value where
  // value is one of the rational numbers with denominators
  // 2,3,4,8,10,16,32,64,128 and numerators from 1 to 2*denominator to be "cleaned up}
  // to numerator/denominator.
  const double relative_tolerance = 256.0*ON_EPSILON*a;
  return (clean_value_tolerance > relative_tolerance) ? clean_value_tolerance : relative_tolerance;
}

static
double ON_CleanNumber(
  double value,
  double clean_value_tolerance
)
{
  if (!ON_IsValid(value))
    return value;

  const double tol = ON_CleanValueTolerance(value, clean_value_tolerance);
  if (fabs(value) <= tol)
    return 0.0;

  const double s = (value < 0.0) ? -1.0 : 1.0;
  const double a = fabs(value);
  double i = 0.0;
  const double f = modf(a, &i);
  if (f <= tol)
    return s*i;
  if ((1.0-f) <= tol)
    return s*(i+1.0);

  // terminate these lists with 0.0.
  const double d1[] = { 2.0, 3.0, 4.0, 8.0, 10.0, 16.0, 32.0, 64.0, 128.0, 0.0 };
  const double d2[] = { 5.0, 6.0, 7.0, 9.0, 12.0, 0.0 };
  const double* d[2] = { d1, d2 };
  for (int pass = 0; pass < 2; pass++)
  {
    for (size_t k = 0; d[pass][k] > 0.0; k++)
    {
      const double y = d[pass][k] * f;
      double x = floor(y);
      if (y - x > 0.5)
        x += 1.0;
      if (fabs(x - y) <= tol)
      {
        // (i*d[pass][k] + x) / d[pass][k] is more precise (repeatable)
        // than i + x/d[pass][k]
        return s*(i*d[pass][k] + x) / d[pass][k];
      }
    }
  }

  return value;
}


static
const ON_wString ON_NTimesPowerOf10AsString(
  double value,
  double tol,
  const ON_wString& g_format,
  ON__UINT64 n,
  int e,
  double* clean_value
)
{
  if (nullptr != clean_value)
    *clean_value = value;

  // returns n*(10^e) as a pretty string.  
  //const ON__UINT64 ten = 10;

  ON__UINT64 q = 1;
  ON__UINT64 i = 0;
  ON__UINT64 f = 0;


  if (e >= 0)
  {
    for (int ie = 0; ie < e; ie++)
      q *= 10;
    i = n*q;
    f = 0;
  }
  else
  {
    // e is negative
    for (int ie = 0; ie > e; ie--)
      q *= 10;
    i = n / q;
    f = n % q;
  }

  const double x = ((double)i) + ((double)f) / ((double)q);
  if (fabs(x - value) <= tol)
  {
    if (0 == f)
    {
      if (nullptr != clean_value)
        *clean_value = x;
      return ON_wString::FormatToString(L"%llu", i);
    }
    wchar_t sf[32] = { 0 };
    size_t sf_capacity = sizeof(sf) / sizeof(sf[i]);
    size_t sfi = 0;
    for (ON__UINT64 r = q / 10; r > 0 && sfi < sf_capacity; r /= 10)
    {
      sf[sfi++] = (wchar_t)(int((f / r)%10) + '0');
    }
    const ON_wString value_as_string = ON_wString::FormatToString(L"%llu.%ls", i, sf);
    double y = ON_DBL_QNAN;
    const int scan_count = value_as_string.Scan( L"%lf", &y );
    if (1 == scan_count && fabs(y - value) <= tol)
    {
      if (nullptr != clean_value)
        *clean_value = y;
      return value_as_string;
    }
  }

  ON_ERROR("Unexpected result.");
  return g_format;
}


static
const ON_wString ON_CleanNumberToString(
  double value,
  double clean_value_tolerance,
  double* clean_value
)
{
  if (nullptr != clean_value)
    *clean_value = value;
  if (!ON_IsValid(value))
    return ON_wString::EmptyString;

  // replace any -0.0 value with 0.0
  if (0.0 == value)
  {
    value = 0.0;
    if (nullptr != clean_value)
      *clean_value = value;
  }

  const double tol = ON_CleanValueTolerance(value, clean_value_tolerance);
  if (fabs(value) <= tol)
  {
    if (nullptr != clean_value)
      *clean_value = 0.0;
    return ON_wString(L"0");
  }

  const ON_wString g_format = ON_wString::FormatToString(L"%.17g", value);
  const ON_wString e_format = ON_wString::FormatToString(L"%.17e", value);
  const wchar_t* s0 = static_cast<const wchar_t*>(e_format);
  if ('-' == s0[0] || '+' == s0[0])
    s0++;
  if ( s0[0] < '0' || s0[0] > '9')
  {
    ON_ERROR("Unexpected double string format.");
    return g_format;
  }

  if ('.' != s0[1])
  {
    return g_format;
  }
  
  const wchar_t* decimal = s0;
  while (0 != *decimal && '.' != *decimal)
    decimal++;
  if ( '.' != decimal[0] || decimal[1] < '0' || decimal[1] > '9')
  {
    return g_format;
  }


  const wchar_t* exponent = s0 + 2;
  while (0 != *exponent && 'e' != *exponent && 'E' != *exponent)
    exponent++;
  if (0 == exponent[0])
    return g_format;

  int e = 0;
  if (0 != exponent[0])
  {
    for (int i = ('+' == exponent[1] || '-' == exponent[1]) ? 2 : 1; 0 != exponent[i]; i++)
    {
      if (exponent[i] < '0' || exponent[i] > '9')
      {
        ON_ERROR("Unexpected double string format.");
        return g_format;
      }
      e = 10 * e + ((int)(exponent[i] - '0'));
    }
    if (e > 0 && '-' == exponent[1])
      e = -e;
  }
  
  const unsigned int max_count = 3;
  unsigned int zero_count = 0;
  unsigned int nine_count = 0;
  const wchar_t* first_nine = nullptr;
  const wchar_t* first_zero = nullptr;

  const ON__UINT64 ten = 10;
  ON__UINT64 n = 0;
  e++; // e is unconditionally decremented when n is initialized.

  for ( const wchar_t* s = s0; s < exponent; s++)
  {
    if ('.' == *s)
      continue;
    if ('0' == *s)
    {
      if (nullptr != first_nine)
      {
        while (first_nine < s)
        {
          n = ten*n + ((ON__UINT64)(*first_nine++ - '0'));
          e--;
        }
        first_nine = nullptr;
        nine_count = 0;
      }
      if (nullptr == first_zero)
        first_zero = s;
      zero_count++;
      if (max_count == zero_count)
      {
        double x = ((double)n)*pow(10.0, e);
        if (fabs(x - value) <= tol)
        {
          return ON_NTimesPowerOf10AsString(value, tol, g_format, n, e, clean_value);
        }
      }
    }
    else if ('9' == *s)
    {
      if (nullptr != first_zero)
      {
        while (first_zero < s)
        {
          n = ten*n + ((ON__UINT64)(*first_zero++ - '0'));
          e--;
        }
        first_zero = nullptr;
        zero_count = 0;
      }
      if (nullptr == first_nine)
        first_nine = s;
      nine_count++;
      if (max_count == nine_count)
      {
        double x = ((double)(n+1))*pow(10.0, e);
        if (fabs(x - value) <= tol)
        {
          return ON_NTimesPowerOf10AsString(value, tol, g_format, n+1, e, clean_value);
        }
        nine_count--;
        n = ten*n + ((ON__UINT64)(*first_nine++ - '0'));
        e--;
      }
    }
    else
    {
      n = ten*n + ((ON__UINT64)(*s - '0'));
      e--;
    }
  }

  return g_format;
}

/*
Parameters:
  value - [in]
    value to test
  
  bImproperFraction - [in]
    True if the returned value can be an improper fraction 
    (1.25 will be 5/4, proper = 0, numerator = 5, denominator = 4).
    False if the returned value should be a proper fraction 
    (1.25 will be 1-1/4, proper = 1, numerator = 1, denominator = 4).

  sign - [out]
    +1.0, -1.0 or 0.0.



Returns:
  True: 
    value is a rational number with a denominator commonly used in annotation.
    value = sign*(proper + numerator/denominator);
  False:
    value is not a rational number with a denominator commonly used in annotation.
*/
static
bool ON_IsAnnotationFractionNumber(
  double value,
  bool bImproperFraction,
  double* sign,
  double* proper,
  double* numerator,
  double* denominator
)
{
  if (nullptr != sign)
    *sign = (value < 0.0) ? -1.0 : ((value > 0.0) ? 1.0 : 0.0);
  if (nullptr != proper)
    *proper = 0.0;
  if (nullptr != numerator)
    *numerator = value;
  if (nullptr != denominator)
    *denominator = 1.0;
  if (!ON_IsValid(value))
    return false;

  const double a = fabs(value);
  double i = 0.0;
  const double f = modf(a, &i);

  const double tol = 4.0*ON_EPSILON*a;

  if ( !(f > tol) )
    return false;

  // terminate this list with 0.0.
  const double d[] = { 2.0, 3.0, 4.0, 8.0, 10.0, 16.0, 32.0, 64.0, 128.0, 0.0 };
  for (size_t k = 0; d[k] > 0.0; k++)
  {
    const double y = d[k] * f;
    double x = floor(y);
    if (y - x > 0.5)
      x += 1.0;
    if (!(fabs(x - y) <= tol))
      continue;
    if (false == bImproperFraction && i >= 1.0)
    {
      if (nullptr != proper)
        *proper = i;
      i = 0.0;
    }
    if (nullptr != numerator)
      *numerator = i*d[k] + x;
    if (nullptr != denominator)
      *denominator = d[k];
    return true;
  }

  return false;
}

ON_LengthValue ON_LengthValue::Create(
    double length_value,
    const class ON_UnitSystem& length_unit_system,
    unsigned int locale_id,
    ON_LengthValue::StringFormat string_format
    //,    double clean_format_tolerance
)
{
  const double clean_format_tolerance = 0.0; // == ON_ZERO_TOLERANCE
  ON_LengthValue rc;
  rc.m_string_format = string_format;
  for (;;)
  {
    bool bFraction = false;
    bool bImproperFraction = false;
    bool bClean = false;
    switch (string_format)
    {
    case ON_LengthValue::StringFormat::ExactDecimal:
      break;
    case ON_LengthValue::StringFormat::ExactProperFraction:
      bFraction = true;
      break;
    case ON_LengthValue::StringFormat::ExactImproperFraction:
      bFraction = true;
      bImproperFraction = true;
      break;
    case ON_LengthValue::StringFormat::CleanDecimal:
      bClean = true;
      break;
    case ON_LengthValue::StringFormat::CleanProperFraction:
      bClean = true;
      bFraction = true;
      break;
    case ON_LengthValue::StringFormat::CleanImproperFraction:
      bClean = true;
      bFraction = true;
      bImproperFraction = true;
      break;
    default:
      break;
    }
    if (false == (length_value == length_value))
      break;
    if (ON::LengthUnitSystem::Unset == length_unit_system.UnitSystem())
      break;
    rc.m_length 
      = bClean
      ? ON_CleanNumber(length_value,clean_format_tolerance)
      : length_value;
    rc.m_length_unit_system = length_unit_system;
    const ON_LengthUnitName name = ON_LengthUnitName::Create(locale_id, length_unit_system.UnitSystem(), length_value > 1.0);

    // DO NOT USE A "PRETTY" FORMAT!
    // It is critical that this string scan back to rc.m_length exactly.
    double sign = ON_DBL_QNAN;
    double proper = ON_DBL_QNAN;
    double numerator = ON_DBL_QNAN;
    double denominator = ON_DBL_QNAN;
    if (
      bFraction 
      && ON_IsAnnotationFractionNumber(rc.m_length, bImproperFraction, &sign, &proper, &numerator, &denominator)
      )
    {
      // (proper*denominator + numerator)/denominator is more precise (repeatable)
      // than proper + numerator/denominator
      rc.m_length = sign*(proper*denominator + numerator)/denominator;
      if (proper != 0.0)
      {
        if (name.LengthUnitNameIsNotEmpty())
          rc.m_length_as_string.Format(L"%0.17g-%0.17g/%0.17g %ls", sign*proper, numerator, denominator, name.LengthUnitName());
        else
          rc.m_length_as_string.Format(L"%0.17g-%0.17g/%0.17g", sign*proper, numerator, denominator);
      }
      else
      {
        if (name.LengthUnitNameIsNotEmpty())
          rc.m_length_as_string.Format(L"%0.17g/%0.17g %ls", numerator, denominator, name.LengthUnitName());
        else
          rc.m_length_as_string.Format(L"%0.17g/%0.17g", numerator, denominator);
      }
    }
    else
    {
      double clean_length = ON_DBL_QNAN;
      const ON_wString clean_length_as_string = ON_CleanNumberToString(rc.m_length, 0.0, &clean_length);
      if (bClean || clean_length == rc.m_length)
      {
        rc.m_length = clean_length;
        if (name.LengthUnitNameIsNotEmpty())
          rc.m_length_as_string.Format(L"%ls %ls", static_cast<const wchar_t*>(clean_length_as_string), name.LengthUnitName());
        else
          rc.m_length_as_string = clean_length_as_string;
      }
      else if (name.LengthUnitNameIsNotEmpty())
        rc.m_length_as_string.Format(L"%0.17g %ls", rc.m_length, name.LengthUnitName());
      else
        rc.m_length_as_string.Format(L"%0.17g", rc.m_length);
    }

    rc.m_context_angle_unit_system = ON::AngleUnitSystem::Radians;
    rc.m_context_locale_id = name.LocaleId();

    return rc;
  }
  return ON_LengthValue::Unset;
}

ON_LengthValue ON_LengthValue::Create(
  double length_value,
  const ON::LengthUnitSystem length_unit_system,
  unsigned int locale_id,
  ON_LengthValue::StringFormat string_format
  //,  double clean_format_tolerance
)
{
  return ON_LengthValue::Create(
    length_value,
    ON_UnitSystem(length_unit_system),
    locale_id,
    string_format
    //,    clean_format_tolerance
  );
}

ON_LengthValue ON_LengthValue::Create(
  double length_value,
  const ON_LengthUnitName& length_unit_system,
  ON_LengthValue::StringFormat string_format
  //,  double clean_format_tolerance
)
{
  return ON_LengthValue::Create(
    length_value,
    length_unit_system.LengthUnit(),
    length_unit_system.LocaleId(),
    string_format
  );
}

double ON_LengthValue::Length(
  const ON_UnitSystem& context_unit_system
) const
{
  if ( ON::LengthUnitSystem::None == context_unit_system.UnitSystem())
    return m_length;
  if ( 
    m_length_unit_system.MetersPerUnit() == context_unit_system.MetersPerUnit()
    && ON::LengthUnitSystem::Unset != context_unit_system.UnitSystem() 
    )
    return m_length;
  return m_length*ON::UnitScale(m_length_unit_system, context_unit_system);
}


double ON_LengthValue::Length(
  ON::LengthUnitSystem context_unit_system
) const
{
  if ( ON::LengthUnitSystem::None == context_unit_system)
    return m_length;
  if ( m_length_unit_system.UnitSystem() == context_unit_system && ON::LengthUnitSystem::Unset != context_unit_system)
    return m_length;
  return m_length*ON::UnitScale(m_length_unit_system, context_unit_system);
}

const ON_UnitSystem& ON_LengthValue::LengthUnitSystem() const
{
  return m_length_unit_system;
}

const ON_wString& ON_LengthValue::LengthAsString() const
{
  m_length_as_string.IsValid(true);
  return m_length_as_string;
}

const wchar_t* ON_LengthValue::LengthAsStringPointer() const
{
  m_length_as_string.IsValid(true);
  return static_cast<const wchar_t*>(m_length_as_string);
}

bool ON_LengthValue::IsUnset() const
{
  return IsSet() ? false : true;
}

bool ON_LengthValue::IsSet() const
{
  return (
    m_length_unit_system.UnitSystem() != ON::LengthUnitSystem::Unset
    && (ON_UNSET_VALUE < m_length && m_length < ON_UNSET_POSITIVE_VALUE)
    && m_length_as_string.IsNotEmpty()
    );
}

bool ON_LengthValue::Write(
  class ON_BinaryArchive& archive
) const
{
  const int content_version = 1;
  if (!archive.BeginWrite3dmAnonymousChunk(content_version))
    return false;

  bool rc = false;
  for (;;)
  {
    // content version = 0
    if (!archive.WriteDouble(m_length))
      break;
    if (!m_length_unit_system.Write(archive))
      break;
    if (!archive.WriteInt(static_cast<unsigned int>(m_context_angle_unit_system)))
      break;
    if (!archive.WriteInt(m_context_locale_id))
      break;
    m_length_as_string.IsValid(true);
    if (!archive.WriteString(m_length_as_string))
      break;

    // content version 1 added m_string_format
    const unsigned int u = static_cast<unsigned char>(m_string_format);
    if (!archive.WriteInt(u))
      break;

    rc = true;
    break;
  }

  if (!archive.EndWrite3dmChunk())
    rc = false;
  return rc;
}

bool ON_LengthValue::Read(
  class ON_BinaryArchive& archive
)
{
  *this = ON_LengthValue::Unset;

  int content_version = 0;
  if (!archive.BeginRead3dmAnonymousChunk(&content_version))
    return false;

  bool rc = false;
  for (;;)
  {
    if (!archive.ReadDouble(&m_length))
      break;
    if (!m_length_unit_system.Read(archive))
      break;

    unsigned int context_angle_unit_system_as_unsigned = static_cast<unsigned int>(m_context_angle_unit_system);
    if (!archive.ReadInt(&context_angle_unit_system_as_unsigned))
      break;
    m_context_angle_unit_system = ON::AngleUnitSystemFromUnsigned(context_angle_unit_system_as_unsigned);
    if (ON::AngleUnitSystem::None == m_context_angle_unit_system || ON::AngleUnitSystem::Unset == m_context_angle_unit_system)
      m_context_angle_unit_system = ON::AngleUnitSystem::Radians;
    if (!archive.ReadInt(&m_context_locale_id))
      break;

    if (!archive.ReadString(m_length_as_string))
      break;

    if (content_version >= 1)
    {
      // content version 1 added m_string_format
      unsigned int u = static_cast<unsigned char>(m_string_format);
      if (!archive.ReadInt(&u))
        break;
      m_string_format = ON_LengthValue::LengthStringFormatFromUnsigned(u);
    }

    rc = true;
    break;
  }
  if (!archive.EndRead3dmChunk())
    rc = false;
  return rc;
}



ON_AngleValue ON_AngleValue::CreateFromString(
  ON_ParseSettings parse_settings,
  const wchar_t* string
)
{
  if ( nullptr == string || 0 == string[0] )
    return ON_AngleValue::Unset;

  const wchar_t* string_end = nullptr;
  const ON_AngleValue angle_value = ON_AngleValue::CreateFromSubString(parse_settings, string, -1, &string_end);
  if (nullptr == string_end || !(string < string_end))
  {
    ON_ERROR("Invalid input parameters.");
    return ON_AngleValue::Unset;
  }

  return angle_value;
}

ON_AngleValue ON_AngleValue::CreateFromSubString(
  ON_ParseSettings parse_settings,
  const wchar_t* string,
  int string_count,
  const wchar_t** string_end
  )
{
  // All calls to some version of ON_AngleValue::CreateFrom*String(...) end up here.
  if (nullptr != string_end && &string != string_end)
    *string_end = string;

  if (string_count < -1)
  {
    ON_ERROR("Invalid string_count parameter.");
    return ON_AngleValue::Unset;
  }

  if (nullptr == string || 0 == string_count || 0 == string[0] )
  {
    // empty string fails silently.
    return ON_AngleValue::Unset;
  }
  
  double angle_value = ON_DBL_QNAN;
  ON_ParseSettings parse_results;
  ON::AngleUnitSystem string_angle_unit_system = ON::AngleUnitSystem::Unset;
  const int parse_count = ON_ParseAngleExpression(
    string,
    string_count,
    parse_settings, 
    &angle_value,
    &parse_results,
    &string_angle_unit_system
  );

  if ( parse_count <= 0 
    || (parse_count > string_count && string_count >= 0)
    || false == (angle_value == angle_value) 
    ) 
  {
    // non-empty string failure generates debugger error.
    ON_ERROR("Input string parameter is not valid.");
    return ON_AngleValue::Unset;
  }

  ON_AngleValue rc;

  rc.m_angle = angle_value;

  if (ON::AngleUnitSystem::Unset != string_angle_unit_system && ON::AngleUnitSystem::None != string_angle_unit_system)
    rc.m_angle_unit_system = string_angle_unit_system;
  else
    rc.m_angle_unit_system = parse_settings.DefaultAngleUnitSystem();

  rc.m_context_length_unit_system = parse_settings.ContextLengthUnitSystem();
  rc.m_context_locale_id = parse_settings.ContextLocaleId();

  rc.m_angle_as_string = ON_wString(string, parse_count);
  rc.m_angle_as_string.TrimLeftAndRight();
  
  if (nullptr != string_end)
    *string_end = string + parse_count;

  return rc;
}


ON_AngleValue ON_AngleValue::Create(
  double angle_value,
  const class ON_AngleUnitName& angle_unit_system,
  ON_AngleValue::StringFormat string_format
)
{
  return ON_AngleValue::Create(
    angle_value,
    angle_unit_system.AngleUnit(),
    angle_unit_system.LocaleId(),
    string_format
  );
}


ON_AngleValue ON_AngleValue::Create(
  double angle_value,
  ON::AngleUnitSystem angle_unit_system,
  unsigned int locale_id,
  ON_AngleValue::StringFormat string_format
)
{
  ON_AngleValue rc;
  for (;;)
  {
    bool bFraction = false;
    bool bClean = false;
    switch (string_format)
    {
    case ON_AngleValue::StringFormat::ExactDecimal:
      break;
    case ON_AngleValue::StringFormat::ExactFraction:
      bFraction = true;
      break;
    case ON_AngleValue::StringFormat::CleanDecimal:
      bClean = true;
      break;
    case ON_AngleValue::StringFormat::CleanFraction:
      bClean = true;
      bFraction = true;
      break;
    default:
      string_format = ON_AngleValue::StringFormat::ExactDecimal;
      break;
    }

    if (false == (angle_value == angle_value))
      break;
    if (ON::AngleUnitSystem::Unset == angle_unit_system)
      break;
    rc.m_angle = angle_value;
    rc.m_angle_unit_system = angle_unit_system;
    const ON_AngleUnitName name = ON_AngleUnitName::Create(locale_id, angle_unit_system, angle_value > 1.0);

    // DO NOT USE A "PRETTY" FORMAT!
    // It is critical that this string scan back to rc.m_angle exactly.
    double sign = ON_DBL_QNAN;
    double proper = ON_DBL_QNAN;
    double numerator = ON_DBL_QNAN;
    double denominator = ON_DBL_QNAN;
    if (
      bFraction 
      && ON_IsAnnotationFractionNumber(rc.m_angle, true, &sign, &proper, &numerator, &denominator)
      )
    {
      rc.m_angle = sign*numerator/denominator;
      if (name.AngleUnitNameIsNotEmpty() )
        rc.m_angle_as_string.Format(L"%0.17g/%0.17g %ls", numerator, denominator, name.AngleUnitName());
      else
        rc.m_angle_as_string.Format(L"%0.17g/%0.17g", numerator, denominator);
    }
    else
    {
      if (name.AngleUnitNameIsNotEmpty())
        rc.m_angle_as_string.Format(L"%0.17g %ls", rc.m_angle, name.AngleUnitName());
      else
        rc.m_angle_as_string.Format(L"%0.17g", rc.m_angle);
    }

    rc.m_context_length_unit_system = ON::LengthUnitSystem::None;
    rc.m_context_locale_id = name.LocaleId();

    return rc;
  }
  return ON_AngleValue::Unset;
}

double ON_AngleValue::Angle(
  ON::AngleUnitSystem context_unit_system
) const
{
  if ( ON::AngleUnitSystem::None == context_unit_system)
    return m_angle;
  if ( m_angle_unit_system == context_unit_system )
    return m_angle;
  return m_angle*ON::AngleUnitScale(m_angle_unit_system, context_unit_system);
}


ON::AngleUnitSystem ON_AngleValue::AngleUnitSystem() const
{
  return m_angle_unit_system;
}

const ON_wString& ON_AngleValue::AngleAsString() const
{
  return m_angle_as_string;
}

const wchar_t* ON_AngleValue::AngleAsStringPointer() const
{
  return static_cast<const wchar_t*>(m_angle_as_string);
}

bool ON_AngleValue::IsUnset() const
{
  return IsSet() ? false : true;
}

bool ON_AngleValue::IsSet() const
{
  return (
    m_angle_unit_system != ON::AngleUnitSystem::Unset
    && (ON_UNSET_VALUE < m_angle && m_angle < ON_UNSET_POSITIVE_VALUE)
    && m_angle_as_string.IsNotEmpty()
    );
}

bool ON_AngleValue::Write(
  class ON_BinaryArchive& archive
) const
{
  const int content_version = 1;
  if (!archive.BeginWrite3dmAnonymousChunk(content_version))
    return false;

  bool rc = false;
  for (;;)
  {
    if (!archive.WriteDouble(m_angle))
      break;
    if (!archive.WriteInt(static_cast<unsigned int>(m_angle_unit_system)))
      break;
    if (!archive.WriteInt(static_cast<unsigned int>(m_context_length_unit_system)))
      break;
    if (!archive.WriteInt(m_context_locale_id))
      break;
    if (!archive.WriteString(m_angle_as_string))
      break;

    // content version 1 added m_string_format
    const unsigned int u = static_cast<unsigned char>(m_string_format);
    if (!archive.WriteInt(u))
      break;

    rc = true;
    break;
  }
  if (!archive.EndWrite3dmChunk())
    rc = false;
  return rc;
}

bool ON_AngleValue::Read(
  class ON_BinaryArchive& archive
)
{
  *this = ON_AngleValue::Unset;
  int content_version = 0;
  if (!archive.BeginRead3dmAnonymousChunk(&content_version))
    return false;

  bool rc = false;
  for (;;)
  {
    if (!archive.ReadDouble(&m_angle))
      break;
    unsigned int angle_unit_system_as_unsigned = static_cast<unsigned int>(m_angle_unit_system);
    if (!archive.ReadInt(&angle_unit_system_as_unsigned))
      break;
    m_angle_unit_system = ON::AngleUnitSystemFromUnsigned(angle_unit_system_as_unsigned);

    unsigned int context_length_unit_system_as_unsigned = static_cast<unsigned int>(m_context_length_unit_system);
    if (!archive.ReadInt(&context_length_unit_system_as_unsigned))
      break;
    m_context_length_unit_system = ON::LengthUnitSystemFromUnsigned(context_length_unit_system_as_unsigned);
    if (ON::LengthUnitSystem::Unset == m_context_length_unit_system)
      m_context_length_unit_system = ON::LengthUnitSystem::None;
    if (!archive.ReadInt(&m_context_locale_id))
      break;

    if (!archive.ReadString(m_angle_as_string))
      break;

    if (content_version >= 1)
    {
      // content version 1 added m_string_format
      unsigned int u = static_cast<unsigned char>(m_string_format);
      if (!archive.ReadInt(&u))
        break;
      m_string_format = ON_AngleValue::AngleStringFormatFromUnsigned(u);
    }


    rc = true;
    break;
  }
  if (!archive.EndRead3dmChunk())
    rc = false;
  return rc;
}

ON_ScaleValue ON_ScaleValue::CreateFromString(
  ON_ParseSettings parse_settings,
  const wchar_t* string
  )
{
  if ( nullptr == string || 0 == string[0] )
    return ON_ScaleValue::Unset;

  const wchar_t* string_end = nullptr;
  const ON_ScaleValue scale_value = ON_ScaleValue::CreateFromSubString(parse_settings, string, -1, &string_end);
  if (nullptr == string_end || !(string < string_end))
  {
    ON_ERROR("Invalid input parameters.");
    return ON_ScaleValue::Unset;
  }

  return scale_value;
}

ON_ScaleValue ON_ScaleValue::CreateFromSubString(
  ON_ParseSettings parse_settings,
  const wchar_t* string,
  int string_count,
  const wchar_t** string_end
)
{
  // All calls to some version of ON_AngleValue::CreateFrom*String(...) end up here.
  if (nullptr != string_end && &string != string_end)
    *string_end = string;

  if (string_count < -1)
  {
    ON_ERROR("Invalid string_count parameter.");
    return ON_ScaleValue::Unset;
  }

  if (nullptr == string || 0 == string_count || 0 == string[0] )
  {
    // empty string fails silently.
    return ON_ScaleValue::Unset;
  }

  const wchar_t* left_side_string_end = nullptr;
  ON_ParseSettings left_side_parse_settings = parse_settings;

  const ON_LengthValue left_side = ON_LengthValue::CreateFromSubString( left_side_parse_settings, string, string_count, &left_side_string_end);

  if (left_side.IsUnset() || nullptr == left_side_string_end || !(string < left_side_string_end))
    return ON_ScaleValue::Unset;

  const size_t left_side_count = left_side_string_end - string;
  if ( -1 != string_count )
  {
    string_count -= (int)left_side_count;
    // 0 = string_count may be ok - conditions checked later.
    if (string_count < 0)
    {
      ON_ERROR("Invalid input parameters.");
      return ON_ScaleValue::Unset;
    }
  }

  const double left_side_length = left_side.Length(ON::LengthUnitSystem::None);
  const ON::LengthUnitSystem left_side_unit_system = left_side.LengthUnitSystem().UnitSystem();

  // look for equal, colon or fraction;
  ON_ScaleValue::ScaleStringFormat format_preference = ON_ScaleValue::ScaleStringFormat::Unset;
  const wchar_t* separator_string = left_side_string_end;
  const wchar_t* separator_string_end = separator_string;
  for (int i = 0; -1 == string_count || i < string_count; i++)
  {
    if (parse_settings.IsInteriorWhiteSpace(separator_string[i]))
      continue;

    switch (separator_string[i])
    {
    case ':':
      format_preference = ON_ScaleValue::ScaleStringFormat::RatioFormat;
      break;

    case '=':
      format_preference = ON_ScaleValue::ScaleStringFormat::EquationFormat;
      break;

    case '/':
      format_preference = ON_ScaleValue::ScaleStringFormat::FractionFormat;
      break;
    }

    if (ON_ScaleValue::ScaleStringFormat::Unset != format_preference)
    {
      for (i++; -1 == string_count || i < string_count; i++)
      {
        if (false == parse_settings.IsInteriorWhiteSpace(separator_string[i]))
          break;
      }
    }
    separator_string_end = separator_string + i;
    break;
  }

  const size_t separator_count = separator_string_end - separator_string;
  if ( -1 != string_count )
  {
    string_count -= (int)separator_count;
    if (string_count <= 0)
    {
      ON_ERROR("Invalid input parameters.");
      return ON_ScaleValue::Unset;
    }
  }

  const wchar_t* right_side_string = separator_string_end;
  const wchar_t* right_side_string_end = right_side_string;
  ON_LengthValue right_side;

  if (ON_ScaleValue::ScaleStringFormat::Unset == format_preference)
  {
    // A single value scanned.
    if (ON::LengthUnitSystem::None != left_side_unit_system)
      return ON_ScaleValue::Unset;
    right_side = ON_LengthValue::Create(1.0, ON::LengthUnitSystem::None, 0, ON_LengthValue::StringFormat::ExactDecimal);

    format_preference
      = (left_side_length == floor(left_side_length) && ON::LengthUnitSystem::None == left_side_unit_system)
        ? ON_ScaleValue::ScaleStringFormat::RatioFormat
        : ON_ScaleValue::ScaleStringFormat::EquationFormat;
  }
  else
  {
    // parse right side
    ON_ParseSettings right_side_parse_settings = parse_settings;
    right_side_parse_settings.SetParseLeadingWhiteSpace(false);
    right_side = ON_LengthValue::CreateFromSubString( right_side_parse_settings, right_side_string, string_count, &right_side_string_end );
  }

  if (right_side.IsUnset())
  {
    ON_ERROR("Invalid input parameters.");
    return ON_ScaleValue::Unset;
  }

  ON_ScaleValue scale_value = ON_ScaleValue::Create(
    left_side,
    right_side,
    format_preference
  );

  if (scale_value.IsUnset() || !(right_side_string_end > string))
  {
    ON_ERROR("Invalid input parameters.");
    return ON_ScaleValue::Unset;
  }

  scale_value.m_scale_as_string = ON_wString(string, (int)(right_side_string_end - string));
  scale_value.m_scale_as_string.TrimLeftAndRight();

  if (nullptr != string_end)
    *string_end = right_side_string_end;

  return scale_value;
}

static double ON_InternalDefuzz( double rel_tol, double x)
{
  if (!(rel_tol >= 4.0*ON_EPSILON))
    rel_tol = 4.0*ON_EPSILON;
  const double s = 256.0;
  const double a = s*fabs(x);
  if (!(a > 255.0))
    return x;

  double ia = floor(a);
  double fa = a - ia;
  if (fa > 0.5)
    ia += 1.0;
  const double y
    = (fabs(a - ia) <= a*rel_tol)
    ? (((x < 0.0) ? -ia : ia)/s)
    : x;

  return y;
}

static double ON_InternalQuotient(
  double rel_tol,
  double numerator,
  double denominator
)
{
  if (numerator == numerator && denominator != 0.0)
  {
    const double r = ON_InternalDefuzz(rel_tol, numerator / denominator);
    const double s = (numerator != 0.0) ? ON_InternalDefuzz(rel_tol, denominator / numerator ) : 0.0;
    return
      (s >= 2.0 && s == floor(s))
      ? (1.0 / s)
      : r;
  }
  ON_ERROR("Invalid input.");
  return ON_DBL_QNAN;
}

static bool ON_Internal_RemoveCommonFactors(const double rel_zero_tol, const double factor, double& x, double& y)
{
  if (false == (x > 0.0 && y > 0.0 && factor > 0.0 && rel_zero_tol >= 0.0 && rel_zero_tol < 0.01))
  {
    ON_ERROR("Invalid input parameters.");
    return false;
  }
  if (1.0 == factor)
    return true; // nothing to do

  if (!(x < 1.0 / ON_EPSILON && y < 1.0 / ON_EPSILON))
  {
    // x or y is too big for this code to work with IEEE double arithmetic.
    return false;
  }

  const double fat_eps = 4.0*ON_EPSILON;
  const double factor_tol = (rel_zero_tol > fat_eps) ? rel_zero_tol : fat_eps;
  if (!(factor > x*factor_tol && factor > y*factor_tol))
  {
    // factor is too small for the ON_ScaleValue context where this
    // function is designed to be used.
    return false;
  }

  bool rc = false;
  for (;;)
  {
    const double s = ON_InternalQuotient(factor_tol,x,factor);
    if (!(s == floor(s)))
      break;


    const double t = ON_InternalQuotient(factor_tol,y,factor);
    if (!(t == floor(t)))
      break;
    
    if (factor >= 1.0)
    {
      if (!(s < x && t < y))
        break;
    }

    rc = true;
    x = s;
    y = t;

    if ( !(factor >= 2.0 && x >= factor*(1.0-fat_eps) && y >= factor*(1.0-fat_eps)) )
      break;
  }

  return rc;
}

static bool ON_Internal_SimplifyRatio(double& x, double& y)
{
  // returns true if the output values of x and y are both integers.
  const double rel_zero_tol = 1.0e-14;
  if (!(x > 0.0 && y > 0.0))
    return false;

  if (fabs(x / y - 1.0) <= rel_zero_tol || fabs(y / x - 1.0) <= rel_zero_tol)
  {
    x = 1.0;
    y = 1.0;
    return true;
  }

  if ( x < y )
    ON_Internal_RemoveCommonFactors(rel_zero_tol, x, x, y);
  else if ( y < x )
    ON_Internal_RemoveCommonFactors(rel_zero_tol, y, y, x);

  const double factors[] = { 2.0, 3.0, 5.0 };
  for ( size_t i = 0; i < sizeof(factors)/sizeof(factors[0]); i++ )
    ON_Internal_RemoveCommonFactors(rel_zero_tol, factors[i], y, x);

  return (x == floor(x) && y == floor(y));
}
  
ON_ScaleValue ON_ScaleValue::Create(
  const class ON_LengthValue& left_side_length,
  const class ON_LengthValue& right_side_length,
  ON_ScaleValue::ScaleStringFormat string_format_preference
)
{
  ON_ScaleValue scale_value = ON_ScaleValue::Unset;

  scale_value.m_left_length = left_side_length;
  scale_value.m_right_length = right_side_length;
  scale_value.m_string_format_preference = string_format_preference;

  if (scale_value.m_left_length.IsUnset() || scale_value.m_right_length.IsUnset())
    return scale_value;

  ON::LengthUnitSystem left_length_unit_system = scale_value.m_left_length.LengthUnitSystem().UnitSystem();
  ON::LengthUnitSystem right_length_unit_system = scale_value.m_right_length.LengthUnitSystem().UnitSystem();
  const double left_length = scale_value.m_left_length.Length(left_length_unit_system);
  const double right_length = scale_value.m_right_length.Length(right_length_unit_system);
  if (false == (left_length > 0.0 && right_length > 0.0))
  {
    // one or both of left_length and right_length is a NAN or negative
    ON_ERROR("Invalid input");
    return scale_value;
  }

  // Dale Lear http://mcneel.myjetbrains.com/youtrack/issue/RH-34709
  // Turns out that stripping unit system information when one side is None
  // was a bad idea. After using ON_ScaleValue in the ON_DimStyle
  // class, preserving the unit systems works better when one
  // is None.
  ////if (ON::LengthUnitSystem::None == left_length_unit_system
  ////  && ON::LengthUnitSystem::None != right_length_unit_system
  ////  )
  ////{
  ////  // remove units from right side 
  ////  scale_value.m_right_length = scale_value.m_right_length.RemoveUnitSystem();
  ////  right_length_unit_system = ON::LengthUnitSystem::None;
  ////}

  ////if (ON::LengthUnitSystem::None != left_length_unit_system
  ////  && ON::LengthUnitSystem::None == right_length_unit_system
  ////  )
  ////{
  ////  // remove units from left side 
  ////  scale_value.m_left_length = scale_value.m_left_length.RemoveUnitSystem();
  ////  left_length_unit_system = ON::LengthUnitSystem::None;
  ////}

  const double left_to_right_scale = ON::UnitScale(scale_value.m_left_length.LengthUnitSystem(), scale_value.m_right_length.LengthUnitSystem());
  const double right_to_left_scale = ON::UnitScale(scale_value.m_right_length.LengthUnitSystem(), scale_value.m_left_length.LengthUnitSystem());

  if (false == (left_to_right_scale > 0.0 && right_to_left_scale > 0.0 && 1.0 == ON_InternalDefuzz(1.0e-14,left_to_right_scale*right_to_left_scale)) )
  {
    // one or both of left_to_right_scale and right_to_left_scale is a NAN or not set correctly
    ON_ERROR("Invalid input");
    ON_InternalDefuzz(1.0e-14, left_to_right_scale*right_to_left_scale);
    return scale_value;
  }
  
  double x = left_length;
  double y = right_length;
  if (left_to_right_scale > right_to_left_scale)
    x *= left_to_right_scale;
  else if ( right_to_left_scale > left_to_right_scale)
    y *= right_to_left_scale;

  if (false == (x > 0.0 && y > 0.0))
  {
    // one or both of x and y is a NAN or not set correctly
    ON_ERROR("Invalid input");
    return scale_value;
  }

  if (!ON_Internal_SimplifyRatio(x, y))
    string_format_preference = ON_ScaleValue::ScaleStringFormat::EquationFormat;

  scale_value.m_left_to_right_scale = ON_InternalQuotient(0.0,x,y);
  scale_value.m_right_to_left_scale = ON_InternalQuotient(0.0,y,x);

  if (scale_value.m_left_to_right_scale >= 2.0 && floor(scale_value.m_left_to_right_scale) == scale_value.m_left_to_right_scale)
  {
    scale_value.m_right_to_left_scale = 1.0 / scale_value.m_left_to_right_scale;
  }
  else if (scale_value.m_right_to_left_scale >= 2.0 && floor(scale_value.m_right_to_left_scale) == scale_value.m_right_to_left_scale)
  {
    scale_value.m_left_to_right_scale = 1.0 / scale_value.m_right_to_left_scale;
  }

  switch (string_format_preference)
  {
  case ON_ScaleValue::ScaleStringFormat::RatioFormat:
    // Do NOT dumb down the %.17g in the format string.
    // If you don't like the format, then make your own string and call
    // ON_ScaleValue::CreateFromString(). It is critical that 
    // the values in the formatted string exactly match the double values.
    scale_value.m_scale_as_string.Format(L"%.17g:%.17g", x, y);
    break;
  case ON_ScaleValue::ScaleStringFormat::FractionFormat:
    // Do NOT dumb down the %.17g in the format string.
    // If you don't like the format, then make your own string and call
    // ON_ScaleValue::CreateFromString(). It is critical that 
    // the values in the formatted string exactly match the double values.
    scale_value.m_scale_as_string.Format(L"%.17g/%.17g", x, y);
    break;
  case ON_ScaleValue::ScaleStringFormat::EquationFormat:
    // no break here
  default:
    scale_value.m_scale_as_string.Format(L"%ls = %ls",
      scale_value.m_left_length.LengthAsStringPointer(), 
      scale_value.m_right_length.LengthAsStringPointer()
    );
    break;
  }

  scale_value.m_string_format_preference = string_format_preference;


  return scale_value;
}

double ON_ScaleValue::LeftToRightScale() const
{
  return m_left_to_right_scale;
}

double ON_ScaleValue::RightToLeftScale() const
{
  return m_right_to_left_scale;
}

const class ON_LengthValue& ON_ScaleValue::LeftLengthValue() const
{
  return m_left_length;
}

const class ON_LengthValue& ON_ScaleValue::RightLengthValue() const
{
  return m_right_length;
}

const ON_wString& ON_ScaleValue::ScaleAsString() const
{
  m_scale_as_string.IsValid(true);
  return m_scale_as_string;
}

const wchar_t* ON_ScaleValue::ScaleAsStringPointer() const
{
  m_scale_as_string.IsValid(true);
  return static_cast<const wchar_t*>(m_scale_as_string);
}

bool ON_ScaleValue::IsUnset() const
{
  return IsSet() ? false : true;
}

bool ON_ScaleValue::IsSet() const
{
  return (
    m_left_length.IsSet()
    && m_right_length.IsSet()
    && (ON_UNSET_VALUE < m_left_to_right_scale && m_left_to_right_scale < ON_UNSET_POSITIVE_VALUE)
    && (ON_UNSET_VALUE < m_right_to_left_scale && m_right_to_left_scale < ON_UNSET_POSITIVE_VALUE)
    && m_scale_as_string.IsNotEmpty()
    );
}

bool ON_ScaleValue::Write(
  class ON_BinaryArchive& archive
) const
{
  const int content_version = 1;
  if (!archive.BeginWrite3dmAnonymousChunk(content_version))
    return false;

  bool rc = false;
  for (;;)
  {
    // content_version = 0
    if (!archive.WriteDouble(m_left_to_right_scale))
      break;
    if (!archive.WriteDouble(m_right_to_left_scale))
      break;
    if (!archive.WriteInt(m_context_locale_id))
      break;
    if (!archive.WriteInt(static_cast<unsigned int>(m_context_length_unit_system)))
      break;
    if (!archive.WriteInt(static_cast<unsigned int>(m_context_angle_unit_system)))
      break;
    m_scale_as_string.IsValid(true);
    if (!archive.WriteString(m_scale_as_string))
      break;
    if (!m_left_length.Write(archive))
      break;
    if (!m_right_length.Write(archive))
      break;

    // content version 1 added m_string_format_preference
    unsigned int u = static_cast<unsigned char>(m_string_format_preference);
    if (!archive.WriteInt(u))
      break;

    rc = true;
    break;
  }

  if (!archive.EndWrite3dmChunk())
    rc = false;
  return rc;
}

ON_ScaleValue::ScaleStringFormat ON_ScaleValue::ScaleStringFormatFromUnsigned(
  unsigned int scale_string_format_as_unsigned
)
{
  switch (scale_string_format_as_unsigned)
  {
    ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::None);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::RatioFormat);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::EquationFormat);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::FractionFormat);
    ON_ENUM_FROM_UNSIGNED_CASE(ON_ScaleValue::ScaleStringFormat::Unset);
  }
  ON_ERROR("Invalid scale_string_format_as_unsigned value.");
  return (ON_ScaleValue::Unset.m_string_format_preference);
}

bool ON_ScaleValue::Read(
  class ON_BinaryArchive& archive
)
{
  *this = ON_ScaleValue::Unset;

  int content_version = 0;
  if (!archive.BeginRead3dmAnonymousChunk(&content_version))
    return false;

  bool rc = false;
  for (;;)
  {
    if (!archive.ReadDouble(&m_left_to_right_scale))
      break;
    if (!archive.ReadDouble(&m_right_to_left_scale))
      break;
    if (!archive.ReadInt(&m_context_locale_id))
      break;

    unsigned int u;

    u = static_cast<unsigned int>(m_context_length_unit_system);
    if (!archive.ReadInt(&u))
      break;
    m_context_length_unit_system = ON::LengthUnitSystemFromUnsigned(u);

    u = static_cast<unsigned int>(m_context_angle_unit_system);
    if (!archive.ReadInt(&u))
      break;
    m_context_angle_unit_system = ON::AngleUnitSystemFromUnsigned(u);

    if (!archive.ReadString(m_scale_as_string))
      break;

    if (!m_left_length.Read(archive))
      break;
    if (!m_right_length.Read(archive))
      break;

    if (content_version >= 1)
    {
      // content version 1 added m_string_format_preference
      u = static_cast<unsigned char>(m_string_format_preference);
      if (!archive.ReadInt(&u))
        break;
      m_string_format_preference = ON_ScaleValue::ScaleStringFormatFromUnsigned(u);
    }

    rc = true;
    break;
  }

  if (!archive.EndRead3dmChunk())
    rc = false;
  return rc;
}

ON_LengthValue::StringFormat ON_LengthValue::LengthStringFormat() const
{
  return m_string_format;
}

unsigned int ON_LengthValue::ContextLocaleId() const
{
  return m_context_locale_id;
}

ON::AngleUnitSystem ON_LengthValue::ContextAngleUnitSystem() const
{
  return m_context_angle_unit_system;
}

const ON_ParseSettings ON_LengthValue::LengthStringParseSettings() const
{
  return ON_ParseSettings(m_length_unit_system.UnitSystem(), m_context_angle_unit_system, m_context_locale_id);
}

ON_ParseSettings const ON_AngleValue::AngleStringParseSettings() const
{
  return ON_ParseSettings(m_context_length_unit_system, m_angle_unit_system, m_context_locale_id);
}

const ON_ParseSettings ON_ScaleValue::ScaleStringParseSettings() const
{
  return ON_ParseSettings(m_context_length_unit_system, m_context_angle_unit_system, m_context_locale_id);
}

void ON_ScaleValue::SwapLeftAndRight()
{
  double x = m_left_to_right_scale;
  m_left_to_right_scale = m_right_to_left_scale;
  m_right_to_left_scale = x;

  ON_LengthValue t = m_left_length;
  m_left_length = m_right_length;
  m_right_length = t;

  // TODO: properly reverse string
  m_scale_as_string = ON_ScaleValue::Create(m_left_length,m_right_length,ON_ScaleValue::ScaleStringFormat::None).ScaleAsString();
}


const ON_SHA1_Hash ON_ScaleValue::ContentHash() const
{
  ON_SHA1 sha1;
  sha1.AccumulateDouble(m_left_to_right_scale);
  sha1.AccumulateDouble(m_right_to_left_scale);
  sha1.AccumulateUnsigned32(m_context_locale_id);
  sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_context_length_unit_system));
  sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_context_angle_unit_system));
  sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_string_format_preference));
  m_scale_as_string.IsValid(true);
  sha1.AccumulateString(m_scale_as_string);
  ON_SHA1_Hash h = m_left_length.ContentHash();
  sha1.AccumulateSubHash(h);
  h = m_right_length.ContentHash();
  sha1.AccumulateSubHash(h);
  return sha1.Hash();
}

int ON_ScaleValue::Compare(
  const ON_ScaleValue& lhs,
  const ON_ScaleValue& rhs
)
{
  double x = lhs.RightToLeftScale();
  double y = rhs.RightToLeftScale();
  if (x < y)
    return -1;
  if (x > y)
    return 1;
  return ON_SHA1_Hash::Compare(lhs.ContentHash(), rhs.ContentHash());
}

const ON_SHA1_Hash ON_LengthValue::ContentHash() const
{
  ON_SHA1 sha1;
  sha1.AccumulateUnsigned32(m_context_locale_id);
  sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_context_angle_unit_system));
  sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_string_format));
  sha1.AccumulateUnsigned32(static_cast<unsigned int>(m_length_unit_system.UnitSystem()));
  m_length_as_string.IsValid(true);
  sha1.AccumulateString(m_length_as_string);
  return sha1.Hash();
}

int ON_LengthValue::Compare(
  const ON_LengthValue& lhs,
  const ON_LengthValue& rhs
)
{
  if (lhs.m_length < rhs.m_length)
    return -1;
  if (lhs.m_length > rhs.m_length)
    return 1;
  return ON_SHA1_Hash::Compare(lhs.ContentHash(), rhs.ContentHash());
}
