/* Distributed under the Apache License, Version 2.0.
   See accompanying NOTICE file for details.*/

#pragma once

#include "LBMUtils.h"

namespace LBM
{
  // Profiles as we step the x-axis
  struct VelocityProfiles
  {
    CSV y;
    CSV z;
  };

  struct RunStatus
  {
    RunStatus() {}
    ~RunStatus() = default;

    bool analytic_velocity = true;
    float max_analytic_velocity_error = -std::numeric_limits<float>::max();
    bool vs_baseline_velocity = true;
    float max_vs_baseline_velocity_error = -std::numeric_limits<float>::max();
    bool vs_analytic_velocity = true;
    float max_vs_analytic_velocity_error = -std::numeric_limits<float>::max();
    float mid_vs_analytic_velocity_error = -std::numeric_limits<float>::max();

    bool analytic_pressure = true;
    float max_analytic_pressure_error = -std::numeric_limits<float>::max();
    bool vs_baseline_pressure = true;
    float max_vs_baseline_pressure_error = -std::numeric_limits<float>::max();
    bool vs_analytic_pressure = true;
    float max_vs_analytic_pressure_error = -std::numeric_limits<float>::max();
    float mid_vs_analytic_pressure_error = -std::numeric_limits<float>::max();

    bool analytic_temperature = true;
    float max_analytic_temperature_error = -std::numeric_limits<float>::max();
    bool vs_baseline_temperature = true;
    float max_vs_baseline_temperature_error = -std::numeric_limits<float>::max();
    bool vs_analytic_temperature = true;
    float max_vs_analytic_temperature_error = -std::numeric_limits<float>::max();
    float mid_vs_analytic_temperature_error = -std::numeric_limits<float>::max();

    bool analytic_wall_stress = true;
    float max_analytic_wall_stress_error = -std::numeric_limits<float>::max();
    bool vs_baseline_wall_stress = true;
    float max_vs_baseline_wall_stress_error = -std::numeric_limits<float>::max();
    bool vs_analytic_wall_stress = true;
    float max_vs_analytic_wall_stress_error = -std::numeric_limits<float>::max();
    float mid_vs_analytic_wall_stress_error = -std::numeric_limits<float>::max();

    std::vector<std::string> errors;

    double total_time_to_run_s = 0;
    double lbm_compute_time_s = 0;
    size_t num_nodes = 0;
    size_t num_preconditioner_iterations = 0;
    size_t num_fluid_iterations = 0;
    size_t num_thermal_iterations = 0;
  };

  struct ProfileComparison
  {
    ProfileComparison() {}
    ~ProfileComparison() = default;

    std::string name ="";
    size_t number_voxels =0;
    size_t number_voxel_failures =0;
    float  average_error =0;
    float  average_error_fraction=0;
  };

  class VerificationReport : public Loggable
  {
    friend class Verification;
  public:
    VerificationReport(Logger* logger) : Loggable(logger) {};
    virtual ~VerificationReport() = default;

    void MissingRun(std::string const& run) { m_run_status[run].errors.push_back("Unable to load"); };

    enum class ComparisonTypes
    {
      AnalyticVelocity = 0, VS_BaselineVelocity, VS_AnalyticVelocity,
    };
    bool CompareProfiles(ComparisonTypes type, std::string const& run_name, std::string const& table_name, const LBM::CSV& baseline, const LBM::CSV& computed, LBM::CSV& error);
    bool CompareValues(std::string const& run_name, std::string const& table_name, const LBM::CSV& baseline, const LBM::CSV& computed, LBM::CSV& error);
    bool RecordRuntime(std::string const& run_name, double runtime_s);
    void WriteReport(const std::string& to_dir);

    double percrent_difference = 2.0;
  protected:
    // Run Name vs Run Status

    std::map<std::string, RunStatus> m_run_status;
    // Run Name vs (Table Name vs List of Comparisons)
    std::map<std::string, std::map<std::string, std::vector<ProfileComparison>>> m_run_profile_comparisons;
  };
  
  class Verification : public Loggable
  {
  public:
    Verification(VerificationReport& rpt) : m_verification_report(rpt){};
    virtual ~Verification() {};
  protected:
    virtual bool AnalyticSolution(LBM::Run& run) = 0;
    void RunComparison(std::string const& baseline_root);

    void GetVelocityProfiles(LBM::Run const& lbm, LBM::VelocityProfiles& profiles, size_t num_slices = 3);
    void GetPressureTemperatureWallStressValues(LBM::Run const& lbm, LBM::CSV& csv, bool analytic_wall_label=true);

    std::string m_run_name;
    std::string m_out_dir;

    LBM::Run m_lbm_run;
    LBM::Run m_analytic_run;

    VerificationReport& m_verification_report;
  };

  class BendVerification : public Verification
  {
  public:
    BendVerification(VerificationReport& rpt) : Verification(rpt) {};
    virtual ~BendVerification() {};

    void RunComparison(std::string const& baseline_root, std::string const& out_dir, const LBM::Run& lbm);

  protected:
    bool AnalyticSolution(LBM::Run& run) override;
    void GenerateGeometry(LBM::Run& run, float radius, float length);
  };

  class BrickVerification : public Verification
  {
  public:
    BrickVerification(VerificationReport& rpt) : Verification(rpt) {};
    virtual ~BrickVerification() {};

    void RunComparison(std::string const& baseline_root, std::string const& out_dir, const LBM::Run& lbm);

  protected:
    bool AnalyticSolution(LBM::Run& run) override;
    void GenerateGeometry(LBM::Run& run, float x, float y, float z);
  };

  class CylinderVerification : public Verification
  {
  public:
    CylinderVerification(VerificationReport& rpt) : Verification(rpt) {};
    virtual ~CylinderVerification() {};

    void RunComparison(std::string const& baseline_root, std::string const& out_dir, const LBM::Run& lbm);

  protected:
    bool AnalyticSolution(LBM::Run& run) override;
    void GenerateGeometry(LBM::Run& run, float radius, float length);
  };

}
