// -*- C++ -*-
//
// This file is part of YODA -- Yet more Objects for Data Analysis
// Copyright (C) 2008-2023 The YODA collaboration (see AUTHORS for details)
//
#include "YODA/ReaderH5.h"
#include "YODA/Exceptions.h"
#include "YODA/Utils/StringUtils.h"

#include <map>
#include <string>

using namespace std;

namespace YODA {


  namespace {

    template <typename... Args, class F>
    constexpr void for_each_arg(F&& f) {
      (( f(Args{}) ), ...);
    }
  }


  /// Singleton creation function
  Reader& ReaderH5::create() {
    static ReaderH5 _instance;
    _instance.registerDefaultTypes<double,int,string>();
    return _instance;
  }

  template<typename ... Args>
  void ReaderH5::registerDefaultTypes() {
    registerType<Counter>();
    registerType<Estimate0D>();
    registerType<Scatter1D>();
    registerType<Scatter2D>();
    registerType<Scatter3D>();

    // add BinnedHisto/BinnedProfile in 1D
    for_each_arg<Args...>([&](auto&& arg) {
      using A1 = decay_t<decltype(arg)>;
      using BH = BinnedHisto<A1>;
      registerType<BH>();
      using BP = BinnedProfile<A1>;
      registerType<BP>();
      using BE = BinnedEstimate<A1>;
      registerType<BE>();

      // add BinnedHisto/BinnedProfile in 2D
      for_each_arg<Args...>([&](auto&& arg) {
        using A2 = decay_t<decltype(arg)>;
        using BH = BinnedHisto<A1,A2>;
        registerType<BH>();
        using BP = BinnedProfile<A1,A2>;
        registerType<BP>();
        using BE = BinnedEstimate<A1,A2>;
        registerType<BE>();

        // add BinnedHisto/BinnedProfile in 3D
        for_each_arg<Args...>([&](auto&& arg) {
          using A3 = decay_t<decltype(arg)>;
          //using BH = BinnedHisto<A1,A2,A3>;
          //registerType<BH>();
          //using BP = BinnedProfile<A1,A2,A3>;
          //registerType<BP>();
          using BE = BinnedEstimate<A1,A2,A3>;
          registerType<BE>();
        });
      });
    });
    registerType<HistoND<3>>();
  }


  void ReaderH5::read(const YODA_H5::File& file, vector<AnalysisObject*>& aos,
                      const string& match, const string& unmatch) {

    vector<regex> patterns, unpatterns;
    for (const string& pat : Utils::split(match,   ",")) { patterns.push_back(regex(pat)); }
    for (const string& pat : Utils::split(unmatch, ",")) { unpatterns.push_back(regex(pat)); }

    // Load edge information
    H5FileManager reader(file);

    // Prepare registry
    TypeRegisterItr thisAOR = _register.cend();
    const TypeRegisterItr endAOR = _register.cend();

    while (reader.next()) {

      // Check that type has been loaded
      thisAOR = _register.find(reader.type());
      if (thisAOR == endAOR) {
        throw ReadError("Unexpected context found: " + reader.type());
      }

      // Check if path is being (un-)matched
      //
      // Since the edge types are not known until run time,
      // (empty) objects still need to be instantiated
      // from the type registry in order to be able to
      // choose the correct set of edges to skip.
      const bool pattern_pass = patternCheck(reader.path(), patterns, unpatterns);
      if (!pattern_pass) {
        thisAOR->second->skip(reader);
        continue;
      }

      // Construct AO
      aos.push_back(thisAOR->second->mkFromH5(reader));
    }
  }

}
