// File: crn_value.h
// This software is in the public domain. Please see license.txt.
#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, ',') != NULL)
         {
            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 = NULL;

            m_type = cDTInvalid;
         }
         else if (m_type == cDTVec3I)
         {
            crnlib_delete(m_pVec3I);
            m_pVec3I = NULL;

            m_type = cDTInvalid;
         }
         else if (m_type == cDTString)
         {
            crnlib_delete(m_pStr);
            m_pStr = NULL;

            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

