/*
 * 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_INJECTOR_STORAGE_H
#define FRUIT_INJECTOR_STORAGE_H

#include <fruit/fruit_forward_decls.h>
#include <fruit/impl/data_structures/fixed_size_allocator.h>
#include <fruit/impl/meta/component.h>
#include <fruit/impl/normalized_component_storage/normalized_bindings.h>

#include <unordered_map>
#include <vector>
#include <mutex>
#include <thread>

namespace fruit {
namespace impl {

template <typename T>
struct GetHelper;

/**
 * A component where all types have to be explicitly registered, and all checks are at runtime.
 * Used to implement Component<>, don't use directly.
 *
 * This class handles the creation of types of the forms:
 * - shared_ptr<C>, [const] C*, [const] C&, C (where C is an atomic type)
 * - Injector<T1, ..., Tk> (with T1, ..., Tk of the above forms).
 */
class InjectorStorage {
public:
  // TODO: remove.
  //  using BindingVectors = std::pair<std::vector<std::pair<TypeId, BindingData>>,
  //                                   std::vector<std::pair<TypeId, MultibindingData>>>;
  using Graph = SemistaticGraph<TypeId, NormalizedBinding>;

  template <typename AnnotatedT>
  using RemoveAnnotations = fruit::impl::meta::UnwrapType<
      fruit::impl::meta::Eval<fruit::impl::meta::RemoveAnnotations(fruit::impl::meta::Type<AnnotatedT>)>>;

  // MSVC 14 has trouble specializing alias templates using expanded pack elements.
  // This is a known issue:
  // https://stackoverflow.com/questions/43411542/metaprogramming-failed-to-specialize-alias-template
  // The workaround is just to use a struct directly.
  template <typename AnnotatedT>
  struct AnnotationRemover {
    using type = RemoveAnnotations<AnnotatedT>;
  };

  template <typename T>
  using NormalizeType = fruit::impl::meta::UnwrapType<
      fruit::impl::meta::Eval<fruit::impl::meta::NormalizeType(fruit::impl::meta::Type<T>)>>;

  template <typename T>
  struct TypeNormalizer {
    using type = NormalizeType<T>;
  };

  template <typename Signature>
  using SignatureType = fruit::impl::meta::UnwrapType<
      fruit::impl::meta::Eval<fruit::impl::meta::SignatureType(fruit::impl::meta::Type<Signature>)>>;

  template <typename Signature>
  using NormalizedSignatureArgs = fruit::impl::meta::Eval<fruit::impl::meta::NormalizeTypeVector(
      fruit::impl::meta::SignatureArgs(fruit::impl::meta::Type<Signature>))>;

  // Prints the specified error and calls exit(1).
  static void fatal(const std::string& error);

  template <typename AnnotatedI, typename AnnotatedC>
  static ComponentStorageEntry createComponentStorageEntryForBind();

  template <typename AnnotatedI, typename AnnotatedC>
  static ComponentStorageEntry createComponentStorageEntryForConstBind();

  template <typename AnnotatedC, typename C>
  static ComponentStorageEntry createComponentStorageEntryForBindInstance(C& instance);

  template <typename AnnotatedC, typename C>
  static ComponentStorageEntry createComponentStorageEntryForBindConstInstance(const C& instance);

  template <typename AnnotatedSignature, typename Lambda>
  static ComponentStorageEntry createComponentStorageEntryForProvider();

  template <typename AnnotatedSignature, typename Lambda, typename AnnotatedI>
  static ComponentStorageEntry createComponentStorageEntryForCompressedProvider();

  template <typename AnnotatedSignature>
  static ComponentStorageEntry createComponentStorageEntryForConstructor();

  template <typename AnnotatedSignature, typename AnnotatedI>
  static ComponentStorageEntry createComponentStorageEntryForCompressedConstructor();

  template <typename AnnotatedT>
  static ComponentStorageEntry createComponentStorageEntryForMultibindingVectorCreator();

  template <typename AnnotatedI, typename AnnotatedC>
  static ComponentStorageEntry createComponentStorageEntryForMultibinding();

  template <typename AnnotatedC, typename C>
  static ComponentStorageEntry createComponentStorageEntryForInstanceMultibinding(C& instance);

  template <typename AnnotatedSignature, typename Lambda>
  static ComponentStorageEntry createComponentStorageEntryForMultibindingProvider();

private:
  // The NormalizedComponentStorage owned by this object (if any).
  // Only used for the 1-argument constructor, otherwise it's nullptr.
  std::unique_ptr<NormalizedComponentStorage> normalized_component_storage_ptr;

  FixedSizeAllocator allocator;

  // A graph with injected types as nodes (each node stores the NormalizedBindingData for the type) and dependencies as
  // edges.
  // For types that have a constructed object already, the corresponding node is stored as terminal node.
  SemistaticGraph<TypeId, NormalizedBinding> bindings;

  // Maps the type index of a type T to the corresponding NormalizedMultibindingSet object (that stores all
  // multibindings).
  std::unordered_map<TypeId, NormalizedMultibindingSet> multibindings;

  // This mutex is used to synchronize concurrent accesses to this InjectorStorage object.
  std::recursive_mutex mutex;

private:
  template <typename AnnotatedC>
  static std::shared_ptr<char> createMultibindingVector(InjectorStorage& storage);

  // If not bound, returns nullptr.
  NormalizedMultibindingSet* getNormalizedMultibindingSet(TypeId type);

  // Looks up the location where the type is (or will be) stored, but does not construct the class.
  template <typename AnnotatedC>
  Graph::node_iterator lazyGetPtr();

  // getPtr() is equivalent to getPtrInternal(lazyGetPtr())
  template <typename C>
  const C* getPtr(Graph::node_iterator itr);

  // Similar to the previous, but takes a node_iterator. Use this when the node_iterator is known, it's faster.
  const void* getPtrInternal(Graph::node_iterator itr);

  // getPtr(typeInfo) is equivalent to getPtr(lazyGetPtr(typeInfo)).
  Graph::node_iterator lazyGetPtr(TypeId type);

  // getPtr(deps, index) is equivalent to getPtr(lazyGetPtr(deps, index)).
  Graph::node_iterator lazyGetPtr(Graph::edge_iterator deps, std::size_t dep_index);

  // Similar to getPtr, but the binding might not exist. Returns nullptr if it doesn't.
  const void* unsafeGetPtr(TypeId type);

  void* getPtrForMultibinding(TypeId type);

  // Returns a std::vector<T*>*, or nullptr if there are no multibindings.
  void* getMultibindings(TypeId type);

  // Constructs any necessary instances, but NOT the instance set.
  void ensureConstructedMultibinding(NormalizedMultibindingSet& multibinding_set);

  template <typename T>
  friend struct GetFirstStage;

  template <typename T>
  friend class fruit::Provider;

  using object_ptr_t = void*;
  using const_object_ptr_t = const void*;

  template <typename I, typename C, typename AnnotatedC>
  static const_object_ptr_t createInjectedObjectForBind(InjectorStorage& injector,
                                                        InjectorStorage::Graph::node_iterator node_itr);

  template <typename C, typename T, typename AnnotatedSignature, typename Lambda>
  static const_object_ptr_t createInjectedObjectForProvider(InjectorStorage& injector, Graph::node_iterator node_itr);

  template <typename I, typename C, typename T, typename AnnotatedSignature, typename Lambda>
  static const_object_ptr_t createInjectedObjectForCompressedProvider(InjectorStorage& injector,
                                                                      Graph::node_iterator node_itr);

  template <typename C, typename AnnotatedSignature>
  static const_object_ptr_t createInjectedObjectForConstructor(InjectorStorage& injector,
                                                               Graph::node_iterator node_itr);

  template <typename I, typename C, typename AnnotatedSignature>
  static const_object_ptr_t createInjectedObjectForCompressedConstructor(InjectorStorage& injector,
                                                                         Graph::node_iterator node_itr);

  template <typename I, typename C, typename AnnotatedCPtr>
  static object_ptr_t createInjectedObjectForMultibinding(InjectorStorage& m);

  template <typename C, typename T, typename AnnotatedSignature, typename Lambda>
  static object_ptr_t createInjectedObjectForMultibindingProvider(InjectorStorage& injector);

public:
  // Wraps a std::vector<ComponentStorageEntry>::iterator as an iterator on tuples
  // (typeId, normalizedBindingData, isTerminal, edgesBegin, edgesEnd)
  struct BindingDataNodeIter {
    std::vector<ComponentStorageEntry, ArenaAllocator<ComponentStorageEntry>>::iterator itr;

    BindingDataNodeIter* operator->();

    void operator++();

    bool operator==(const BindingDataNodeIter& other) const;
    bool operator!=(const BindingDataNodeIter& other) const;

    std::ptrdiff_t operator-(BindingDataNodeIter other) const;

    TypeId getId();
    NormalizedBinding getValue();
    bool isTerminal();
    const TypeId* getEdgesBegin();
    const TypeId* getEdgesEnd();
  };

  /**
   * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool.
   */
  InjectorStorage(ComponentStorage&& storage, const std::vector<TypeId, ArenaAllocator<TypeId>>& exposed_types,
                  MemoryPool& memory_pool);

  /**
   * The MemoryPool is only used during construction, the constructed object *can* outlive the memory pool.
   */
  InjectorStorage(const NormalizedComponentStorage& normalized_storage, ComponentStorage&& storage,
                  MemoryPool& memory_pool);

  // This is just the default destructor, but we declare it here to avoid including
  // normalized_component_storage.h in fruit.h.
  ~InjectorStorage();

  InjectorStorage(InjectorStorage&&) = delete;
  InjectorStorage& operator=(InjectorStorage&&) = delete;

  InjectorStorage(const InjectorStorage& other) = delete;
  InjectorStorage& operator=(const InjectorStorage& other) = delete;

  // Usually get<T>() returns a T.
  // However, get<Annotated<Annotation1, T>>() returns a T, not an Annotated<Annotation1, T>.
  template <typename AnnotatedT>
  RemoveAnnotations<AnnotatedT> get();

  // Similar to the above, but specifying the node_iterator of the type. Use this together with lazyGetPtr when the
  // node_iterator is known, it's faster.
  // Note that T should *not* be annotated.
  template <typename T>
  T get(InjectorStorage::Graph::node_iterator node_iterator);

  // Looks up the location where the type is (or will be) stored, but does not construct the class.
  // get<AnnotatedT>() is equivalent to get<AnnotatedT>(lazyGetPtr<Apply<NormalizeType, AnnotatedT>>(deps, dep_index))
  // and also                        to get<T>         (lazyGetPtr<Apply<NormalizeType, AnnotatedT>>(deps, dep_index))
  // dep_index is the index of the dep in `deps'.
  template <typename AnnotatedC>
  Graph::node_iterator lazyGetPtr(Graph::edge_iterator deps, std::size_t dep_index,
                                  Graph::node_iterator bindings_begin) const;

  // Returns nullptr if AnnotatedC was not bound.
  template <typename AnnotatedC>
  const RemoveAnnotations<AnnotatedC>* unsafeGet();

  template <typename AnnotatedC>
  const std::vector<RemoveAnnotations<AnnotatedC>*>& getMultibindings();

  void eagerlyInjectMultibindings();
};

} // namespace impl
} // namespace fruit

#include <fruit/impl/injector/injector_storage.defn.h>

#endif // FRUIT_INJECTOR_STORAGE_H