#ifndef WRITER_BASE_HPP
#define WRITER_BASE_HPP

#include <output/IWriter.hpp>
#include <utils/PugsAssert.hpp>

#include <limits>
#include <optional>
#include <string>

class MeshVariant;
class OutputNamedItemDataSet;
class NamedDiscreteFunction;

class WriterBase : public IWriter
{
 public:
  class PeriodManager
  {
   private:
    const double m_time_period;
    mutable double m_next_time;

    mutable std::vector<double> m_saved_times;

   public:
    double
    timePeriod() const
    {
      return m_time_period;
    }

    double
    nextTime() const
    {
      return m_next_time;
    }

    void
    setNextTime(double next_time)
    {
      m_next_time = next_time;
    }

    size_t
    nbSavedTimes() const
    {
      return m_saved_times.size();
    }

    double
    savedTime(size_t i) const
    {
      Assert(i < m_saved_times.size());
      return m_saved_times[i];
    }

    double
    getLastTime() const
    {
      if (m_saved_times.size() > 0) {
        return m_saved_times[m_saved_times.size() - 1];
      } else {
        return std::numeric_limits<double>::lowest();
      }
    }

    void
    setSaveTime(double time) const
    {
      if (m_saved_times.size() == 0) {
        m_next_time = time;
      }
      m_next_time += m_time_period;
      m_saved_times.push_back(time);
    }

    PeriodManager(const PeriodManager&) = default;
    PeriodManager(PeriodManager&&)      = default;
    PeriodManager(double time_period) : m_time_period{time_period}, m_next_time{std::numeric_limits<double>::lowest()}
    {}
  };

 protected:
  const std::string m_base_filename;

  std::optional<PeriodManager> m_period_manager;

  mutable std::optional<std::string> m_signature;

 private:
  void _checkSignature(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;

  template <typename DiscreteFunctionType>
  static void _registerDiscreteFunction(const std::string& name, const DiscreteFunctionType&, OutputNamedItemDataSet&);

 protected:
  void _checkConnectivity(const std::shared_ptr<const MeshVariant>& mesh_v,
                          const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;

  void _checkMesh(const std::shared_ptr<const MeshVariant>& mesh_v,
                  const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;

  std::shared_ptr<const MeshVariant> _getMesh(
    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;

  OutputNamedItemDataSet _getOutputNamedItemDataSet(
    const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const;

  virtual void _writeAtTime(const MeshVariant& mesh_v,
                            const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                            double time) const = 0;

  virtual void _write(const MeshVariant& mesh_v,
                      const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const = 0;

  virtual void _writeMesh(const MeshVariant& mesh_v) const = 0;

 public:
  void write(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const final;

  void writeIfNeeded(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                     double time) const final;

  void writeForced(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                   double time) const final;

  void writeOnMesh(const std::shared_ptr<const MeshVariant>& mesh_v,
                   const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const final;

  void writeOnMeshIfNeeded(const std::shared_ptr<const MeshVariant>& mesh_v,
                           const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                           double time) const final;
  void writeOnMeshForced(const std::shared_ptr<const MeshVariant>& mesh_v,
                         const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
                         double time) const final;

  void writeMesh(const std::shared_ptr<const MeshVariant>& mesh_v) const final;
  void writeMesh(const MeshVariant& mesh_v) const final;

  WriterBase() = delete;

  WriterBase(const std::string& base_filename, const double& time_period);

  WriterBase(const std::string& base_filename);

  virtual ~WriterBase() = default;
};

#endif   // WRITER_BASE_HPP