//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2025 California Institute of Technology
//
// LDASTools frameCPP is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools frameCPP is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include "JsonArchive.hh"
#include <algorithm>
#include <iomanip>

namespace FrameCPP
{
    namespace Archive
    {
        //=================================================================
        // Constructor and Destructor
        //=================================================================

        JsonArchive::JsonArchive(std::ostream& os, 
                                unsigned int ver,
                                bool pretty,
                                bool include_types)
            : base_t(boost::archive::no_header)
            , m_os(os)
            , m_version(ver)
            , m_pretty_print(pretty)
            , m_include_types(include_types)
            , m_indent_level(0)
        {
            // Start with root object
            m_os << "{";
            if (m_pretty_print)
            {
                m_os << std::endl;
            }
            m_context.push(JsonContext(JsonContext::OBJECT, "root"));
            m_indent_level = 1;

            // Write metadata header
            if (m_pretty_print)
            {
                writeIndent();
            }
            m_os << "\"_metadata\": {";
            if (m_pretty_print) m_os << std::endl;
            
            m_indent_level++;
            if (m_pretty_print) writeIndent();
            m_os << "\"version\": " << m_version << ",";
            if (m_pretty_print) m_os << std::endl;
            
            if (m_pretty_print) writeIndent();
            m_os << "\"archive_type\": \"JsonArchive\",";
            if (m_pretty_print) m_os << std::endl;
            
            if (m_pretty_print) writeIndent();
            m_os << "\"boost_version\": " << BOOST_VERSION;
            if (m_pretty_print) m_os << std::endl;
            
            m_indent_level--;
            if (m_pretty_print) writeIndent();
            m_os << "},";
            if (m_pretty_print) m_os << std::endl;

            // Mark that we've written the first element
            m_context.top().first_element = false;
        }

        JsonArchive::~JsonArchive()
        {
            // Close root object
            if (m_pretty_print)
            {
                m_os << std::endl;
            }
            m_os << "}";
            if (m_pretty_print)
            {
                m_os << std::endl;
            }
        }

        //=================================================================
        // Core serialization methods
        //=================================================================

        void JsonArchive::save(const std::string& value)
        {
            writeRawValue("\"" + escapeJson(value) + "\"");
        }

        void JsonArchive::save(const char* value)
        {
            if (value)
            {
                save(std::string(value));
            }
            else
            {
                writeRawValue("null");
            }
        }

        void JsonArchive::save_end(const char* name)
        {
            endObject();
        }


        void JsonArchive::save_binary(const void* address, std::size_t count)
        {
            // Represent binary data as base64 or hex string
            beginArray("binary_data");
            const unsigned char* bytes = static_cast<const unsigned char*>(address);
            for (std::size_t i = 0; i < count && i < 64; ++i)  // Limit to first 64 bytes
            {
                writeArrayElement(static_cast<int>(bytes[i]));
            }
            if (count > 64)
            {
                writeComma();
                writeIndent();
                m_os << "\"_truncated\": " << (count - 64) << " bytes";
            }
            endArray();
        }

        //=================================================================
        // JSON structure methods
        //=================================================================

        void JsonArchive::beginObject(const char* name)
        {
            if (!inArray())
            {
                writeComma();
            }
            writeIndent();
            m_os << "{";
            if (m_pretty_print)
            {
                m_os << std::endl;
            }
            
            m_context.push(JsonContext(JsonContext::OBJECT, name ? name : ""));
            m_indent_level++;
        }

        void JsonArchive::endObject()
        {
            if (m_context.empty() || m_context.top().type != JsonContext::OBJECT)
            {
                return; // Error: mismatched begin/end
            }
            
            m_context.pop();
            m_indent_level--;
            
            if (m_pretty_print)
            {
                m_os << std::endl;
                writeIndent();
            }
            m_os << "}";
            
            // Update parent context
            if (!m_context.empty())
            {
                m_context.top().first_element = false;
            }
        }

        void JsonArchive::beginArray(const char* name)
        {
            if (!inArray())
            {
                writeComma();
                writeIndent();
                if (name)
                {
                    m_os << "\"" << name << "\": ";
                }
            }
            else
            {
                writeComma();
                writeIndent();
            }
            
            m_os << "[";
            if (m_pretty_print)
            {
                m_os << std::endl;
            }
            
            m_context.push(JsonContext(JsonContext::ARRAY, name ? name : ""));
            m_indent_level++;
        }

        void JsonArchive::endArray()
        {
            if (m_context.empty() || m_context.top().type != JsonContext::ARRAY)
            {
                return; // Error: mismatched begin/end
            }
            
            m_context.pop();
            m_indent_level--;
            
            if (m_pretty_print)
            {
                m_os << std::endl;
                writeIndent();
            }
            m_os << "]";
            
            // Update parent context
            if (!m_context.empty())
            {
                m_context.top().first_element = false;
            }
        }

        //=================================================================
        // Helper methods
        //=================================================================

        void JsonArchive::writeIndent()
        {
            if (m_pretty_print)
            {
                for (int i = 0; i < m_indent_level; ++i)
                {
                    m_os << "  ";
                }
            }
        }

        void JsonArchive::writeComma()
        {
            if (!m_context.empty() && !m_context.top().first_element)
            {
                m_os << ",";
                if (m_pretty_print)
                {
                    m_os << std::endl;
                }
            }
        }

        std::string JsonArchive::escapeJson(const std::string& str)
        {
            std::string escaped;
            escaped.reserve(str.length() + str.length() / 10); // Reserve extra space for escapes
            
            for (char c : str)
            {
                switch (c)
                {
                    case '"':  escaped += "\\\""; break;
                    case '\\': escaped += "\\\\"; break;
                    case '/':  escaped += "\\/"; break;
                    case '\b': escaped += "\\b"; break;
                    case '\f': escaped += "\\f"; break;
                    case '\n': escaped += "\\n"; break;
                    case '\r': escaped += "\\r"; break;
                    case '\t': escaped += "\\t"; break;
                    default:
                        if (static_cast<unsigned char>(c) < 32)
                        {
                            // Control characters
                            std::ostringstream oss;
                            oss << "\\u" << std::hex << std::setw(4) << std::setfill('0') 
                                << static_cast<int>(static_cast<unsigned char>(c));
                            escaped += oss.str();
                        }
                        else
                        {
                            escaped += c;
                        }
                        break;
                }
            }
            return escaped;
        }

        void JsonArchive::writeRawValue(const std::string& value)
        {
            if (inArray())
            {
                writeComma();
                writeIndent();
            }
            else if (!m_context.empty())
            {
                writeComma();
            }
            
            m_os << value;
            
            if (!m_context.empty())
            {
                m_context.top().first_element = false;
            }
        }

        bool JsonArchive::inArray() const
        {
            return !m_context.empty() && m_context.top().type == JsonContext::ARRAY;
        }

        bool JsonArchive::inObject() const
        {
            return !m_context.empty() && m_context.top().type == JsonContext::OBJECT;
        }

    } // namespace Archive
} // namespace FrameCPP