/*

  rawtran -  RAW to FITS converter
  Copyright (C) 2007-x  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/>.

  Ideas:

  * read infos from files to prevent direct run of dcraw (no needs
    interprocess communication + nice fine tunning parameters)

*/

#define _XOPEN_SOURCE

#include "pixmap.h"
#include "rawtran.h"

#include <time.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <float.h>
#include <stdlib.h>
#include <error.h>
#include <fitsio.h>

#include <assert.h>

#define MAXEXIF 21

#define CLIP(flux,low,high) (flux > low ? (flux < high ? flux : high) : low)

unsigned short array(int xdim,int ydim, unsigned short *a, int i, int j)
{
  assert((0 <= i && i < xdim) && (0 <= j && j < ydim));
  return a[j*xdim + i];
}

unsigned short ftran(int n, float e, const float cmatrix[], float x[])
{
  int j,c;
  float s;

  s = 0.0;
  for(j = 0; j < n; j++)
    s += cmatrix[j]*x[j];

  c = s / e + 0.5;
  return CLIP(c,0,65535);
}

int rawtran(char *raw, char *fitsname, int type, char *filter,
	    char *opts, char *execs, char *adds, char *dark, int ev)
{
  fitsfile *file;
  int status, npixels, iso, naxis;
  size_t nbytes;
  long naxes[3];
  unsigned short int *flux;
  int i,j,k,m,jj,width,height,nexif,idx,ix,jx;
  float g1,g2,x,y,exptime=-1.0,focus=-1.0;
  char exif[MAXEXIF][FLEN_CARD+1];
  char stime[FLEN_CARD+1] = "";
  char etime[FLEN_CARD+1] = "";
  char camera[FLEN_CARD+1] = "";
  char aperture[FLEN_CARD+1] = "";
  char *key;
  const char *ver = "Created by " PACKAGE_NAME " ver." VERSION ": " PACKAGE_URL;

  /*  transposed matrix for CIE 1931 XYZ to Johnson BVR transformation, D65 */
  const float cmatrix[][3] = {{ 0.55995,    0.16213,   -0.12843},
			      {-0.02186,    1.01475,   -0.24956},
			      {-0.14007,   -0.26207,    0.90307}};
  /* "matrix" converts CIE 1931 XYZ to scotopic curve */
  const float scovec[3] = { 0.36169, 1.18214, -0.80498 };
  const float unfvec[3] = { 1.0/3.0, 1.0/3.0,  1.0/3.0 };
  /* a mean energy of photons in B,V,R filters [eV] */
  const float evec[3] = { 2.7936, 2.2387, 1.7857 };
  const float uvec[3] = { 1.0,    1.0,    1.0 };
  const float *eph = ev == 0 ? evec : uvec;
  /* approximate scotopic photon energy [eV] */
  const float esco = (eph[0] + eph[1]) / 2.0;
  float zyx[3];

  PIXMAP *p;
  FILE *dcraw;
  struct tm tm;

  const char *dcraw_info_command = "dcraw -i -v -c ";
  const char *dcraw_command = "dcraw -c ";
  const char *dark_comment = "Darkframe subtracted: ";
  const char *rum_comment = "Command: ";
  char *com, *photsys, *add;

  assert(execs);

  flux = NULL;

  m = adds ? strlen(adds) : 0;
  k = dark ? strlen(dark) : 0;
  add = malloc(strlen(execs)+k+m+7);
  /* 7 stand for " -K " (and only occupy space when dark is unused) */

  /* add dark to options */
  strcpy(add,execs);
  if( dark ) {
    strcat(add," -K ");
    strcat(add,dark);
    strcat(add," ");
  }
  if( adds ) {
    strcat(add," ");
    strcat(add,adds);
  }

  /* run dcraw to get EXIF info */
  com = malloc(strlen(dcraw_info_command)+strlen(raw)+1);
  strcpy(com,dcraw_info_command);
  strcat(com,raw);
  if( (dcraw = popen(com,"r")) == NULL ) {
    fprintf(stderr,"Error: failed to invoke: `%s'\n",dcraw_info_command);
    free(com);
    return(1);
  }
  for( nexif = 0, i = 0; i < MAXEXIF && fgets(exif[i],FLEN_CARD,dcraw); i++) {
    for( j = 0; exif[i][j] != '\n'; j++);
    exif[i][j] = '\0';
  }
  nexif = i;
  pclose(dcraw);
  free(com);

  /* decode exif data */
  for( i = 0; i < nexif; i++) {

    /* time of start of exposure ? */
    key = "Timestamp: ";
    if( strstr(exif[i],key) ) {
      strncpy(stime,exif[i]+strlen(key),FLEN_CARD);
      if( strptime(stime,"%a %b %d %T %Y",&tm) != NULL )
	strftime(stime, sizeof(stime), "%Y-%m-%dT%T", &tm);
      else
	strcpy(stime,"");
    }

    /* exposure time */
    key = "Shutter:";
    if( strstr(exif[i],key) ) {
      strncpy(etime,exif[i]+strlen(key),FLEN_CARD);
      if( strstr(etime,"/") ){
	  sscanf(etime,"%f/%f",&x,&y);
	  exptime = x/y;
      }
      else
	  sscanf(etime,"%f",&exptime);
    }

    /* camera */
    key = "Camera: ";
    if( strstr(exif[i],key) )
      strncpy(camera,exif[i]+strlen(key),FLEN_CARD);

    /* ISO speed */
    key = "ISO speed:";
    if( strstr(exif[i],key) ) {
      if( sscanf(exif[i]+strlen(key),"%d",&iso) != 1 )
	iso = -1;
    }

    /* aperture */
    key = "Aperture: ";
    if( strstr(exif[i],key) )
      strncpy(aperture,exif[i]+strlen(key),FLEN_CARD);

    /* focus */
    key = "Focal length:";
    if( strstr(exif[i],key) ) {
      if( sscanf(exif[i]+strlen(key),"%f",&focus) != 1 )
	focus = -1.0;
    }
  } /* exif */


  /* run dcraw to get PIXMAP */
  com = malloc(strlen(dcraw_command)+strlen(opts)+strlen(add)+strlen(raw)+3);
  strcpy(com,dcraw_command);
  strcat(com,opts);
  strcat(com," ");
  strcat(com,add);
  strcat(com," ");
  strcat(com,raw);
  free(add);
  if( (dcraw = popen(com,"r")) == NULL ) {
    fprintf(stderr,"Error: failed to invoke: `%s'\n",com);
    free(com);
    return(1);
  }

  if( (p = pixmap(dcraw)) == NULL ) {
    pclose(dcraw);
    fprintf(stderr,"Error: failed to import data by `%s'.\n",com);
    fprintf(stderr,"Please, check that dcraw can properly decode file `%s'.\n",
	    raw);
    free(com);
    return(1);
  }
  pclose(dcraw);


  /* data decoding */
  if( type == 0 ) {
    /* standard band */

    assert(p->colors == 3);

    if( filter == NULL ) {
      /* fully coloured frame */

      width = p->width;
      height = p->height;

      naxis = 3;
      naxes[0] = width;
      naxes[1] = height;
      naxes[2] = 3;
      npixels = 3*width*height;
      nbytes = npixels*sizeof(unsigned short int);
      if( (flux = malloc(nbytes)) == NULL ) {
	perror(__FILE__);
	return(1);
      }
      memcpy(flux,p->data,nbytes);

    }
    else {

      width = p->width;
      height = p->height;

      naxis = 2;
      naxes[0] = width;
      naxes[1] = height;
      npixels = width*height;
      nbytes = npixels*sizeof(unsigned short int);
      if( (flux = malloc(nbytes)) == NULL ) {
	  perror(__FILE__);
	  return(1);
      }

      /* select index */
      if( strcmp(filter,"X") == 0 || strcmp(filter,"R") == 0 )
	idx = 2;
      else if( strcmp(filter,"Y") == 0 || strcmp(filter,"V") == 0 )
	idx = 1;
      else if( strcmp(filter,"Z") == 0 || strcmp(filter,"B") == 0 )
	idx = 0;

      /* selecting XYZ */
      if( strcmp(filter,"X") == 0 || strcmp(filter,"Y") == 0 ||
	  strcmp(filter,"Z") == 0 ) {

	memcpy(flux,p->data + idx*npixels,nbytes);
      }
      else if( strcmp(filter,"R") == 0 || strcmp(filter,"V") == 0 ||
	       strcmp(filter,"B") == 0 ) {

	k = 0;
	for(j = 0; j < height; j++) {
	  jj = j*width;
	  for(i = 0; i < width; i++) {
	    for(m = 0; m < 3; m++)
	      zyx[m] = p->data[m*npixels + jj + i];
	    flux[k++] = ftran(3,eph[idx],cmatrix[idx],zyx);
	  }
	}
      }
      else if( strcmp(filter,"scotopic") == 0 || strcmp(filter,"clear") == 0 ) {

	k = 0;
	for(j = 0; j < height; j++) {
	  jj = j*width;
	  for(i = 0; i < width; i++) {
	    for(m = 0; m < 3; m++)
	      zyx[m] = p->data[m*npixels + jj + i];
	    if( strcmp(filter,"scotopic") == 0 )
	      flux[k++] = ftran(3,esco,scovec,zyx);
	    else
	      flux[k++] = ftran(3,eph[1],unfvec,zyx);
	  }
	}
      }
    }
  }

  else if( type == 1 ) {
    /* instrumental */

    if( strcmp(filter,"all") == 0 ) {

      width = p->width / 2;
      height = p->height / 2;

      naxis = 3;
      naxes[0] = width;
      naxes[1] = height;
      naxes[2] = 4;
      npixels = width*height;
      nbytes = 4*npixels*sizeof(unsigned short int);
      if( (flux = malloc(nbytes)) == NULL ) {
	perror(__FILE__);
	return(1);
      }

      /* loop over all pixels and separate instrumental RGGB to four bands */
      /* colours are arranged as: */
      /* flux[0..npixels] (blue), */
      /* flux[npixels+1..2*npixels] (green1), */
      /* flux[2*npixels+1..3*npixels] (green2), */
      /* flux[3*npixels+1..4*npixels] (red) */

      k = 0;
      for(j = 0; j < 2*height; j = j + 2)
	for(i = 0; i < 2*width; i = i + 2) {

	  flux[k] = array(p->width,p->height,p->data,i+1,j);
	  flux[npixels+k] = array(p->width,p->height,p->data,i+1,j+1);
	  flux[2*npixels+k] = array(p->width,p->height,p->data,i,j);
	  flux[3*npixels+k] = array(p->width,p->height,p->data,i,j+1);

	  k++;
	}
      npixels = 4*npixels;

    }

    else if( strcmp(filter,"plain") == 0 ) {

      width = p->width;
      height = p->height;

      naxis = 2;
      naxes[0] = width;
      naxes[1] = height;
      npixels = 2*width*height;
      nbytes = npixels*sizeof(unsigned short int);
      if( (flux = malloc(nbytes)) == NULL ) {
	perror(__FILE__);
	return(1);
      }
      memcpy(flux,p->data,npixels);

    }
    else if( strcmp(filter,"Ri") == 0 || strcmp(filter,"Gi") == 0 ||
	     strcmp(filter,"Bi") == 0 || strcmp(filter,"Gi1") == 0 ||
	     strcmp(filter,"Gi2") == 0 ) {

      width = p->width / 2;
      height = p->height / 2;

      naxis = 2;
      naxes[0] = width;
      naxes[1] = height;
      npixels = width*height;
      nbytes = npixels*sizeof(unsigned short int);
      if( (flux = malloc(nbytes)) == NULL ) {
	perror(__FILE__);
	return(1);
      }

      if( strcmp(filter,"Gi") == 0 ) {

	k = 0;
	for(j = 0; j < 2*height; j = j + 2 )
	  for(i = 0; i < 2*width; i = i + 2 ) {
	    g1 = array(p->width,p->height,p->data,i+1,j+1);
	    g2 = array(p->width,p->height,p->data,i,j);
	    flux[k++] = (g1 + g2) / 2 + 0.5;
	  }
      }
      else {

	ix = 0; jx = 0;
	if( strcmp(filter,"Ri") == 0 ) { ix = 0; jx = 1; }
	if( strcmp(filter,"Bi") == 0 ) { ix = 1; jx = 0; }
	if( strcmp(filter,"Gi1") == 0 ){ ix = 1; jx = 1; }
	if( strcmp(filter,"Gi2") == 0 ){ ix = 0; jx = 0; }

	k = 0;
	for(j = 0; j < 2*height; j = j + 2)
	  for(i = 0; i < 2*width; i = i + 2)
	    flux[k++] = array(p->width,p->height,p->data,i+ix,j+jx);
      }
    }
  }
  pixmap_free(p);

  assert(flux != NULL);

  /* output */
  status = 0;
  fits_create_file(&file, fitsname, &status);

  fits_create_img(file, USHORT_IMG, naxis, naxes, &status);

  /* determine photometry system */
  if( filter == NULL ) {
    fits_update_key(file,TSTRING,"CSPACE","CIE 1931 XYZ",
		    "Colour space (CIE 1931 XYZ colour space)",&status);
  }
  else {
    photsys = "Instrumental";
    if( type == 0 ) {
      if ( strcmp(filter,"X") == 0 || strcmp(filter,"Y") == 0 ||
	   strcmp(filter,"Z") == 0 )
	photsys = "CIE 1931 XYZ";
      else if( strcmp(filter,"B") == 0 || strcmp(filter,"V") == 0 ||
	       strcmp(filter,"R") == 0 )
	photsys = "Johnson";
    }
    fits_update_key(file,TSTRING,"PHOTSYS",photsys,
		    "photometry filter system",&status);
    fits_update_key(file,TSTRING,"FILTER",filter,
		    "Spectral filter or colour space component",&status);
  }
  if( filter == NULL || strcmp(photsys,"Johnson") == 0 ||
      strcmp(filter,"scotopic") == 0 || strcmp(filter,"clear") == 0)
      fits_update_key(file,TSTRING,"BUNIT",ev==0 ? "photon" : "eV",
		      "photon-like or energy-like quantity",&status);

  fits_update_key(file,TSTRING,"DATE-OBS",stime,"time of exposure",&status);
  if( exptime > 0 )
    fits_update_key_fixflt(file,"EXPTIME",exptime,5,"[s] Exposure time",&status);
  if( iso > 0 )
    fits_update_key(file,TSHORT,"ISO",&iso,"ISO speed",&status);
  fits_update_key(file,TSTRING,"INSTRUME",camera,
		  "Camera manufacturer and model",&status);
  fits_update_key(file,TSTRING,"APERTURE",aperture,"Aperture",&status);
  if( focus > 0.0 )
    fits_update_key_fixflt(file,"FOCUS",focus,1,"[mm] Focal length",&status);
  if( dark ) {
    add = malloc(strlen(dark_comment)+strlen(dark)+1);
    strcpy(add,dark_comment);
    strcat(add,dark);
    fits_write_comment(file,add,&status);
    free(add);
  }
  if( com ) {
    add = malloc(strlen(rum_comment)+strlen(com)+1);
    strcpy(add,rum_comment);
    strcat(add,com);
    fits_write_comment(file,add,&status);
    free(add);
  }
  fits_write_comment(file,ver,&status);

  fits_write_comment(file,"EXIF data info - begin",&status);
  for( i = 0; i < nexif; i++)
    fits_write_comment(file,exif[i],&status);
  fits_write_comment(file,"EXIF data info - end",&status);

  fits_write_img(file, TUSHORT, 1, npixels, flux, &status);

  fits_close_file(file, &status);
  fits_report_error(stderr,status);

  free(flux);
  free(com);

  return(status);
}
