// Copyright (c) 2023, 2024, Oracle and/or its affiliates.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0,
// as published by the Free Software Foundation.
//
// This program is designed to work with certain software (including
// but not limited to OpenSSL) that is licensed under separate terms,
// as designated in a particular file or component or in included license
// documentation.  The authors of MySQL hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have either included with
// the program or referenced in the documentation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License, version 2.0, for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.

/// @file
/// Experimental API header

#include "mysql/utils/return_status.h"

namespace mysql::serialization {

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::encode_field(
    const Field_type &field) {
  m_archive << Field_wrapper<const Field_type, field_size_defined>(field);
  m_archive.put_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::decode_field(
    Field_type &field) {
  m_archive >> Field_wrapper<Field_type, field_size_defined>(field);
  m_archive.process_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::encode_field(
    const Field_type &field, Serializer_enum_tag) {
  auto enumeration_value_encoded = utils::to_underlying(field);
  using Enumeration_type = decltype(enumeration_value_encoded);
  m_archive << Field_wrapper<Enumeration_type, field_size_defined>(
      enumeration_value_encoded);
  m_archive.put_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::decode_field(
    Field_type &field, Serializer_enum_tag) {
  auto enumeration_value_encoded = utils::to_underlying(field);
  using Enumeration_type = decltype(enumeration_value_encoded);
  m_archive >> Field_wrapper<Enumeration_type, field_size_defined>(
                   enumeration_value_encoded);
  auto [enumeration_value_decoded, conversion_code] =
      mysql::utils::to_enumeration<Field_type, Enumeration_type>(
          enumeration_value_encoded);
  field = enumeration_value_decoded;
  if (conversion_code == utils::Return_status::error) {
    m_error =
        Serialization_error(__FILE__, __LINE__, "Data integrity error",
                            Serialization_error_type::data_integrity_error);
  }
  m_archive.process_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::encode_field(
    const Field_type &field, Serializer_vector_list_tag) {
  uint64_t encoded_size = field.size();
  m_archive << create_varlen_field_wrapper(encoded_size);
  using value_type = typename Field_type::value_type;
  for (const auto &internal_field : field) {
    m_archive.put_entry_separator();
    // we use default size for internal fields (0)
    encode_field<value_type, 0>(internal_field);
  }
  m_archive.put_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::decode_field(
    Field_type &field, Serializer_vector_list_tag) {
  uint64_t encoded_size;
  m_archive >> create_varlen_field_wrapper(encoded_size);
  field.clear();
  using value_type = typename Field_type::value_type;
  for (uint64_t internal_id = 0; internal_id < encoded_size; ++internal_id) {
    value_type internal_field;
    // we use default size for internal fields (0)
    decode_field<value_type, 0>(internal_field);
    field.push_back(internal_field);
    m_archive.process_entry_separator();
  }
  m_archive.process_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::encode_field(
    const Field_type &field, Serializer_serializable_tag) {
  Serializer_default<Archive_concrete_type>::encode_serializable(0, 0, field,
                                                                 true);
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::decode_field(
    Field_type &field, Serializer_serializable_tag) {
  // For serializable inside container, we don't check serializable id,
  // since it must be there. Passing 0 as serializable_end_pos.
  Serializer_default<Archive_concrete_type>::decode_serializable(0, 0, 0, field,
                                                                 true);
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::encode_field(
    const Field_type &field, Serializer_map_tag) {
  uint64_t encoded_size = field.size();
  m_archive << create_varlen_field_wrapper(encoded_size);
  for (auto &pair : field) {
    using key_type = typename Field_type::key_type;
    using mapped_type = typename Field_type::mapped_type;
    // cannot choose fixed number of bytes for map key type and mapped type,
    // we use default size for internal fields (0)
    encode_field<key_type, 0>(pair.first);
    m_archive.put_entry_separator();
    encode_field<mapped_type, 0>(pair.second);
    m_archive.put_entry_separator();
  }
  m_archive.put_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::decode_field(
    Field_type &field, Serializer_map_tag) {
  uint64_t encoded_size;
  m_archive >> create_varlen_field_wrapper(encoded_size);
  field.clear();
  for (uint64_t entry_id = 0; entry_id < encoded_size; ++entry_id) {
    using key_type = typename Field_type::key_type;
    using mapped_type = typename Field_type::mapped_type;
    key_type key;
    mapped_type value;
    // cannot choose fixed number of bytes for map key type and mapped type,
    // we use default size for internal fields (0)
    decode_field<key_type, 0>(key);
    m_archive.process_entry_separator();
    decode_field<mapped_type, 0>(value);
    m_archive.process_entry_separator();
    field.insert(std::make_pair(key, value));
  }
  m_archive.process_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::encode_field(
    const Field_type &field, Serializer_set_tag) {
  uint64_t encoded_size = field.size();
  m_archive << create_varlen_field_wrapper(encoded_size);
  using value_type = typename Field_type::value_type;
  for (const auto &internal_field : field) {
    // we use default size for internal fields (0)
    encode_field<value_type, 0>(internal_field);
    m_archive.put_entry_separator();
  }
  m_archive.put_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::decode_field(
    Field_type &field, Serializer_set_tag) {
  uint64_t encoded_size;
  m_archive >> create_varlen_field_wrapper(encoded_size);
  using value_type = typename Field_type::value_type;
  field.clear();
  for (uint64_t internal_id = 0; internal_id < encoded_size; ++internal_id) {
    value_type internal_field;
    // we use default size for internal fields (0)
    decode_field<value_type, 0>(internal_field);
    field.emplace(internal_field);
    m_archive.process_entry_separator();
  }
  m_archive.process_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::encode_field(
    const Field_type &field, Serializer_array_tag) {
  using value_type = std::remove_reference_t<decltype(
      *std::begin(std::declval<Field_type &>()))>;
  for (const auto &internal_field : field) {
    // we use default size for internal fields (0)
    encode_field<value_type, 0>(internal_field);
    m_archive.put_entry_separator();
  }
  m_archive.put_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
void Serializer_default<Archive_concrete_type>::decode_field(
    Field_type &field, Serializer_array_tag) {
  using value_type = std::remove_reference_t<decltype(
      *std::begin(std::declval<Field_type &>()))>;
  for (auto &internal_field : field) {
    // we use default size for internal fields (0)
    decode_field<value_type, 0>(internal_field);
    m_archive.process_entry_separator();
  }
  m_archive.process_field_separator();
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
std::size_t Serializer_default<Archive_concrete_type>::get_field_size(
    const Field_type &field) {
  return Archive_concrete_type::template get_size(
      Field_wrapper<const Field_type, field_size_defined>(field));
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
std::size_t Serializer_default<Archive_concrete_type>::get_field_size(
    const Field_type &field, Serializer_enum_tag) {
  auto enumeration_value_encoded = utils::to_underlying(field);
  using Enumeration_type = decltype(enumeration_value_encoded);
  return get_field_size<Enumeration_type, field_size_defined>(
      enumeration_value_encoded);
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
std::size_t Serializer_default<Archive_concrete_type>::get_field_size(
    const Field_type &field, Serializer_vector_list_tag) {
  std::size_t field_size = 0;
  using value_type = typename Field_type::value_type;
  uint64_t encoded_size = field.size();
  field_size += get_field_size<uint64_t, 0>(encoded_size);
  for (const auto &internal_field : field) {
    field_size += get_field_size<value_type, 0>(internal_field);
  }
  return field_size;
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
std::size_t Serializer_default<Archive_concrete_type>::get_field_size(
    const Field_type &field, Serializer_serializable_tag) {
  return Serializer_default<Archive_concrete_type>::get_size_serializable(
      0, field, true);
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
std::size_t Serializer_default<Archive_concrete_type>::get_field_size(
    const Field_type &field, Serializer_map_tag) {
  using key_type = typename Field_type::key_type;
  using mapped_type = typename Field_type::mapped_type;
  std::size_t field_size = 0;
  uint64_t encoded_size = field.size();
  field_size += get_field_size<uint64_t, 0>(encoded_size);
  for (auto &pair : field) {
    field_size += get_field_size<key_type, 0>(pair.first);
    field_size += get_field_size<mapped_type, 0>(pair.second);
  }
  return field_size;
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
std::size_t Serializer_default<Archive_concrete_type>::get_field_size(
    const Field_type &field, Serializer_set_tag) {
  std::size_t field_size = 0;
  uint64_t encoded_size = field.size();
  field_size += get_field_size<uint64_t, 0>(encoded_size);
  using value_type = typename Field_type::value_type;
  for (const auto &internal_field : field) {
    field_size += get_field_size<value_type, 0>(internal_field);
  }
  return field_size;
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined, typename Enabler>
std::size_t Serializer_default<Archive_concrete_type>::get_field_size(
    const Field_type &field, Serializer_array_tag) {
  std::size_t field_size = 0;
  using value_type = std::remove_reference_t<decltype(
      *std::begin(std::declval<Field_type &>()))>;
  for (const auto &internal_field : field) {
    field_size += get_field_size<value_type, 0>(internal_field);
  }
  return field_size;
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined>
bool Serializer_default<Archive_concrete_type>::encode_field_id(
    Level_type level, Field_id_type field_id,
    const Field_definition<Field_type, field_size_defined> &field_definition) {
  bool is_provided = false;
  if (this->is_good() == true) {
    is_provided = field_definition.run_encode_predicate();
    if (is_provided) {
      for (size_t level_id = 0; level_id < level; ++level_id) {
        m_archive.put_level_separator();
      }
      m_archive << create_varlen_field_wrapper(field_id);
      m_archive.put_entry_separator();
    }
  }
  return is_provided;
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined>
bool Serializer_default<Archive_concrete_type>::decode_field_id(
    Level_type level, Field_id_type field_id,
    Field_definition<Field_type, field_size_defined> &field_definition) {
  bool is_provided = false;
  if (this->is_good() == true) {
    is_provided = true;
    for (size_t level_id = 0; level_id < level; ++level_id) {
      m_archive.process_level_separator();
    }
    Field_id_type field_type_read;
    m_archive.peek(create_varlen_field_wrapper(field_type_read));
    if (field_id != field_type_read || this->is_good() == false) {
      field_definition.run_field_missing();
      is_provided = false;
    }
    if (is_provided) {
      m_archive >> create_varlen_field_wrapper(field_type_read);
      m_archive.process_entry_separator();
    }
  }
  return is_provided;
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined>
void Serializer_default<Archive_concrete_type>::encode(
    Level_type level, Field_id_type field_id,
    const Field_definition<Field_type, field_size_defined> &field_definition) {
  bool is_provided = encode_field_id(level, field_id, field_definition);
  if (is_provided) {
    encode_field<Field_type, field_size_defined>(field_definition.get_ref());
  }
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined>
void Serializer_default<Archive_concrete_type>::decode(
    Level_type level, Field_id_type field_id, std::size_t serializable_end_pos,
    Field_definition<Field_type, field_size_defined> &field_definition) {
  // serializable_end_pos = 0 is a marker for serializers that don't check end
  // pos
  if (m_archive.get_read_pos() == serializable_end_pos &&
      serializable_end_pos != 0) {  // reached the end
    // missing field handling here
    field_definition.run_field_missing();
    return;
  }
  bool is_provided = decode_field_id(level, field_id, field_definition);
  if (is_provided) {
    decode_field<Field_type, field_size_defined>(field_definition.get_ref());
  }
}

template <class Serializable_type>
Field_id_type find_last_non_ignorable_field_id(
    const Serializable_type &serializable) {
  Field_id_type last_non_ignorable_field_id = 0;
  auto func_s = [&last_non_ignorable_field_id](const auto &nested,
                                               auto nested_id) {
    if (nested.is_any_field_provided() && nested.is_ignorable() == false) {
      last_non_ignorable_field_id = nested_id + 1;
    }
  };
  auto func_f = [&last_non_ignorable_field_id](
      const auto &field, auto processed_field_id) -> auto {
    if (field.run_encode_predicate() && field.is_field_ignorable() == false) {
      last_non_ignorable_field_id = processed_field_id + 1;
    }
  };
  serializable.do_for_each_field(func_s, func_f);
  return last_non_ignorable_field_id;
}

template <class Archive_concrete_type>
template <class Serializable_type>
void Serializer_default<Archive_concrete_type>::encode_serializable_metadata(
    Level_type, Field_id_type field_id, const Serializable_type &serializable,
    bool skip_id) {
  using Serializer_type = Serializer_default<Archive_concrete_type>;

  uint64_t encoded_size =
      Serializer_type::get_size_serializable(field_id, serializable, skip_id);

  Field_id_type last_non_ignorable_field_id =
      find_last_non_ignorable_field_id(serializable);

  if (skip_id == false) {
    m_archive << create_varlen_field_wrapper(field_id);
    m_archive.put_field_separator();
  }
  m_archive << create_varlen_field_wrapper(encoded_size);
  m_archive.put_field_separator();
  m_archive << create_varlen_field_wrapper(last_non_ignorable_field_id);
  m_archive.put_field_separator();
}

template <class Archive_concrete_type>
template <class Serializable_type>
std::size_t
Serializer_default<Archive_concrete_type>::decode_serializable_metadata(
    Level_type, Field_id_type field_id, Serializable_type &serializable,
    bool skip_id) {
  uint64_t encoded_size = 0;
  Field_id_type last_non_ignorable_field_id = 0;
  if (skip_id == false) {
    Field_id_type field_type_read;
    m_archive >> create_varlen_field_wrapper(field_type_read);
    m_archive.process_field_separator();
    if (field_type_read != field_id || this->is_good() == false) {
      m_error =
          Serialization_error(__FILE__, __LINE__, "Missing field id",
                              Serialization_error_type::field_id_mismatch);
    }
  }

  if (this->is_good()) {
    auto last_known_field_id = serializable.get_last_field_id();
    m_archive >> create_varlen_field_wrapper(encoded_size);
    m_archive.process_field_separator();
    m_archive >> create_varlen_field_wrapper(last_non_ignorable_field_id);
    m_archive.process_field_separator();

    if (last_known_field_id + 1 < last_non_ignorable_field_id) {
      m_error = Serialization_error(
          __FILE__, __LINE__,
          "Unknown, non ignorable fields found in the data stream.",
          Serialization_error_type::unknown_field);
    }
  }
  return static_cast<std::size_t>(encoded_size);
}

template <class Archive_concrete_type>
template <class Field_type, Field_size field_size_defined>
std::size_t Serializer_default<Archive_concrete_type>::get_size_field_def(
    Field_id_type field_id,
    const Field_definition<Field_type, field_size_defined> &field_definition) {
  std::size_t calculated_size = 0;
  bool is_provided = field_definition.run_encode_predicate();
  if (is_provided) {
    auto size_id_type = Archive_concrete_type::template get_size(
        create_varlen_field_wrapper(field_id));
    calculated_size = get_field_size<Field_type, field_size_defined>(
                          field_definition.get_ref()) +
                      size_id_type;
  }
  return calculated_size;
}

template <class Archive_concrete_type>
template <class Serializable_concrete_type>
std::size_t Serializer_default<Archive_concrete_type>::get_size_serializable(
    Field_id_type field_id, const Serializable_concrete_type &serializable,
    bool skip_id) {
  std::size_t serializable_overhead_type = 0;
  if (skip_id == false) {
    serializable_overhead_type = Archive_concrete_type::template get_size(
        create_varlen_field_wrapper(field_id));
  }
  auto serializable_size = serializable.template get_size_internal<Base_type>();
  auto serializable_overhead_size = Archive_concrete_type::template get_size(
      create_varlen_field_wrapper(serializable_size));

  Field_id_type last_non_ignorable_field_id =
      find_last_non_ignorable_field_id(serializable);

  auto serializable_overhead_last_non_ignorable_field_id =
      Archive_concrete_type::template get_size(
          create_varlen_field_wrapper(last_non_ignorable_field_id));
  return serializable_overhead_type + serializable_overhead_size +
         serializable_overhead_last_non_ignorable_field_id + serializable_size;
}

}  // namespace mysql::serialization
