//
// 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
//

#ifndef FRAMECPP_ARCHIVE_JSONARCHIVE_JSONARCHIVE_HH
#define FRAMECPP_ARCHIVE_JSONARCHIVE_JSONARCHIVE_HH

#include <iostream>
#include <string>
#include <sstream>
#include <stack>
#include <vector>
#include <memory>
#include <typeinfo>
#include <cxxabi.h>
#include <type_traits>
#include <iomanip>
#include <cstring>

#include <boost/archive/detail/common_oarchive.hpp>
#include <boost/archive/detail/register_archive.hpp>
#include <boost/archive/archive_exception.hpp>
#include <boost/archive/basic_archive.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/access.hpp>
#include <boost/version.hpp>

namespace FrameCPP
{
    namespace Archive
    {
        /// \brief JSON output archive for structured JSON representation of Frame objects
        ///
        /// This archive provides JSON output functionality compatible with boost::serialization,
        /// generating well-formed JSON suitable for programmatic consumption and analysis.
        ///
        /// Features:
        /// - Hierarchical JSON structure with proper nesting
        /// - Type information included as metadata
        /// - Array support with proper JSON array syntax
        /// - String escaping for JSON compliance
        /// - Configurable formatting (pretty print vs compact)
        /// - Version information in metadata
        ///
        /// Usage:
        /// ```cpp
        /// std::ostringstream oss;
        /// JsonArchive ar(oss, 9, true);  // version 9, pretty print
        /// ar << boost::serialization::make_nvp("frame", frameObject);
        /// std::string json = oss.str();
        /// ```
        class JsonArchive : public boost::archive::detail::common_oarchive<JsonArchive>
        {
        private:
            /// \brief JSON node context for tracking nesting
            struct JsonContext
            {
                enum Type { OBJECT, ARRAY };
                Type type;
                bool first_element;
                std::string name;
                
                JsonContext(Type t, const std::string& n = "") 
                    : type(t), first_element(true), name(n) {}
            };

        public:
            typedef boost::archive::detail::common_oarchive<JsonArchive> base_t;

            /// \brief Constructor for JsonArchive
            ///
            /// \param[in,out] os Output stream for JSON data
            /// \param[in] ver Frame specification version
            /// \param[in] pretty Enable pretty printing with indentation
            /// \param[in] include_types Include type information in JSON
            JsonArchive(std::ostream& os, 
                       unsigned int ver = 9,
                       bool pretty = true,
                       bool include_types = true);

            ~JsonArchive();

            //=================================================================
            // Core serialization interface (required by boost::serialization)
            //=================================================================

            /// \brief Save fundamental types
            template<typename T>
            void save(const T& value);

            /// \brief Save string types
            void save(const std::string& value);
            void save(const char* value);
            
            /// \brief Save arrays
            template<typename T, std::size_t N>
            void save(const T (&arr)[N]);

            /// \brief Save arrays
            template<typename T>
            void save_array(const T* data, std::size_t count);
            
            /// \brief Save pointer types
            template<typename T>
            void save(T* const& ptr);
            
            /// \brief Save STL vector types (prevents boost internal type issues)
            template<typename T>
            void save_override(const std::vector<T>& vec);
            
            /// \brief Save STL vector named value pairs
            template<typename T>
            void save_override(const boost::serialization::nvp<std::vector<T>>& nvp);

            /// \brief Named value pair handling
            template<typename T>
            void save_override(const boost::serialization::nvp<T>& nvp);

            /// \brief Handle boost archive internal types (required by boost::serialization)
            void save_override(const boost::archive::version_type& t) { /* ignore */ }
            void save_override(const boost::archive::object_id_type& t) { /* ignore */ }
            void save_override(const boost::archive::object_reference_type& t) { /* ignore */ }
            void save_override(const boost::archive::class_id_type& t) { /* ignore */ }
            void save_override(const boost::archive::class_id_optional_type& t) { /* ignore */ }
            void save_override(const boost::archive::class_id_reference_type& t) { /* ignore */ }
            void save_override(const boost::archive::class_name_type& t) { /* ignore */ }
            void save_override(const boost::archive::tracking_type& t) { /* ignore */ }
            void save_override(const boost::serialization::version_type& t) { /* ignore */ }
            void save_override(const boost::serialization::collection_size_type& t) { /* ignore */ }

            /// \brief Generic save_override fallback 
            template<typename T>
            void save_override(const T& t) 
            { 
                // For fundamental types, arrays, string-like types, and pointers - use save()
                if constexpr (std::is_fundamental_v<T> || 
                             std::is_same_v<T, std::string> ||
                             std::is_array_v<T> ||
                             std::is_pointer_v<T> ||
                             std::is_same_v<T, const char*>)
                {
                    save(t);
                }
                else if constexpr (std::is_class_v<T>)
                {
                    // For class types, use boost serialization (boost internal types handled by explicit overrides)
                    beginObject();
                    boost::serialization::serialize_adl(*this, const_cast<T&>(t), m_version);
                    endObject();
                }
                // All other types (boost internals) are silently ignored
            }

            /// \brief Stream operator for serializable objects
            template<typename T>
            JsonArchive& operator<<(const T& obj);

            /// \brief Stream operator for named value pairs
            template<typename T>
            JsonArchive& operator<<(const boost::serialization::nvp<T>& nvp);

            /// \brief Handle class begin/end
            template<typename T>
            void save_start(const char* name);
            
            void save_end(const char* name);

            //=================================================================
            // JSON-specific methods
            //=================================================================

            /// \brief Begin a JSON object
            void beginObject(const char* name = nullptr);

            /// \brief End a JSON object
            void endObject();

            /// \brief Begin a JSON array
            void beginArray(const char* name = nullptr);

            /// \brief End a JSON array
            void endArray();

            /// \brief Write a key-value pair
            template<typename T>
            void writeKeyValue(const char* key, const T& value);

            /// \brief Write array element
            template<typename T>
            void writeArrayElement(const T& value);

            /// \brief Enable/disable pretty printing
            void setPrettyPrint(bool enable) { m_pretty_print = enable; }

            /// \brief Enable/disable type information
            void setIncludeTypes(bool enable) { m_include_types = enable; }

            //=================================================================
            // Boost serialization requirements
            //=================================================================

            // Implement required archive concept methods
            typedef boost::mpl::bool_<false> is_loading;
            typedef boost::mpl::bool_<true> is_saving;

            template<class T>
            void register_type(const T* = nullptr) {}

            void save_binary(const void* address, std::size_t count);

            unsigned int get_library_version() { return m_version; }

            // Split member functions for proper serialization
            BOOST_SERIALIZATION_SPLIT_MEMBER()

        private:
            std::ostream& m_os;                    ///< Output stream
            unsigned int m_version;                ///< Frame specification version
            bool m_pretty_print;                   ///< Enable pretty printing
            bool m_include_types;                  ///< Include type information
            std::stack<JsonContext> m_context;     ///< Context stack for nesting
            int m_indent_level;                    ///< Current indentation level
            
            /// \brief Write indentation for pretty printing
            void writeIndent();

            /// \brief Write comma if needed
            void writeComma();

            /// \brief Escape string for JSON
            std::string escapeJson(const std::string& str);

            /// \brief Get demangled type name
            template<typename T>
            std::string getTypeName();

            /// \brief Write raw JSON value
            void writeRawValue(const std::string& value);

            /// \brief Check if we're in an array context
            bool inArray() const;

            /// \brief Check if we're in an object context  
            bool inObject() const;
        };

        //=====================================================================
        // Template Implementation
        //=====================================================================

        template<typename T>
        void FrameCPP::Archive::JsonArchive::save(const T& value)
        {
            static_assert(std::is_fundamental_v<T>, "Use save_override() for non-fundamental types");
            
            // Handle comma and indentation directly (don't use writeArrayElement to avoid recursion)
            writeComma();
            if (m_pretty_print && inArray())
            {
                writeIndent();
            }
            
            // Write the actual value based on type
            if (std::is_same<T, bool>::value)
            {
                m_os << (value ? "true" : "false");
            }
            else if (std::is_floating_point<T>::value)
            {
                    // Use scientific notation for very large/small values
                    if (std::abs(static_cast<double>(value)) > 1e6 || 
                        (std::abs(static_cast<double>(value)) < 1e-3 && value != 0))
                    {
                        m_os << std::scientific << std::setprecision(6) << value;
                    }
                    else
                    {
                        m_os << std::fixed << std::setprecision(6) << value;
                    }
                }
                else
                {
                    m_os << value;
                }
            if (!m_context.empty())
            {
                m_context.top().first_element = false;
            }
        }

        template<typename T>
        void FrameCPP::Archive::JsonArchive::save_array(const T* data, std::size_t count)
        {
            beginArray();
            for (std::size_t i = 0; i < count; ++i)
            {
                writeArrayElement(data[i]);
            }
            endArray();
        }

        template<typename T, std::size_t N>
        void FrameCPP::Archive::JsonArchive::save(const T (&arr)[N])
        {
            if constexpr (std::is_same_v<T, char>)
            {
                // Handle character arrays as strings
                save(std::string(arr, strnlen(arr, N)));
            }
            else
            {
                // Handle other arrays as JSON arrays
                save_array(arr, N);
            }
        }

        template<typename T>
        void FrameCPP::Archive::JsonArchive::save(T* const& ptr)
        {
            if (ptr)
            {
                // For pointers to frame objects, serialize the pointed-to object
                beginObject();
                if constexpr (std::is_const_v<T>) {
                    // For const objects, create a non-const copy to serialize
                    auto copy = *ptr;
                    boost::serialization::serialize_adl(*this, copy, m_version);
                } else {
                    // For non-const objects, serialize directly
                    boost::serialization::serialize_adl(*this, *ptr, m_version);
                }
                endObject();
            }
            else
            {
                writeRawValue("null");
            }
        }

        template<typename T>
        void FrameCPP::Archive::JsonArchive::save_override(const boost::serialization::nvp<T>& nvp)
        {
            // Start a new key-value pair in object context
            if (inObject())
            {
                writeComma();
                writeIndent();
                m_os << "\"" << nvp.name() << "\": ";
                
                // Add type information if enabled
                if (m_include_types && !std::is_fundamental<T>::value)
                {
                    beginObject();
                    writeIndent();
                    m_os << "\"_type\": \"" << getTypeName<T>() << "\",";
                    m_os << std::endl;
                    writeIndent();
                    m_os << "\"_value\": ";
                }
            }
            
            // Serialize the value
            *this << nvp.value();
            
            // Close type wrapper if needed
            if (inObject() && m_include_types && !std::is_fundamental<T>::value)
            {
                endObject();
            }
            
            if (!m_context.empty())
            {
                m_context.top().first_element = false;
            }
        }

        template<typename T>
        void FrameCPP::Archive::JsonArchive::save_start(const char* name)
        {
            beginObject(name);
        }

        template<typename T>
        void FrameCPP::Archive::JsonArchive::writeKeyValue(const char* key, const T& value)
        {
            writeComma();
            writeIndent();
            m_os << "\"" << key << "\": ";
            save(value);
        }

        template<typename T>
        void FrameCPP::Archive::JsonArchive::writeArrayElement(const T& value)
        {
            writeComma();
            writeIndent();
            save(value);
        }

        template<typename T>
        std::string FrameCPP::Archive::JsonArchive::getTypeName()
        {
            int status;
            std::unique_ptr<char, void(*)(void*)> demangled(
                abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status),
                std::free
            );
            
            if (status == 0 && demangled)
            {
                return std::string(demangled.get());
            }
            return typeid(T).name();
        }

        template<typename T>
        FrameCPP::Archive::JsonArchive& FrameCPP::Archive::JsonArchive::operator<<(const T& obj)
        {
            // Use boost serialization system
            this->save_override(obj);
            return *this;
        }

        template<typename T>
        FrameCPP::Archive::JsonArchive& FrameCPP::Archive::JsonArchive::operator<<(const boost::serialization::nvp<T>& nvp)
        {
            save_override(nvp);
            return *this;
        }

        template<typename T>
        void FrameCPP::Archive::JsonArchive::save_override(const std::vector<T>& vec)
        {
            // Handle std::vector directly to avoid boost internal types (item_version_type)
            beginArray();
            for (const auto& item : vec)
            {
                writeComma();
                writeIndent();
                save_override(item);  // Use save_override for proper object serialization
            }
            endArray();
        }

        template<typename T>
        void FrameCPP::Archive::JsonArchive::save_override(const boost::serialization::nvp<std::vector<T>>& nvp)
        {
            // Handle named std::vector to avoid boost internal types
            if (inObject())
            {
                writeComma();
                writeIndent();
                m_os << "\"" << nvp.name() << "\": ";
            }
            
            save_override(nvp.value());
            
            if (!m_context.empty())
            {
                m_context.top().first_element = false;
            }
        }

    } // namespace Archive
} // namespace FrameCPP

// Register the archive
BOOST_SERIALIZATION_REGISTER_ARCHIVE(FrameCPP::Archive::JsonArchive)

#endif // FRAMECPP_ARCHIVE_JSONARCHIVE_JSONARCHIVE_HH