/*
 * nall - C++ template library
 *
 * Copyright © 2006-2020 byuu et al
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#pragma once

//serializer: a class designed to save and restore the state of classes.
//
//benefits:
//- data() will be portable in size (it is not necessary to specify type sizes.)
//- data() will be portable in endianness (always stored internally as little-endian.)
//- one serialize function can both save and restore class states.
//
//caveats:
//- only plain-old-data can be stored. complex classes must provide serialize(serializer&);
//- floating-point usage is not portable across different implementations

#include <cstdint>
#include <utility>

struct serializer;

/* This value is used by bsnes-jg as a state version number, since the state
   format broke during the 2.0.2 development cycle. The number is a hex
   representation of the bsnes-jg version when the state format last changed,
   where each byte represents the patch, minor, and major version from the
   least to most significant byte. The previously used "signature" in the state
   is used to determine whether the state was created previous to the first
   post-fork state format break.
*/
static const unsigned serializerVersion = 0x00020002;

template<typename T>
struct has_serialize {
  template<typename C> static char test(decltype(std::declval<C>().serialize(std::declval<serializer&>()))*);
  template<typename C> static long test(...);
  static const bool value = sizeof(test<T>(0)) == sizeof(char);
};

struct serializer {
  enum Mode : unsigned { Load, Save, Size };

  unsigned version = serializerVersion;

  Mode mode() const;
  const uint8_t* data() const;
  unsigned size() const;

  void setMode(Mode);

  template<typename T> serializer& boolean(T& value) {
    if(_mode == Save) {
      _data[_size++] = (bool)value;
    } else if(_mode == Load) {
      value = (bool)_data[_size++];
    } else if(_mode == Size) {
      _size += 1;
    }
    return *this;
  }

  template<typename T> serializer& integer(T& value) {
    enum : unsigned { intsize = std::is_same<bool, T>::value ? 1 : sizeof(T) };
    if(_mode == Save) {
      uint64_t copy = value;
      for(unsigned n = 0; n < intsize; ++n) _data[_size++] = copy, copy >>= 8;
    } else if(_mode == Load) {
      value = 0;
      for(unsigned n = 0; n < intsize; ++n) value |= (T)_data[_size++] << (n << 3);
    } else if(_mode == Size) {
      _size += intsize;
    }
    return *this;
  }

  template<typename T, int N> serializer& array(T (&_array)[N]) {
    for(unsigned n = 0; n < N; ++n) operator()(_array[n]);
    return *this;
  }

  template<typename T> serializer& array(T _array, unsigned size) {
    for(unsigned n = 0; n < size; ++n) operator()(_array[n]);
    return *this;
  }

  //optimized specializations

  template<int N> serializer& array(uint8_t (&data)[N]) {
    return array(data, N);
  }

  template<typename T> serializer& operator()(T& value, typename std::enable_if<has_serialize<T>::value>::type* = 0) {
    value.serialize(*this);
    return *this;
  }

  template<typename T> serializer& operator()(T& value, typename std::enable_if<std::is_integral<T>::value>::type* = 0) {
    return integer(value);
  }

  serializer& operator=(const serializer&);
  serializer();
  serializer(const serializer&);
  serializer(unsigned);
  serializer(const uint8_t*, unsigned);
  ~serializer();

private:
  Mode _mode = Size;
  uint8_t* _data = nullptr;
  unsigned _size = 0;
  unsigned _capacity = 0;
};
