// -*- 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/WriterYODA1.h"

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

#include <iostream>
#include <iomanip>
using namespace std;

namespace YODA {


  /// Singleton creation function
  Writer& WriterYODA1::create() {
    static WriterYODA1 _instance;
    _instance.setPrecision(6);
    return _instance;
  }


  void WriterYODA1::_writeAnnotations(std::ostream& os, const AnalysisObject& ao) {
    os << scientific << setprecision(_aoprecision);
    for (const string& a : ao.annotations()) {
      if (a.empty()) continue;
      /// @todo Write out floating point annotations as scientific notation
      string ann = ao.annotation(a);
      // remove stpurious line returns at the end of a string so that we don't
      // end up with two line returns.
      ann.erase(std::remove(ann.begin(), ann.end(), '\n'), ann.end());
      os << a << ": " << ann << "\n";
    }
    os << "---\n";
  }

  void WriterYODA1::writeAO(std::ostream& stream, const AnalysisObject& ao) {
    const string aotype = ao.type();
    if (aotype == "Counter") {
      writeCounter(stream, dynamic_cast<const Counter&>(ao));
    } else if (aotype == "Histo1D") {
      writeHisto1D(stream, dynamic_cast<const Histo1D&>(ao));
    } else if (aotype == "Histo2D") {
      writeHisto2D(stream, dynamic_cast<const Histo2D&>(ao));
    } else if (aotype == "Profile1D") {
      writeProfile1D(stream, dynamic_cast<const Profile1D&>(ao));
    } else if (aotype == "Profile2D") {
      writeProfile2D(stream, dynamic_cast<const Profile2D&>(ao));
    } else if (aotype == "Scatter1D") {
			writeScatter1D(stream, dynamic_cast<const Scatter1D&>(ao));
    } else if (aotype == "Scatter2D") {
      writeScatter2D(stream, dynamic_cast<const Scatter2D&>(ao));
    } else if (aotype == "Scatter3D") {
      writeScatter3D(stream, dynamic_cast<const Scatter3D&>(ao));
    } else {
      stream << "# Type " << aotype << " not supported in the legacy writer.\n";
      // Skip writing other AO types since they were not supported in YODA1.
    }
  }


  void WriterYODA1::writeCounter(std::ostream& os, const Counter& c) {
    ios_base::fmtflags oldflags = os.flags();
    os << scientific << showpoint << setprecision(_aoprecision);

    os << "BEGIN YODA_" << Utils::toUpper("COUNTER") << "_V2 " << c.path() << "\n";
    _writeAnnotations(os, c);
    os << "# sumW\t sumW2\t numEntries\n";
    os << c.sumW()  << "\t" << c.sumW2() << "\t" << c.numEntries() << "\n";
    os << "END YODA_" << Utils::toUpper("COUNTER") << "_V2\n\n";

    os.flags(oldflags);
  }


  void WriterYODA1::writeHisto1D(std::ostream& os, const Histo1D& h) {
    ios_base::fmtflags oldflags = os.flags();
    os << scientific << showpoint << setprecision(_aoprecision);

    os << "BEGIN YODA_" << Utils::toUpper("HISTO1D") << "_V2 " << h.path() << "\n";
    _writeAnnotations(os, h);
    try {
      os << "# Mean: " << h.xMean() << "\n";
      os << "# Area: " << h.integral() << "\n";
    } catch (LowStatsError& e) {
      //
    }
    os << "# ID\t ID\t sumw\t sumw2\t sumwx\t sumwx2\t numEntries\n";
    os << "Total   \tTotal   \t";
    os << h.sumW()  << "\t" << h.sumW2()  << "\t";
    os << h.sumWA(0) << "\t" << h.sumWA2(0) << "\t";
    os << h.numEntries() << "\n";
    os << "Underflow\tUnderflow\t";
    os << h.bin(0).sumW()  << "\t" << h.bin(0).sumW2()  << "\t";
    os << h.bin(0).sumWX() << "\t" << h.bin(0).sumWX2() << "\t";
    os << h.bin(0).numEntries() << "\n";
    os << "Overflow\tOverflow\t";
    os << h.bin(h.numBins()+1).sumW()  << "\t" << h.bin(h.numBins()+1).sumW2()  << "\t";
    os << h.bin(h.numBins()+1).sumWX() << "\t" << h.bin(h.numBins()+1).sumWX2() << "\t";
    os << h.bin(h.numBins()+1).numEntries() << "\n";
    os << "# xlow\t xhigh\t sumw\t sumw2\t sumwx\t sumwx2\t numEntries\n";
    for (const auto& b : h.bins()) {
      os << b.xMin() << "\t" << b.xMax() << "\t";
      os << b.sumW()    << "\t" << b.sumW2()    << "\t";
      os << b.sumWX()   << "\t" << b.sumWX2()   << "\t";
      os << b.numEntries() << "\n";
    }
    os << "END YODA_" << Utils::toUpper("HISTO1D") << "_V2\n\n";

    os.flags(oldflags);
  }


  void WriterYODA1::writeHisto2D(std::ostream& os, const Histo2D& h) {
    ios_base::fmtflags oldflags = os.flags();
    os << scientific << showpoint << setprecision(_aoprecision);
    os << "BEGIN YODA_" << Utils::toUpper("HISTO2D") << "_V2 " << h.path() << "\n";
    _writeAnnotations(os, h);
    try {
      //if ( h.totalDbn().numEntries() > 0 )
      os << "# Mean: (" << h.xMean() << ", " << h.yMean() << ")\n";
      os << "# Volume: " << h.integral() << "\n";
    } catch (LowStatsError& e) {
      //
    }
    os << "# ID\t ID\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t sumwxy\t numEntries\n";
    // Total distribution
    os << "Total   \tTotal   \t";
    os << h.sumW()   << "\t" << h.sumW2()  << "\t";
    os << h.sumWA(0)  << "\t" << h.sumWA2(0) << "\t";
    os << h.sumWA(1)  << "\t" << h.sumWA2(1) << "\t";
    os << h.crossTerm(0,1) << "\t";
    os << h.numEntries() << "\n";
    // Outflows
    /// @todo Disabled for now, reinstate with a *full* set of outflow info to allow marginalisation
    os << "# 2D outflow persistency not currently supported until API is stable\n";
    // Bins
    os << "# xlow\t xhigh\t ylow\t yhigh\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t sumwxy\t numEntries\n";
    for (const auto& b : h.bins()) {
      os << b.xMin() << "\t" << b.xMax() << "\t";
      os << b.yMin() << "\t" << b.yMax() << "\t";
      os << b.sumW()     << "\t" << b.sumW2()     << "\t";
      os << b.sumWX()    << "\t" << b.sumWX2()    << "\t";
      os << b.sumWY()    << "\t" << b.sumWY2()    << "\t";
      os << b.sumWXY()   << "\t";
      os << b.numEntries() << "\n";
    }
    os << "END YODA_" << Utils::toUpper("HISTO2D") << "_V2\n\n";

    os.flags(oldflags);
  }


  void WriterYODA1::writeProfile1D(std::ostream& os, const Profile1D& p) {
    ios_base::fmtflags oldflags = os.flags();
    os << scientific << showpoint << setprecision(_aoprecision);

    os << "BEGIN YODA_" << Utils::toUpper("PROFILE1D") << "_V2 " << p.path() << "\n";
    _writeAnnotations(os, p);
    os << "# ID\t ID\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t numEntries\n";
    os << "Total   \tTotal   \t";
    os << p.sumW()  << "\t" << p.sumW2()  << "\t";
    os << p.sumWA(0) << "\t" << p.sumWA2(0) << "\t";
    os << p.sumWA(1) << "\t" << p.sumWA2(1) << "\t";
    os << p.numEntries() << "\n";
    os << "Underflow\tUnderflow\t";
    os << p.bin(0).sumW()  << "\t" << p.bin(0).sumW2()  << "\t";
    os << p.bin(0).sumWX() << "\t" << p.bin(0).sumWX2() << "\t";
    os << p.bin(0).sumWY() << "\t" << p.bin(0).sumWY2() << "\t";
    os << p.bin(0).numEntries() << "\n";
    os << "Overflow\tOverflow\t";
    os << p.bin(p.numBins()+1).sumW()  << "\t" << p.bin(p.numBins()+1).sumW2()  << "\t";
    os << p.bin(p.numBins()+1).sumWX() << "\t" << p.bin(p.numBins()+1).sumWX2() << "\t";
    os << p.bin(p.numBins()+1).sumWY() << "\t" << p.bin(p.numBins()+1).sumWY2() << "\t";
    os << p.bin(p.numBins()+1).numEntries() << "\n";
    os << "# xlow\t xhigh\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t numEntries\n";
    for (const auto& b : p.bins()) {
      os << b.xMin() << "\t" << b.xMax() << "\t";
      os << b.sumW()    << "\t" << b.sumW2()    << "\t";
      os << b.sumWX()   << "\t" << b.sumWX2()   << "\t";
      os << b.sumWY()   << "\t" << b.sumWY2()   << "\t";
      os << b.numEntries() << "\n";
    }
    os << "END YODA_" << Utils::toUpper("PROFILE1D") << "_V2\n\n";

    os.flags(oldflags);
  }


  void WriterYODA1::writeProfile2D(std::ostream& os, const Profile2D& p) {
    ios_base::fmtflags oldflags = os.flags();
    os << scientific << showpoint << setprecision(_aoprecision);

    os << "BEGIN YODA_" << Utils::toUpper("PROFILE2D") << "_V2 " << p.path() << "\n";
    _writeAnnotations(os, p);
    os << "# sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t sumwz\t sumwz2\t sumwxy\t numEntries\n";
    // Total distribution
    os << "Total   \tTotal   \t";
    os << p.sumW()   << "\t" << p.sumW2()  << "\t";
    os << p.sumWA(0)  << "\t" << p.sumWA2(0) << "\t";
    os << p.sumWA(1)  << "\t" << p.sumWA2(1) << "\t";
    os << p.sumWA(2)  << "\t" << p.sumWA2(2) << "\t";
    os << p.crossTerm(0,1) << "\t"; // << td.sumWXZ() << "\t" << td.sumWYZ() << "\t";
    os << p.numEntries() << "\n";
    // Outflows
    /// @todo Disabled for now, reinstate with a *full* set of outflow info to allow marginalisation
    os << "# 2D outflow persistency not currently supported until API is stable\n";
    // Bins
    os << "# xlow\t xhigh\t ylow\t yhigh\t sumw\t sumw2\t sumwx\t sumwx2\t sumwy\t sumwy2\t sumwz\t sumwz2\t sumwxy\t numEntries\n";
    for (const auto& b : p.bins()) {
      os << b.xMin() << "\t" << b.xMax() << "\t";
      os << b.yMin() << "\t" << b.yMax() << "\t";
      os << b.sumW()     << "\t" << b.sumW2()     << "\t";
      os << b.sumWX()    << "\t" << b.sumWX2()    << "\t";
      os << b.sumWY()    << "\t" << b.sumWY2()    << "\t";
      os << b.sumWZ()    << "\t" << b.sumWZ2()    << "\t";
      os << b.sumWXY()   << "\t"; // << b.sumWXZ()    << "\t" << b.sumWYZ() << "\t";
      os << b.numEntries() << "\n";
    }
    os << "END YODA_" << Utils::toUpper("PROFILE2D") << "_V2\n\n";

    os.flags(oldflags);
  }


  void WriterYODA1::writeScatter1D(std::ostream& os, const Scatter1D& s) {
    ios_base::fmtflags oldflags = os.flags();
    os << scientific << showpoint << setprecision(_aoprecision);

    // we promised not to modify const s, but we want to add an annotation
    // we did not promise to not modify the *clone* of s...
    auto sclone =  s.clone();

    os << "BEGIN YODA_" << Utils::toUpper("SCATTER1D") << "_V2 " << s.path() << "\n";
    _writeAnnotations(os, sclone);

    //write headers
    std::string headers="# xval\t xerr-\t xerr+\t";
    os << headers << "\n";

    //write points
    for (const Point1D& pt : s.points()) {
      // fill central value
      os << pt.x() << "\t" << pt.xErrMinus() << "\t" << pt.xErrPlus() ;
      os <<  "\n";
    }
    os << "END YODA_" << Utils::toUpper("SCATTER1D") << "_V2\n\n";

    os << flush;
    os.flags(oldflags);
  }


  void WriterYODA1::writeScatter2D(std::ostream& os, const Scatter2D& s) {
    ios_base::fmtflags oldflags = os.flags();
    os << scientific << showpoint << setprecision(_aoprecision);
    os << "BEGIN YODA_" << Utils::toUpper("SCATTER2D") << "_V2 " << s.path() << "\n";

    // Write annotations.
    // We promised not to modify const s, but we want to add an annotation;
    // We did not promise to not modify the *clone* of s;
    // Judge not, lest ye be judged
    auto sclone = s.clone();
    _writeAnnotations(os, sclone);

    //write headers
    /// @todo Change ordering to {vals} {errs} {errs} ...
    std::string headers="# xval\t xerr-\t xerr+\t yval\t yerr-\t yerr+\t";
    os << headers << "\n";

    //write points
    for (const Point2D& pt : s.points()) {
      /// @todo Change ordering to {vals} {errs} {errs} ...
      // fill central value
      os << pt.x() << "\t" << pt.xErrMinus() << "\t" << pt.xErrPlus() << "\t";
      os << pt.y() << "\t" << pt.yErrMinus() << "\t" << pt.yErrPlus() ;
      os <<  "\n";
    }
    os << "END YODA_" << Utils::toUpper("SCATTER2D") << "_V2\n\n";

    os << flush;
    os.flags(oldflags);
  }


  void WriterYODA1::writeScatter3D(std::ostream& os, const Scatter3D& s) {
    ios_base::fmtflags oldflags = os.flags();
    os << scientific << showpoint << setprecision(_aoprecision);
    os << "BEGIN YODA_" << Utils::toUpper("SCATTER3D") << "_V2 " << s.path() << "\n";

    // write annotations
    // we promised not to modify const s, but we want to add an annotation
    // we did not promise to not modify the *clone* of s...
    auto sclone =  s.clone();
    _writeAnnotations(os, sclone);

    //write headers
    /// @todo Change ordering to {vals} {errs} {errs} ...
    std::string headers="# xval\t xerr-\t xerr+\t yval\t yerr-\t yerr+\t zval\t zerr-\t zerr+\t";
    os << headers << "\n";

    //write points
    for (const Point3D& pt : s.points()) {
      /// @todo Change ordering to {vals} {errs} {errs} ...
      // fill central value
      os << pt.x() << "\t" << pt.xErrMinus() << "\t" << pt.xErrPlus() << "\t";
      os << pt.y() << "\t" << pt.yErrMinus() << "\t" << pt.yErrPlus() << "\t";
      os << pt.z() << "\t" << pt.zErrMinus() << "\t" << pt.zErrPlus() ;
      os <<  "\n";
    }
    os << "END YODA_" << Utils::toUpper("SCATTER3D") << "_V2\n\n";

    os << flush;
    os.flags(oldflags);
  }


}
