#include "Fractal/IO/JSON.h"
#include "Fractal/IO/IReader.h"
#include "Fractal/IO/IWriter.h"
#include "Fractal/IO/ISerializedObject.h"
#include "Fractal/Ref.h"

#include "ctString.h"
#include "ctStringValue.h"

namespace Fractal
{
  namespace Impl
  {
    class JSONReader : public IJSONReader
    {
      FRACTAL_DEFAULT_INTERFACE;
    public:
      JSONReader(IStream* pStream)
        : m_pReader(Fractal_CreateStringReader(pStream))
      {}

      bool Read(flOUT ISerializedObject* pSerialized) override
      {
        do
        {
          ReadToken();
          ReadItem(pSerialized);
        }
        while (!m_eof);

        return true;
      }

      bool ReadItem(flOUT ISerializedObject* pSerialized)
      {
        if (Token().length() == 0)
          return false;
        if (Token() == "{")
          return ReadObject(pSerialized);
        else if (Token() == "[")
          return ReadArray(pSerialized);
        else
          return ReadValue(pSerialized);
      }

      bool ReadObject(ISerializedObject* pInto)
      {
        ctString key = "";

        ReadToken();
        while (ReadString(&key))
        {
          if (ReadToken() != ":")
            return false;

          ReadToken();
          ReadItem(pInto->Add(key));
          ReadToken();

          if (Token() == "}")
            break;

          if (Token() == ",")
            ReadToken();
        }

        return true;
      }

      bool ReadArray(ISerializedObject* pInto)
      {
        Ref<ISerializedObject> pItem = nullptr;
        ReadToken();
        while (!m_eof && Token() != "]")
        {
          pItem = Fractal_CreateSerializedObject();
          if (Token() == ",")
            ReadToken();

          if (ReadItem(pItem.Get()))
            pInto->Add(pItem.Get());
          ReadToken();
        }

        return true;
      }

      bool ReadValue(ISerializedObject* pInto)
      {
        ctString strValue;
        if (Token() == "null")
        {
          pInto->SetObjectType(SerializedObjectType_Null);
        }
        else if (ReadString(&strValue))
        {
          pInto->SetString(strValue.c_str());
        }
        else
        {
          ctStringValue value;
          value.Parse(Token());
          switch (value.GetType())
          {
          case ctType_Float64: pInto->SetFloat(value.AsFloat()); break;
          case ctType_Int64:   pInto->SetInt(value.AsInt()); break;
          case ctType_Uint8:   pInto->SetBool(value.AsBool()); break;
          case ctType_Unknown: return false;
          }
        }

        return true;
      }

      bool ReadString(ctString* pOut)
      {
        *pOut = "";
        if (Token() != "\"")
          return false;
        ReadToken("\"");
        if (Token() != "\"")
        {
          *pOut = Token();
          ReadToken();
        }
        return true;
      }

      bool FindToken(const ctString& token)
      {
        while (ReadToken() != token)
          if (m_eof)
            return false;
        return true;
      }

      const ctString& Token() const
      {
        return m_token;
      }

      const ctString& ReadToken()
      {
        return ReadToken(m_delim);
      }

      const ctString& ReadToken(ctString const& delim)
      {
        m_token = ctString();

        while (!m_eof && m_token.length() == 0)
        {
          char nextByte = 0;
          bool endToken = false;
          while (!m_eof && !endToken)
          {
            if (!m_pReader->Stream()->Peek(&nextByte, 1))
            {
              m_eof = true;
              break;
            }

            endToken |= delim.find(nextByte) != -1;
            if (!endToken || m_token.length() == 0)
            {
              m_token += nextByte;
              m_pReader->Stream()->Seek(1, SeekOrigin_Current);
            }
          }

          m_token = m_token.trim();
        }

        return m_token;
      }

      IStream* Stream() const override
      {
        return m_pReader->Stream();
      }

    private:
      ctString m_delim = "\",{}[] \r\n\t";
      ctString m_token;
      bool m_eof = false;
      Ref<IStringReader> m_pReader;
    };

    class JSONWriter : public IJSONWriter
    {
      FRACTAL_DEFAULT_INTERFACE;
    public:
      JSONWriter(IStream* pStream)
        : m_pWriter(Fractal_CreateStringWriter(pStream))
      {}

      bool Write(flIN ISerializedObject const* pSerialized) override
      {
        if (pSerialized == nullptr)
          return false;

        switch (pSerialized->GetObjectType())
        {
        case SerializedObjectType_Array:
          m_pWriter->Write("[", 1);
          Indent();
          NewLine();

          for (int64_t i = 0; i < pSerialized->GetArraySize(); ++i)
          {
            ISerializedObject* pItem = pSerialized->Get(i);
            Write(pItem);

            if (i < pSerialized->GetArraySize() - 1)
            {
              m_pWriter->Write(",", 1);
              NewLine();
            }
          }

          Unindent();
          NewLine();
          m_pWriter->Write("]", 1);
          break;

        case SerializedObjectType_Object:
          m_pWriter->Write("{", 1);

          Indent();
          NewLine();

          for (int64_t i = 0; i < pSerialized->GetMemberCount(); ++i)
          {
            char const* name = pSerialized->GetMemberName(i);
            m_pWriter->Write("\"", 1);
            m_pWriter->Write(name);
            m_pWriter->Write("\"", 1);
            m_pWriter->Write(": ", 2);
            ISerializedObject* pItem = pSerialized->Get(name);
            Write(pItem);

            if (i < pSerialized->GetMemberCount() - 1)
            {
              m_pWriter->Write(",", 1);
              NewLine();
            }
          }

          Unindent();
          NewLine();
          m_pWriter->Write("}", 1);
          break;

        case SerializedObjectType_Bool:
          m_pWriter->Write(pSerialized->GetBool());
          break;

        case SerializedObjectType_Int:
          m_pWriter->Write(pSerialized->GetInt());
          break;

        case SerializedObjectType_Float:
          m_pWriter->Write(pSerialized->GetFloat());
          break;

        case SerializedObjectType_String:
          m_pWriter->Write("\"", 1);
          m_pWriter->Write(pSerialized->GetString());
          m_pWriter->Write("\"", 1);
          break;

        case SerializedObjectType_Null:
          m_pWriter->Write("null", 4);
          break;
        }
        return true;
      }

      IStream* Stream() const override
      {
        return m_pWriter->Stream();
      }

      void NewLine()
      {
        m_pWriter->Write("\n", 1);
        m_pWriter->Write(m_indentLevel, m_indentLevel.length());
      }

      void Indent()   { m_indentLevel = m_indentLevel + "  "; }
      void Unindent() { m_indentLevel = m_indentLevel.substr(ctMax(0ll, m_indentLevel.length() - 2)); }

    private:
      ctString m_indentLevel = "";
      Ref<IStringWriter> m_pWriter;
    };
  }
}

Fractal::IJSONWriter* Fractal_CreateJSONWriter(flIN Fractal::IStream* pWriteStream)
{
  return flNew Fractal::Impl::JSONWriter(pWriteStream);
}

Fractal::IJSONReader* Fractal_CreateJSONReader(flIN Fractal::IStream* pReadStream)
{
  return flNew Fractal::Impl::JSONReader(pReadStream);
}
#include "Fractal/IO/JSON.h"
#include "Fractal/IO/IReader.h"
#include "Fractal/IO/IWriter.h"
#include "Fractal/IO/ISerializedObject.h"
#include "Fractal/Ref.h"

#include "ctString.h"
#include "ctStringValue.h"

namespace Fractal
{
  namespace Impl
  {
    class JSONReader : public IJSONReader
    {
      FRACTAL_DEFAULT_INTERFACE;
    public:
      JSONReader(IStream* pStream)
        : m_pReader(Fractal_CreateStringReader(pStream))
      {}

      bool Read(flOUT ISerializedObject* pSerialized) override
      {
        do
        {
          ReadToken();
          ReadItem(pSerialized);
        }
        while (!m_eof);

        return true;
      }

      bool ReadItem(flOUT ISerializedObject* pSerialized)
      {
        if (Token().length() == 0)
          return false;
        if (Token() == "{")
          return ReadObject(pSerialized);
        else if (Token() == "[")
          return ReadArray(pSerialized);
        else
          return ReadValue(pSerialized);
      }

      bool ReadObject(ISerializedObject* pInto)
      {
        ctString key = "";

        ReadToken();
        while (ReadString(&key))
        {
          if (ReadToken() != ":")
            return false;

          ReadToken();
          ReadItem(pInto->Add(key));
          ReadToken();

          if (Token() == "}")
            break;

          if (Token() == ",")
            ReadToken();
        }

        return true;
      }

      bool ReadArray(ISerializedObject* pInto)
      {
        Ref<ISerializedObject> pItem = nullptr;
        ReadToken();
        while (!m_eof && Token() != "]")
        {
          pItem = Fractal_CreateSerializedObject();
          if (Token() == ",")
            ReadToken();

          if (ReadItem(pItem.Get()))
            pInto->Add(pItem.Get());
          ReadToken();
        }

        return true;
      }

      bool ReadValue(ISerializedObject* pInto)
      {
        ctString strValue;
        if (Token() == "null")
        {
          pInto->SetObjectType(SerializedObjectType_Null);
        }
        else if (ReadString(&strValue))
        {
          pInto->SetString(strValue.c_str());
        }
        else
        {
          ctStringValue value;
          value.Parse(Token());
          switch (value.GetType())
          {
          case ctType_Float64: pInto->SetFloat(value.AsFloat()); break;
          case ctType_Int64:   pInto->SetInt(value.AsInt()); break;
          case ctType_Uint8:   pInto->SetBool(value.AsBool()); break;
          case ctType_Unknown: return false;
          }
        }

        return true;
      }

      bool ReadString(ctString* pOut)
      {
        *pOut = "";
        if (Token() != "\"")
          return false;
        ReadToken("\"");
        if (Token() != "\"")
        {
          *pOut = Token();
          ReadToken();
        }
        return true;
      }

      bool FindToken(const ctString& token)
      {
        while (ReadToken() != token)
          if (m_eof)
            return false;
        return true;
      }

      const ctString& Token() const
      {
        return m_token;
      }

      const ctString& ReadToken()
      {
        return ReadToken(m_delim);
      }

      const ctString& ReadToken(ctString const& delim)
      {
        m_token = ctString();

        while (!m_eof && m_token.length() == 0)
        {
          char nextByte = 0;
          bool endToken = false;
          while (!m_eof && !endToken)
          {
            if (!m_pReader->Stream()->Peek(&nextByte, 1))
            {
              m_eof = true;
              break;
            }

            endToken |= delim.find(nextByte) != -1;
            if (!endToken || m_token.length() == 0)
            {
              m_token += nextByte;
              m_pReader->Stream()->Seek(1, SeekOrigin_Current);
            }
          }

          m_token = m_token.trim();
        }

        return m_token;
      }

      IStream* Stream() const override
      {
        return m_pReader->Stream();
      }

    private:
      ctString m_delim = "\",{}[] \r\n\t";
      ctString m_token;
      bool m_eof = false;
      Ref<IStringReader> m_pReader;
    };

    class JSONWriter : public IJSONWriter
    {
      FRACTAL_DEFAULT_INTERFACE;
    public:
      JSONWriter(IStream* pStream)
        : m_pWriter(Fractal_CreateStringWriter(pStream))
      {}

      bool Write(flIN ISerializedObject const* pSerialized) override
      {
        if (pSerialized == nullptr)
          return false;

        switch (pSerialized->GetObjectType())
        {
        case SerializedObjectType_Array:
          m_pWriter->Write("[", 1);
          Indent();
          NewLine();

          for (int64_t i = 0; i < pSerialized->GetArraySize(); ++i)
          {
            ISerializedObject* pItem = pSerialized->Get(i);
            Write(pItem);

            // Write separator if this is not the last element
            if (i < pSerialized->GetArraySize() - 1)
            {
              m_pWriter->Write(",", 1);
              NewLine();
            }
          }

          Unindent();
          NewLine();
          m_pWriter->Write("]", 1);
          break;

        case SerializedObjectType_Object:
          m_pWriter->Write("{", 1);

          Indent();
          NewLine();

          for (int64_t i = 0; i < pSerialized->GetMemberCount(); ++i)
          {
            char const* name = pSerialized->GetMemberName(i);
            m_pWriter->Write("\"", 1);
            m_pWriter->Write(name);
            m_pWriter->Write("\"", 1);
            m_pWriter->Write(": ", 2);
            ISerializedObject* pItem = pSerialized->Get(name);
            Write(pItem);
            // Write separator if this is not the last element
            if (i < pSerialized->GetMemberCount() - 1)
            {
              m_pWriter->Write(",", 1);
              NewLine();
            }
          }

          Unindent();
          NewLine();
          m_pWriter->Write("}", 1);
          break;

        case SerializedObjectType_Bool:
          m_pWriter->Write(pSerialized->GetBool());
          break;

        case SerializedObjectType_Int:
          m_pWriter->Write(pSerialized->GetInt());
          break;

        case SerializedObjectType_Float:
          m_pWriter->Write(pSerialized->GetFloat());
          break;

        case SerializedObjectType_String:
          m_pWriter->Write("\"", 1);
          m_pWriter->Write(pSerialized->GetString());
          m_pWriter->Write("\"", 1);
          break;

        case SerializedObjectType_Null:
          m_pWriter->Write("null", 4);
          break;
        }
        return true;
      }

      IStream* Stream() const override
      {
        return m_pWriter->Stream();
      }

      void NewLine()
      {
        m_pWriter->Write("\n", 1);
        m_pWriter->Write(m_indentLevel, m_indentLevel.length());
      }

      void Indent()   { m_indentLevel = m_indentLevel + "  "; }
      void Unindent() { m_indentLevel = m_indentLevel.substr(ctMax(0ll, m_indentLevel.length() - 2)); }

    private:
      ctString m_indentLevel = "";
      Ref<IStringWriter> m_pWriter;
    };
  }
}

Fractal::IJSONWriter* Fractal_CreateJSONWriter(flIN Fractal::IStream* pWriteStream)
{
  return flNew Fractal::Impl::JSONWriter(pWriteStream);
}

Fractal::IJSONReader* Fractal_CreateJSONReader(flIN Fractal::IStream* pReadStream)
{
  return flNew Fractal::Impl::JSONReader(pReadStream);
}
