/*****************************************************************************/
/* kernel-io.c								     */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Base functions for manipulating convolution kernels.			     */
/*****************************************************************************/

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#include <fits/fits.h>

#include "fitsh.h"

#include "math/fit/lmfit.h"
#include "math/poly.h"
#include "io/tokenize.h"
#include "io/iof.h"

#include "tensor.h"
#include "kernel.h"

/*****************************************************************************/

int dump_kernel(FILE *fw,kernel *k)
{
 fits *img;
 img=fits_create();

 img->i.data=k->image;
 img->i.sx=img->i.sy=2*k->hsize+1;
 img->i.bit=-32;

 fits_set_standard(img,NULL);
 fits_set_image_params(img);

 fits_write(fw,img);
 img->i.data=NULL;
 img->i.sx=img->i.sy=0;
 fits_free(img);
 return(0);
}

/*****************************************************************************/

int kernel_info_dump_image(FILE *fw,kernel *k,int is_comment)
{
 int	i,j,n;
 n=k->hsize*2+1;
 for ( i=0 ; i<n ; i++ )
  {	if ( is_comment )	fprintf(fw,"#");
	if ( i==0 )	fprintf(fw,"image =");
	else		fprintf(fw,"       ");
	for ( j=0 ; j<n ; j++ )
	 {	fprintf(fw," %10g",k->image[i][j]);
		if ( i<n-1 || j<n-1 )	fprintf(fw,",");
	 }
	if ( i<n-1 )	fprintf(fw," \\\n");
	else		fprintf(fw,"\n");
  }
 return(0);
}
int kernel_info_write(FILE *fw,kernellist *kl)
{
 int	i,p,q,n,nvar,order,nkernel,hsize,fsize;
 kernel	*k,*kernels;
 double	***image;

 nkernel=kl->nkernel;
 kernels=kl->kernels;

 fprintf(fw,"# Kernel set description file (automatically generated by ficonv)\n");
 fprintf(fw,"# Do not edit unless you really know what you are doing!!!\n");
 fprintf(fw,"\n");

 fprintf(fw,"[global]\n");
 fprintf(fw,"type   = %d\n",kl->type);
 if ( kl->type==1 )
  {	fprintf(fw,"offset = %10g, %10g\n",kl->ox,kl->oy);
	fprintf(fw,"scale  = %10g\n",kl->scale);
  }
 fprintf(fw,"[end]\n\n");

 for ( n=0 ;  n<nkernel ; n++ )
  {	k=&kernels[n];
	fprintf(fw,"[kernel] # %d\n",n);
	switch ( k->type )
	 {   case KERNEL_UNKNOWN:
		fprintf(fw,"type = unknown\n");	
		fprintf(fw,"hsize = %d\n",k->hsize);
		if ( k->image != NULL )
			kernel_info_dump_image(fw,k,0);
		fprintf(fw,"offset = %10g\n",k->offset);
		break;
	     case KERNEL_BACKGROUND:
		fprintf(fw,"type = background\n");
		break;
	     case KERNEL_IDENTITY:
		fprintf(fw,"type = identity\n");	
		break;
	     case KERNEL_GAUSSIAN:
		fprintf(fw,"type = gaussian\n");
		fprintf(fw,"hsize = %d\n",k->hsize);
		fprintf(fw,"sigma = %g\n",k->sigma);
		fprintf(fw,"basis = %d,%d\n",k->bx,k->by);
		if ( k->image != NULL )
			kernel_info_dump_image(fw,k,1);
	 	break;
	     case KERNEL_DDELTA:
		fprintf(fw,"type = ddelta\n");
		fprintf(fw,"basis = %d,%d\n",k->bx,k->by);
		if ( k->image != NULL )
			kernel_info_dump_image(fw,k,1);
		break;
	 }
	fprintf(fw,"order = %d\n",k->order);
	nvar=(k->order+1)*(k->order+2)/2;
	if ( k->coeff != NULL && kl->type==1 )
	 {	fprintf(fw,"coeff    =");
		for ( i=0 ; i<nvar ; i++ )
		 {	fprintf(fw," %10g",k->coeff[i]);
			if ( i<nvar-1 )	fprintf(fw,",");
		 }
		fprintf(fw,"\n");
 	 }
	fprintf(fw,"[end]\n\n");
  };
 fprintf(fw,"# Total number of kernels written: %d\n",nkernel);

 if ( kl->type==1 )	/* dump also kernel image */
  {	hsize=0;order=0;
 	for ( n=0 ; n<nkernel ; n++ )
	 {	k=&kernels[n];
		if ( k->type==KERNEL_BACKGROUND && k->coeff != NULL )
		 {	fprintf(fw,"# Background:\n");
			nvar=(k->order+1)*(k->order+2)/2;
			fprintf(fw,"# ");
			for ( i=0 ; i<nvar ; i++ )
			 {	fprintf(fw," %10g",k->coeff[i]);	}
			fprintf(fw,"\n");
		 }
		else 
		 {	if ( k->hsize>hsize )	hsize=k->hsize;
			if ( k->order>order )	order=k->order;
		 }
 	 }
	fsize=2*hsize+1;
	nvar=(order+1)*(order+2)/2;
	image=tensor_alloc_3d(double,fsize,fsize,nvar);
	for ( n=0 ; n<nvar ; n++ )
	 {	for ( p=0 ; p<fsize ; p++ )
		 {	for ( q=0 ; q<fsize ; q++ )
			 {	image[n][p][q]=0.0;		}
		 }
	 }
	for ( n=0 ; n<nkernel ; n++ )
	 {	k=&kernels[n];
		nvar=(k->order+1)*(k->order+2)/2;
		if ( k->type == KERNEL_BACKGROUND )	continue;
		else if ( k->type == KERNEL_IDENTITY )
		 {	for (  i=0 ; i<nvar ; i++ )
			 {	image[i][hsize][hsize]+=k->coeff[i];	}
		 }
		else
		 {	for ( i=0 ; i<nvar ; i++ )
			 {	for ( p=-k->hsize ; p<=k->hsize ; p++ )
				 {  for ( q=-k->hsize ; q<=k->hsize ; q++ )
				     {	image[i][hsize+p][hsize+q]+=
					k->image[k->hsize+p][k->hsize+q]*k->coeff[i];
				     }
				 }
			 }
		 }
	 }
	fprintf(fw,"# Image:\n");
	nvar=(order+1)*(order+2)/2;
	for ( p=-hsize ; p<=hsize ; p++ )
	 {	for ( q=-hsize ; q<=hsize ; q++ )
		 {	fprintf(fw,"# %3d %3d  ",q,p);
			for ( i=0 ; i<nvar ; i++ )
			 {	fprintf(fw," %12g",image[i][hsize+p][hsize+q]);	}
			fprintf(fw,"\n");
		 }
	 }
	tensor_free(image);
  }

 return(0);
}

/******************************************************************************
 in_block=0:	-
	   =1:	[global] ... [end]
	   =2:	[kernel] ... [end]
******************************************************************************/

int kernel_info_read(FILE *fr,kernellist *kl)
{
 kernel *kernels,*k;
 int	nkernel,n;
 char	*rbuff,*cmd[3];
 int	in_block;
 
 kl->kernels=kernels=NULL;
 kl->nkernel=nkernel=0;

 kl->ox=0.0;kl->oy=0.0;
 kl->scale=1.0;

 rbuff=NULL;in_block=0;
 k=NULL;
 while ( ! feof(fr) )
  {	if ( rbuff != NULL )	free(rbuff);
	rbuff=freadline_bs(fr);
	if ( rbuff==NULL )	break;
	remove_spaces_and_comments(rbuff);
	n=tokenize_char(rbuff,cmd,'=',2);
	if ( n==0 )	continue;
	if ( strcmp(cmd[0],"[kernel]")==0 )
	 {	if ( ! in_block )
		 {	in_block=2;
			kernels=(kernel *)realloc(kernels,sizeof(kernel)*(nkernel+1));
			k=&kernels[nkernel];
			k->type=-1;
			k->order=0;
			k->coeff=NULL;
			k->hsize=0;
		 }
		else
		 {	free(rbuff);return(1);		}
	 }
	else if ( strcmp(cmd[0],"[global]")==0 )
	 {	if ( ! in_block )
		 {	in_block=1;			}
		else
		 {	free(rbuff);return(1);		}
	 }
	else if ( strcmp(cmd[0],"[end]")==0 )
	 {	if ( in_block==1 )
		 {	in_block=0;			}
		else if ( in_block==2 )
		 {	in_block=0;
			nkernel++;
		 }
		else
		 {	free(rbuff);return(1);		}
	 }
	else if ( ! in_block )
	 {	free(rbuff);return(1);		}
	else if ( cmd[1]==NULL )
	 {	free(rbuff);return(1);		}

	else if ( in_block==1 )
	 {	if ( strcmp(cmd[0],"offset")==0 )
		 {	sscanf(cmd[1],"%lg,%lg",&kl->ox,&kl->oy);	}
		else if ( strcmp(cmd[0],"scale")==0 )
		 {	sscanf(cmd[1],"%lg",&kl->scale);		}
		else if ( strcmp(cmd[0],"type")==0 )
		 {	sscanf(cmd[1],"%d",&kl->type);			}
		else
		 {	free(rbuff);return(1);		}
	 }
	else if ( in_block==2 )
	 {	if ( strcmp(cmd[0],"type")==0 )
		 {	if ( k->type >= 0 )
			 {	free(rbuff);return(1);		}
			     if ( strcmp(cmd[1],"unknown")==0 )		k->type=KERNEL_UNKNOWN;
			else if ( strcmp(cmd[1],"background")==0 )	k->type=KERNEL_BACKGROUND;
			else if ( strcmp(cmd[1],"identity")==0 )	k->type=KERNEL_IDENTITY;
			else if ( strcmp(cmd[1],"gaussian")==0 )	k->type=KERNEL_GAUSSIAN;
			else if ( strcmp(cmd[1],"ddelta")==0 )		k->type=KERNEL_DDELTA;
			else
			 {	free(rbuff);return(1);		}
		 }
		else if ( strcmp(cmd[0],"order")==0 )
		 {	sscanf(cmd[1],"%d",&k->order);			}
		else if ( strcmp(cmd[0],"coeff")==0 )
		 {	int	i,nvar;
			char	**icmd;
			nvar=(k->order+1)*(k->order+2)/2;
			icmd=(char **)malloc(sizeof(char *)*(nvar+1));
			k->coeff=(double *)malloc(sizeof(double)*(nvar));
			tokenize_char(cmd[1],icmd,',',nvar);
			for ( i=0 ; i<nvar ; i++ )
			 {	if ( icmd[i]==NULL )
				 {	free(rbuff);return(1);		}
				if ( sscanf(icmd[i],"%lg",&k->coeff[i])<1 ||
				! isfinite(k->coeff[i]) )
				 {	free(rbuff);return(1);		}
			 }
			free(icmd);
		 }
		else if ( strcmp(cmd[0],"hsize")==0 && ( k->type==KERNEL_UNKNOWN || k->type==KERNEL_GAUSSIAN ) )
		 {	sscanf(cmd[1],"%d",&k->hsize);			}
		else if ( strcmp(cmd[0],"basis")==0 && ( k->type==KERNEL_DDELTA  || k->type==KERNEL_GAUSSIAN ) )
		 {	sscanf(cmd[1],"%d,%d",&k->bx,&k->by);		}
		else if ( strcmp(cmd[0],"sigma")==0 && ( k->type==KERNEL_GAUSSIAN ) )
		 {	sscanf(cmd[1],"%lg",&k->sigma);			}
		else if ( strcmp(cmd[0],"offset")==0 && ( k->type==KERNEL_UNKNOWN ) )
		 {	sscanf(cmd[1],"%lg",&k->offset);		}
		else if ( strcmp(cmd[0],"image")==0 && ( k->type==KERNEL_UNKNOWN ) )
		 {	int	i,n,size;
			char	**icmd;
			size=2*k->hsize+1;
			n=size*size;
			icmd=(char **)malloc(sizeof(char *)*(n+1));
			k->image=matrix_alloc(size);
			tokenize_char(cmd[1],icmd,',',n);
			for ( i=0 ; i<n ; i++ )
			 {	if ( icmd[i]==NULL )
				 {	free(rbuff);return(1);		}
				sscanf(icmd[i],"%lg",&k->image[i/size][i%size]);
			 }
			free(icmd);
		 }
		else
		 {	free(rbuff);return(1);		}
	 }
  }
 
 kl->kernels=kernels;
 kl->nkernel=nkernel;
 return(0);
}

/*****************************************************************************/

kernel *add_kernel_empty(kernellist *kl)
{
 kernel *k;

 if ( kl->nkernel==0 )	kl->kernels=(kernel *)malloc(sizeof(kernel));
 else			kl->kernels=(kernel *)realloc(kl->kernels,sizeof(kernel)*(kl->nkernel+1));

 kl->nkernel++;

 k=&kl->kernels[kl->nkernel-1];

 k->type=-1;
 k->hsize=0;
 k->bx=k->by=0;
 k->sigma=0.0;
 k->image=NULL;
 k->order=0;
 k->coeff=NULL;

 return(k);
}

int add_kernel_gaussian(kernellist *kl,double sigma,int hx,int hy,int hsize,int sporder)
{
 kernel *k;
 k=add_kernel_empty(kl);
 k->type=KERNEL_GAUSSIAN;
 k->sigma=sigma;
 k->bx=hx;
 k->by=hy;
 k->hsize=hsize; 
 k->order=sporder;
 return(0);
}
int add_kernel_gaussian_set(kernellist *kl,double sigma,int order,int hsize,int sporder)
{
 int	i,j;
 for ( i=0 ; i<=order ; i++ )
  {	for ( j=0 ; j<=i ; j++ )
	 {	add_kernel_gaussian(kl,sigma,i-j,j,hsize,sporder);	}
  }
 return(0);
}
int add_kernel_background(kernellist *kl,int sporder)
{
 kernel *k;
 k=add_kernel_empty(kl);
 k->type=KERNEL_BACKGROUND;
 k->order=sporder;
 return(0);
}
int add_kernel_identity(kernellist *kl,int sporder)
{
 kernel *k;
 k=add_kernel_empty(kl);
 k->type=KERNEL_IDENTITY;
 k->order=sporder;
 return(0);
}
int add_kernel_linear(kernellist *kl,int px,int py,int sporder)
{
 kernel *k;
 k=add_kernel_empty(kl);
 k->type=KERNEL_DDELTA;
 k->bx=px,k->by=py;
 k->order=sporder;
 return(0);
}
int add_kernel_shift(kernellist *kl,int sporder)
{
 add_kernel_linear(kl,1,0,sporder);
 add_kernel_linear(kl,0,1,sporder);
 add_kernel_linear(kl,1,1,sporder);
 return(0);
}
int add_kernel_linear_set(kernellist *kl,int ksize,int sporder)
{
 int	i;
 for ( i=-ksize ; i<=ksize ; i++ )
  {	add_kernel_linear(kl,i,-ksize,sporder);
	add_kernel_linear(kl,i,+ksize,sporder);
  }
 for ( i=-ksize+1 ; i<=ksize-1 ; i++ )
  {	add_kernel_linear(kl,-ksize,i,sporder);
	add_kernel_linear(kl,+ksize,i,sporder);
  }
 return(0);
}

int kernel_init_images(kernellist *kl)
{
 int	i,fnn,fnt;
 kernel	*k;

 for ( i=0 ; i<kl->nkernel ; i++ )
  {	k=&kl->kernels[i];
	if ( k->type==KERNEL_GAUSSIAN )
	 {	kernel_image_calc_gaussian(k);		}
	if ( k->type==KERNEL_DDELTA )
	 {	kernel_image_calc_linear(k);		}
  }

 fnn=-1,fnt=0;
 for ( i=0 ; i<kl->nkernel && fnn<0 ; i++ )
  {	k=&kl->kernels[i];
	if ( k->type==KERNEL_IDENTITY )
		fnn=i,fnt=KERNEL_IDENTITY;
	if ( k->type==KERNEL_GAUSSIAN && k->bx%2==0 && k->by%2==0 )
		fnn=i,fnt=KERNEL_GAUSSIAN;
  }
 if ( fnn>=0 && fnt==KERNEL_IDENTITY )
  {	for ( i=fnn+1 ; i<kl->nkernel ; i++ )
	 {	k=&kl->kernels[i];
		if ( k->type==KERNEL_GAUSSIAN && k->bx%2==0 && k->by%2==0 )
			k->image[k->hsize][k->hsize]-=1.0;	
	 }
  }
 else if ( fnn>=0 && fnt==KERNEL_GAUSSIAN )
  {	for ( i=fnn+1 ; i<kl->nkernel ; i++ )
	 {	k=&kl->kernels[i];
		if ( k->type==KERNEL_GAUSSIAN && k->bx%2==0 && k->by%2==0 )
			kernel_image_subtract(k,&kl->kernels[fnn]);
	 }
  }
 return(0);
}

int create_kernels_from_kernelarg(char *kernelarg,kernellist *kl)
{
 char	*karg,*cmd[32],*icmd[4],*jcmd[4];
 int	is_bg,is_id,is_g,is_d,is_s,i,j,linear_set_added;

 kl->nkernel=0;
 kl->kernels=NULL;

 kl->ox=kl->oy=0.0;
 kl->scale=1.0;
 kl->type=0;

 karg=(char *)malloc(strlen(kernelarg)+1);
 strcpy(karg,kernelarg);
 remove_spaces_and_comments(karg);

 is_bg=is_id=is_g=is_d=is_s=-1;

 tokenize_char(karg,cmd,';',31);

 for ( i=0 ; cmd[i] != NULL ; i++ )
  {	     if ( cmd[i][0]=='i' )	is_id=i;
	else if ( cmd[i][0]=='b' )	is_bg=i;
	else if ( cmd[i][0]=='g' )	is_g=i;
	else if ( cmd[i][0]=='d' )	is_d=i;
	else if ( cmd[i][0]=='s' )	is_s=i;
	else
	 {	free(karg);return(1);		}
  }
 if ( is_bg>=0 )
  {	int	sporder;
	tokenize_char(cmd[is_bg],jcmd,'/',2);
	sporder=-1;
	if ( jcmd[1] != NULL )
	 {	sscanf(jcmd[1],"%d",&sporder);		}
	else
		sporder=0;
	if ( sporder<0 )	
	 {	free(karg);return(1);		}
	add_kernel_background(kl,sporder);
  }
 if ( is_id>=0 )
  {	int	sporder;
	tokenize_char(cmd[is_id],jcmd,'/',2);
	sporder=-1;
	if ( jcmd[1] != NULL )
	 {	sscanf(jcmd[1],"%d",&sporder);		}
	else
		sporder=0;
	if ( sporder<0 )	
	 {	free(karg);return(1);		}
	add_kernel_identity(kl,sporder);
  }
 if ( is_s>=0 )
  {	int	sporder;
	tokenize_char(cmd[is_s],jcmd,'/',2);
	sporder=-1;
	if ( jcmd[1] != NULL )
	 {	sscanf(jcmd[1],"%d",&sporder);		}
	else
		sporder=0;
	if ( sporder<0 )	
	 {	free(karg);return(1);		}
	add_kernel_shift(kl,sporder);
  }

 for ( i=0 ; cmd[i] != NULL ; i++ )
  {	double	sigma;
	int	hsize,order;
	int	sporder;
	if ( cmd[i][0] != 'g' )	continue;
	tokenize_char(cmd[i],icmd,'=',2);
	if ( icmd[1]==NULL )
	 {	free(karg);return(1);		}
	sporder=0;
	if ( sscanf(icmd[1],"%d,%lg,%d/%d",&hsize,&sigma,&order,&sporder)<3 )
	 {	free(karg);return(1);		}
	if ( sporder<0 )
	 {	free(karg);return(1);		}
	add_kernel_gaussian_set(kl,sigma,order,hsize,sporder);
  }
 linear_set_added=0;
 for ( i=0 ; cmd[i] != NULL ; i++ )
  {	int	hsize;
	int	sporder;
	if ( cmd[i][0] != 'd' ) continue;
	tokenize_char(cmd[i],icmd,'=',2);
	if ( icmd[1]==NULL )
	 {	free(karg);return(1);		}
	sporder=0;
	if ( sscanf(icmd[1],"%d/%d",&hsize,&sporder)<1 )
	 {	free(karg);return(1);		}
	if ( sporder<0 )
	 {	free(karg);return(1);		}
	for ( j=linear_set_added+1 ; j<=hsize ; j++ )
	 {	add_kernel_linear_set(kl,j,sporder);		}
	linear_set_added=hsize;
  }

 free(karg);
 return(0);
}

/*****************************************************************************/
