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

#include "LBM.h"
#include "RunUtils.h"
#ifdef VTK_EXTENSION
  #include "VTKUtils.h"
#endif

// Support legacy input decks
bool LoadLegacyScenario(std::string const& legacy_dir, LBM::Run& lbm);

int main(int argc, char* argv[])
{
  Logger log("./LBMLegacyDriver.log");
  // Specify which CUDA device to use, or leave black to automatically choose the largest device found.
  if (!cuda_device_check(log))
  {
    log.Error("CUDA device check failed, ensure you have specified a valid CUDA device id.");
    return 1;
  }
  std::stringstream ss;
  LBM::RunConfig runset;

  try
  {
    for (LBM::RunIO run : runset.GetLegacyDirRuns("/cylinder"))
    {
      LBM::Run lbm(run.output_base_path + ".log");
      if (!LoadLegacyScenario(run.input, lbm))
      {
        lbm.Error("Unable to load configuration file.");
        return 1;
      }
      if (!lbm.Compute())
      {
        lbm.Error("Error running LBM.");
        return 1;
      }
      lbm.Info("Writing outputs to " + run.output_base_path);
      // Metrics
      auto m = LBM::VTK::ComputeMetrics(lbm);
      ss << *m;
      lbm.Info(ss);
#ifdef VTK_EXTENSION
      // Write Things with VTK
      LBM::VTK::WriteMetrics(run.output_base_path, *m);
      LBM::VTK::WriteVTR(run.output_base_path + ".vtr", lbm);
#endif
    }
  }
  catch (std::exception ex)
  {
    log.Error(ex.what());
    return 1;
  }

  return 0;
}


bool ContainsLegacyScenario(std::string const& dir)
{
  std::string config_file = dir + "/setprob.data";
  std::string voxel_file = dir + "/voxelgrid.data";
  std::string grid_file = dir + "/grid.data";

  if (!FileExists(config_file))
    return false;
  if (!FileExists(voxel_file))
    return false;
  if (!FileExists(grid_file))
    return false;
  return true;
}

const std::vector<LBM::RunIO>& LBM::RunConfig::GetLegacyDirRuns(std::string const& sub_dir)
{
  return GetLegacyRuns(m_legacy_dir + sub_dir);
}
const std::vector<LBM::RunIO>& LBM::RunConfig::GetLegacyRuns(std::string const& root)
{
  m_legacy_runs.clear();
  std::vector<std::string> files;
  ListFiles(root, files, "setprob.data");
  // Take off the file from what's returned to get the directory
  for (std::string file : files)
  {
    std::string dir = file.substr(0, file.find("setprob.data"));
    if (!ContainsLegacyScenario(dir))
      continue;

    LBM::RunIO io;
    io.input = dir;
    // Create our output directory and name our output files

    std::string name = HasEnding(root, "/") ? root.substr(0, root.size() - 1) : root;
    name = name.substr(name.find_last_of("/"));
    std::string out_dir = "./legacy_runs/" + dir.substr(m_legacy_dir.length());
    CreatePath(out_dir);
    io.output_base_path = out_dir + name;
    m_legacy_runs.push_back(io);
  }
  return m_legacy_runs;
}

bool LoadLegacyScenario(std::string const& dir, LBM::Run& lbm)
{
  try
  {
    std::string line;
    std::istringstream iss;

    if (!ContainsLegacyScenario(dir))
      return false;

    std::string config_file = dir + "/setprob.data";
    std::string voxel_file = dir + "/voxelgrid.data";
    std::string grid_file = dir + "/grid.data";

    /////////////////
    // CONFIG FILE //
    /////////////////
    std::ifstream cFile(config_file, std::ios::in);

    float value;
    std::string name;
    int line_num = 0;
    while (std::getline(cFile, line))
    {
      if (line_num++ < 4)// Skip the header
        continue;
      iss.clear();
      iss.str(line);
      if (!(iss >> value >> name))
      {
        lbm.Error("Unable to read dimensions from grid file: " + config_file);
        return false;
      }
      if (name.compare("pRef") == 0)
      {
        lbm.cfg.atmospheric_pressure = value; continue;
      }
      if (name.compare("cSound") == 0)
      {
        lbm.cfg.speed_of_sound = value; continue;
      }
      if (name.compare("visc") == 0)
      {
        lbm.cfg.viscosity = value; continue;
      }
      if (name.compare("alphaT") == 0)
      {
        lbm.cfg.thermal_coefficient = value; continue;
      }
      if (name.compare("rho") == 0)
      {
        lbm.cfg.density = value; continue;
      }
      if (name.compare("WallTemp") == 0)
      {
        lbm.cfg.wall_temperature = value; continue;
      }
      if (name.compare("AirTemp") == 0)
      {
        lbm.cfg.fluid_temperature = value; continue;
      }
      if (name.compare("iRunMode") == 0)
      {
        if (value == 1)
        {
          // Impose a pressure drop
          lbm.cfg.inlet_boundary_condition = LBM::BoundaryCondition::Pressure;
          lbm.cfg.outlet_boundary_condition = LBM::BoundaryCondition::Pressure;
        }
        else
        {
          lbm.cfg.inlet_boundary_condition = LBM::BoundaryCondition::Flow;
          lbm.cfg.outlet_boundary_condition = LBM::BoundaryCondition::Flow;
        }
        continue;
      }
      if (name.compare("delp") == 0 && lbm.cfg.inlet_boundary_condition == LBM::BoundaryCondition::Pressure)
      {
        lbm.cfg.inlet_pressure = 0;
        lbm.cfg.outlet_pressure = -value;
        continue;
      }
      else if (name.compare("Qflow") == 0 && lbm.cfg.inlet_boundary_condition == LBM::BoundaryCondition::Flow)
      {
        lbm.cfg.inlet_volumetric_flow_rate = value / 6e04f;
        lbm.cfg.outlet_volumetric_flow_rate = value / 6e04f;
        continue;
      }
    }

    // Imposed volumetric flow rate
    //cfg.inlet_boundary_condition = BoundaryCondition::Flow;
    //cfg.inlet_value = 0.016/6e04;       // m^3/s (Volumetric airflow during inspiration (?). Conversion is made form l/min form numerical consistency)
    //cfg.outlet_boundary_condition = BoundaryCondition::Flow;
    //cfg.outlet_value = 0.016/6e04;

    ///////////////
    // GRID FILE //
    ///////////////

    std::ifstream gFile(grid_file, std::ios::in);
    // We only need to read the first 2 lines
    // Line 1
    std::getline(gFile, line);
    iss.str(line);
    if (!(iss >> lbm.in.dimensions[0] >> lbm.in.dimensions[1] >> lbm.in.dimensions[2]))
    {
      lbm.Error("Unable to read dimensions from grid file: " + grid_file);
      return false;
    }
    // Line 2
    iss.clear();
    std::getline(gFile, line);
    iss.str(line);
    if (!(iss >> lbm.in.grid_spacing))
    {
      lbm.Error("Error occurred while reading grid spacing from grid file: " + grid_file);
      return false;
    }

    ////////////////
    // VOXEL FILE //
    ////////////////

    lbm.in.labels.clear();
    lbm.in.source_labels.clear();
    lbm.in.source_to_lbm_label_map.clear();
    size_t total_cells = lbm.in.dimensions[0] * lbm.in.dimensions[1] * lbm.in.dimensions[2];
    size_t num_wall_cells = 0;
    size_t num_inflow_cells = 0;
    size_t num_outflow_cells = 0;
    size_t num_inactive_cells = 0;
    size_t num_interior_cells = 0;
    size_t num_unknown_cells = 0;

    std::ifstream vFile(voxel_file, std::ios::in | std::ios::binary);
    if (vFile)
    {
      char* buffer = new char[total_cells];
      vFile.read(buffer, total_cells);

      for (size_t i = 0; i < total_cells; ++i)
      {
        switch (buffer[i])
        {
        case 0:
        {
          lbm.in.labels.push_back(LBM::BoundaryTypes::Inactive); //inactive - outside of the region of interest
          num_inactive_cells++;
          continue;
        }
        case 1:
        {
          lbm.in.labels.push_back(LBM::BoundaryTypes::Wall);  //interior of the region of interest
          num_interior_cells++;
          continue;
        }
        case 10:
        {
          lbm.in.labels.push_back(LBM::BoundaryTypes::Inlet);  //inlet boundary condition area
          num_inflow_cells++;
          continue;
        }
        case 20:
        {
          lbm.in.labels.push_back(LBM::BoundaryTypes::Outlet);  //outlet boundary condition area
          num_outflow_cells++;
          continue;
        }
        default:
          num_unknown_cells++;
          continue;
        }
      }
      delete[] buffer;
    }
    else
    {
      lbm.Error("Unable to open " + voxel_file + " file for reading.");
      return false;
    }
    lbm.Info("Successfully read legacy files in " + dir);
    lbm.Info("Initial Boundary Codes:");
    lbm.Info("Grid Size (x, y, z) = (" + std::to_string(lbm.in.dimensions[0]) + ","
      + std::to_string(lbm.in.dimensions[1]) + ","
      + std::to_string(lbm.in.dimensions[2]) + ")");
    lbm.Info("Number of Nodes: " + std::to_string(total_cells));
    lbm.Info("Number of Inactive Nodes: " + std::to_string(num_inactive_cells));
    lbm.Info("Number of Interior Nodes: " + std::to_string(num_interior_cells));
    lbm.Info("Number of Wall Nodes: " + std::to_string(num_wall_cells));
    lbm.Info("Number of Inflow Nodes: " + std::to_string(num_inflow_cells));
    lbm.Info("Number of Outflow Nodes: " + std::to_string(num_outflow_cells));
    lbm.Info("Number of Unknown Nodes: " + std::to_string(num_unknown_cells));
  }
  catch (std::exception ex)
  {
    lbm.Error(ex.what());
    return false;
  }
  return true;
}
