/*

  rawtran -  RAW to FITS converter
  Copyright (C) 2007 - 2019  Filip Hroch, Masaryk University, Brno, CZ

  Rawtran is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Rawtran is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Rawtran.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "rawtran.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <assert.h>

#ifdef HAVE_PTHREAD_H
#include <pthread.h>

typedef struct {
  int status; /* =-1 (undefined), 0(run), 1(finish) */
  int index;  /* index in a list */
  int retval; /* return value */
  pthread_t thread; /* thread ID */
} PROC;

int ncpu;
PROC *cpu;
pthread_mutex_t mutex;
pthread_cond_t cond;

typedef struct {
  char *raw, *fits;
  int type, ev;
  char *filter;
  char *opts, *execs, *adds;
  char *dark;
} FRAME;

void *turn(void *data)
{
  FRAME *f = (FRAME *) data;
  int retval = rawtran(f->raw,f->fits,f->type,f->filter,
		       f->opts,f->execs,f->adds,f->dark,f->ev);

  pthread_t this = pthread_self();
  pthread_mutex_lock(&mutex);
  for(int j = 0; j < ncpu; j++)
    if( cpu[j].status == 0 && pthread_equal(cpu[j].thread,this) ) {
      cpu[j].status = 1;
      cpu[j].retval = retval;
    }
  pthread_cond_signal(&cond);
  pthread_mutex_unlock(&mutex);
  pthread_exit(NULL);
}
#endif

void help();
void version();
int dcraw_presented();
int in_filters(char *, char *[]);


static char *dark = NULL;

static void cleanup(int signo)
{
  if( dark )
    unlink(dark);
  exit(1);
}

int main(int argc,char *argv[])
{
  char *fits = NULL;
  char *opts = NULL, *execs = "-q 3 -w", *adds = NULL;
  int type = 0;
  int status = 1;
  int nraw = 0;
  int ev = 0;
  int clobber = 1;
  char *filter = NULL;
  char *standard_filters[] = {"X","Y","Z","R","V","B","clear","scotopic", NULL};
  char *instr_filters[] = {"Ri","Gi","Gi1","Gi2","Bi","plain","all", NULL};

  if( argc == 1 ) {
    help();
    return(!dcraw_presented());
  }

  for(int i = 1; i < argc; i++) {

    /* help */
    if( strcmp(argv[i],"--help") == 0 || strcmp(argv[i],"-h") == 0 ){
      help();
      return(!dcraw_presented());
    }

    /* version and license */
    if( strcmp(argv[i],"--version") == 0 ){
      version();
      return(0);
    }

    /* output name */
    else if( strcmp(argv[i],"-o") == 0 && i++ < argc )
      fits = argv[i];

    /* clobber */
    else if( strcmp(argv[i],"--no-clobber") == 0 )
      clobber = 0;

    /* save energy-like quantity */
    else if( strcmp(argv[i],"-eV") == 0 )
      ev = 1;

    /* select band */
    else if( strcmp(argv[i],"-c") == 0 && i++ < argc )
      filter = argv[i];

    /* conversion switches */
    else if( strcmp(argv[i],"-C") == 0 && i++ < argc )
      opts = argv[i];

    /* default switches */
    else if( strcmp(argv[i],"-X") == 0 && i++ < argc )
      execs = argv[i];

    /* additional switches */
    else if( strcmp(argv[i],"-A") == 0 && i++ < argc )
      adds = argv[i];

    else if( (strcmp(argv[i],"-D") == 0 || strcmp(argv[i],"-E") == 0)
	     && i+1 < argc ) {
      if( dark ) {
	fprintf(stderr,"Error: options -D and -E should not to be specified"
		" together.\n");
	status = 1;
	goto finish;
      }
      /* RAW dark frame */
      if( strcmp(argv[i],"-D") == 0 ) {
	dark = darksh(argv[++i]);
	if( dark == NULL ) {
	  fprintf(stderr,"Error: Failed to use `%s' as a RAW dark frame.\n",
		  argv[i]);
	  status = 1;
	  goto finish;
	}
      }
      /* FITS dark frame */
      else if( strcmp(argv[i],"-E") == 0 ) {
	dark = darkfits(argv[++i]);
	if( dark == NULL ) {
	  fprintf(stderr,"Error: Failed to use `%s' as a FITS dark frame.\n",
		  argv[i]);
	  status = 1;
	  goto finish;
	}
      }
    }

    else {

      if( *argv[i] == '-' ) {
	fprintf(stderr,"Warning: an unknown switch: `%s'.\n",argv[i]);
	status = 1;
	goto finish;
      }

      /* index of the first frame on command line */
      if( nraw == 0 )
	nraw = i;
    }
  }

  if( nraw == 0 ) {
    fprintf(stderr,"Error: unspecified raw photo(s).\n");
    return(1);
  }

  if( argc - nraw > 1 && fits ) {
    fprintf(stderr,"Error: Set of output filename (by -o) is uncompatible"
	    " with list of RAWs.\n");
    status = 1;
    goto finish;
  }

  /* determine type of conversion */
  if( filter == NULL || in_filters(filter,standard_filters) ) {
    type = 0;
    if( ! opts )
      opts = "-4 -o 5";
  }
  else if( in_filters(filter,instr_filters) ) {
    type = 1;
    if( ! opts )
      opts = "-4 -D";
  }
  else {
    fprintf(stderr,"Error: an unknown filter specified.\n");
    return(1);
  }

  /* The default locale is set to prevent to use of a non-english
     locale while launch of dcraw. More than 7 bits violates
     the FITS standard. */
  setenv("LC_ALL","C",1);

  /* setup signal handler for long term computations */
  signal(SIGINT, cleanup);
  signal(SIGTERM, cleanup);

#ifdef HAVE_PTHREAD_H
  pthread_attr_t attr;
  FRAME *frm;
  int nfrm = argc - nraw;

  /* prepare params */
  frm = malloc(nfrm*sizeof(FRAME));
  for(int i = 0; i < nfrm; i++) {
    frm[i].raw = argv[nraw+i];
    if( fits == NULL )
      frm[i].fits = fitspath(argv[nraw+i],".fits",clobber);
    else {
      frm[i].fits = malloc(strlen(fits) + 2);
      strcpy(frm[i].fits, clobber ? "!" : "");
      strcat(frm[i].fits,fits);
    }
    frm[i].type = type;
    frm[i].filter = filter;
    frm[i].opts = opts;
    frm[i].execs = execs;
    frm[i].adds = adds;
    frm[i].dark = dark;
    frm[i].ev = ev;
  }

  /* available CPUs  */
  ncpu = sysconf(_SC_NPROCESSORS_ONLN);
  if( ncpu == -1 )
    ncpu = 1;
  /* activate CPU monitors */
  cpu = malloc(ncpu*sizeof(PROC));
  for(int j = 0; j < ncpu; j++ ) {
    cpu[j].status = -1;
    cpu[j].thread = 0;
  }

  /* mutex and cond */
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init (&cond, NULL);

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

  status = 0;
  int nlast = 0;
  while( 1 ) {

    pthread_mutex_lock(&mutex);
    for(int j = 0; j < ncpu && nlast < nfrm; j++ ) {
      if( cpu[j].status == -1 ) {
	cpu[j].index = nlast;
	cpu[j].status = 0;
	if( pthread_create(&(cpu[j].thread),&attr,turn,(void *)&frm[nlast]) ){
	  perror("main() on create");
	  exit(1);
	}
	nlast++;
      }
    }
    pthread_mutex_unlock(&mutex);

    /* check current state */
    int q = 0;
    pthread_mutex_lock(&mutex);
    for(int j = 0; j < ncpu; j++)
      if( cpu[j].status != -1 )
	q = 1;
    pthread_mutex_unlock(&mutex);
    if( q == 0 ) break;

    /* wait */
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond,&mutex);
    pthread_mutex_unlock(&mutex);

    /* collect results */
    pthread_mutex_lock(&mutex);
    for(int j = 0; j < ncpu; j++ ) {

      if( cpu[j].status == 1 ) {
	int n = cpu[j].index;

	/* join and keep result */
	if( pthread_join(cpu[j].thread, NULL) ) {
	  perror("main() on join");
	  exit(1);
	}
	if( cpu[j].retval ) {
	  status = 2;
	  fprintf(stderr,"Error: rawtran failed for `%s'.\n",frm[n].raw);
	}
	cpu[j].status = -1;
      }
    }
    pthread_mutex_unlock(&mutex);
  }

  pthread_attr_destroy(&attr);
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);
  for(int i = 0; i < nfrm; i++) free(frm[i].fits);
  free(frm);
  free(cpu);

#else
  /* loop over filenames and invoke rawtran itself */
  status = 0;
  for(int i = nraw; i < argc; i++ ) {
    char *fitsname;
    if( fits == NULL )
      fitsname = fitspath(argv[i],".fits",clobber);
    else {
      fitsname = malloc(strlen(fits) + 2);
      strcpy(fitsname, clobber ? "!" : "");
      strcat(fitsname,fits);
    }
    if( rawtran(argv[i],fitsname,type,filter,opts,execs,adds,dark,ev) != 0 ) {
      status = 2;
      fprintf(stderr,"Error: rawtran failed for `%s'.\n",raw);
    }
    free(fitsname);
  }
#endif

 finish:
  if( dark ) {
    unlink(dark);
    free(dark);
  }
  return status;
}

void help()
{
  fprintf(stdout,
	 "RAWTRAN    RAW to FITS converter\n"
	 "Usage: rawtran [options] file(s)\n"
	 "Options:\n"
	 " -c  select a band (X,Y,Z,B,V,R,clear,scotopic,Ri,Gi,Gi1,Gi2,Bi,plain,all)\n"
	 "   * standard bands:\n"
	 "         XYZ stand for colour bands of CIE 1931 colourspace\n"
	 "         BVR stand for colour bands of Johnson UBVRI colour system\n"
	 "         clear stand for sum of X+Y+Z colour bands\n"
	 "         scotopic stand for simulated low-light level vision\n"
	 "   * instrumental bands:\n"
	 "         Ri,Gi1,Gi2,Bi stand for Bayer's mask elements\n"
	 "         Gi stand for arithmetic mean of Gi-s, Gi=(Gi1 + Gi2)/2\n"
	 "         plain stand for spatially not separated Bayer mask\n"
	 "         all stand for 4D image with B,Gi1,Gi2,R layers\n"
         " -o  output filename (considered for single file input)\n"
         " --no-clobber do not overwrite an existing file\n"
	 " -eV store an energy-like quantity (rather than photon-like)\n"
	 " -A  additional options passed to dcraw (default: '')\n"
	 " -C  conversions for dcraw (default: '-4 -o 5' standard,'-4 -D' instrumental)\n"
	 " -D  use the RAW frame as a dark frame\n"
	 " -E  use the FITS frame as a dark frame\n"
	 " -X  options passed to dcraw (default: '-q 3 -w')\n"
         " -h,--help\t give this help\n"
         "    --version\t display software version and license\n");
}

void version()
{
  fprintf(stdout,"RAWTRAN, %s, (C) 2007-2019 F. Hroch, Masaryk University in Brno, CZ\n\n",VERSION);
  fprintf(stdout,"RAWTRAN comes with ABSOLUTELY NO WARRANTY.\n"
	  "You may redistribute copies of RAWTRAN\n"
	  "under the terms of the GNU General Public License.\n"
	  "For more information about these matters, see the file named COPYING.\n");
}

int dcraw_presented()
{
  /*

    This part is looking for dcraw by an UNPORTABLE way:
      * closing of std. output by >&- works under sh only
      * the WEXITSTATUS may work under GNU only
      * WEXITSTATUS is used for right identification of status dcraw

   */

  int status;
  status = system("dcraw >&-");

  if( WEXITSTATUS(status) == 1 ) {
    fprintf(stderr,"Info: dcraw is available.\n");
    return(1);
  }
  else {
    fprintf(stderr,"\nWarning: dcraw not found.\n\n");
    return(0);
  }
}


int in_filters(char *filter, char *filters[])
{
  /* determine type of conversion */
  int i;

  for(i = 0; filters[i] != NULL; i++)
    if( strcmp(filters[i],filter) == 0 )
      return 1;

  return 0;
}
