/*
 * 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_PARTIAL_COMPONENT_STORAGE_DEFN_H
#define FRUIT_PARTIAL_COMPONENT_STORAGE_DEFN_H

#include <fruit/impl/component_storage/partial_component_storage.h>

#include <fruit/impl/bindings.h>
#include <fruit/impl/injector/injector_storage.h>
#include <fruit/impl/util/call_with_tuple.h>
#include <fruit/impl/util/type_info.h>
#include <utility>

namespace fruit {
namespace impl {

template <>
class PartialComponentStorage<> {
public:
  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    (void)entries;
  }

  std::size_t numBindings() const {
    return 0;
  }
};

template <typename I, typename C, typename... PreviousBindings>
class PartialComponentStorage<Bind<I, C>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) // NOLINT(google-explicit-constructor)
      : previous_storage(previous_storage) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings();
  }
};

template <typename Signature, typename... PreviousBindings>
class PartialComponentStorage<RegisterConstructor<Signature>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) // NOLINT(google-explicit-constructor)
      : previous_storage(previous_storage) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings();
  }
};

template <typename C, typename C1, typename... PreviousBindings>
class PartialComponentStorage<BindInstance<C, C1>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  C& instance;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, C& instance)
      : previous_storage(previous_storage), instance(instance) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(InjectorStorage::createComponentStorageEntryForBindInstance<C, C>(instance));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <typename C, typename C1, typename... PreviousBindings>
class PartialComponentStorage<BindConstInstance<C, C1>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  const C& instance;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, const C& instance)
      : previous_storage(previous_storage), instance(instance) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(InjectorStorage::createComponentStorageEntryForBindConstInstance<C, C>(instance));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <typename C, typename Annotation, typename C1, typename... PreviousBindings>
class PartialComponentStorage<BindInstance<fruit::Annotated<Annotation, C>, C1>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  C& instance;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, C& instance)
      : previous_storage(previous_storage), instance(instance) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(
        InjectorStorage::createComponentStorageEntryForBindInstance<fruit::Annotated<Annotation, C>, C>(instance));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <typename C, typename Annotation, typename C1, typename... PreviousBindings>
class PartialComponentStorage<BindConstInstance<fruit::Annotated<Annotation, C>, C1>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  const C& instance;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, const C& instance)
      : previous_storage(previous_storage), instance(instance) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(
        InjectorStorage::createComponentStorageEntryForBindConstInstance<fruit::Annotated<Annotation, C>, C>(instance));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <typename... Params, typename... PreviousBindings>
class PartialComponentStorage<RegisterProvider<Params...>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) // NOLINT(google-explicit-constructor)
      : previous_storage(previous_storage) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings();
  }
};

template <typename C, typename... PreviousBindings>
class PartialComponentStorage<AddInstanceMultibinding<C>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  C& instance;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, C& instance)
      : previous_storage(previous_storage), instance(instance) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(InjectorStorage::createComponentStorageEntryForInstanceMultibinding<C, C>(instance));
    entries.push_back(InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<C>());
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 2;
  }
};

template <typename C, typename Annotation, typename... PreviousBindings>
class PartialComponentStorage<AddInstanceMultibinding<fruit::Annotated<Annotation, C>>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  C& instance;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, C& instance)
      : previous_storage(previous_storage), instance(instance) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(
        InjectorStorage::createComponentStorageEntryForInstanceMultibinding<fruit::Annotated<Annotation, C>, C>(
            instance));
    entries.push_back(
        InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<fruit::Annotated<Annotation, C>>());
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 2;
  }
};

template <typename C, typename... PreviousBindings>
class PartialComponentStorage<AddInstanceVectorMultibindings<C>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  std::vector<C>& instances;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, std::vector<C>& instances)
      : previous_storage(previous_storage), instances(instances) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    for (auto i = instances.rbegin(), i_end = instances.rend(); i != i_end; ++i) {
      // TODO: consider optimizing this so that we need just 1 MULTIBINDING_VECTOR_CREATOR entry (removing the
      // assumption that each multibinding entry is always preceded by that).
      entries.push_back(InjectorStorage::createComponentStorageEntryForInstanceMultibinding<C, C>(*i));
      entries.push_back(InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<C>());
    }
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + instances.size() * 2;
  }
};

template <typename C, typename Annotation, typename... PreviousBindings>
class PartialComponentStorage<AddInstanceVectorMultibindings<fruit::Annotated<Annotation, C>>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  std::vector<C>& instances;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, std::vector<C>& instances)
      : previous_storage(previous_storage), instances(instances) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    for (auto i = instances.rbegin(), i_end = instances.rend(); i != i_end; ++i) {
      // TODO: consider optimizing this so that we need just 1 MULTIBINDING_VECTOR_CREATOR entry (removing the
      // assumption that each multibinding entry is always preceded by that).
      entries.push_back(
          InjectorStorage::createComponentStorageEntryForInstanceMultibinding<fruit::Annotated<Annotation, C>, C>(*i));
      entries.push_back(
          InjectorStorage::createComponentStorageEntryForMultibindingVectorCreator<fruit::Annotated<Annotation, C>>());
    }
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + instances.size() * 2;
  }
};

template <typename I, typename C, typename... PreviousBindings>
class PartialComponentStorage<AddMultibinding<I, C>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) // NOLINT(google-explicit-constructor)
      : previous_storage(previous_storage) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings();
  }
};

template <typename... Params, typename... PreviousBindings>
class PartialComponentStorage<AddMultibindingProvider<Params...>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) // NOLINT(google-explicit-constructor)
      : previous_storage(previous_storage) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings();
  }
};

template <typename DecoratedSignature, typename Lambda, typename... PreviousBindings>
class PartialComponentStorage<RegisterFactory<DecoratedSignature, Lambda>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage) // NOLINT(google-explicit-constructor)
      : previous_storage(previous_storage) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings();
  }
};

template <typename OtherComponent, typename... PreviousBindings>
class PartialComponentStorage<InstallComponent<OtherComponent()>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  OtherComponent (*fun)();

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, OtherComponent (*fun1)(),
                          std::tuple<>)
      : previous_storage(previous_storage), fun(fun1) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(ComponentStorageEntry::LazyComponentWithNoArgs::create(fun));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <typename OtherComponent, typename... Args, typename... PreviousBindings>
class PartialComponentStorage<InstallComponent<OtherComponent(Args...)>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  OtherComponent (*fun)(Args...);
  std::tuple<Args...> args_tuple;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage,
                          OtherComponent (*fun1)(Args...), std::tuple<Args...> args_tuple)
      : previous_storage(previous_storage), fun(fun1), args_tuple(std::move(args_tuple)) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) {
    entries.push_back(ComponentStorageEntry::LazyComponentWithArgs::create(fun, std::move(args_tuple)));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <std::size_t i, typename ComponentFunctionsTuple>
struct AddAllComponentStorageEntries {
    inline void operator()(FixedSizeVector<ComponentStorageEntry>& entries,
                           ComponentFunctionsTuple& component_functions_tuple) {
      AddAllComponentStorageEntries<i - 1, ComponentFunctionsTuple>()(entries, component_functions_tuple);
      entries.push_back(createEntry(std::move(std::get<i - 1>(component_functions_tuple))));
    }

    template <typename ComponentType>
    inline ComponentStorageEntry createEntry(
        fruit::ComponentFunction<ComponentType> component_function) {
      return ComponentStorageEntry::LazyComponentWithNoArgs::create(std::move(component_function));
    }

    template <typename ComponentType, typename Arg, typename... Args>
    inline ComponentStorageEntry createEntry(
        fruit::ComponentFunction<ComponentType, Arg, Args...> component_function) {
      return ComponentStorageEntry::LazyComponentWithArgs::create(std::move(component_function));
    }
};

template <typename ComponentFunctionsTuple>
struct AddAllComponentStorageEntries<0, ComponentFunctionsTuple> {
    inline void operator()(FixedSizeVector<ComponentStorageEntry>&,
                           ComponentFunctionsTuple&) {}
};

template <typename ComponentFunctionsTuple>
void addAllComponentStorageEntries(FixedSizeVector<ComponentStorageEntry>& entries,
                                   ComponentFunctionsTuple&& component_functions_tuple) {
  AddAllComponentStorageEntries<std::tuple_size<ComponentFunctionsTuple>::value,
                                ComponentFunctionsTuple>()(
      entries, component_functions_tuple);
}

template <typename... ComponentFunctions, typename... PreviousBindings>
class PartialComponentStorage<InstallComponentFunctions<ComponentFunctions...>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  std::tuple<ComponentFunctions...> component_functions_tuple;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage,
                          std::tuple<ComponentFunctions...> component_functions_tuple)
      : previous_storage(previous_storage), component_functions_tuple(std::move(component_functions_tuple)) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) {
    addAllComponentStorageEntries(entries, std::move(component_functions_tuple));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + sizeof...(ComponentFunctions);
  }
};

template <typename OtherComponent, typename... PreviousBindings>
class PartialComponentStorage<PartialReplaceComponent<OtherComponent()>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  OtherComponent (*fun)();

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage, OtherComponent (*fun1)(),
                          std::tuple<>)
      : previous_storage(previous_storage), fun(fun1) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(ComponentStorageEntry::LazyComponentWithNoArgs::createReplacedComponentEntry(fun));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <typename OtherComponent, typename... ReplacedFunArgs, typename... PreviousBindings>
class PartialComponentStorage<PartialReplaceComponent<OtherComponent(ReplacedFunArgs...)>, PreviousBindings...> {
private:
  PartialComponentStorage<PreviousBindings...>& previous_storage;
  OtherComponent (*fun)(ReplacedFunArgs...);
  std::tuple<ReplacedFunArgs...> args_tuple;

public:
  PartialComponentStorage(PartialComponentStorage<PreviousBindings...>& previous_storage,
                          OtherComponent (*fun1)(ReplacedFunArgs...), std::tuple<ReplacedFunArgs...> args_tuple)
      : previous_storage(previous_storage), fun(fun1), args_tuple(std::move(args_tuple)) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) {
    entries.push_back(
        ComponentStorageEntry::LazyComponentWithArgs::createReplacedComponentEntry(fun, std::move(args_tuple)));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <typename OtherComponent, typename... PreviousBindings, typename... ReplacedFunArgs>
class PartialComponentStorage<ReplaceComponent<OtherComponent(ReplacedFunArgs...), OtherComponent()>,
                              PreviousBindings...> {
private:
  using previous_storage_t =
      PartialComponentStorage<PartialReplaceComponent<OtherComponent(ReplacedFunArgs...)>, PreviousBindings...>;

  previous_storage_t& previous_storage;
  OtherComponent (*fun)();

public:
  PartialComponentStorage(previous_storage_t& previous_storage, OtherComponent (*fun1)(), std::tuple<>)
      : previous_storage(previous_storage), fun(fun1) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) const {
    entries.push_back(ComponentStorageEntry::LazyComponentWithNoArgs::createReplacementComponentEntry(fun));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

template <typename OtherComponent, typename... ReplacedFunArgs, typename... ReplacementFunArgs,
          typename... PreviousBindings>
class PartialComponentStorage<
    ReplaceComponent<OtherComponent(ReplacedFunArgs...), OtherComponent(ReplacementFunArgs...)>, PreviousBindings...> {
private:
  using previous_storage_t =
      PartialComponentStorage<PartialReplaceComponent<OtherComponent(ReplacedFunArgs...)>, PreviousBindings...>;

  previous_storage_t& previous_storage;
  OtherComponent (*fun)(ReplacementFunArgs...);
  std::tuple<ReplacementFunArgs...> args_tuple;

public:
  PartialComponentStorage(previous_storage_t& previous_storage, OtherComponent (*fun1)(ReplacementFunArgs...),
                          std::tuple<ReplacementFunArgs...> args_tuple)
      : previous_storage(previous_storage), fun(fun1), args_tuple(std::move(args_tuple)) {}

  void addBindings(FixedSizeVector<ComponentStorageEntry>& entries) {
    entries.push_back(
        ComponentStorageEntry::LazyComponentWithArgs::createReplacementComponentEntry(fun, std::move(args_tuple)));
    previous_storage.addBindings(entries);
  }

  std::size_t numBindings() const {
    return previous_storage.numBindings() + 1;
  }
};

} // namespace impl
} // namespace fruit

#endif // FRUIT_PARTIAL_COMPONENT_STORAGE_DEFN_H
