/**
 *
 * @file
 * Copyright 2025 Digitalwerk GmbH.
 *
 *     This Source Code Form is subject to the terms of the Mozilla
 *     Public License, v. 2.0. If a copy of the MPL was not distributed
 *     with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * If it is not possible or desirable to put the notice in a particular file, then
 * You may include the notice in a location (such as a LICENSE file in a
 * relevant directory) where a recipient would be likely to look for such a notice.
 *
 * You may add additional accurate notices of copyright ownership.
 */

#include "decoder.h"
#include <ddl/ddl.h>
#include <adtfstreaming3/streammetatypeplain.h>
#include <adtfstreaming3/streammetatypestring.h>
#include <adtfmediadescription/stream_meta_type_default.h>

namespace py = pybind11;
using namespace std::literals;

namespace decoder::MODULE_RELEASE_ABI_VERSION
{
    class DDLDecoder final : public Decoder
    {
    public:
        DDLDecoder() = delete;
        DDLDecoder(const StreamType& stream_type)
        {
            _representation = stream_type.property_value(
                                  adtf::mediadescription::stream_meta_type_default::strMDDataSerialized) == "true" ?
                                  ddl::serialized :
                                  ddl::deserialized;
            _factory = ddl::codec::CodecFactory(
                stream_type.property_value(adtf::mediadescription::stream_meta_type_default::strMDStructProperty),
                stream_type.property_value(adtf::mediadescription::stream_meta_type_default::strMDDefinitionsProperty));
        }

        DDLDecoder(const DDLDecoder&) = delete;
        DDLDecoder(DDLDecoder&&) = delete;

        DDLDecoder& operator=(const DDLDecoder&) = delete;
        DDLDecoder& operator=(DDLDecoder&&) = delete;

        py::object decode(const void* data, size_t size) override
        {
            const auto decoder = _factory.makeStaticDecoderFor(data, size, _representation);
            return decode_elements(decoder.getElements());
        }

    private:
        py::object variant_to_py(const a_util::variant::Variant& value)
        {
            using namespace a_util::variant;
            switch (value.getType())
            {
                case VT_Bool: return py::cast(value.asBool()); break;
                case VT_Int8: return py::cast(value.asInt8()); break;
                case VT_UInt8: return py::cast(value.asUInt8()); break;
                case VT_Int16: return py::cast(value.asInt16()); break;
                case VT_UInt16: return py::cast(value.asUInt16()); break;
                case VT_Int32: return py::cast(value.asInt32()); break;
                case VT_UInt32: return py::cast(value.asUInt32()); break;
                case VT_Int64: return py::cast(value.asInt64()); break;
                case VT_UInt64: return py::cast(value.asUInt64()); break;
                case VT_Float32: return py::cast(value.asFloat()); break;
                case VT_Float64: return py::cast(value.asDouble()); break;
                default: throw std::runtime_error("unsupported type");
            }
        }

        py::object decode_element(const ddl::codec::StaticDecoder::Element& element)
        {
            switch (element.getType())
            {
                case ddl::codec::ElementType::cet_sub_codec:
                {
                    return decode_elements(element.getChildElements());
                }
                default:
                {
                    return variant_to_py(element.getVariantValue());
                }
            }
        }

        py::object decode_elements(const ddl::codec::StaticDecoder::Elements& elements)
        {
            py::object decoded_element = py::module_::import("types").attr("SimpleNamespace")();
            for (const auto& element : elements)
            {
                py::object attribute;
                if (element.getArraySize() > 1)
                {
                    py::list array;
                    for (size_t array_index = 0; array_index < element.getArraySize(); ++array_index)
                    {
                        array.append(decode_element(element.getArrayElement(array_index)));
                    }
                    attribute = array;
                }
                else
                {
                    attribute = decode_element(element);
                }

                decoded_element.attr(element.getBaseName().c_str()) = attribute;
            }

            return decoded_element;
        }

        ddl::codec::CodecFactory _factory;
        ddl::tDataRepresentation _representation;
    };

    class StringDecoder final : public Decoder
    {
    public:
        StringDecoder() = delete;
        StringDecoder(const StreamType& stream_type)
        {
            using namespace adtf::streaming;

            const auto encoding = get_encoding_type(stream_type.property_value(stream_meta_type_string::Encoding));
            if (encoding == stream_meta_type_string::EncodingType::w_char_16 ||
                encoding == stream_meta_type_string::EncodingType::utf16)
            {
                _words = true;
                if (std::to_string(PLATFORM_BYTEORDER) !=
                    stream_type.property_value(stream_meta_type_string::DataEndianess))
                {
                    _swap = true;
                }
            }
        }

        StringDecoder(const StringDecoder&) = delete;
        StringDecoder(StringDecoder&&) = delete;

        StringDecoder& operator=(const StringDecoder&) = delete;
        StringDecoder& operator=(StringDecoder&&) = delete;

        py::object decode(const void* data, size_t size) override
        {
            if (_words)
            {
                if (_swap)
                {
                    const auto word_count = size / sizeof(char16_t);
                    std::u16string result(word_count, 0);

                    for (auto character_index = 0ull; character_index < word_count; ++character_index)
                    {
                        const auto value = static_cast<const char16_t*>(data)[character_index];
                        result[character_index] = (value >> 8) | (value << 8);
                    }

                    return py::cast(result);
                }
                else
                {
                    return py::cast(std::u16string(static_cast<const char16_t*>(data), size / sizeof(char16_t)));
                }
            }
            else
            {
                return py::cast(std::string(static_cast<const char*>(data), size));
            }
        }

    private:
        static adtf::streaming::stream_meta_type_string::EncodingType get_encoding_type(const std::string& encoding)
        {
            using namespace adtf::streaming;

            if (encoding == stream_meta_type_string::EncodingValueCChar)
            {
                return stream_meta_type_string::EncodingType::c_char;
            }
            else if (encoding == stream_meta_type_string::EncodingValueUnspecified)
            {
                return stream_meta_type_string::EncodingType::unspecified;
            }
            else if (encoding == stream_meta_type_string::EncodingValueUTF8)
            {
                return stream_meta_type_string::EncodingType::utf8;
            }
            else if (encoding == stream_meta_type_string::EncodingValueWChar)
            {
                return stream_meta_type_string::EncodingType::w_char_16;
            }
            else if (encoding == stream_meta_type_string::EncodingValueUTF16)
            {
                return stream_meta_type_string::EncodingType::utf16;
            }
            else
            {
                return stream_meta_type_string::EncodingType::unknown;
            }
        }

        bool _words = false;
        bool _swap = false;
    };

    class PlainDecoder final : public Decoder
    {
    public:
        PlainDecoder() = delete;
        PlainDecoder(const StreamType& stream_type)
        {
            const auto plaintype =
                stream_type.property_value(adtf::streaming::stream_meta_type_plain::PlainTypeProperty);
            _decoder = _type_decoders.at(plaintype).get();
        }

        PlainDecoder(const PlainDecoder&) = delete;
        PlainDecoder(PlainDecoder&&) = delete;

        PlainDecoder& operator=(const PlainDecoder&) = delete;
        PlainDecoder& operator=(PlainDecoder&&) = delete;

        py::object decode(const void* data, size_t size) override
        {
            return _decoder->decode(data, size);
        }

    private:
        template<typename T>
        class plain_decoder : public Decoder
        {
            py::object decode(const void* data, size_t size) override
            {
                if (size < sizeof(T))
                {
                    throw std::runtime_error("Invalid data size");
                }
                return py::cast(*static_cast<const T*>(data));
            }
        };

        template<typename T>
        class plain_array_decoder : public Decoder
        {
            py::object decode(const void* data, size_t size) override
            {
                const auto values = static_cast<const T*>(data);
                return py::cast(std::vector<T>(values, values + (size / sizeof(T))));
            }
        };

        class bool_array_decoder : public Decoder
        {
            py::object decode(const void* data, size_t size) override
            {
                const auto bools = static_cast<const bool*>(data);
                return py::cast(std::vector<bool>(bools, bools + size));
            }
        };

        static inline std::unordered_map<std::string, std::shared_ptr<Decoder>> _type_decoders = {
            {"UINT8"s, std::make_shared<plain_decoder<uint8_t>>()},
            {"INT8"s, std::make_shared<plain_decoder<int8_t>>()},
            {"UINT16"s, std::make_shared<plain_decoder<uint16_t>>()},
            {"INT16"s, std::make_shared<plain_decoder<int16_t>>()},
            {"UINT32"s, std::make_shared<plain_decoder<uint32_t>>()},
            {"INT32"s, std::make_shared<plain_decoder<int32_t>>()},
            {"UINT64"s, std::make_shared<plain_decoder<uint64_t>>()},
            {"INT64"s, std::make_shared<plain_decoder<int64_t>>()},
            {"FLOAT32"s, std::make_shared<plain_decoder<float>>()},
            {"FLOAT64"s, std::make_shared<plain_decoder<double>>()},
            {"BOOL"s, std::make_shared<plain_decoder<bool>>()},
            {"UINT8_ARRAY"s, std::make_shared<plain_array_decoder<uint8_t>>()},
            {"INT8_ARRAY"s, std::make_shared<plain_array_decoder<int8_t>>()},
            {"UINT16_ARRAY"s, std::make_shared<plain_array_decoder<uint16_t>>()},
            {"INT16_ARRAY"s, std::make_shared<plain_array_decoder<int16_t>>()},
            {"UINT32_ARRAY"s, std::make_shared<plain_array_decoder<uint32_t>>()},
            {"INT32_ARRAY"s, std::make_shared<plain_array_decoder<int32_t>>()},
            {"UINT64_ARRAY"s, std::make_shared<plain_array_decoder<uint64_t>>()},
            {"INT64_ARRAY"s, std::make_shared<plain_array_decoder<int64_t>>()},
            {"FLOAT32_ARRAY"s, std::make_shared<plain_array_decoder<float>>()},
            {"FLOAT64_ARRAY"s, std::make_shared<plain_array_decoder<double>>()},
            {"BOOL_ARRAY"s, std::make_shared<bool_array_decoder>()},
        };

        Decoder* _decoder = nullptr;
    };

    std::unique_ptr<Decoder> create_decoder(const StreamType& type)
    {
        using namespace adtf::streaming;

        const auto stream_meta_type = type.meta_type();
        if (stream_meta_type == stream_meta_type_string::MetaTypeName)
        {
            return std::make_unique<StringDecoder>(type);
        }
        else if (stream_meta_type == stream_meta_type_plain::MetaTypeName)
        {
            return std::make_unique<PlainDecoder>(type);
        }

        if (!type.property_value(adtf::mediadescription::stream_meta_type_default::strMDDefinitionsProperty).empty())
        {
            return std::make_unique<DDLDecoder>(type);
        }

        return {};
    }
} // namespace decoder::MODULE_RELEASE_ABI_VERSION