text: // File: crn_value.h
  // See Copyright Notice and license at the end of inc/crnlib.h
  #pragma once
  #include "crn_strutils.h"
  #include "crn_dynamic_string.h"
  #include "crn_vec.h"
  
  namespace crnlib {
  enum value_data_type {
    cDTInvalid,
    cDTString,
    cDTBool,
    cDTInt,
    cDTUInt,
    cDTFloat,
    cDTVec3F,
    cDTVec3I,
  
    cDTTotal
  };
  
  extern const char* gValueDataTypeStrings[cDTTotal + 1];
  
  class value {
   public:
    value()
        : m_type(cDTInvalid) {
    }
  
    value(const char* pStr)
        : m_pStr(crnlib_new<dynamic_string>(pStr)), m_type(cDTString) {
    }
  
    value(const dynamic_string& str)
        : m_pStr(crnlib_new<dynamic_string>(str)), m_type(cDTString) {
    }
  
    explicit value(bool v)
        : m_bool(v), m_type(cDTBool) {
    }
  
    value(int v)
        : m_int(v), m_type(cDTInt) {
    }
  
    value(uint v)
        : m_uint(v), m_type(cDTUInt) {
    }
  
    value(float v)
        : m_float(v), m_type(cDTFloat) {
    }
  
    value(const vec3F& v)
        : m_pVec3F(crnlib_new<vec3F>(v)), m_type(cDTVec3F) {
    }
  
    value(const vec3I& v)
        : m_pVec3I(crnlib_new<vec3I>(v)), m_type(cDTVec3I) {
    }
  
    ~value() {
      switch (m_type) {
        case cDTString:
          crnlib_delete(m_pStr);
          break;
        case cDTVec3F:
          crnlib_delete(m_pVec3F);
          break;
        case cDTVec3I:
          crnlib_delete(m_pVec3I);
          break;
        default:
          break;
      }
    }
  
    value(const value& other)
        : m_type(cDTInvalid) {
      *this = other;
    }
  
    value& operator=(const value& other) {
      if (this == &other)
        return *this;
  
      change_type(other.m_type);
  
      switch (other.m_type) {
        case cDTString:
          m_pStr->set(*other.m_pStr);
          break;
        case cDTBool:
          m_bool = other.m_bool;
          break;
        case cDTInt:
          m_int = other.m_int;
          break;
        case cDTUInt:
          m_uint = other.m_uint;
          break;
        case cDTFloat:
          m_float = other.m_float;
          break;
        case cDTVec3F:
          m_pVec3F->set(*other.m_pVec3F);
          break;
        case cDTVec3I:
          m_pVec3I->set(*other.m_pVec3I);
          break;
        default:
          break;
      }
      return *this;
    }
  
    inline value_data_type get_data_type() const { return m_type; }
  
    void clear() {
      clear_dynamic();
  
      m_type = cDTInvalid;
    }
  
    void set_string(const char* pStr) {
      set_str(pStr);
    }
  
    void set_int(int v) {
      clear_dynamic();
      m_type = cDTInt;
      m_int = v;
    }
  
    void set_uint(uint v) {
      clear_dynamic();
      m_type = cDTUInt;
      m_uint = v;
    }
  
    void set_bool(bool v) {
      clear_dynamic();
      m_type = cDTBool;
      m_bool = v;
    }
  
    void set_float(float v) {
      clear_dynamic();
      m_type = cDTFloat;
      m_float = v;
    }
  
    void set_vec(const vec3F& v) {
      change_type(cDTVec3F);
      m_pVec3F->set(v);
    }
  
    void set_vec(const vec3I& v) {
      change_type(cDTVec3I);
      m_pVec3I->set(v);
    }
  
    bool parse(const char* p) {
      if ((!p) || (!p[0])) {
        clear();
        return false;
      }
  
      if (_stricmp(p, "false") == 0) {
        set_bool(false);
        return true;
      } else if (_stricmp(p, "true") == 0) {
        set_bool(true);
        return true;
      }
  
      if (p[0] == '\"') {
        dynamic_string str;
        str = p + 1;
        if (!str.is_empty()) {
          if (str[str.get_len() - 1] == '\"') {
            str.left(str.get_len() - 1);
            set_str(str);
  
            return true;
          }
        }
      }
  
      if (strchr(p, ',') != nullptr) {
        float fx = 0, fy = 0, fz = 0;
  #ifdef _MSC_VER
        if (sscanf_s(p, "%f,%f,%f", &fx, &fy, &fz) == 3)
  #else
        if (sscanf(p, "%f,%f,%f", &fx, &fy, &fz) == 3)
  #endif
        {
          bool as_float = true;
          int ix = 0, iy = 0, iz = 0;
  #ifdef _MSC_VER
          if (sscanf_s(p, "%i,%i,%i", &ix, &iy, &iz) == 3)
  #else
          if (sscanf(p, "%i,%i,%i", &ix, &iy, &iz) == 3)
  #endif
          {
            if ((ix == fx) && (iy == fy) && (iz == fz))
              as_float = false;
          }
  
          if (as_float)
            set_vec(vec3F(fx, fy, fz));
          else
            set_vec(vec3I(ix, iy, iz));
  
          return true;
        }
      }
  
      const char* q = p;
      bool success = string_to_uint(q, m_uint);
      if ((success) && (*q == 0)) {
        set_uint(m_uint);
        return true;
      }
  
      q = p;
      success = string_to_int(q, m_int);
      if ((success) && (*q == 0)) {
        set_int(m_int);
        return true;
      }
  
      q = p;
      success = string_to_float(q, m_float);
      if ((success) && (*q == 0)) {
        set_float(m_float);
        return true;
      }
  
      set_string(p);
  
      return true;
    }
  
    dynamic_string& get_as_string(dynamic_string& dst) const {
      switch (m_type) {
        case cDTInvalid:
          dst.clear();
          break;
        case cDTString:
          dst = *m_pStr;
          break;
        case cDTBool:
          dst = m_bool ? "TRUE" : "FALSE";
          break;
        case cDTInt:
          dst.format("%i", m_int);
          break;
        case cDTUInt:
          dst.format("%u", m_uint);
          break;
        case cDTFloat:
          dst.format("%f", m_float);
          break;
        case cDTVec3F:
          dst.format("%f,%f,%f", (*m_pVec3F)[0], (*m_pVec3F)[1], (*m_pVec3F)[2]);
          break;
        case cDTVec3I:
          dst.format("%i,%i,%i", (*m_pVec3I)[0], (*m_pVec3I)[1], (*m_pVec3I)[2]);
          break;
        default:
          break;
      }
  
      return dst;
    }
  
    bool get_as_int(int& val, uint component = 0) const {
      switch (m_type) {
        case cDTInvalid: {
          val = 0;
          return false;
        }
        case cDTString: {
          const char* p = m_pStr->get_ptr();
          return string_to_int(p, val);
        }
        case cDTBool:
          val = m_bool;
          break;
        case cDTInt:
          val = m_int;
          break;
        case cDTUInt: {
          if (m_uint > INT_MAX) {
            val = 0;
            return false;
          }
          val = m_uint;
          break;
        }
        case cDTFloat: {
          if ((m_float < INT_MIN) || (m_float > INT_MAX)) {
            val = 0;
            return false;
          }
          val = (int)m_float;
          break;
        }
        case cDTVec3F: {
          if (component > 2) {
            val = 0;
            return false;
          }
          if (((*m_pVec3F)[component] < INT_MIN) || ((*m_pVec3F)[component] > INT_MAX)) {
            val = 0;
            return false;
          }
          val = (int)(*m_pVec3F)[component];
          break;
        }
        case cDTVec3I: {
          if (component > 2) {
            val = 0;
            return false;
          }
          val = (int)(*m_pVec3I)[component];
          break;
        }
        default:
          break;
      }
      return true;
    }
  
    bool get_as_uint(uint& val, uint component = 0) const {
      switch (m_type) {
        case cDTInvalid: {
          val = 0;
          return false;
        }
        case cDTString: {
          const char* p = m_pStr->get_ptr();
          return string_to_uint(p, val);
        }
        case cDTBool: {
          val = m_bool;
          break;
        }
        case cDTInt: {
          if (m_int < 0) {
            val = 0;
            return false;
          }
          val = (uint)m_int;
          break;
        }
        case cDTUInt: {
          val = m_uint;
          break;
        }
        case cDTFloat: {
          if ((m_float < 0) || (m_float > UINT_MAX)) {
            val = 0;
            return false;
          }
          val = (uint)m_float;
          break;
        }
        case cDTVec3F: {
          if (component > 2) {
            val = 0;
            return false;
          }
          if (((*m_pVec3F)[component] < 0) || ((*m_pVec3F)[component] > UINT_MAX)) {
            val = 0;
            return false;
          }
          val = (uint)(*m_pVec3F)[component];
          break;
        }
        case cDTVec3I: {
          if (component > 2) {
            val = 0;
            return false;
          }
          if ((*m_pVec3I)[component] < 0) {
            val = 0;
            return false;
          }
          val = (uint)(*m_pVec3I)[component];
          break;
        }
        default:
          break;
      }
      return true;
    }
  
    bool get_as_bool(bool& val, uint component = 0) const {
      switch (m_type) {
        case cDTInvalid: {
          val = false;
          return false;
        }
        case cDTString: {
          const char* p = m_pStr->get_ptr();
          return string_to_bool(p, val);
        }
        case cDTBool: {
          val = m_bool;
          break;
        }
        case cDTInt: {
          val = (m_int != 0);
          break;
        }
        case cDTUInt: {
          val = (m_uint != 0);
          break;
        }
        case cDTFloat: {
          val = (m_float != 0);
          break;
        }
        case cDTVec3F: {
          if (component > 2) {
            val = false;
            return false;
          }
          val = ((*m_pVec3F)[component] != 0);
          break;
        }
        case cDTVec3I: {
          if (component > 2) {
            val = false;
            return false;
          }
          val = ((*m_pVec3I)[component] != 0);
          break;
        }
        default:
          break;
      }
      return true;
    }
  
    bool get_as_float(float& val, uint component = 0) const {
      switch (m_type) {
        case cDTInvalid: {
          val = 0;
          return false;
        }
        case cDTString: {
          const char* p = m_pStr->get_ptr();
          return string_to_float(p, val);
        }
        case cDTBool: {
          val = m_bool;
          break;
        }
        case cDTInt: {
          val = (float)m_int;
          break;
        }
        case cDTUInt: {
          val = (float)m_uint;
          break;
        }
        case cDTFloat: {
          val = m_float;
          break;
        }
        case cDTVec3F: {
          if (component > 2) {
            val = 0;
            return false;
          }
          val = (*m_pVec3F)[component];
          break;
        }
        case cDTVec3I: {
          if (component > 2) {
            val = 0;
            return false;
          }
          val = (float)(*m_pVec3I)[component];
          break;
        }
        default:
          break;
      }
      return true;
    }
  
    bool get_as_vec(vec3F& val) const {
      switch (m_type) {
        case cDTInvalid: {
          val.clear();
          return false;
        }
        case cDTString: {
          const char* p = m_pStr->get_ptr();
          float x = 0, y = 0, z = 0;
  #ifdef _MSC_VER
          if (sscanf_s(p, "%f,%f,%f", &x, &y, &z) == 3)
  #else
          if (sscanf(p, "%f,%f,%f", &x, &y, &z) == 3)
  #endif
          {
            val.set(x, y, z);
            return true;
          } else {
            val.clear();
            return false;
          }
        }
        case cDTBool: {
          val.set(m_bool);
          break;
        }
        case cDTInt: {
          val.set(static_cast<float>(m_int));
          break;
        }
        case cDTUInt: {
          val.set(static_cast<float>(m_uint));
          break;
        }
        case cDTFloat: {
          val.set(m_float);
          break;
        }
        case cDTVec3F: {
          val = *m_pVec3F;
          break;
        }
        case cDTVec3I: {
          val.set((float)(*m_pVec3I)[0], (float)(*m_pVec3I)[1], (float)(*m_pVec3I)[2]);
          break;
        }
        default:
          break;
      }
      return true;
    }
  
    bool get_as_vec(vec3I& val) const {
      switch (m_type) {
        case cDTInvalid: {
          val.clear();
          return false;
        }
        case cDTString: {
          const char* p = m_pStr->get_ptr();
          float x = 0, y = 0, z = 0;
  #ifdef _MSC_VER
          if (sscanf_s(p, "%f,%f,%f", &x, &y, &z) == 3)
  #else
          if (sscanf(p, "%f,%f,%f", &x, &y, &z) == 3)
  #endif
          {
            if ((x < INT_MIN) || (x > INT_MAX) || (y < INT_MIN) || (y > INT_MAX) || (z < INT_MIN) || (z > INT_MAX)) {
              val.clear();
              return false;
            }
            val.set((int)x, (int)y, (int)z);
            return true;
          } else {
            val.clear();
            return false;
          }
  
          break;
        }
        case cDTBool: {
          val.set(m_bool);
          break;
        }
        case cDTInt: {
          val.set(m_int);
          break;
        }
        case cDTUInt: {
          val.set(m_uint);
          break;
        }
        case cDTFloat: {
          val.set((int)m_float);
          break;
        }
        case cDTVec3F: {
          val.set((int)(*m_pVec3F)[0], (int)(*m_pVec3F)[1], (int)(*m_pVec3F)[2]);
          break;
        }
        case cDTVec3I: {
          val = *m_pVec3I;
          break;
        }
        default:
          break;
      }
      return true;
    }
  
    bool set_zero() {
      switch (m_type) {
        case cDTInvalid: {
          return false;
        }
        case cDTString: {
          m_pStr->empty();
          break;
        }
        case cDTBool: {
          m_bool = false;
          break;
        }
        case cDTInt: {
          m_int = 0;
          break;
        }
        case cDTUInt: {
          m_uint = 0;
          break;
        }
        case cDTFloat: {
          m_float = 0;
          break;
        }
        case cDTVec3F: {
          m_pVec3F->clear();
          break;
        }
        case cDTVec3I: {
          m_pVec3I->clear();
          break;
        }
        default:
          break;
      }
      return true;
    }
  
    bool is_vector() const {
      switch (m_type) {
        case cDTVec3F:
        case cDTVec3I:
          return true;
        default:
          break;
      }
      return false;
    }
  
    uint get_num_components() const {
      switch (m_type) {
        case cDTVec3F:
        case cDTVec3I:
          return 3;
        default:
          break;
      }
      return 1;
    }
  
    bool is_numeric() const {
      switch (m_type) {
        case cDTInt:
        case cDTUInt:
        case cDTFloat:
        case cDTVec3F:
        case cDTVec3I:
          return true;
        default:
          break;
      }
      return false;
    }
  
    bool is_float() const {
      switch (m_type) {
        case cDTFloat:
        case cDTVec3F:
          return true;
        default:
          break;
      }
      return false;
    }
  
    bool is_integer() const {
      switch (m_type) {
        case cDTInt:
        case cDTUInt:
        case cDTVec3I:
          return true;
        default:
          break;
      }
      return false;
    }
  
    bool is_signed() const {
      switch (m_type) {
        case cDTInt:
        case cDTFloat:
        case cDTVec3F:
        case cDTVec3I:
          return true;
        default:
          break;
      }
      return false;
    }
  
    bool is_string() const {
      return m_type == cDTString;
    }
  
    int serialize(void* pBuf, uint buf_size, bool little_endian) const {
      uint buf_left = buf_size;
  
      uint8 t = (uint8)m_type;
      if (!utils::write_obj(t, pBuf, buf_left, little_endian))
        return -1;
  
      switch (m_type) {
        case cDTString: {
          int bytes_written = m_pStr->serialize(pBuf, buf_left, little_endian);
          if (bytes_written < 0)
            return -1;
  
          pBuf = static_cast<uint8*>(pBuf) + bytes_written;
          buf_left -= bytes_written;
  
          break;
        }
        case cDTBool: {
          if (!utils::write_obj(m_bool, pBuf, buf_left, little_endian))
            return -1;
          break;
        }
        case cDTInt:
        case cDTUInt:
        case cDTFloat: {
          if (!utils::write_obj(m_float, pBuf, buf_left, little_endian))
            return -1;
          break;
        }
        case cDTVec3F: {
          for (uint i = 0; i < 3; i++)
            if (!utils::write_obj((*m_pVec3F)[i], pBuf, buf_left, little_endian))
              return -1;
          break;
        }
        case cDTVec3I: {
          for (uint i = 0; i < 3; i++)
            if (!utils::write_obj((*m_pVec3I)[i], pBuf, buf_left, little_endian))
              return -1;
          break;
        }
        default:
          break;
      }
  
      return buf_size - buf_left;
    }
  
    int deserialize(const void* pBuf, uint buf_size, bool little_endian) {
      uint buf_left = buf_size;
  
      uint8 t;
      if (!utils::read_obj(t, pBuf, buf_left, little_endian))
        return -1;
  
      if (t >= cDTTotal)
        return -1;
  
      m_type = static_cast<value_data_type>(t);
  
      switch (m_type) {
        case cDTString: {
          change_type(cDTString);
  
          int bytes_read = m_pStr->deserialize(pBuf, buf_left, little_endian);
          if (bytes_read < 0)
            return -1;
  
          pBuf = static_cast<const uint8*>(pBuf) + bytes_read;
          buf_left -= bytes_read;
  
          break;
        }
        case cDTBool: {
          if (!utils::read_obj(m_bool, pBuf, buf_left, little_endian))
            return -1;
          break;
        }
        case cDTInt:
        case cDTUInt:
        case cDTFloat: {
          if (!utils::read_obj(m_float, pBuf, buf_left, little_endian))
            return -1;
          break;
        }
        case cDTVec3F: {
          change_type(cDTVec3F);
  
          for (uint i = 0; i < 3; i++)
            if (!utils::read_obj((*m_pVec3F)[i], pBuf, buf_left, little_endian))
              return -1;
          break;
        }
        case cDTVec3I: {
          change_type(cDTVec3I);
  
          for (uint i = 0; i < 3; i++)
            if (!utils::read_obj((*m_pVec3I)[i], pBuf, buf_left, little_endian))
              return -1;
          break;
        }
        default:
          break;
      }
  
      return buf_size - buf_left;
    }
  
    void swap(value& other) {
      for (uint i = 0; i < cUnionSize; i++)
        std::swap(m_union[i], other.m_union[i]);
  
      std::swap(m_type, other.m_type);
    }
  
   private:
    void clear_dynamic() {
      if (m_type == cDTVec3F) {
        crnlib_delete(m_pVec3F);
        m_pVec3F = nullptr;
  
        m_type = cDTInvalid;
      } else if (m_type == cDTVec3I) {
        crnlib_delete(m_pVec3I);
        m_pVec3I = nullptr;
  
        m_type = cDTInvalid;
      } else if (m_type == cDTString) {
        crnlib_delete(m_pStr);
        m_pStr = nullptr;
  
        m_type = cDTInvalid;
      }
    }
  
    void change_type(value_data_type type) {
      if (type != m_type) {
        clear_dynamic();
  
        m_type = type;
  
        switch (m_type) {
          case cDTString:
            m_pStr = crnlib_new<dynamic_string>();
            break;
          case cDTVec3F:
            m_pVec3F = crnlib_new<vec3F>();
            break;
          case cDTVec3I:
            m_pVec3I = crnlib_new<vec3I>();
            break;
          default:
            break;
        }
      }
    }
  
    void set_str(const dynamic_string& s) {
      if (m_type == cDTString)
        m_pStr->set(s);
      else {
        clear_dynamic();
  
        m_type = cDTString;
        m_pStr = crnlib_new<dynamic_string>(s);
      }
    }
  
    void set_str(const char* p) {
      if (m_type == cDTString)
        m_pStr->set(p);
      else {
        clear_dynamic();
  
        m_type = cDTString;
        m_pStr = crnlib_new<dynamic_string>(p);
      }
    }
  
    enum { cUnionSize = 1 };
  
    union {
      bool m_bool;
      int m_int;
      uint m_uint;
      float m_float;
  
      vec3F* m_pVec3F;
      vec3I* m_pVec3I;
      dynamic_string* m_pStr;
  
      uint m_union[cUnionSize];
    };
  
    value_data_type m_type;
  };
  
  }  // namespace crnlib
  