/*
 * Author : Rémi Cresson
 */

#include "service_internal_hpc.h"
//#include "response_print.h"
//#include "server_internal.h"
#include "ssh_api.h" // WPS server-side API (WPS<->Cluster)
#include <string>     // std::string, std::to_string
/**
 * Remote application prefix
 */
const std::string REMOTE_APPLICATION_PREFIX = "otbcli_"; // TODO: get this from cfg

/**
 * Global OTB counter 
 */
int hpcCounter=0;

/**
 * A pointer to the conf maps containing the main.cfg settings
 */
maps* m_HPCConf;

/**
 * Replace all occurence of from by to in a str string
 *
 * @param str the string to transform
 * @param from the string to replace
 * @param to the string used as replacement
 * @return the resulting string 
 */
std::string ReplaceString(std::string str, const std::string& from, const std::string& to) {
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
    }
    return str;
}

struct FileToTransfert
{
  std::string local;
  std::string remote;
  std::string commandLineKey;
};

struct CommandLineAtom
{
  std::string key;
  std::string value;
};

// Dirty routine to get separate logs TODO: remove this
void clearLog()
{
  std::ofstream myfile;
  myfile.open ("/var/www/tmpWps/output_debug_log.txt");
  myfile << "Log:\n";
  myfile.close();
}

// Dirty routine to get separate logs TODO: remove this
void log(char * s)
{
  std::ofstream myfile;
  myfile.open ("/var/www/tmpWps/output_debug_log.txt", std::ios::app);
  myfile << s;
  myfile << "\n";
  myfile.close();
}

// Dirty routine to get separate logs TODO: remove this
void log(std::string s)
{
  log((char*) s.c_str());
}


/*

// FOR DEBUG

void pipeMap(std::stringstream &log, map * it)
{
if (it==NULL)
return;
if (it->name==NULL)
return;
for (;;)
{
log << "\nNAME=" << it->name << "\tVALUE=" << it->value;
if (it->next!=NULL)
{
it = it->next;
}
else
{
break;
}
}
}

void pipeElements(std::stringstream &log, elements * it)
{
if (it==NULL)
return;
if (it->name==NULL)
return;
for (;;)
{
log << "\nNAME=" << it->name << "\nContents:";
pipeMap(log, it->content);
if (it->next!=NULL)
{
it = it->next;
}
else
{
break;
}
}
}
*/


/*
 * get the input image extension
 */
std::string get_file_extension(map * mimeTypeMap)
{
  std::string ext;
  if(strncasecmp(mimeTypeMap->value,"image/jp2",9)==0)
    ext="j2k";
  if(strncasecmp(mimeTypeMap->value,"image/tiff",10)==0)
    ext="tiff";
  else if(strncasecmp(mimeTypeMap->value,"image/png",9)==0)
    ext="png";
  else if(strncasecmp(mimeTypeMap->value,"image/jpeg",10)==0)
    ext="jpeg";
  else if(strncasecmp(mimeTypeMap->value,"text/xml",8)==0)
    ext="gml";
  else
  {
    ext="unknow";
    log("ERROR: Unknow file format, mimetype=" + std::string(mimeTypeMap->value));
  }
  return ext;
}

/**
 * Transform local filename (wps server side) into remote filename (cluster side)
 */
std::string LocalFilenameToRemoteFilename(std::string &localfilename)
{
  char sep = '/';
  size_t i = localfilename.rfind(sep, localfilename.length());
  if (i != std::string::npos)
  {
     return(REMOTE_SCRATCH_DIRECTORY + localfilename.substr(i+1, localfilename.length() - i));
  }

   return("");
}

/**
 * Upload a list of files. Return 0 if Ok, else the number of failed transfert
 */
int UploadFiles(std::vector<FileToTransfert> list)
{
  int rc = 0;
  for (unsigned int i = 0 ; i < list.size(); i++)
  {
    log ("Upload "  + list[i].local + " to " + list[i].remote);
    if (upload_file(list[i].local, list[i].remote) != 0)
    {
      rc = -1;
      log("\tError: could not transfert file");
    }
  }
  return rc;
}

/**
 * Download a list of files. Return 0 if Ok, else the number of failed transfert
 */
int DownloadFiles(std::vector<FileToTransfert> list)
{
  int rc = 0;
  for (unsigned int i = 0 ; i < list.size(); i++)
  {
    log ("Download "  + list[i].remote + " to " + list[i].local);
    if (download_file(list[i].local, list[i].remote) != 0)
    {
      rc ++;
      log("\tError: could not transfert file");
    }
  }
  return rc;
}

/**
 * Load and run an OTB Application corresponding to the service by using inputs parameters.
 * Define the m_HPCConf 
 *
 * @param main_conf the conf maps containing the main.cfg settings
 * @param request the map containing the HTTP request
 * @param s the service structure, describe the .zcfg
 * @param real_inputs the maps containing the inputs
 * @param real_outputs the maps containing the outputs
 */
int zoo_hpc_support(maps** main_conf,map* request,service* s,maps **real_inputs,maps **real_outputs){
  clearLog();

  maps* m=*main_conf;
  maps* inputs=*real_inputs;
  maps* outputs=*real_outputs;

  // Temporary directory (e.g. /var/www/tmpWps)
  map* tmpPath=getMapFromMaps(m,"main","tmpPath");

  // SID (kind of hashcode)
  map* tmpSid_main=getMapFromMaps(m,"lenv","usid");

  // Save inputs to the temporary directory 
  dumpMapsValuesToFiles(main_conf,real_inputs);

  // Get the list of keys/values (input of application)
  std::vector<CommandLineAtom> commandLineAtoms;
  std::vector<FileToTransfert> filesToDownload;
  std::vector<FileToTransfert> filesToUpload;

  // Browse inputs
  elements * it = s->inputs;
  if (it->name!=NULL)
  {
    for (;;)
    {
      // get the current input key
      map * input_key=getMapFromMaps(inputs,it->name,"value");
log("processing key " + std::string(it->name));
      // if associated value is NULL, we skip this key/value
      if (strcmp(input_key->value,"NULL")!=0)
      {
        std::string key, value;
        key = std::string(it->name);
log("processed key " + key + " is not null");
        // if the key's value is ComplexData, then we check the associated cache_file in 
        // order to upload file(s)
        if (strcmp(it->format,"ComplexData")==0 )
        {
log("key related to complex data");
          // value can be a list, so we need to iterate through the map
          map* input_cache_file = getMapFromMaps(inputs,it->name,"cache_file");
          if (input_cache_file == NULL)
          {
            // passed by reference TODO: something with the url?
          }
          else
          {

            // Check if the key has multiple values (i.e. list)
            value = std::string(input_cache_file->value);
            FileToTransfert fileToTransfert;
            fileToTransfert.local = value;
            fileToTransfert.remote = LocalFilenameToRemoteFilename(value);
            fileToTransfert.commandLineKey = std::string(it->name);
            filesToUpload.push_back(fileToTransfert);
            value = fileToTransfert.remote;
            map* tmpLength=getMapFromMaps(inputs,it->name,"length");
            if(tmpLength!=NULL)
            {
              // key may have multiple parameters
              int len=atoi(tmpLength->value);
              for(int k=1;k<len;k++)
              {
                char tmp[15];
                sprintf(tmp,"cache_file_%d",k);
                log(std::string(tmp));
                map* tmpVal=getMapFromMaps(inputs,it->name,tmp);
                if(tmpVal!=NULL){
                  std::string filename(tmpVal->value);
                  FileToTransfert fileToTransfert;
                  fileToTransfert.local = filename;
                  fileToTransfert.remote = LocalFilenameToRemoteFilename(filename);
                  fileToTransfert.commandLineKey = std::string(it->name);
                  filesToUpload.push_back(fileToTransfert);
                  value += " " + fileToTransfert.remote;
                }
              }
            }
          } // passed by reference/expression
        }  // is complex data
        else
        {
          value = input_key->value;
        }

        // get the current output key
        maps* output_key=getMaps(outputs,it->name);
        if (output_key!=NULL)
        {

          // If the current output key is not NULL, we consider its value an output parameter.
          // As an current input key already exists, we check if it's the same. Then we concatenate
          // the output key value with the generated filename in order to respect the convention
          // of OTB. e.g. -out output_file.tif uint8. In this example, uint8 is the
          // current input value and output_file the current output value

          // check that output_key is the same as input_key
          if (key.compare(std::string(output_key->name)) != 0)
          {
            log("ERROR: key is not the same at the input/output");
            return SERVICE_FAILED;
          }

          // get output (file?) type
          map* mimeTypeMap = getMapFromMaps(outputs,output_key->name,"mimeType");
          if (mimeTypeMap->value == NULL)
          {
            log("ERROR: mimeTypeMap is NULL");
            return SERVICE_FAILED;
          }
          std::string extension = get_file_extension(mimeTypeMap);

          // construct the local output file name
          // TODO: Make something generalizable to other apps than OTB...
          std::string filename = std::string(tmpPath->value) + std::string(s->name) // temp. path + application name
        	+ "_" + key // application output name (e.g. "out")
        	+ "_" + std::string(tmpSid_main->value)  // sid
        	+ "." + extension; // output filename extension (e.g. ".tiff")

          // add the filename to the list of files to download
          FileToTransfert fileToTransfert;
          fileToTransfert.local = filename;
          fileToTransfert.remote = LocalFilenameToRemoteFilename(filename);
          fileToTransfert.commandLineKey = key;
          filesToDownload.push_back(fileToTransfert);
          log ( "\nAdd file to download after execution : \n\tLocal:" + fileToTransfert.local + "\n\tRemote:" + fileToTransfert.remote);

          // the stored value is the filename followed by the datatype value (OTB convention)
          value = fileToTransfert.remote
        	+ " " + value; // output data type (e.g. "uint16")  
        }

        // we add the key/value
	 log ( "\nAdd key=" + key + " (format: "  + std::string(it->format) + ") value=" + value);
        CommandLineAtom commandLineAtom;
        commandLineAtom.key = key;
        commandLineAtom.value = value;
        commandLineAtoms.push_back(commandLineAtom);
      } // key is not NULL

      if (it->next!=NULL)
      {
        it = it->next;
      }
      else
      {
        break;
      }
    }
  }

  // Browse outputs
  elements * out_it = s->outputs;
  if (out_it->name!=NULL)
  {
    for (;;)
    {
      // get the current output key
      map * output_key=getMapFromMaps(outputs,out_it->name,"value");
      std::string output_key_name(out_it->name);
      log("processing output key " + output_key_name + "(" + std::string(out_it->format) + ")" );

      if (strcmp(out_it->format,"ComplexData")==0 )
      {
        // get the current associated input key
        map * input_key=getMapFromMaps(inputs,out_it->name,"value");
  
        // if associated value is not NULL, we skip this key/value (already done when browsing inputs)
        if (input_key!=NULL)
        {
log("This key is already incorporated in the commandline");
        }
        else
        {
log("We have an output key to add.");

          // get output (file?) type
          std::string key(out_it->name);
          map* mimeTypeMap = getMapFromMaps(outputs,key.c_str(),"mimeType");
          if (mimeTypeMap==NULL)
          {
            log("mimeTypeMap=NULL");
          }
          else if (mimeTypeMap->value == NULL)
          {
            log("ERROR: mimeTypeMap is NULL");
            return SERVICE_FAILED;
          }

          std::string extension = get_file_extension(mimeTypeMap);

          // construct the local output file name
          std::string filename = std::string(tmpPath->value) + std::string(s->name) // temp. path + application name
        	+ "_" + key // application output name (e.g. "out")
        	+ "_" + std::string(tmpSid_main->value)  // sid
        	+ "." + extension; // output filename extension (e.g. ".tiff")

          // add the filename to the list of files to download
          FileToTransfert fileToTransfert;
          fileToTransfert.local = filename;
          fileToTransfert.remote = LocalFilenameToRemoteFilename(filename);
          fileToTransfert.commandLineKey = key;
          filesToDownload.push_back(fileToTransfert);
          log ( "\nAdd file to download after execution : \n\tLocal:" + fileToTransfert.local + "\n\tRemote:" + fileToTransfert.remote);

        // we add the key/value
	    CommandLineAtom commandLineAtom;
        commandLineAtom.key = key;
        commandLineAtom.value = fileToTransfert.remote;
        commandLineAtoms.push_back(commandLineAtom);
        }
      }
      if (out_it->next!=NULL)
      {
        out_it = out_it->next;
      }
      else
      {
        break;
      }
    }
  }

  // Create the commandline
  std::string commandLine(REMOTE_APPLICATION_PREFIX + std::string(s->name));
  for (unsigned int i = 0 ; i < commandLineAtoms.size(); i++)
  {
    commandLine += " -" + commandLineAtoms[i].key + " " + commandLineAtoms[i].value;
  }
  log ( "\nCommand line (cluster side) is:" + commandLine);

  // Upload files
  int rc = UploadFiles(filesToUpload);
  if (rc==0)
  {
    log ( "\nFiles were successfuly uploaded" );
  }
  else
  {
    log ( "\nSome upload(s) failed" );
    return SERVICE_FAILED;
  }

  // Execute the job (synchronous mode)
  std::string executionResult = "";
  rc = submit_command_sync(commandLine, executionResult);
  if (rc==0)
  {
    log ( "\nJob successfuly executed (" + executionResult + ")" );
  }
  else
  {
    log ( "\nError during job execution : " + executionResult );
    return SERVICE_FAILED;
  }
  
  // Download results
  rc = DownloadFiles(filesToDownload);
  if (rc==0)
  {
    log ( "\nFiles were successfuly downloaded" );
  }
  else
  {
    log ( "\nWarning, some download(s) failed" );
  }

  // Encapsulate results in output 
  // As we are lazy, we just iterate over the downloaded files
  // (no need to check filesToDownload consistency since the previous
  // step not returned)
  for (unsigned int i = 0 ; i < filesToDownload.size() ; i++)
  {
    log("Encapsulating " + filesToDownload[i].local + " (" + filesToDownload[i].commandLineKey + ")");
    setMapInMaps(outputs,filesToDownload[i].commandLineKey.c_str(),"generated_file",filesToDownload[i].local.c_str());
  }

  log("Service succeeded");
  return SERVICE_SUCCEEDED;

}
