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

#include "yaml-cpp/yaml.h"
#ifdef YAML_NAMESPACE
#define YAML YAML_NAMESPACE
#endif

#include <iostream>
#include <locale>
#include <regex>

using namespace std;

namespace YODA {

  /// Singleton creation function
  Reader& ReaderFLAT::create() {
    static ReaderFLAT _instance;
    _instance.registerDefaultTypes();
    return _instance;
  }


  void ReaderFLAT::registerDefaultTypes() {
    registerType<Scatter1D>();
    registerType<Scatter2D>();
    registerType<Scatter3D>();
  }


  void ReaderFLAT::read(istream& stream, vector<AnalysisObject*>& aos,
                                         const std::string& match,
                                         const std::string& unmatch) {

    /// State of the parser: line number, line, parser context, and pointer(s) to the object currently being assembled
    //unsigned int nline = 0;
    string s, pathcurr, annscurr, typestr = "";

    AnalysisObject* aocurr = nullptr; //< Generic current AO pointer (useful or not?)

    bool in_anns = false, pattern_pass = true;

    TypeRegisterItr thisAOR = _register.end();

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

    // Loop over all lines of the input file
    while (Utils::getline(stream, s)) {
      //nline += 1;

      // Trim the line
      Utils::itrim(s);

      // Ignore blank lines
      if (s.empty()) continue;

      // Ignore comments (whole-line only, without indent, and still allowed for compatibility on BEGIN/END lines)
      if (s.find("#") == 0 && s.find("BEGIN") == string::npos && s.find("END") == string::npos) continue;


      // Now the context-sensitive part
      if (typestr == "") {

        // We require a BEGIN line to start a context
        if (s.find("BEGIN ") == string::npos) throw ReadError("Unexpected line in FLAT format parsing when BEGIN expected");

        // Split into parts
        vector<string> parts;
        istringstream iss(s); string tmp;

        iss.imbue(std::locale::classic()); // Interpret numbers in the "C" locale

        while (iss >> tmp) {
          if (tmp != "#") parts.push_back(tmp);
        }

        // Extract context from BEGIN type
        assert(parts.size() >= 2 && parts[0] == "BEGIN");
        const string ctxstr = parts[1];

        // Extract the AO type
        typestr = ctxstr;
        if (ctxstr == "VALUE") {
          typestr = "SCATTER1D";
        }
        else if (ctxstr == "HISTO1D" || "HISTOGRAM") {
          typestr = "SCATTER2D";
          // @todo these probably need a mapping of lo/hi edge to x+/-err
        }
        else if (ctxstr == "HISTO2D" || "HISTOGRAM2D") {
          typestr = "SCATTER3D";
          // @todo same as above
        }

        // Get block path if possible
        pathcurr = (parts.size() >= 3) ? parts[2] : "";
        pattern_pass = patternCheck(pathcurr, patterns, unpatterns);
        if (!pattern_pass)  continue;

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

        // cout << aocurr->path() << " " << nline << " " << context << endl;
        continue;

      }
      else if (s.find("BEGIN ") != string::npos) {
        // Throw error if a BEGIN line is found
        throw ReadError("Unexpected BEGIN line in FLAT format parsing before ending current BEGIN..END block");
      }
      else if (s.find("END ") != string::npos) {
        // Clear/reset context and register AO if END line is found
        /// @todo Throw error if mismatch between BEGIN (context) and END types

        if (!pattern_pass) {
          pattern_pass = true;
          typestr = "";
          continue;
        }

        // Set the new context and create a new AO to populate
        aocurr = thisAOR->second->assemble(pathcurr);

        // Set all annotations
        try {
          YAML::Node anns = YAML::Load(annscurr);
          // for (YAML::const_iterator it = anns.begin(); it != anns.end(); ++it) {
          for (const auto& it : anns) {
            const string key = it.first.as<string>();
            // const string val = it.second.as<string>();
            YAML::Emitter em;
            em << YAML::Flow << it.second; //< use single-line formatting, for lists & maps
            const string val = em.c_str();
            aocurr->setAnnotation(key, val);
          }
        } catch (...) {
          /// @todo Is there a case for just giving up on these annotations, printing the error msg, and keep going? As an option?
          const string err = "Problem during annotation parsing of YAML block:\n'''\n" + annscurr + "\n'''";
          // cerr << err << endl;
          throw ReadError(err);
        }

        aos.push_back(aocurr);
        annscurr.clear();
        aocurr = nullptr;
        in_anns = false;
        typestr = "";
        continue; ///< @todo Improve... would be good to avoid these continues
      }
      else {
        /// @todo Flatten conditional blocks with more else-ifs?

        if (!pattern_pass)  continue;

        // Extract annotations for all types
        //const size_t ieq = s.find("=");
        //if (ieq != string::npos) {
        if (in_anns) {
          if (s == "---") {
            in_anns = false;
          } else {
            annscurr += (annscurr.empty() ? "" : "\n") + s;
          }
          continue;
        }

        // DATA PARSING
        thisAOR->second->parse(s);

      }
    }

  }


}
