// file      : bbot/utility.txx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <iostream> // cin

#include <libbutl/fdstream.hxx>

#include <libbutl/manifest-parser.hxx>
#include <libbutl/manifest-serializer.hxx>

#include <bbot/diagnostics.hxx>

namespace bbot
{
  // run_*()
  //
  template <typename I, typename O, typename E, typename... A>
  process
  run_io_start (tracer& t,
                I&& in,
                O&& out,
                E&& err,
                const process_env& pe,
                A&&... args)
  {
    try
    {
      return butl::process_start_callback (
        t,
        forward<I> (in),
        forward<O> (out),
        forward<E> (err),
        pe,
        forward<A> (args)...);
    }
    catch (const process_error& e)
    {
      fail << "unable to execute " << *pe.path << ": " << e << endf;
    }
  }

  template <typename P>
  process_exit::code_type
  run_io_finish_exit (tracer&, process& pr, const P& p, bool fh)
  {
    try
    {
      pr.wait ();

      const process_exit& e (*pr.exit);

      if (!e.normal ())
        fail (fh) << "process " << p << " " << e;

      return e.code ();
    }
    catch (const process_error& e)
    {
      fail (fh) << "unable to execute " << p << ": " << e << endf;
    }
  }

  template <typename P>
  inline void
  run_io_finish (tracer& t, process& pr, const P& p, bool fh)
  {
    if (run_io_finish_exit (t, pr, p, fh) != 0)
      fail (fh) << "process " << p << " exited with non-zero code";
  }

  template <typename I, typename O, typename E, typename P, typename... A>
  inline process_exit::code_type
  run_io_exit (tracer& t, I&& in, O&& out, E&& err, const P& p, A&&... args)
  {
    process pr (run_io_start (t,
                              forward<I> (in),
                              forward<O> (out),
                              forward<E> (err),
                              p,
                              forward<A> (args)...));

    return run_io_finish_exit (t, pr, p);
  }

  template <typename I, typename O, typename E, typename P, typename... A>
  inline void
  run_io (tracer& t, I&& in, O&& out, E&& err, const P& p, A&&... args)
  {
    process pr (run_io_start (t,
                              forward<I> (in),
                              forward<O> (out),
                              forward<E> (err),
                              p,
                              forward<A> (args)...));
    run_io_finish (t, pr, p);
  }

  // curl
  //
  template <typename I, typename O, typename... A>
  http_curl::
  http_curl (tracer& t,
             I&& in,
             O&& out,
             method_type m,
             const string& url,
             A&&... options)
      : butl::curl (t,
                    forward<I> (in),
                    forward<O> (out),
                    2,                // Diagnostics to stderr.
                    m,
                    url,
                    "-A", BBOT_USER_AGENT,
                    forward<A> (options)...)
  {
  }

  template <typename I, typename O, typename... A>
  tftp_curl::
  tftp_curl (tracer& t,
             I&& in,
             O&& out,
             method_type m,
             const string& url,
             A&&... options)
      : butl::curl (t,
                    forward<I> (in),
                    forward<O> (out),
                    2,                // Diagnostics to stderr.
                    m,
                    url,
                    forward<A> (options)...)
  {
  }

  // *_manifest()
  //
  template <typename T>
  T
  parse_manifest (const path& f, const char* what, bool fh, bool iu)
  {
    using namespace butl;

    try
    {
      if (f.string () == "-")
        return parse_manifest<T> (std::cin, "<stdin>", what, fh, iu);

      if (!file_exists (f))
        fail (fh) << what << " manifest file " << f << " does not exist";

      bool d (f.extension () == "lz4");

      ifdstream ifs (f,
                     (d ? fdopen_mode::binary : fdopen_mode::none),
                     (d
                      ? ifdstream::badbit
                      : ifdstream::badbit | ifdstream::failbit));
      ilz4stream izs;

      if (d)
        izs.open (ifs, true /* end */);

      return parse_manifest<T> (d
                                ? static_cast<istream&> (izs)
                                : static_cast<istream&> (ifs),
                                f.string (), what, fh, iu);
    }
    catch (const invalid_argument& e) // Invalid compressed content.
    {
      fail (fh) << "invalid " << what << " manifest " << f << ": " << e
                << endf;
    }
    catch (const system_error& e) // EACCES, etc.
    {
      fail (fh) << "unable to access " << what << " manifest " << f << ": "
                << e << endf;
    }
  }

  template <typename T>
  T
  parse_manifest (istream& is,
                  const string& name,
                  const char* what,
                  bool fh,
                  bool iu)
  {
    using namespace butl;

    try
    {
      manifest_parser p (is, name);
      return T (p, iu);
    }
    catch (const manifest_parsing& e)
    {
      fail (fh) << "invalid " << what << " manifest: " << name << ':'
                << e.line << ':' << e.column << ": " << e.description << endf;
    }
    catch (const io_error& e)
    {
      fail (fh) << "unable to read " << what << " manifest " << name << ": "
                << e << endf;
    }
  }

  template <typename T>
  void
  serialize_manifest (const T& m, const path& f, const char* what, bool ll)
  {
    using namespace std;
    using namespace butl;

    try
    {
      ofdstream ofs (f, fdopen_mode::binary);
      auto_rmfile arm (f); // Try to remove on failure ignoring errors.

      serialize_manifest (m, ofs, f.string (), what, true, ll);

      ofs.close ();
      arm.cancel ();
    }
    catch (const system_error& e) // EACCES, etc.
    {
      fail << "unable to access " << what << " manifest " << f << ": " << e;
    }
  }

  template <typename T>
  void
  serialize_manifest (const T& m,
                      ostream& os,
                      const string& name,
                      const char* what,
                      bool fh,
                      bool ll)
  {
    using namespace butl;

    try
    {
      manifest_serializer s (os, name, ll);
      m.serialize (s);
      return;
    }
    catch (const manifest_serialization& e)
    {
      fail (fh) << "invalid " << what << " manifest: " << e.description;
    }
    catch (const io_error& e)
    {
      fail (fh) << "unable to write to " << what << " manifest " << name
                << ": " << e;
    }
  }
}
