// File: data_stream_serializer.h
// This software is in the public domain. Please see license.txt.
#pragma once
#include "crn_data_stream.h"

namespace crnlib
{
   // Defaults to little endian mode.
   class data_stream_serializer
   {
   public:
      data_stream_serializer() : m_pStream(NULL), m_little_endian(true) { }
      data_stream_serializer(data_stream* pStream) : m_pStream(pStream), m_little_endian(true) { }
      data_stream_serializer(data_stream& stream) : m_pStream(&stream), m_little_endian(true) { }
      data_stream_serializer(const data_stream_serializer& other) : m_pStream(other.m_pStream), m_little_endian(other.m_little_endian) { }
      
      data_stream_serializer& operator= (const data_stream_serializer& rhs) { m_pStream = rhs.m_pStream; m_little_endian = rhs.m_little_endian; return *this; }

      data_stream* get_stream() const { return m_pStream; }
      void set_stream(data_stream* pStream) { m_pStream = pStream; }

      const dynamic_string& get_name() const { return m_pStream ? m_pStream->get_name() : g_empty_dynamic_string; }

      bool get_error() { return m_pStream ? m_pStream->get_error() : false; }
      
      bool get_little_endian() const { return m_little_endian; }
      void set_little_endian(bool little_endian) { m_little_endian = little_endian; }
      
      bool write(const void* pBuf, uint len)
      {
         return m_pStream->write(pBuf, len) == len;
      }
                  
      bool read(void* pBuf, uint len)
      {
         return m_pStream->read(pBuf, len) == len;
      }
      
      // size = size of each element, count = number of elements, returns actual count of elements written
      uint write(const void* pBuf, uint size, uint count)
      {
         uint actual_size = size * count;
         if (!actual_size)
            return 0;
         uint n = m_pStream->write(pBuf, actual_size);
         if (n == actual_size)
            return count;
         return n / size;
      }

      // size = size of each element, count = number of elements, returns actual count of elements read
      uint read(void* pBuf, uint size, uint count)
      {
         uint actual_size = size * count;
         if (!actual_size)
            return 0;
         uint n = m_pStream->read(pBuf, actual_size);
         if (n == actual_size)
            return count;
         return n / size;
      }
      
      bool write_chars(const char* pBuf, uint len)
      {
         return write(pBuf, len);
      }
      
      bool read_chars(char* pBuf, uint len)
      {
         return read(pBuf, len);
      }
      
      bool skip(uint len)
      {
         return m_pStream->skip(len) == len;
      }
      
      template<typename T>
      bool write_object(const T& obj)
      {
         if (m_little_endian == c_crnlib_little_endian_platform)
            return write(&obj, sizeof(obj));
         else
         {
            uint8 buf[sizeof(T)];
            uint buf_size = sizeof(T);
            void* pBuf = buf;
            utils::write_obj(obj, pBuf, buf_size, m_little_endian);
            
            return write(buf, sizeof(T));
         }
      }
      
      template<typename T>
      bool read_object(T& obj)
      {
         if (m_little_endian == c_crnlib_little_endian_platform)
            return read(&obj, sizeof(obj));
         else
         {
            uint8 buf[sizeof(T)];
            if (!read(buf, sizeof(T)))
               return false;
            
            uint buf_size = sizeof(T);
            const void* pBuf = buf;
            utils::read_obj(obj, pBuf, buf_size, m_little_endian);
            
            return true;
         }
      }
         
      template<typename T>
      bool write_value(T value)
      {
         return write_object(value);
      }
      
      template<typename T>
      T read_value(const T& on_error_value = T())
      {
         T result;
         if (!read_object(result))
            result = on_error_value;
         return result;
      }
      
      template<typename T>
      bool write_enum(T e)
      {
         int val = static_cast<int>(e);
         return write_object(val);
      }

      template<typename T>
      T read_enum()
      {
         return static_cast<T>(read_value<int>());
      }
      
      // Writes uint using a simple variable length code (VLC).
      bool write_uint_vlc(uint val)
      {
         do
         {
            uint8 c = static_cast<uint8>(val) & 0x7F;
            if (val <= 0x7F)
               c |= 0x80;
               
            if (!write_value(c))
               return false;
            
            val >>= 7;
         } while (val);
         
         return true;
      }
      
      // Reads uint using a simple variable length code (VLC).
      bool read_uint_vlc(uint& val)
      {
         val = 0;
         uint shift = 0;
         
         for ( ; ; )
         {
            if (shift >= 32)
               return false;
               
            uint8 c;
            if (!read_object(c))
               return false;
            
            val |= ((c & 0x7F) << shift);
            shift += 7;
            
            if (c & 0x80)
               break;
         }
         
         return true;
      }
      
      bool write_c_str(const char* p)
      {
         uint len = static_cast<uint>(strlen(p));
         if (!write_uint_vlc(len))
            return false;
         
         return write_chars(p, len);
      }
      
      bool read_c_str(char* pBuf, uint buf_size)
      {
         uint len;
         if (!read_uint_vlc(len))
            return false;
         if ((len + 1) > buf_size)
            return false;
         
         pBuf[len] = '\0';
         
         return read_chars(pBuf, len);
      }
      
      bool write_string(const dynamic_string& str)
      {
         if (!write_uint_vlc(str.get_len()))
            return false;

         return write_chars(str.get_ptr(), str.get_len());
      }
      
      bool read_string(dynamic_string& str)
      {
         uint len;
         if (!read_uint_vlc(len))
            return false;
         
         if (!str.set_len(len))
            return false;
         
         if (len)
         {
            if (!read_chars(str.get_ptr_raw(), len))
               return false;
            
            if (memchr(str.get_ptr(), 0, len) != NULL)
            {
               str.truncate(0);
               return false;
            }
         }
         
         return true;
      }
                  
      template<typename T>
      bool write_vector(const T& vec)
      {
         if (!write_uint_vlc(vec.size()))
            return false;
         
         for (uint i = 0; i < vec.size(); i++)
         {
            *this << vec[i];
            if (get_error())
               return false;
         }
         
         return true;            
      };
      
      template<typename T>
      bool read_vector(T& vec, uint num_expected = UINT_MAX)
      {
         uint size;
         if (!read_uint_vlc(size))
            return false;
         
         if ((size * sizeof(T::value_type)) >= 2U*1024U*1024U*1024U)
            return false;
         
         if ((num_expected != UINT_MAX) && (size != num_expected))
            return false;
       
         vec.resize(size);
         for (uint i = 0; i < vec.size(); i++)
         {
            *this >> vec[i];
            
            if (get_error())
               return false;
         }
         
         return true;
      }

      bool read_entire_file(crnlib::vector<uint8>& buf)
      {
         return m_pStream->read_array(buf);
      }

      bool write_entire_file(const crnlib::vector<uint8>& buf)
      {
         return m_pStream->write_array(buf);
      }
      
      // Got this idea from the Molly Rocket forums.
      // fmt may contain the characters "1", "2", or "4".
      bool writef(char *fmt, ...) 
      { 
         va_list v; 
         va_start(v, fmt); 
         
         while (*fmt) 
         { 
            switch (*fmt++) 
            { 
               case '1': 
               { 
                  const uint8 x = static_cast<uint8>(va_arg(v, uint)); 
                  if (!write_value(x))
                     return false;
               } 
               case '2': 
               { 
                  const uint16 x = static_cast<uint16>(va_arg(v, uint)); 
                  if (!write_value(x))
                     return false;
               } 
               case '4': 
               { 
                  const uint32 x = static_cast<uint32>(va_arg(v, uint)); 
                  if (!write_value(x))
                     return false;
               } 
               case ' ':
               case ',':
               {
                  break;
               }
               default: 
               {
                  CRNLIB_ASSERT(0); 
                  return false;
               }                  
            } 
         } 
         
         va_end(v); 
         return true;
      } 
      
      // Got this idea from the Molly Rocket forums.
      // fmt may contain the characters "1", "2", or "4".
      bool readf(char *fmt, ...) 
      { 
         va_list v; 
         va_start(v, fmt); 

         while (*fmt) 
         { 
            switch (*fmt++) 
            { 
               case '1': 
               { 
                  uint8* x = va_arg(v, uint8*); 
                  CRNLIB_ASSERT(x);
                  if (!read_object(*x))
                     return false;
               } 
               case '2': 
               { 
                  uint16* x = va_arg(v, uint16*); 
                  CRNLIB_ASSERT(x);
                  if (!read_object(*x))
                     return false;
               } 
               case '4': 
               { 
                  uint32* x = va_arg(v, uint32*);
                  CRNLIB_ASSERT(x);
                  if (!read_object(*x))
                     return false;
               }
               case ' ':
               case ',':
               {
                  break;
               }
               default: 
               {
                  CRNLIB_ASSERT(0); 
                  return false;
               }                  
            } 
         } 

         va_end(v); 
         return true;
      } 
            
   private:
      data_stream* m_pStream;

      bool m_little_endian;
   };
   
   // Write operators
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, bool val)           { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, int8 val)           { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, uint8 val)          { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, int16 val)          { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, uint16 val)         { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, int32 val)          { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, uint32 val)         { serializer.write_uint_vlc(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, int64 val)          { serializer.write_value(val); return serializer; } 
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, uint64 val)         { serializer.write_value(val); return serializer; } 
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, long val)           { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, unsigned long val)  { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, float val)          { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, double val)         { serializer.write_value(val); return serializer; }
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, const char* p)      { serializer.write_c_str(p); return serializer; }
   
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, const dynamic_string& str)
   {
      serializer.write_string(str);
      return serializer;
   }
   
   template<typename T>
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, const crnlib::vector<T>& vec)
   {
      serializer.write_vector(vec);
      return serializer;
   }
   
   template<typename T>
   inline data_stream_serializer& operator<< (data_stream_serializer& serializer, const T* p)
   {
      serializer.write_object(*p);
      return serializer;
   }
   
   // Read operators
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, bool& val)           { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, int8& val)           { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, uint8& val)          { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, int16& val)          { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, uint16& val)         { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, int32& val)          { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, uint32& val)         { serializer.read_uint_vlc(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, int64& val)          { serializer.read_object(val); return serializer; } 
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, uint64& val)         { serializer.read_object(val); return serializer; } 
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, long& val)           { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, unsigned long& val)  { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, float& val)          { serializer.read_object(val); return serializer; }
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, double& val)         { serializer.read_object(val); return serializer; }
      
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, dynamic_string& str)
   {
      serializer.read_string(str);
      return serializer;
   }
   
   template<typename T>
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, crnlib::vector<T>& vec)
   {
      serializer.read_vector(vec);
      return serializer;
   }
   
   template<typename T>
   inline data_stream_serializer& operator>> (data_stream_serializer& serializer, T* p)
   {
      serializer.read_object(*p);
      return serializer;
   }

} // namespace crnlib







