#include <type_traits>
#include <cstdint>
#include <cctype>
#include <vector>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>


// Some basic types that should build serializable structures from
template<typename T>
using Array      = std::vector<T>;
using Boolean    = bool;
using Byte       = uint8_t;
using Char       = int8_t;
using Unsigned32 = uint32_t;
using Unsigned64 = uint64_t;
using Integer32  = int32_t;
using Integer64  = int64_t;
using Float32    = float;
using Float64    = double;
using String     = std::string;
// discourage 16bit types, CPUs aren't so efficient at these
// TODO: enums

/*
 
   about enums:
   
        Looks like C++17 might make reflection of enums nice.
            http://aantron.github.io/better-enums/demo/C++17ReflectionProposal.html
        In the mean time, something like this gets close with the use of an ugly macro where the enums are declared
        however it provides similar syntax to what C++17 may have for using the reflection
            http://melpon.org/wandbox/permlink/QelcwZNLi4gIx8Ux
 
 */

struct EnumBaseValue
{
  Integer32   id;
  String      name;
};


struct EnumBase
{
  String                name;
  Array<EnumBaseValue>  values;
};

#if 0

#if 1
template <class V, class T>
void Visit(V& v, T& var, typename V::Visit(T&))//, typename std::enable_if< ! std::is_function< decltype(T::Visit)(V&) >::value , T >::type* = 0 )
// typename std::enable_if< false /* !std::is_function<T::Visit>::value*/ >::type
//void Visit(V& v, T& var,  typename std::enable_if< std::is_function<T::Visit>::type , T >::type* = 0 )
{
  v.Visit(var);
}
#endif

template <class V, class T>
void Visit(V& v, T& var) // , typename T::Visit)
//, typename std::enable_if< std::is_function< decltype(T::Visit)(V&) >::value , T >::type* = 0 )
{
  var.Visit(v);
}

template <class V>
void Visit(V& v, Integer32& i)
{
  v.Visit(i);
}

template <class V>
void Visit(V& v, String& str)
{
  v.Visit(str);
}

template <class V, class T>
void Visit(V& v, Array<T>& arr)
{
  v.Visit(arr);
}

#endif


class XMLSerializerVisitor
{
public:
  XMLSerializerVisitor()
  {
    reset();
  }

  void Enter(const char* name)
  {
    indent(depth);
    output += "<";
    output += name;
    output += ">\n";
    depth++;
  }

  void Exit(const char* name)
  {
    depth--;
    indent(depth);
    output += "</";
    output += name;
    output += ">\n";
  }

  template <class T>
  void Visit(Array<T>& arr)
  {
/*
    Integer32 siz = arr.size();
    Visit("length", siz);
    for (int i = 0; i < arr.size(); i++)
    {
      char buf[1024];
      snprintf(buf, 1024, "element[%i]", i);
      Visit(buf, arr.at(i));
    }
*/
    for (auto i : arr)
    {
      Visit("item", i);
    }
  }

  void Visit(Integer32& var)
  {
    indent(depth);
    output += std::to_string(var);
    output += "\n";
  }

  void Visit(String& var)
  {
    // TODO: limitiation is that the string can not have a new line
    indent(depth);
    output += var;
    output += "\n";
  }

  template <class T>
  void Visit(const char* name, T& var)
  {
    Enter(name);
    //::Visit(*this, var);
    Visit(var);
    Exit(name);
  }

  template <class T>
  void Visit(T& var)
  {
    var.Visit(*this);
  }

  std::string& Output()
  {
    output += "</xml>\n";
    return output;
  }
  void reset()
  {
    output = "<xml>\n";
    depth = 1;
  }
private:
  int depth = 0;
  std::string output;
  
  void indent(int spaces)
  {
    for(int i = 0; i < spaces; i++)
    {
      output += "  ";
    }
  }
};



class XMLDeserializerVisitor
{
public:
  XMLDeserializerVisitor(std::string data) : input(data)
  {
    String str;
    Visit(str);
    // deserialize the <xml> tag
  }

  void Enter(const char* name)
  {
    String str;
    Visit(str);
    //printf("enter deserializing for -%s-, got -%s-\n", name, str.c_str());
  }
  void Exit(const char* name)
  {
    String str;
    Visit(str);
    //printf("exit deserializing for -%s-, got -%s-\n", name, str.c_str());
  }

  template <class T>
  void Visit(Array<T>& arr)
  {
/*
    Integer32 siz = 0;
    Visit("length", siz);
    arr.resize(siz);
    //printf("deserializing array of len: %i\n", siz);
    for (int i = 0; i < arr.size(); i++)
    {
      char buf[1024];
      snprintf(buf, 1024, "element[%i]", i);
      Visit(buf, arr.at(i));
    }
*/
    while (true)
    {
      int oldOffset = offset;
      String str;
      Visit(str);
      offset = oldOffset;
      if (str != "<item>")
        break;

      T item;
      Visit("item", item);
      arr.push_back(item);
    }
  }

  void Visit(Integer32& var)
  {
    const char* ptr = input.c_str();
    char* endPtr;
    var = strtol(ptr + offset, &endPtr, 10);
    offset = endPtr - ptr;
  }

  void Visit(String& var)
  {
    stripWhiteSpace();
    int newLineOff = input.find("\n", offset);
    if (newLineOff != std::string::npos)
        var = input.substr(offset, newLineOff-offset);
    offset = newLineOff;
  }
/*
  template <class T>
  void Visit(const char* name, T& var)
  {
    Enter(name);
    ::Visit(*this, var);
    Exit(name);
  }
  */
  template <class T>
  void Visit(const char* name, T& var)
  {
    Enter(name);
    Visit(var);
    Exit(name);
  }

  template <class T>
  void Visit(T& var)
  {
    var.Visit(*this);
  }

private:
  std::string input;
  int offset = 0;
  void stripWhiteSpace()
  {
    while (isspace(input.c_str()[offset]))
    {
        offset++;
    }
  }
};


class JsonSerializerVisitor
{
public:
  JsonSerializerVisitor()
  {
    reset();
  }

  void Enter(const char* name)
  {
    output += indent(depth) + quoted(name) + " : {\n";
    depth++;
  }

  void Exit(const char* name)
  {
    output += "\n" + indent(--depth) + "}\n";
  }

  template <class T>
  void Visit(Array<T>& arr)
  {
    output += "[ ";
    for (int i = 0; i < arr.size(); i++)
    {
      Visit(arr.at(i));
      if ( i != arr.size() - 1 )
        output += " ,";
      output += " ";
    }
    output += "]";
  }

  void Visit(Integer32& var)
  {
    output += std::to_string(var);
  }

  void Visit(String& var)
  {
    output += quoted(var.c_str());
  }

  template <class T>
  void Visit(const char* name, T& var)
  {
    if (lastDepth == depth)
        output += " ,\n";
    lastDepth = depth;

    output += indent(depth) + quoted(name) + " : ";
    // ::Visit(*this, var);
    Visit(var);
  }

  template <class T>
  void Visit(T& var)
  {
    var.Visit(*this);
  }

  std::string& Output()
  {
    output += "}\n";
    return output;
  }
  void reset()
  {
    output = "{\n";
    depth = 1;
  }
private:
  int lastDepth = 0;
  int depth = 0;
  std::string output;
  
  std::string quoted(const char* name)
  {
    return std::string("\"" + std::string(name) + "\"");
  }
  std::string indent(int spaces)
  {
    return std::string(spaces*2, ' ');
  }
};



class JsonDeserializerVisitor
{
public:
  JsonDeserializerVisitor(std::string data) : input(data)
  {
    String str;
    ParseString(str); // gets the '{' 
    assert(str == "{");
  }

  void ParseString(String& var)
  {
    stripWhiteSpace();
    int pos = offset;
    while (input.c_str()[pos])
    {
        if (isspace(input.c_str()[pos]))
        {
            var = input.substr(offset, pos-offset);
            offset = pos;
            return;
        }
        pos++;
    }
  }

  void Enter(const char* name)
  {
    String str;
    ParseString(str); // gets the name
    //printf("name: -%s-", quoted(name).c_str());
    //printf("str:  -%s-", str.c_str());
    assert(str == quoted(name));
    ParseString(str); // gets the ':'
    assert(str == ":");
    ParseString(str); // gets the '{'
    assert(str == "{");
    //printf("enter deserializing for -%s-, got -%s-\n", name, str.c_str());
  }
  void Exit(const char* name)
  {
    String str;
    ParseString(str); // gets the '}'
    assert(str == "}");
    //printf("exit deserializing for -%s-, got -%s-\n", name, str.c_str());
  }

  template <class T>
  void Visit(Array<T>& arr)
  {
    String str;
    ParseString(str); // gets the '['
    assert(str == "[");
    while (true)
    {
      T item;
      Visit(item);
      arr.push_back(item);

      String str;
      ParseString(str); // gets the ',' or ']' or '],'
      if (str != ",")
      {
        //printf("expecting ]");
        //printf("str:  -%s-", str.c_str());
        assert(str == "]");
        break;
      }
    }
  }

  void Visit(Integer32& var)
  {
    const char* ptr = input.c_str();
    char* endPtr;
    var = strtol(ptr + offset, &endPtr, 10);
    offset = endPtr - ptr;
  }

  void Visit(String& var)
  {
    // TODO
    stripWhiteSpace();
    int newLineOff = input.find("\"", offset + 1);
    if (newLineOff != std::string::npos)
        var = input.substr(offset + 1, newLineOff - offset - 1);
    offset = newLineOff + 1;
  }

  template <class T>
  void Visit(const char* name, T& var)
  {
    String str;
    ParseString(str); // gets the name
    //printf("name: -%s-", quoted(name).c_str());
    //printf("str:  -%s-", str.c_str());
    assert(str == quoted(name));
    ParseString(str); // gets the ':'
    assert(str == ":");
    // ::Visit(*this, var);
    Visit(var);

    int oldOffset = offset;
    ParseString(str); // gets the ',' if there
    if (str != ",")
        offset = oldOffset;
  }

  template <class T>
  void Visit(T& var)
  {
    var.Visit(*this);
  }

private:
  std::string input;
  int offset = 0;
  std::string quoted(const char* name)
  {
    return std::string("\"" + std::string(name) + "\"");
  }
  void stripWhiteSpace()
  {
    while (isspace(input.c_str()[offset]))
    {
        offset++;
    }
  }
};



struct Person
{
  Boolean        m_bool;
  Byte           m_uint8;
  Char           m_int8;
  Unsigned32     m_uint32;
  Unsigned64     m_uint64;
  Integer32      m_int32;
  Integer64      m_int64;
  Float32        m_flt32;
  Float64        m_flt64;

  Integer32      id;
  String         name;
  Array<String>  email;
  Array<String>  phoneNumbers;

  // This can do both the serializing and de-serializing
  // This might be good to have generated, but possibly being more explicit could be better too
  // Also note the members could be private and this will still work
  template <class V>
  void Visit(V& v)
  {
    v.Enter("person");
    v.Visit("id",id);
    v.Visit("name",name);
    v.Visit("email",email);
    v.Visit("numbers",phoneNumbers);
    v.Exit("person");
  }
};


/*
struct Employer
{
  String  name;
  
};
*/

/*
struct Property
{
  Property() : m_type(PT_Invalid) {}
  ~Property() {}

  explicit Property(Boolean    a_bool  ) : m_type(PT_Boolean   ) { m_basicType.m_bool   = a_bool  ; }
  explicit Property(Byte       a_uint8 ) : m_type(PT_Byte      ) { m_basicType.m_uint8  = a_uint8 ; }
  explicit Property(Char       a_int8  ) : m_type(PT_Char      ) { m_basicType.m_int8   = a_int8  ; }
  explicit Property(Unsigned32 a_uint32) : m_type(PT_Unsigned32) { m_basicType.m_uint32 = a_uint32; }
  explicit Property(Unsigned64 a_uint64) : m_type(PT_Unsigned64) { m_basicType.m_uint64 = a_uint64; }
  explicit Property(Integer32  a_int32 ) : m_type(PT_Integer32 ) { m_basicType.m_int32  = a_int32 ; }
  explicit Property(Integer64  a_int64 ) : m_type(PT_Integer64 ) { m_basicType.m_int64  = a_int64 ; }
  explicit Property(Float32    a_flt32 ) : m_type(PT_Float32   ) { m_basicType.m_flt32  = a_flt32 ; }
  explicit Property(Float64    a_flt64 ) : m_type(PT_Float64   ) { m_basicType.m_flt64  = a_flt64 ; }
  explicit Property(const Array<Property>& a_array ) : m_type(PT_Array ) { m_array      = a_array ; }
  explicit Property(const String&          a_string) : m_type(PT_String) { m_string     = a_string; }

  enum PropertyType
  {
    // Uninitialize state
    PT_Invalid    = 0,
    // Basic types
    PT_Boolean    = 1, 
    PT_Byte       = 2, 
    PT_Char       = 3, 
    PT_Unsigned32 = 4,
    PT_Unsigned64 = 5,
    PT_Integer32  = 6, 
    PT_Integer64  = 7, 
    PT_Float32    = 8, 
    PT_Float64    = 9, 
    // Complex types
    PT_Array      = 10,
    PT_String     = 11,
  };

  PropertyType     m_type;

  union {
    Boolean        m_bool;
    Byte           m_uint8;
    Char           m_int8;
    Unsigned32     m_uint32;
    Unsigned64     m_uint64;
    Integer32      m_int32;
    Integer64      m_int64;
    Float32        m_flt32;
    Float64        m_flt64;
  } m_basicType;

  Array<Property>  m_array;
  String           m_string;

  // This can do both the serializing and de-serializing
  // This might be good to have generated, but possibly being more explicit could be better too
  // Also note the members could be private and this will still work
  template <class V>
  void Visit(V& v)
  {
    v.Enter("Property");
    Integer32 t = m_type;
    v.Visit("type", t);
    switch (m_type)
    {
      case PT_Boolean:
        v.Visit("value", m_basicType.m_bool); break;
      case PT_Byte:
        v.Visit("value", m_basicType.m_uint8); break;
      case PT_Char:
        v.Visit("value", m_basicType.m_int8); break;
      case PT_Unsigned32:
        v.Visit("value", m_basicType.m_uint32); break;
      case PT_Unsigned64:
        v.Visit("value", m_basicType.m_uint64); break;
      case PT_Integer32:
        v.Visit("value", m_basicType.m_int32); break;
      case PT_Integer64:
        v.Visit("value", m_basicType.m_int64); break;
      case PT_Float32:
        v.Visit("value", m_basicType.m_flt32); break;
      case PT_Float64:
        v.Visit("value", m_basicType.m_flt64); break;
      case PT_Array:
        v.Visit("value", m_array); break;
      case PT_String:
        v.Visit("value", m_string); break;
    }
    v.Exit("Property");
  }
};
*/


int main(int argc, char* argv[])
{
  std::string input;

  {  
    Person a;
    a.id = 10;
    a.name = "blah";
    a.email.push_back("em@ail.com");
    a.email.push_back("other@add.com");
    a.phoneNumbers.push_back("123555");

    XMLSerializerVisitor serializer;
    serializer.Visit(a);
    printf("%s", serializer.Output().c_str());
    input = serializer.Output();
  }

  {
    Person b;

    XMLDeserializerVisitor deserializer(input);
    deserializer.Visit(b);

    {
      XMLSerializerVisitor serializer;
      serializer.Visit(b);
      printf("%s", serializer.Output().c_str());
    }
  }


  {  
    Person a;
    a.id = 10;
    a.name = "blah";
    a.email.push_back("em@ail.com");
    a.email.push_back("other@add.com");
    a.phoneNumbers.push_back("123555");

    JsonSerializerVisitor serializer;
    serializer.Visit(a);
    printf("%s", serializer.Output().c_str());
    input = serializer.Output();
  }

  {
    Person b;

    JsonDeserializerVisitor deserializer(input);
    deserializer.Visit(b);

    {
      JsonSerializerVisitor serializer;
      serializer.Visit(b);
      printf("%s", serializer.Output().c_str());
    }
  }
/*
  {  
    Property p("hello world");
    Property i(100);
    Array<Property> vals;
    vals.push_back(p);
    vals.push_back(i);
    Property a(vals);

    JsonSerializerVisitor serializer;
    serializer.Visit(a);
    printf("%s", serializer.Output().c_str());
    input = serializer.Output();
  }
*/
}




