/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * 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 FRUIT_COMPONENT_STORAGE_ENTRY_H
#define FRUIT_COMPONENT_STORAGE_ENTRY_H

#include <fruit/impl/component_storage/binding_deps.h>
#include <fruit/impl/data_structures/arena_allocator.h>
#include <fruit/impl/data_structures/semistatic_graph.h>
#include <fruit/impl/fruit_internal_forward_decls.h>

namespace fruit {
namespace impl {

/**
 * This represents a generic entry in ComponentStorage.
 * We use a single POD (this struct) to represent any binding so that ComponentStorage can hold a single vector, instead
 * of having to hold multiple vectors (each of which potentially requiring allocation/deallocation when a
 * ComponentStorage is constructed/destroyed).
 * This way each ComponentStorage can hold a single vector and do a single allocation.
 */
struct ComponentStorageEntry {
  enum class Kind {
#if FRUIT_EXTRA_DEBUG
    INVALID,
#endif
    BINDING_FOR_CONSTRUCTED_OBJECT,
    BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION,
    BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION,
    BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION,
    COMPRESSED_BINDING,
    MULTIBINDING_FOR_CONSTRUCTED_OBJECT,
    MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_ALLOCATION,
    MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_NO_ALLOCATION,
    // This is not an actual binding, it's an "addendum" to multibinding bindings that specifies how the multibinding
    // vector can be created. Unlike real multibinding entries, this *can* be deduped.
    MULTIBINDING_VECTOR_CREATOR,

    LAZY_COMPONENT_WITH_NO_ARGS,
    LAZY_COMPONENT_WITH_ARGS,

    // Component replacements are stored as a REPLACEMENT_LAZY_COMPONENT_* entry followed by a REPLACED_LAZY_COMPONENT_*
    // entry. Note that the args are independent: e.g. a component with args can be replaced by a component with no
    // args. This also means that the type_id of the two entries can be different (since it's the type_id of the
    // function signature rather than just of the Component<...>).
    REPLACED_LAZY_COMPONENT_WITH_NO_ARGS,
    REPLACED_LAZY_COMPONENT_WITH_ARGS,
    REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS,
    REPLACEMENT_LAZY_COMPONENT_WITH_ARGS,

    // These markers are used in expandLazyComponents(), see the comments there for details.
    COMPONENT_WITH_ARGS_END_MARKER,
    COMPONENT_WITHOUT_ARGS_END_MARKER,
  };

#if FRUIT_EXTRA_DEBUG
  mutable
#endif
      Kind kind;

  // This is usually the TypeId for the bound type, except:
  // * when kind==COMPRESSED_BINDING, this is the interface's TypeId
  // * when kind==*LAZY_COMPONENT_*, this is the TypeId of the
  //       Component<...>-returning function.
  TypeId type_id;

  /**
   * This represents an entry in ComponentStorage for a binding (not a multibinding) that holds an already-constructed
   * object.
   */
  struct BindingForConstructedObject {
    using object_ptr_t = const void*;

    // The already-constructed object. We do *not* own this, this object must outlive the injector.
    // This is a const pointer because in some cases it might be a const binding.
    // We can cast this to a non-const pointer when we're sure that the original binding was for a non-const reference.
    object_ptr_t object_ptr;

#if FRUIT_EXTRA_DEBUG
    bool is_nonconst;
#endif
  };

  /**
   * This represents an entry in ComponentStorage for a binding (not a multibinding) that holds an object that needs to
   * be constructed (potentially after injecting any dependencies).
   */
  struct BindingForObjectToConstruct {
    // This is a const pointer because this might be a const binding. If not, we'll cast this back to a non-const
    // pointer when we need to.
    using object_t = const void*;
    using create_t = object_t (*)(InjectorStorage&, SemistaticGraph<TypeId, NormalizedBinding>::node_iterator);

    // The return value of this function is a pointer to the constructed object (guaranteed to be !=nullptr).
    // Once the object is constructed (at injection time), the injector owns that object.
    create_t create;

    // The type IDs that this type depends on.
    const BindingDeps* deps;

#if FRUIT_EXTRA_DEBUG
    bool is_nonconst;
#endif
  };

  /**
   * This represents an entry in ComponentStorage for a multibinding that holds an already-constructed
   * object.
   */
  struct MultibindingForConstructedObject {
    using object_ptr_t = void*;

    // The already-constructed object. We do *not* own this, this object must outlive the injector.
    object_ptr_t object_ptr;
  };

  /**
   * This represents an entry in ComponentStorage for a multibinding that holds an object that needs to
   * be constructed (potentially after injecting any dependencies).
   */
  struct MultibindingForObjectToConstruct {

    using object_t = void*;
    using create_t = object_t (*)(InjectorStorage&);

    // The return value of this function is a pointer to the constructed object (guaranteed to be !=nullptr).
    // Once the object is constructed (at injection time), the injector owns that object.
    create_t create;

    // The type IDs that this type depends on.
    const BindingDeps* deps;
  };

  /**
   * This is not an actual binding, it's an "addendum" to multibinding bindings that specifies how the multibinding
   * vector can be created. Unlike real multibinding entries, this *can* be deduped.
   */
  struct MultibindingVectorCreator {

    using get_multibindings_vector_t = std::shared_ptr<char> (*)(InjectorStorage&);

    // Returns the std::vector<T*> of instances, or nullptr if none.
    // Caches the result in the `v' member of NormalizedMultibindingData.
    get_multibindings_vector_t get_multibindings_vector;
  };

  // A CompressedBinding with interface_id==getTypeId<I>() and class_id==getTypeId<C>() means that if:
  // * C is not exposed by the component
  // * I is the only node that depends on C
  // * There are no multibindings that directly depend on C
  // The BindingData for C is BindingForObjectToConstruct(
  // Then, taken create1, needs_reallocation such that the ComponentStorageEntry for c_type_id is
  // BindingForObjectToConstruct(createC, deps, needs_allocation), we can remove the binding for I and C and replace
  // them
  // with just a binding for I, with BindingForObjectToConstruct(create, deps, needs_allocation).
  struct CompressedBinding {

    using create_t = BindingForObjectToConstruct::create_t;

    // TypeId for the implementation.
    TypeId c_type_id;

    // The return value of this function is a pointer to the constructed object (guaranteed to be !=nullptr).
    // Once the object is constructed (at injection time), the injector owns that object.
    create_t create;
  };

  /**
   * This represents an entry in ComponentStorage for a lazy component with no arguments.
   */
  struct LazyComponentWithNoArgs {
    // An arbitrary function type, used as type for the field `erased_fun`.
    // Note that we can't use void* here, since data pointers might not have the same size as function pointers.
    using erased_fun_t = void (*)();

    // The function that will be invoked to create the Component.
    // Here we don't know the type, it's only known at construction time.
    erased_fun_t erased_fun;

    using entry_vector_t = std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>;

    // The function that allows to add this component's bindings to the given ComponentStorage.
    using add_bindings_fun_t = void (*)(erased_fun_t, entry_vector_t&);
    add_bindings_fun_t add_bindings_fun;

    template <typename Component>
    static void addBindings(erased_fun_t erased_fun, entry_vector_t& entries);

    template <typename Component>
    static ComponentStorageEntry create(Component (*fun)());

    template <typename Component>
    static ComponentStorageEntry create(fruit::ComponentFunction<Component> component_function);

    template <typename Component>
    static ComponentStorageEntry createReplacedComponentEntry(Component (*fun)());

    template <typename Component>
    static ComponentStorageEntry createReplacementComponentEntry(Component (*fun)());

    bool operator==(const LazyComponentWithNoArgs&) const;

    void addBindings(entry_vector_t& entries) const;

    std::size_t hashCode() const;

    bool isValid() const;
  };

  /**
   * This represents an entry in ComponentStorage for a lazy component with arguments.
   */
  struct LazyComponentWithArgs {
    class ComponentInterface {
    public:
      // An arbitrary function type, used as type for the field `erased_fun`.
      // Note that we can't use void* here, since data pointers might not have the same size as function pointers.
      using erased_fun_t = void (*)();

      // The function that will be invoked to create the Component.
      // Here we don't know the type, it's only known to the LazyComponent implementation.
      // We store this here instead of in the LazyComponent implementation so that we can do a quick comparison on the
      // pointer without virtual calls (and we can then do the rest of the comparison via virtual call if needed).
      erased_fun_t erased_fun;

      using entry_vector_t = std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>;

      explicit ComponentInterface(erased_fun_t erased_fun);

      virtual ~ComponentInterface() = default;

      // Checks if *this and other are equal, assuming that this->fun and other.fun are equal.
      virtual bool areParamsEqual(const ComponentInterface& other) const = 0;

      bool operator==(const ComponentInterface& other) const;

      virtual void addBindings(entry_vector_t& component_storage_entries) const = 0;
      virtual std::size_t hashCode() const = 0;
      virtual ComponentInterface* copy() const = 0;

      /**
       * Returns the type ID of the real `fun` object stored by the implementation.
       * We use this instead of the `typeid` operator so that we don't require RTTI.
       */
      virtual TypeId getFunTypeId() const = 0;
    };

    template <typename Component, typename... Args>
    static ComponentStorageEntry create(Component (*fun)(Args...), std::tuple<Args...> args_tuple);

    template <typename Component, typename Arg, typename... Args>
    static ComponentStorageEntry create(fruit::ComponentFunction<Component, Arg, Args...> component_function);

    template <typename Component, typename... Args>
    static ComponentStorageEntry createReplacedComponentEntry(Component (*fun)(Args...),
                                                              std::tuple<Args...> args_tuple);

    template <typename Component, typename... Args>
    static ComponentStorageEntry createReplacementComponentEntry(Component (*fun)(Args...),
                                                                 std::tuple<Args...> args_tuple);

    LazyComponentWithArgs(LazyComponentWithArgs&&) = default;
    LazyComponentWithArgs& operator=(LazyComponentWithArgs&&) = default;

    // Note: we must allow these (and use the default implementations) since this class is used in a union so it must be
    // a POD. However when we need a real object we must call the other constructor above, and when we need a copy we
    // must
    // call copy() explicitly.
    LazyComponentWithArgs() = default; // LCOV_EXCL_LINE
    LazyComponentWithArgs(const LazyComponentWithArgs&) = default;
    LazyComponentWithArgs& operator=(const LazyComponentWithArgs&) = default;

    LazyComponentWithArgs copy() const;
    void destroy() const;

    ComponentInterface* component;
  };

  union {
    // Valid iff kind is BINDING_FOR_CONSTRUCTED_OBJECT.
    BindingForConstructedObject binding_for_constructed_object;

    // Valid iff kind is BINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_[NO_]ALLOCATION
    // or BINDING_FOR_OBJECT_TO_CONSTRUCT_WITH_UNKNOWN_ALLOCATION.
    BindingForObjectToConstruct binding_for_object_to_construct;

    // Valid iff kind is MULTIBINDING_FOR_CONSTRUCTED_OBJECT.
    MultibindingForConstructedObject multibinding_for_constructed_object;

    // Valid iff kind is MULTIBINDING_FOR_OBJECT_TO_CONSTRUCT_THAT_NEEDS_[NO_]ALLOCATION.
    MultibindingForObjectToConstruct multibinding_for_object_to_construct;

    // Valid iff kind is MULTIBINDING_VECTOR_CREATOR.
    MultibindingVectorCreator multibinding_vector_creator;

    // Valid iff kind is COMPRESSED_BINDING.
    CompressedBinding compressed_binding;

    // Valid iff kind is LAZY_COMPONENT_WITH_NO_ARGS, REPLACED_LAZY_COMPONENT_WITH_NO_ARGS or
    // REPLACEMENT_LAZY_COMPONENT_WITH_NO_ARGS.
    LazyComponentWithNoArgs lazy_component_with_no_args;

    // Valid iff kind is LAZY_COMPONENT_WITH_ARGS, REPLACED_LAZY_COMPONENT_WITH_ARGS or
    // REPLACEMENT_LAZY_COMPONENT_WITH_ARGS.
    LazyComponentWithArgs lazy_component_with_args;
  };

  // We use a custom method instead of a real copy constructor so that all copies are explicit (since copying is a
  // fairly expensive operation).
  ComponentStorageEntry copy() const;

  // We use a custom method instead of a real destructor, so that we can hold these in a std::vector but still destroy
  // them when desired.
  void destroy() const;
};

// We can't have this assert in debug mode because we add debug-only fields that increase the size.
#if !FRUIT_EXTRA_DEBUG
// This is not required for correctness, but 4 64-bit words should be enough to hold this object, if not we'd end up
// using more memory/CPU than expected.
static_assert(sizeof(ComponentStorageEntry) <= 4 * sizeof(std::uint64_t),
              "Error: a ComponentStorageEntry doesn't fit in 32 bytes as we expected");
#endif

} // namespace impl
} // namespace fruit

#include <fruit/impl/component_storage/component_storage_entry.defn.h>

#endif // FRUIT_COMPONENT_STORAGE_ENTRY_H
