// -*- C++ -*-
//
// This file is part of YODA -- Yet more Objects for Data Analysis
// Copyright (C) 2008-2025 The YODA collaboration (see AUTHORS for details)
//
#include "YODA/WriterYODA.h"
#include "YODA/WriterFLAT.h"
#include "YODA/Config/BuildConfig.h"

#ifdef HAVE_HDF5
#include "YODA/WriterH5.h"
#endif

#ifdef HAVE_LIBZ
#define _XOPEN_SOURCE 700
#include "zstr/zstr.hpp"
#endif

#include <iostream>
#include <locale>
#include <typeinfo>
#include <sstream>
#include <memory>
using namespace std;

namespace YODA {


  Writer& mkWriter(const string& name) {
    // Determine the format from the string (a file or file extension)
    const size_t lastdot = name.find_last_of(".");
    string fmt = Utils::toLower(lastdot == string::npos ? name : name.substr(lastdot+1));
    const bool compress = (fmt == "gz"s) || (fmt == "h5"s && enableH5compression);
    //cout << "***" << compress << endl;
    if (compress && fmt == "gz"s) {
      #ifndef HAVE_LIBZ
      throw UserError("YODA was compiled without zlib support: can't write " + name);
      #endif
      const size_t lastbutonedot = (lastdot == string::npos) ? string::npos : name.find_last_of(".", lastdot-1);
      fmt = Utils::toLower(lastbutonedot == string::npos ? name : name.substr(lastbutonedot+1));
    }
    // Create the appropriate Writer
    Writer* w = nullptr;
    #ifdef HAVE_HDF5
    if (Utils::startswith(fmt, "h5"))   w = &WriterH5::create();
    #endif
    if (Utils::startswith(fmt, "yoda")) w = &WriterYODA::create();
    else if (Utils::startswith(fmt, "dat" )) w = &WriterFLAT::create(); ///< @todo Improve/remove... .ydat?
    else if (Utils::startswith(fmt, "flat")) w = &WriterFLAT::create();
    if (!w) throw UserError("Format cannot be identified from string '" + name + "'");
    w->useCompression(compress);
    return *w;
  }


  void Writer::write(const std::string& filename, const AnalysisObject& ao) {
    std::vector<const AnalysisObject*> vec{&ao};
    write(filename, vec);
  }

  // Canonical writer function using H5 file
  #ifdef HAVE_HDF5
  void Writer::write(YODA_H5::File& file, const std::vector<const AnalysisObject*>& aos) {
    writeAOS(file, aos);
  }
  #endif

  // Canonical writer function using stream, including compression handling
  void Writer::write(ostream& stream, const vector<const AnalysisObject*>& aos, int precision) {
    std::unique_ptr<std::ostream> zos;
    std::ostream* os = &stream;

    setPrecision(precision);

    // Write numbers in the "C" locale
    std::locale prev_locale = os->getloc();
    os->imbue(std::locale::classic());

    // Wrap the stream if needed
    if (_compress) {
      #ifdef HAVE_LIBZ
      // Doesn't work to always create zstr wrapper: have to only create if compressing :-/
      // zstr::ostream zstream(stream);
      // ostream& os = _compress ? zstream : stream;
      os = new zstr::ostream(stream);
      zos.reset(os);
      #else
      throw UserError("YODA was compiled without zlib support: can't write to a compressed stream");
      #endif
    }

    // Write the data components
    /// @todo Remove the head/body/foot distinction?
    writeHead(*os);
    bool first = true;
    for (const AnalysisObject* aoptr : aos) {
      setAOPrecision( aoptr->annotation("WriterDoublePrecision", 0) );
      if (!first) *os << "\n"; //< blank line between items
      writeBody(*os, aoptr);
      first = false;
    }
    writeFoot(*os);
    *os << flush;

    os->imbue(prev_locale);
  }


  void Writer::writeBody(ostream& stream, const AnalysisObject* ao) {
    if (!ao) throw WriteError("Attempting to write a null AnalysisObject*");
    writeBody(stream, *ao);
  }

  void Writer::writeBody(ostream& stream, const AnalysisObject& ao) {
    try {
      writeAO(stream, ao);
    }
    catch(...) {
      ostringstream oss;
      oss << "Unrecognised analysis object type " << ao.type() << " in Writer::write";
      throw Exception(oss.str());
    }
  }


}
