

// remove subtree
#define _XOPEN_SOURCE 500
#include <ftw.h> // tree walk
#include <unistd.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h> // strtol
#include <string.h> // strsep
#include <sys/stat.h> // dir handling
#include <dirent.h> // dir handling
#include <libgen.h> // basename
#include <limits.h> //OPEN_MAX

#include <linux/limits.h> // PATH_MAX NAME_MAX

#include "m4vl.hpp"
//#include "utils.h"
//#include "dbg.h"
#include "io.hpp"


#define cleanup_and_return(xmpath, rrcc) {rmrf(xmpath);return rc;}

extern char * usec_timestamp(char * ts, size_t ts_len); // FIXME from cutout.cpp

/* three below to get ThreadID & getpid */
#include <sys/types.h>
#include <sys/syscall.h>
#include <unistd.h>

using namespace std;

char * get_pid_tid(char pidtidstr[NAME_MAX])
{
   long int tid = syscall(__NR_gettid); // ThreadID
   sprintf(pidtidstr,"%d-%ld",getpid(),tid);

   return pidtidstr;
}





int rmrf_unlink_cb(const char *fpath, const struct stat */*sb*/, int /*typeflag*/, struct FTW */*ftwbuf*/)
{
   //DBG_PRINTF("%s: removing: %s\n",__func__,fpath);
   LOG_STREAM << string{__func__} + ": removing: " + string{fpath} << endl;

   int rv = remove(fpath);

   if (rv) {
      //DBG_PRINTF("%s: remove: %s for %s\n",__func__,strerror(errno),fpath);
      LOG_STREAM << string{__func__} + ": remove: " + string{strerror(errno)} + " for " + string{fpath} << endl;
   }

   return rv;
}
int rmrf(char *path)
{
   return nftw(path, rmrf_unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
}

// the popen does not catch stderr only stdout. Redirect stderr to stdout.
// how about threadsafety ?? --> popen
// returns 0 if Ok
//         non-zero on failure
int exec_montage_cmd(const char * argcmd) {

   LOG_trace(__func__);

   int rc = 0;
   FILE *fp;
#define BUFSIZE 128
   char buf[BUFSIZE];

   char cmd[1024];//FIXME length
   sprintf(cmd,"{ time %s ; } 2>&1",argcmd);

   //DBG_PRINTF("%s: %s\n",__func__,cmd);
   LOG_STREAM << __func__ << ": " << cmd << endl;

   if ((fp = popen(cmd, "r")) == NULL) {
      //DBG_PRINTF("Error opening pipe, or !\n");
      LOG_STREAM << "Error opening pipe, or !" << endl;
      rc = -10;
      goto f_end;
   }

   while (fgets(buf, BUFSIZE, fp) != NULL) {
      // Do whatever you want here...
      //DBG_PRINTF("%s: OUTPUT: %s\n", __func__, buf);
      LOG_STREAM << __func__ << ": OUTPUT: " << buf << endl;
   }

   // NOTE seems like Montage-commands 
   // in case of error   exit with status 1 and write [struct stat="ERROR", msg % ...]
   // in case of success exit with status 0 and write [struct stat="OK", msg % ...]


   // int pclose() - returns exit status of the cmd as given by wait4()/waitpid()
   // from net: pclose() return -1 if there was
   // an error obtaining the command's exit status, otherwise it
   // returns the exit status itself.
   // As commands traditionally set exit status 0 to indicate success,
   // by checking if(pclose(fp)) we're testing for errors in either pclose()
   // or the command.
   int clrc;
   if ((clrc = pclose(fp))) {
      if(clrc == -1) {
         // DBG_PRINTF("%s: pclose: Command not found: %s\n",__func__,cmd);
   	 LOG_STREAM << __func__ << ": pclose: Command not found: " << cmd << endl;
         rc = -20;
      }
      else {
         //DBG_PRINTF("%s: pclose: Command exited with error status: %d\n",__func__,clrc);
   	 LOG_STREAM << __func__ << ": pclose: Command exited with error status:: " << clrc << endl;
         rc = -21;
      }
      goto f_end;
   }

f_end:
   return rc;
}

void printdir(const char * dir) {

   LOG_trace(__func__);

   // DEBUG: list dir
   DIR *dp;
   struct dirent *ep;

   dp = opendir (dir);
   if (dp != NULL)
   {
      while ((ep = readdir (dp))){
         // skip cur and parent dir
         if( (strncmp( ep->d_name, ".",  1 ) == 0) ||
               (strncmp( ep->d_name, "..", 2 ) == 0) ) {
            continue;
         }
         //DBG_PRINTF("%s[%s]: %s\n",__func__,dir,ep->d_name);
   	 LOG_STREAM << __func__ << "[" << dir <<  "]: " << ep->d_name << endl;
      }
      (void) closedir (dp);
   }
   else {
      //DBG_PRINTF("%s: opendir: %s for %s\n",__func__,strerror(errno),dir);
      LOG_STREAM << __func__ << ": opendir: " << strerror(errno) << " for " << dir << endl;
   }
}

// return 0 if merge successful, merged file created
//        non-zero for errors
int M4VL_mergefiles(struct merge_files * p, size_t nfiles, char * fitsfiles[], char * argmerged, size_t argmerged_len) {

   LOG_trace(__func__);


   int rc = 0;
   // max possible path name length
   //#define MPATHLEN PATH_MAX
   char mdir[MPATHLEN];// to be cretaed by timestamp and pid+tid
#define TS_LEN (256)
   char ts[TS_LEN];
   char pidtidstr[NAME_MAX];
   sprintf(mdir, "MERGE_%s_%s",
         usec_timestamp(ts,TS_LEN),
         get_pid_tid(pidtidstr));

   // global strings (for all merge commands)
   // subtree where montage works
   char mpath[MPATHLEN+1];
   sprintf(mpath,"%s/%s",p->mroot,mdir);
   // dir where input fits files are
   char mpathin[MPATHLEN + 4];
   sprintf(mpathin,"%s/%s",mpath,"in");
   // dir where montage places re-projected fits files
   char mpathproj[MPATHLEN + 8];
   sprintf(mpathproj,"%s/%s",mpath,"proj");

   if(mkdir(mpath, S_IRWXU)){
      // FIXME threadsafe is strerror_r()
      //DBG_PRINTF("%s: mkdir: %s for %s\n",__func__,strerror(errno),mpath);
      LOG_STREAM << __func__ << ": mkdir: " << strerror(errno) << " for " << mpath << endl;
      rc = -10;
      return rc;
   }
   if(mkdir(mpathin, S_IRWXU)){
      //DBG_PRINTF("%s: mkdir: %s for %s\n",__func__,strerror(errno),mpathin);
      LOG_STREAM << __func__ << ": mkdir: " << strerror(errno) << " for " << mpathin << endl;
      rc = -11;
      cleanup_and_return(mpath, rc);
   }
   if(mkdir(mpathproj, S_IRWXU)){
      //DBG_PRINTF("%s: mkdir: %s for %s\n",__func__,strerror(errno),mpathproj);
      LOG_STREAM << __func__ << ": mkdir: " << strerror(errno) << " for " << mpathproj << endl;
      rc = -12;
      cleanup_and_return(mpath, rc);
   }

   // make a link of files to be merged into the merge in-dir
   size_t i;
   for (i=0;i<nfiles;i++){

      char fn[MPATHLEN + MFNAMELEN];
      strcpy(fn, fitsfiles[i]);

      //DBG_PRINTF("%s: symlink src : %s\n",__func__,fn);
      LOG_STREAM << __func__ << ": symlink src: " << fn << endl;

      char destpath[MPATHLEN + 8];  
      snprintf (destpath, sizeof(destpath), "%s/%s", mpathin, basename(fn));

      //DBG_PRINTF("%s: symlink dest: %s\n",__func__,destpath);
      LOG_STREAM << __func__ << ": symlink src: " << destpath << endl;

      if(symlink(fn,destpath)) {
         // DBG_PRINTF("%s: symlink: %s\n",__func__,strerror(errno));
         LOG_STREAM << __func__ << ": symlink: " << strerror(errno) << endl;
         rc = -20;
         cleanup_and_return(mpath, rc);
      }
   }
   printdir(mpath);
   printdir(mpathin);

   // NAXIS from vlkbif.java::merge
   long dim = strtol(p->prefix,NULL,10);
   //DBG_PRINTF("%s: merge dim(%s): %ld\n",__func__,p->prefix,dim);
   LOG_STREAM << __func__ << ": merge dim(" << p->prefix << "): " << dim << endl;

   //
   // 1 create common header
   //
   //#define MFNAMELEN NAME_MAX
   char intbl[MPATHLEN + MFNAMELEN];
   sprintf(intbl,"%s/inputs.tbl",mpath);

   // FIXME assume 5 params which could be full pathnames
#define MPARAMSCNT 5
#define MCMDLEN (MPARAMSCNT*(MPATHLEN + MFNAMELEN))
   // current merge command
   int mrc = 0;
   char mcmd[MCMDLEN];
   sprintf(mcmd,"mImgtbl %s %s",mpathin,intbl);
   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -30;
      cleanup_and_return(mpath, rc);
   }
   printdir(mpath);

   char cmnhdr[MPATHLEN + MFNAMELEN];
   sprintf(cmnhdr,"%s/common.hdr",mpath);
   sprintf(mcmd,"mMakeHdr %s %s %s",intbl,cmnhdr,"GAL");
   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -31;
      cleanup_and_return(mpath, rc);
   }
   printdir(mpath);
   //    system("cat /tmp/MERGECTEST/common.hdr");

   //
   // 2 reproject all input files
   //
   // DEBUG: list dir --> or use scandir() ??
   DIR *dp;
   struct dirent *ep;

   dp = opendir (mpathin);
   if (dp == NULL)  {
      //DBG_PRINTF("%s: opendir: %s\n",__func__,strerror(errno));
      LOG_STREAM << __func__ << ": opendir: " << strerror(errno) << endl;
      rc = -31;
      cleanup_and_return(mpath, rc);
   }

   while ((ep = readdir (dp))){
      // skip cur and parent dir
      if( (strncmp( ep->d_name, ".",  1 ) == 0) ||
            (strncmp( ep->d_name, "..", 2 ) == 0) ) {
         continue;
      }
      //DBG_PRINTF("%s[%s]: %s\n",__func__,mpathin, ep->d_name);
      LOG_STREAM << __func__ << "[" << mpathin  <<"]: " << ep->d_name << endl;

      char inf[MPATHLEN + MFNAMELEN + 8];
      sprintf(inf,"%s/%s",mpathin,ep->d_name);
      char projf[MPATHLEN + MFNAMELEN + 16];
      sprintf(projf,"%s/%s-proj",mpathproj,ep->d_name);

      if(dim == 2) {
         sprintf(mcmd,"mProjectQL %s %s %s",inf,projf,cmnhdr);
      } else {
         sprintf(mcmd,"mProjectCube %s %s %s",inf,projf,cmnhdr);
      }

      if((mrc = exec_montage_cmd(mcmd))) {
         rc = -32;
         cleanup_and_return(mpath, rc);
      }
   }
   (void) closedir (dp);

   printdir(mpathproj);

   //
   // 3. merge projected cubes
   //
   //  String projtbl = FITScutpath + mergedir + "/projected.tbl";
   //  String[] mImgtblproj = {"mImgtbl", projpath, projtbl};
   //  result.add(execMontage(mImgtblproj));
   char projtbl[MPATHLEN + MFNAMELEN];
   sprintf(projtbl,"%s/projected.tbl",mpath);

   sprintf(mcmd,"mImgtbl %s %s",mpathproj,projtbl);
   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -33;
      cleanup_and_return(mpath, rc);
   }
   printdir(mpath);
   //
   // FIXME2: in Java result.add() was returning a string with full montage command and its returncode and stdout/err
   //    String returnStr = exitVal + " " + cmdoutput + " fullCmd: " + fullCmd;
   //
   //  exec_montage_cmd() should do similar:
   //
   //  int rc exec_montage_cmd(...,struct execMontage * pexit) where:
   //  struct execMontage { int cmdstatus; char * cmdoutput; char * fullcmd }
   //  and rc is error code of the execMonatge() call itself (not that of argcmd)
   //
   //
   //    String[] mAddCube = {"mAddCube", "-p", projpath, projtbl, cmnhdr, mergedfile};
   //    result.add(execMontage(mAddCube));
   char merged[MPATHLEN + MFNAMELEN];
   // strip terminating slash if exist
   if( p->mresdir[strlen(p->mresdir)-1] == '/'){
      sprintf(merged,"%svlkb-merged_%sD_%s_%s.fits",p->mresdir,p->prefix,ts,pidtidstr);
   }else{
      sprintf(merged,"%s/vlkb-merged_%sD_%s_%s.fits",p->mresdir,p->prefix,ts,pidtidstr);
   }
   if(dim == 2){
      // John Good/Montage: mAdd with -n if mProjectQL used
      sprintf(mcmd,"mAdd -n -p %s %s %s %s",mpathproj,projtbl,cmnhdr,merged);
   } else {
      sprintf(mcmd,"mAddCube -p %s %s %s %s",mpathproj,projtbl,cmnhdr,merged);
   }

   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -34;
      cleanup_and_return(mpath, rc);
   }
   printdir(mpath);

   // FIXME this is terrible !!
   for(i=0;i<argmerged_len;i++) argmerged[i] = 0;
   strncpy(argmerged,merged,argmerged_len);

   //DBG_PRINTF("%s: removing subtree...\n",__func__);
   LOG_STREAM << __func__ << ": removing subtree..." << endl;
   rmrf(mpath);
   return rc;
}



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// parallelizing:
// split M4VL_mergefiles into 3 steps
// * create common header        M4VL_mergefiles_common_header( ... OUT: mpath)
// * re-project one input file to common header M4VL_mergefiles_reprojection( ..., IN: mpath, fileA.fits, ... )
// * merge together reprojected files     M4VL_mergefiles_add_reprojected(..., IN: mpath, ... )

void merge_config_print(const struct merge_config * s)
{
#if 0
   DBG_PRINTF("%s: %s\n",__func__,s->mpath);
   DBG_PRINTF("%s: %s\n",__func__,s->mpathin);
   DBG_PRINTF("%s: %s\n",__func__,s->mpathproj);
   DBG_PRINTF("%s: %s\n",__func__,s->cmnhdr);
   DBG_PRINTF("%s: %s\n",__func__,s->mresdir);
   DBG_PRINTF("%s: %s\n",__func__,s->merged);
#else
   LOG_STREAM << __func__ << ": " << s->mpath << endl;
   LOG_STREAM << __func__ << ": " << s->mpathin << endl;
   LOG_STREAM << __func__ << ": " << s->mpathproj << endl;
   LOG_STREAM << __func__ << ": " << s->cmnhdr << endl;
   LOG_STREAM << __func__ << ": " << s->mresdir << endl;
   LOG_STREAM << __func__ << ": " << s->merged << endl;
#endif
}


// path composition:
// <mroot>/MERGE_<timstamp>_<pidtid>/{in,proj}
void M4VL_merge_config_init(
      const char * jobid,    // IN some identifier to separate paraallel merrge requests
      const char * mroot,    // IN dir where merge will create temporary working dirs
      const char * mresdir,  // IN result merged file put to this dir
      unsigned long dim,     // IN dimensionalty of data (2D | 3D)
      struct merge_config * s) // OUT
{
   strcpy(s->mroot,    mroot);
   strcpy(s->mresdir,  mresdir);
   s->dim = dim;

   char mdir[MPATHLEN];// to be created by timestamp and pid+tid
   /*    sprintf(mdir, "MERGE_%s_%s",
         usec_timestamp(s->ts,TS_LEN),
         get_pid_tid(s->pidtidstr));
         */    sprintf(mdir, "MERGE_%s",jobid);


   sprintf(s->mpath,      "%s/%s", s->mroot,  mdir);

   sprintf(s->mpathin,    "%s/%s", s->mpath,  "in");
   sprintf(s->mpathproj,  "%s/%s", s->mpath,  "proj");


   sprintf(s->cmnhdr,"%s/common.hdr",s->mpath);

   //char merged[MPATHLEN + MFNAMELEN];
   // strip terminating slash if exist
   if( s->mresdir[strlen(s->mresdir)-1] == '/'){
      sprintf(s->merged,"%svlkb-merged_%luD_%s.fits",s->mresdir,s->dim,jobid);
      //sprintf(s->merged,"%svlkb-merged_%luD_%s_%s.fits",s->mresdir,s->dim,s->ts,s->pidtidstr);
   }else{
      sprintf(s->merged,"%s/vlkb-merged_%luD_%s.fits",s->mresdir,s->dim,jobid);
      //sprintf(s->merged,"%s/vlkb-merged_%luD_%s_%s.fits",s->mresdir,s->dim,s->ts,s->pidtidstr);
   }

   merge_config_print(s);
}


// return 0 if common header successfully generated
//        non-zero for errors
int M4VL_mergefiles_common_header(
      struct merge_config * s,               // IN  config (working dir & file names)
      size_t nfiles, const char * fitsfiles[])  // IN  pathnames of FITS-files to merge
{
   LOG_trace(__func__);

   int rc = 0;

   if(mkdir(s->mpath, S_IRWXU)){
      // FIXME threadsafe is strerror_r()
      //DBG_PRINTF("%s: mkdir: %s for %s\n",__func__,strerror(errno),s->mpath);
      LOG_STREAM << __func__ << ": mkdir: " << strerror(errno) << " for " << s->mpath << endl;
      rc = -10;
      return rc;
   }
   if(mkdir(s->mpathin, S_IRWXU)){
      //DBG_PRINTF("%s: mkdir: %s for %s\n",__func__,strerror(errno),s->mpathin);
      LOG_STREAM << __func__ << ": mkdir: " << strerror(errno) << " for " << s->mpathin << endl;
      rc = -11;
      cleanup_and_return(s->mpath,rc);
   }
   if(mkdir(s->mpathproj, S_IRWXU)){
      //DBG_PRINTF("%s: mkdir: %s for %s\n",__func__,strerror(errno),s->mpathproj);
      LOG_STREAM << __func__ << ": mkdir: " << strerror(errno) << " for " << s->mpathproj << endl;
      rc = -12;
      cleanup_and_return(s->mpath,rc);
   }

   // make a link of files to be merged into the merge in-dir
   size_t i;
   for (i=0;i<nfiles;i++){

      const char * fn = fitsfiles[i];

      //DBG_PRINTF("%s: symlink src : %s\n",__func__,fn);
      LOG_STREAM << __func__ << ": symlink src: " << fn << endl;

      char destpath[MPATHLEN + 4];  
      // FIXME be consequent: sprintf xor snprintf ?!
      char base_fn[2048 + 16];
      strcpy(base_fn,fn);
      snprintf (destpath, sizeof(destpath), "%s/%s", s->mpathin, basename(base_fn));

      //DBG_PRINTF("%s: symlink dest: %s\n",__func__,destpath);
      LOG_STREAM << __func__ << ": symlink dest: " << destpath << endl;

      if(symlink(fn,destpath)) {
         //DBG_PRINTF("%s: symlink: %s\n",__func__,strerror(errno));
         LOG_STREAM << __func__ << ": symlink: " << strerror(errno) << endl;
         rc = -20;
         cleanup_and_return(s->mpath,rc);
      }
   }
   printdir(s->mpath);
   printdir(s->mpathin);

   //
   // 1 create common header
   //
#define MFNAMELEN NAME_MAX
   char intbl[MPATHLEN + MFNAMELEN];
   sprintf(intbl,"%s/inputs.tbl",s->mpath);

   // current merge command
   int mrc = 0;
   char mcmd[MCMDLEN];
   sprintf(mcmd,"mImgtbl %s %s",s->mpathin,intbl);
   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -30;
      cleanup_and_return(s->mpath,rc);
   }
   printdir(s->mpath);

   sprintf(mcmd,"mMakeHdr %s %s %s",intbl,s->cmnhdr,"GAL");
   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -31;
      cleanup_and_return(s->mpath,rc);
   }
   printdir(s->mpath);
   //    system("cat /tmp/MERGECTEST/common.hdr");

   return rc;
}



// return 0 if success
//        non-zero for errors
int M4VL_mergefiles_reproject(
      struct merge_config * s, // IN config (working dir & file names)
      const char * fitsfile)   // IN  pathnames of FITS-files to merge
{
   LOG_trace(__func__);

   int rc = 0;

   char base_fn[MPATHLEN + MFNAMELEN];
   strcpy(base_fn, fitsfile);

   char inf[2*(MPATHLEN + MFNAMELEN)];
   sprintf(inf,"%s/%s", s->mpathin, base_fn);

   char projf[2*(MPATHLEN + MFNAMELEN)];
   sprintf(projf,"%s/%s-proj", s->mpathproj, base_fn);

   char mcmd[MCMDLEN + 512];
   int mrc;

   if(s->dim == 2) {
      sprintf(mcmd,"mProjectQL %s %s %s", inf, projf, s->cmnhdr);
   } else {
      sprintf(mcmd,"mProjectCube %s %s %s", inf, projf, s->cmnhdr);
   }

   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -32;
      cleanup_and_return(s->mpath,rc);
   }

   return rc;
}


// return 0 if success
//        non-zero for errors
int M4VL_mergefiles_add_reprojected(
      struct merge_config * s,               // IN config (working dir & file names)
      char * argmpath, size_t argmpath_maxlen)  // OUT pathname of merged FTS-file (up to max name length)
{
   LOG_trace(__func__);   

   int rc = 0;

   char projtbl[MPATHLEN + MFNAMELEN];
   sprintf(projtbl,"%s/projected.tbl",s->mpath);

   char mcmd[MCMDLEN];
   int mrc;

   sprintf(mcmd,"mImgtbl %s %s",s->mpathproj,projtbl);
   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -33;
      cleanup_and_return(s->mpath,rc);
   }
   printdir(s->mpath);

   if(s->dim == 2){
      // John Good/Montage: mAdd with -n if mProjectQL used
      sprintf(mcmd,"mAdd -n -p %s %s %s %s",s->mpathproj,projtbl,s->cmnhdr,s->merged);
   } else {
      sprintf(mcmd,"mAddCube -p %s %s %s %s",s->mpathproj,projtbl,s->cmnhdr,s->merged);
   }

   if((mrc = exec_montage_cmd(mcmd))) {
      rc = -34;
      cleanup_and_return(s->mpath,rc);
   }
   printdir(s->mpath);

   // FIXME this is terrible !!
   size_t i;
   for(i=0;i<argmpath_maxlen;i++) argmpath[i] = 0;
   strncpy(argmpath,s->merged,argmpath_maxlen);

   //DBG_PRINTF("%s: removing subtree...\n",__func__);
   LOG_STREAM << __func__ << ": removing subtree..." << endl;
   rmrf(s->mpath);
   return rc;
}


// run split variant

// return 0 if merge successful, merged file created
//        non-zero for errors
int M4VL_mergefiles_split(struct merge_files * p, size_t nfiles, char * fitsfiles[], char * argmerged, size_t argmerged_len)
{
   int rc = 0;

   unsigned long dim = strtol(p->prefix,NULL,10);
   // FIXME prefix was (mis)used to carry dimensionality

   struct merge_config mconfig;

   M4VL_merge_config_init("DUMMYJOBD",p->mroot, p->mresdir, dim, &mconfig);
   merge_config_print(&mconfig);

   rc = M4VL_mergefiles_common_header(&mconfig, nfiles, (const char**)fitsfiles);

   size_t i;
   for( i = 0; i<nfiles; i++)
   {
      rc = M4VL_mergefiles_reproject(&mconfig, basename(fitsfiles[i]));
      if(rc) cleanup_and_return(mconfig.mpath, rc);
   }

   rc = M4VL_mergefiles_add_reprojected(&mconfig, argmerged, argmerged_len);

   return rc;
}


