#include <iostream>
#include <fstream>
#include <string>
#include <map>
// for size_t
#include <cstring>
// for getenv
#include <cstdio>
#include <cstdlib>

/**
 * Rudimentary read and parse Fortran namelist file routine
 *
 * @param filename ascii file to open
 * @param kv key=value hash table to fill in
 */
int
cnbFillInNamelistValues(const std::string& filename, std::map<std::string, 
		     std::string> &kv) {

  std::ifstream file;
  file.open(filename.c_str());
  
  if (file) {
    std::string line;
    while (std::getline(file, line)) {
      std::map<std::string, std::string>::iterator i;
      for (i = kv.begin(); i != kv.end(); ++i) {
	size_t loc1 = line.find(i->first);
	if (loc1 != std::string::npos) {
	  // found key
	  size_t loc2 = line.find('=', loc1+1);
	  // looking for "string" or 'string'
	  // assume that " and ' don't occur on the same line!
	  size_t loc3 = std::min(line.find('"', loc2+1), 
				 line.find('\'', loc2+1));
	  size_t loc4 = std::min(line.find('"', loc3+1), 
				 line.find('\'', loc3+1)); 
	  std::string val(line, loc3+1, loc4-loc3-1);
	  i->second = val;
	}	
      }
    }
    return 0;
  }
  else {
    // file does not exist
    return 1;
  }
}

/**
 * Return and test value of env variable NUBEAM_ACTION 
 * @param action either "init" or "step" (out)
 * @param init true if "init" (out)
 * @return error code (0=OK)
 */
int 
cnbGetAction(std::string &action, bool &init) {

  char* action_char = getenv("NUBEAM_ACTION");
  action = action_char;
  if (!action_char) {
    std::cerr << "?cnubeam_comp_exec: must set NUBEAM_ACTION environment variable!\n"; 
    return 1;
  }

  init = false;
  if (action == "INIT" || action == "init") {
    init = true;
  }
  else if (action == "STEP" || action == "step") {
    init = false;
  }
  else {
    std::cerr << " ?cnubeam_comp_exec: unrecognized NUBEAM_ACTION value=" << 
      action << '\n';
    return 2;
  }
  return 0;
}

/**
 * Get workpath 
 * @param workpath (can be "") (out)
 * @param workpath_cwd true if workpath is set (out)
 * @return error code (0=OK)
 */
int 
cnbGetWorkpath(std::string &workpath, bool &workpath_cwd) {
  char* workpath_char = getenv("NUBEAM_WORKPATH");
  workpath = "";
  workpath_cwd = false;
  if (workpath_char) {
    workpath = workpath_char;
    workpath_cwd = true;
    std::cout << " %cnubeam_comp_exec: workpath for NUBEAM files: " <<
      workpath << '\n';
    workpath = workpath_char;
  }
  return 0;
}

/**
 * Get value of PREACTDIR environment variable
 * @param preactdir value of $PREACTDIR (out)
 * @return error code (0=OK)
 */
int 
cnbGetPreactdir(std::string &preactdir) {
  char* preactdir_char = getenv("PREACTDIR");
  if (!preactdir_char) {
    return 1;
  }
  preactdir = preactdir_char;
  return 0;
}

/**
 * Get value of ADASDIR environment variable
 * @param adasdir value of $ADASDIR (out)
 * @return error code (0=OK)
 */
int 
cnbGetAdasdir(std::string &adasdir) {
  char* adasdir_char = getenv("ADASDIR");
  if (!adasdir_char) {
    return 1;
  }
  adasdir = adasdir_char;
  return 0;
}

/**
 * Get value of MAX_MPI_INT_BLOCK environment variable
 * @param max_mpi_int_block value of $MAX_MPI_INT_BLOCK (out)
 * @return error code (0=OK)
 */
int 
cnbGetMax_mpi_int_block(std::string &max_mpi_int_block) {
  char* max_mpi_int_block_char = getenv("MAX_MPI_INT_BLOCK");
  max_mpi_int_block ="";
  if (max_mpi_int_block_char) {
    max_mpi_int_block = max_mpi_int_block_char;
  }
  return 0;
}

/**
 * Get value of MAX_MPI_R8_BLOCK environment variable
 * @param max_mpi_r8_block value of $MAX_MPI_R8_BLOCK (out)
 * @return error code (0=OK)
 */
int 
cnbGetMax_mpi_r8_block(std::string &max_mpi_r8_block) {
  char* max_mpi_r8_block_char = getenv("MAX_MPI_R8_BLOCK");
  max_mpi_r8_block ="";
  if (max_mpi_r8_block_char) {
    max_mpi_r8_block = max_mpi_r8_block_char;
  }
  return 0;
}


/**
 * Parse namelist, fill in hash table and check values
 *
 * @param init flag for init or step 
 * @param filename
 * @param kv name=value pairs
 * @param iinit 1 if init_namelist or init_cdf are set
 * @param istep 1 if step
 * @param ipso true for separate plasma_state output
 * @param ps_update_flag true to write update file at end
 * @return error code (0=OK)
 */
int 
cnbParseNamelistFile(const int init, std::string &filename, 
		      std::map<std::string, std::string> &kv,
		      int &iinit, int &istep, bool &ipso, 
		      bool &ps_update_flag) {

  int ier = 0;
  // parse namelist
  kv["input_plasma_state"] = "";
  kv["output_plasma_state"] = "";
  kv["plasma_state_update"] = "";
  kv["private_fld_state"] = "";
  kv["private_ptcl_state"] = "";
  kv["init_namelist"] = "";
  kv["init_cdf"] = "";
  kv["step_namelist"] = "";
  kv["step_cdf"] = "";
  kv["RF_idata"] = "";
  kv["RF_idata"] = "";
  ier = cnbFillInNamelistValues(filename, kv);
  if (ier != 0) {
    // file does not exist
    std::cerr << "ERROR: " << filename << " does not exist.\n";
    return 1;
  }
  std::map<std::string, std::string>::iterator i;
  for (i = kv.begin(); i != kv.end(); ++i) {
    std::cout << ' ' << i->first << "=" << i->second << '\n';
  }

  if (kv["input_plasma_state"] == "") {
    std::cerr << "ERROR:  \"input_plasma_state\" not specified.\n";
    return 2;
  }

  iinit = 0;
  istep = 0;
  ipso = false;
  ps_update_flag = false;

  if ( (kv["init_namelist"] != "") || (kv["init_cdf"] != "") ) {
    iinit = 1;
  }
  if ( (kv["step_namelist"] != "") || (kv["step_cdf"] != "") ) {
    istep = 1;
  }
  
  if ( (kv["output_plasma_state"] != "") && (kv["plasma_state_update"] != "") ) {
    std::cerr << "  ERROR: cannot set both these to non-blank values:\n";
    std::cerr << "   output_plasma_state: " << kv["output_plasma_state"] << '\n';
    std::cerr << "   plasma_state_update: " << kv["plasma_state_update"] << '\n';
    return 3;
  }
  else {
    ps_update_flag = (kv["plasma_state_update"] != "");
    ipso = (kv["output_plasma_state"] != "");
  }

  if (kv["private_fld_state"] == "") {
    std::cerr << " ERROR: NUBEAM internal field state file\n";
    std::cerr << " \"private_fld_state\" must be specified.\n";
    return 4;
  }
  
  if (kv["private_ptcl_state"] == "") {
    std::cerr << " ERROR: NUBEAM internal ptcl state file";
    std::cerr << "  \"private_ptcl_state\" must be specified.\n";
    return 5;
  }

  if (init) {
    if (iinit == 0) {
      std::cerr << " ERROR: INIT action requires input file:\n";
      std::cerr << " \"init_namelist\" or \"init_cdf\" must be specified.\n";
      return 6;
    }
    // (it is optional whether step input is specified, when INIT is on).
  }
  else {
    if (iinit == 1) {
      std::cerr << " ERROR: STEP action: init filenames:\n";
      std::cerr << " \"init_namelist\" or \"init_cdf\" must NOT be specified.\n";
      return 7;
    }
    if (istep == 0) {
      std::cerr << " ERROR: STEP action requires input file:\n";
      std::cerr << " \"step_namelist\" or \"step_cdf\" must be specified.\n";
      return 8;
    }
  }
  return 0;
}

/** 
 * Error procedure
 * @param ier error code
 * @param msg message
 * @param line source file line number
 */
void 
cnbErrorCheck(const int ier, const std::string &msg, const int line) {
  if (ier != 0) {
    std::cerr << "***ERROR***: " << msg << " at line " << line << '\n';
  }
}
