// Copyright 2019 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_CORE_KEY_TYPE_MANAGER_H_
#define TINK_CORE_KEY_TYPE_MANAGER_H_

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <tuple>
#include <type_traits>
#include <typeinfo>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "tink/core/template_util.h"
#include "tink/input_stream.h"
#include "tink/internal/fips_utils.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "proto/tink.pb.h"

namespace crypto {
namespace tink {

namespace internal {
// InternalKeyFactory should not be used directly: it is an implementation
// detail. The internal key factory provides the functions which are required
// if a KeyTypeManager can create new keys: ValidateKeyFormat and
// CreateKey. The special case where KeyFormatProto = void implies that the
// functions do not exist.
template <typename KeyProto, typename KeyFormatProto>
class InternalKeyFactory {
 public:
  virtual ~InternalKeyFactory() = default;

  // Validates a key format proto.  KeyFormatProtos
  // on which this function returns a non-ok status will not be passed to
  // CreateKey or DeriveKey.
  virtual absl::Status ValidateKeyFormat(
      const KeyFormatProto& key_format) const = 0;
  // Creates a new key. This is expected to be randomized.
  virtual absl::StatusOr<KeyProto> CreateKey(
      const KeyFormatProto& key_format) const = 0;
  // Creates a new key. Only needs to be overridden if it should be possible to
  // derive keys of this type. This must be deterministic. Furthermore, in order
  // to support long term usability of old keys, the KeyFormatProto should be
  // versioned.
  virtual absl::StatusOr<KeyProto> DeriveKey(const KeyFormatProto& key_format,
                                             InputStream* input_stream) const {
    return absl::Status(absl::StatusCode::kUnimplemented,
                        "Deriving key not implemented for this key type.");
  }
};

// Template specialization for when KeyFormatProto = void. The compiler will
// pick the most specialized template when compiling.
template <typename KeyProto>
class InternalKeyFactory<KeyProto, void> {
 public:
  virtual ~InternalKeyFactory() = default;
};

}  // namespace internal

// We declare a KeyTypeManager without giving an implementation. We then
// provide a specialization only for the case where PrimitivesList is a
// List with multiple interfaces primitives. This allows to ensure
// that such is always the case.
template <typename KeyProto, typename KeyFormatProto, typename PrimitivesList>
class KeyTypeManager;

// A KeyTypeManager manages a single key proto. This includes
//  * parsing and validating keys
//  * parsing and validating key formats (in case generating keys is allowed).
//  * creating primitives.
// To implement, one should subclass KeyTypeManager with the corresponding
// KeyProto as a template parameter; KeyFormatProto should be void in case
// the key manager cannot produce keys and a protobuf otherwise.
//
// The constructor should take unique pointers to primitive factories.
//
// KeyTypeManager uses templates for KeyProto, KeyFormatProto and a list of
// Primitives which have to be provided as a List.
template <typename KeyProtoParam, typename KeyFormatProtoParam,
          typename... Primitives>
class KeyTypeManager<KeyProtoParam, KeyFormatProtoParam, List<Primitives...>>
    : public internal::InternalKeyFactory<KeyProtoParam, KeyFormatProtoParam> {
 public:
  static_assert(
      !crypto::tink::internal::HasDuplicates<Primitives...>::value,
      "List or primitives contains a duplicate, which is not allowed.");
  // The types used in this key type manager; these can be useful when writing
  // templated code.
  using KeyProto = KeyProtoParam;
  using KeyFormatProto = KeyFormatProtoParam;
  using PrimitiveList = List<Primitives...>;

  // A PrimitiveFactory<Primitive> knows how to create instances of the
  // Primitive.
  template <typename Primitive>
  class PrimitiveFactory {
   public:
    virtual ~PrimitiveFactory() = default;
    virtual absl::StatusOr<std::unique_ptr<Primitive>> Create(
        const KeyProto& key) const = 0;
  };

  // Creates a new KeyTypeManager. The parameter(s) primitives must be some
  // number of unique_ptr<PrimitiveFactory<P>> types.
  explicit KeyTypeManager(
      std::unique_ptr<PrimitiveFactory<Primitives>>... primitives)
      : primitive_factories_{std::move(primitives)...} {}

  // Returns the type_url identifying the key type handled by this manager.
  virtual const std::string& get_key_type() const = 0;
  // Returns the version of this key manager.
  virtual uint32_t get_version() const = 0;

  // Returns the key material type for this key type.
  virtual google::crypto::tink::KeyData::KeyMaterialType key_material_type()
      const = 0;

  // Validates the key. Returns util::OkStatus() if the key is valid,
  // and an invalid argument error otherwise.
  virtual absl::Status ValidateKey(const KeyProto& key) const = 0;

  // Creates a new primitive using one of the primitive factories passed in at
  // construction time.
  template <typename Primitive>
  absl::StatusOr<std::unique_ptr<Primitive>> GetPrimitive(
      const KeyProto& key) const {
    return GetPrimitiveImpl<Primitive>(key);
  }

  // Returns the FIPS compatibility of this KeyTypeManager.
  virtual internal::FipsCompatibility FipsStatus() const {
    return internal::FipsCompatibility::kNotFips;
  }

 private:
  // TODO(C++17) replace with `constexpr if` after migration
  template <typename Primitive>
  typename std::enable_if<
      !internal::OccursInTuple<Primitive, std::tuple<Primitives...>>::value,
      absl::StatusOr<std::unique_ptr<Primitive>>>::type
  GetPrimitiveImpl(const KeyProto& key) const {
    return absl::Status(
        absl::StatusCode::kInvalidArgument,
        absl::StrCat("No PrimitiveFactory was registered for type ",
                     typeid(Primitive).name()));
  }
  template <typename Primitive>
  typename std::enable_if<
      internal::OccursInTuple<Primitive, std::tuple<Primitives...>>::value,
      absl::StatusOr<std::unique_ptr<Primitive>>>::type
  GetPrimitiveImpl(const KeyProto& key) const {
    // TODO(C++14) replace with std::get<T> after migration
    constexpr size_t index =
        internal::IndexOf<Primitive, List<Primitives...>>::value;
    return std::get<index>(primitive_factories_)->Create(key);
  }

  std::tuple<std::unique_ptr<PrimitiveFactory<Primitives>>...>
      primitive_factories_;
};

}  // namespace tink
}  // namespace crypto

#endif  // TINK_CORE_KEY_TYPE_MANAGER_H_
