// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

#ifndef TINK_INTERNAL_SERIALIZATION_REGISTRY_H_
#define TINK_INTERNAL_SERIALIZATION_REGISTRY_H_

#include <map>
#include <memory>
#include <string>
#include <typeindex>
#include <typeinfo>
#include <utility>

#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "absl/types/optional.h"
#include "tink/internal/key_parser.h"
#include "tink/internal/key_serializer.h"
#include "tink/internal/parameters_parser.h"
#include "tink/internal/parameters_serializer.h"
#include "tink/internal/parser_index.h"
#include "tink/internal/serialization.h"
#include "tink/internal/serializer_index.h"
#include "tink/key.h"
#include "tink/parameters.h"
#include "tink/secret_key_access_token.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"

namespace crypto {
namespace tink {
namespace internal {

class SerializationRegistry {
 public:
  class Builder {
   public:
    // Neither movable nor copyable.
    Builder(const Builder& other) = delete;
    Builder& operator=(const Builder& other) = delete;

    // Creates initially empty serialization registry builder.
    Builder() = default;
    // Creates serialization registry builder by initially copying entries from
    // `registry`.
    explicit Builder(const SerializationRegistry& registry);

    // Registers parameters `parser`. Returns an error if a different parameters
    // parser has already been registered.
    absl::Status RegisterParametersParser(ParametersParser* parser);

    // Registers parameters `serializer`. Returns an error if a different
    // parameters serializer has already been registered.
    absl::Status RegisterParametersSerializer(ParametersSerializer* serializer);

    // Registers key `parser`. Returns an error if a different key parser has
    // already been registered.
    absl::Status RegisterKeyParser(KeyParser* parser);

    // Registers key `serializer`. Returns an error if a different key
    // serializer has already been registered.
    absl::Status RegisterKeySerializer(KeySerializer* serializer);

    // Creates serialization registry from this builder.
    SerializationRegistry Build() &&;

   private:
    Builder(
        const absl::flat_hash_map<ParserIndex, ParametersParser*>&
            parameters_parsers,
        const absl::flat_hash_map<SerializerIndex, ParametersSerializer*>&
            parameters_serializers,
        absl::flat_hash_map<ParserIndex, KeyParser*> key_parsers,
        absl::flat_hash_map<SerializerIndex, KeySerializer*> key_serializers)
        : parameters_parsers_(parameters_parsers),
          parameters_serializers_(parameters_serializers),
          key_parsers_(std::move(key_parsers)),
          key_serializers_(std::move(key_serializers)) {}

    absl::flat_hash_map<ParserIndex, ParametersParser*> parameters_parsers_;
    absl::flat_hash_map<SerializerIndex, ParametersSerializer*>
        parameters_serializers_;
    absl::flat_hash_map<ParserIndex, KeyParser*> key_parsers_;
    absl::flat_hash_map<SerializerIndex, KeySerializer*> key_serializers_;
  };

  // Movable and copyable.
  SerializationRegistry(SerializationRegistry&& other) = default;
  SerializationRegistry& operator=(SerializationRegistry&& other) = default;
  SerializationRegistry(const SerializationRegistry& other) = default;
  SerializationRegistry& operator=(const SerializationRegistry& other) =
      default;

  // Creates empty serialization registry.
  SerializationRegistry() = default;

  // Parses `serialization` into a `Parameters` instance.
  absl::StatusOr<std::unique_ptr<Parameters>> ParseParameters(
      const Serialization& serialization) const;

  // Similar to `ParseParameters` but falls back to legacy proto parameters
  // serialization if the corresponding parameters parser is not found.
  absl::StatusOr<std::unique_ptr<Parameters>> ParseParametersWithLegacyFallback(
      const Serialization& serialization) const;

  // Serializes `parameters` into a `Serialization` instance.
  template <typename SerializationT>
  absl::StatusOr<std::unique_ptr<Serialization>> SerializeParameters(
      const Parameters& parameters) const {
    SerializerIndex index = SerializerIndex::Create<SerializationT>(parameters);
    auto it = parameters_serializers_.find(index);
    if (it == parameters_serializers_.end()) {
      return absl::Status(
          absl::StatusCode::kNotFound,
          absl::StrFormat(
              "No parameters serializer found for parameters type %s",
              typeid(parameters).name()));
    }

    return parameters_serializers_.at(index)->SerializeParameters(parameters);
  }

  // Parses `serialization` into a `Key` instance.
  absl::StatusOr<std::unique_ptr<Key>> ParseKey(
      const Serialization& serialization,
      absl::optional<SecretKeyAccessToken> token) const;

  // Similar to `ParseKey` but falls back to legacy proto key serialization if
  // the corresponding key parser is not found.
  absl::StatusOr<std::unique_ptr<Key>> ParseKeyWithLegacyFallback(
      const Serialization& serialization, SecretKeyAccessToken token) const;

  // Serializes `parameters` into a `Serialization` instance.
  template <typename SerializationT>
  absl::StatusOr<std::unique_ptr<Serialization>> SerializeKey(
      const Key& key, absl::optional<SecretKeyAccessToken> token) const {
    SerializerIndex index = SerializerIndex::Create<SerializationT>(key);
    auto it = key_serializers_.find(index);
    if (it == key_serializers_.end()) {
      return absl::Status(
          absl::StatusCode::kNotFound,
          absl::StrFormat("No key serializer found for key type %s",
                          typeid(key).name()));
    }

    return key_serializers_.at(index)->SerializeKey(key, token);
  }

 private:
  SerializationRegistry(
      absl::flat_hash_map<ParserIndex, ParametersParser*>
          parameters_parsers,
      absl::flat_hash_map<SerializerIndex, ParametersSerializer*>
          parameters_serializers,
      absl::flat_hash_map<ParserIndex, KeyParser*> key_parsers,
      absl::flat_hash_map<SerializerIndex, KeySerializer*>
          key_serializers)
      : parameters_parsers_(std::move(parameters_parsers)),
        parameters_serializers_(std::move(parameters_serializers)),
        key_parsers_(std::move(key_parsers)),
        key_serializers_(std::move(key_serializers)) {}

  absl::flat_hash_map<ParserIndex, ParametersParser*> parameters_parsers_;
  absl::flat_hash_map<SerializerIndex, ParametersSerializer*>
      parameters_serializers_;
  absl::flat_hash_map<ParserIndex, KeyParser*> key_parsers_;
  absl::flat_hash_map<SerializerIndex, KeySerializer*> key_serializers_;
};

}  // namespace internal
}  // namespace tink
}  // namespace crypto

#endif  // TINK_INTERNAL_SERIALIZATION_REGISTRY_H_
