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

convert.c (C-Munipack project)
Public helper functions
Copyright (C) 2003 David Motl, dmotl@volny.cz

This program 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 2
of the License, or (at your option) any later version.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fitsio.h>

#include "config.h"
#include "comfun.h"
#include "cmpack_common.h"
#include "cmpack_version.h"
#include "mem.h"
#include "wcsobj.h"
#include "linsolver.h"

/*****************   Initialization/finalization   ***************************/

static int initialized = 0;

/* Initialize the library */
void cmpack_init(void)
{
	if (!initialized) {
		cmpack_mem_init();	
		cmpack_wcs_init();
		fits_init_cfitsio();
		initialized = 1;
	}
}

/* Finalize the library */
void cmpack_cleanup(void)
{
	cmpack_wcs_clean();
	cmpack_mem_clean();
	initialized = 0;
}

/**************** Common public functions ****************/

/* Returns CMunipack's version identifier */
const char *cmpack_versionid(void)
{
	return VERSION;
}

/* Returns CMunipack's name */
const char *cmpack_packagename(void)
{
	return CMAKE_PROJECT_NAME;
}

/* Get major version number */
int cmpack_major_version(void)
{
	return CMPACK_MAJOR_VERSION;
}

/* Get minor version number */
int cmpack_minor_version(void)
{
	return CMPACK_MINOR_VERSION;
}

/* Get revision number */
int cmpack_revision_number(void)
{
	return CMPACK_REVISION_NUMBER;
}

/* Check library version */
int cmpack_check_version(int major, int minor, int revision)
{
	return (major==CMPACK_MAJOR_VERSION) && ((CMPACK_MINOR_VERSION>minor) ||
		(CMPACK_MINOR_VERSION==minor && CMPACK_REVISION_NUMBER>=revision));
}

/* Translate error code to a text */
char *cmpack_formaterror(int status)
{
	char buf[MAXLINE];

  	switch (status)
	{
	case CMPACK_ERR_MEMORY:
	    return cmpack_strdup("Insufficient memory");
	case CMPACK_ERR_KEY_NOT_FOUND:
	    return cmpack_strdup("Key not found");
	case CMPACK_ERR_COL_NOT_FOUND:
	    return cmpack_strdup("Column not found");
	case CMPACK_ERR_ROW_NOT_FOUND:
	    return cmpack_strdup("Row not found");
	case CMPACK_ERR_AP_NOT_FOUND:
	    return cmpack_strdup("Aperture not found");
	case CMPACK_ERR_READ_ONLY:
	    return cmpack_strdup("File is open for reading only");
	case CMPACK_ERR_CLOSED_FILE:
		return cmpack_strdup("Operation not allowed on closed file");
	case CMPACK_ERR_OPEN_ERROR:
	    return cmpack_strdup("Cannot open the file");
	case CMPACK_ERR_READ_ERROR:
	    return cmpack_strdup("Error while reading the file");
	case CMPACK_ERR_WRITE_ERROR:
	    return cmpack_strdup("Error while writing the file");
    case CMPACK_ERR_UNKNOWN_FORMAT:
	    return cmpack_strdup("Unsupported file format");
	case CMPACK_ERR_BUFFER_TOO_SMALL:
		return cmpack_strdup("Buffer is too small");
	case CMPACK_ERR_INVALID_CONTEXT:
		return cmpack_strdup("Invalid context");
	case CMPACK_ERR_OUT_OF_RANGE:
		return cmpack_strdup("Index is out of range");
	case CMPACK_ERR_UNDEF_VALUE:
		return cmpack_strdup("Undefined value");
	case CMPACK_ERR_MAG_NOT_FOUND:
		return cmpack_strdup("Measurement not found");
	case CMPACK_ERR_STAR_NOT_FOUND:
		return cmpack_strdup("Object not found");
	case CMPACK_ERR_NOT_IMPLEMENTED:
		return cmpack_strdup("Unsupported operation");
	case CMPACK_ERR_ACCESS_DENIED:
		return cmpack_strdup("Access denied");
	case CMPACK_ERR_FILE_NOT_FOUND:
		return cmpack_strdup("The file does not exist");
    case CMPACK_ERR_INVALID_SIZE:
	    return cmpack_strdup("Invalid dimensions of the image");
    case CMPACK_ERR_INVALID_DATE:
	    return cmpack_strdup("Invalid format of date or time");
    case CMPACK_ERR_INVALID_PAR:
	    return cmpack_strdup("Invalid value of input parameter");
	case CMPACK_ERR_INVALID_RA:
	    return cmpack_strdup("Invalid format of right ascension");
	case CMPACK_ERR_INVALID_DEC:
	    return cmpack_strdup("Invalid format of declination");
	case CMPACK_ERR_INVALID_EXPTIME:
	    return cmpack_strdup("Invalid value of exposure duration");
	case CMPACK_ERR_INVALID_BITPIX:
		return cmpack_strdup("Image image data format");
	case CMPACK_ERR_INVALID_LON:
		return cmpack_strdup("Invalid format of longitude");
	case CMPACK_ERR_INVALID_LAT:
		return cmpack_strdup("Invalid format of latitude");
	case CMPACK_ERR_INVALID_JULDAT:
		return cmpack_strdup("Invalid date/time of observation");
	case CMPACK_ERR_INVALID_WCS:
		return cmpack_strdup("No valid WCS data found");
	case CMPACK_ERR_CANT_OPEN_SRC:
	    return cmpack_strdup("Cannot open the source file");
	case CMPACK_ERR_CANT_OPEN_OUT:
	    return cmpack_strdup("Cannot open the output file");
	case CMPACK_ERR_CANT_OPEN_BIAS:
		return cmpack_strdup("Bias frame not found");
    case CMPACK_ERR_CANT_OPEN_DARK:
	    return cmpack_strdup("Dark frame not found");
    case CMPACK_ERR_CANT_OPEN_FLAT:
	    return cmpack_strdup("Flat frame not found");
    case CMPACK_ERR_CANT_OPEN_REF:
	    return cmpack_strdup("Reference file not found");
    case CMPACK_ERR_CANT_OPEN_PHT:
	    return cmpack_strdup("Photometry file not found");
    case CMPACK_ERR_DIFF_SIZE_SRC:
	    return cmpack_strdup("Input frames are not compatible (different sizes)");
	case CMPACK_ERR_DIFF_SIZE_BIAS:
		return cmpack_strdup("Dimensions of bias frame and dark frame are different");
    case CMPACK_ERR_DIFF_SIZE_DARK:
	    return cmpack_strdup("Dimensions of dark frame and scientific image are different");
    case CMPACK_ERR_DIFF_SIZE_FLAT:
	    return cmpack_strdup("Dimensions of flat frame and scientific image are different");
	case CMPACK_ERR_DIFF_BITPIX_SRC:
		return cmpack_strdup("Input frames are not compatible (different image data type)");
    case CMPACK_ERR_NO_INPUT_FILES:
	    return cmpack_strdup("No input files");
    case CMPACK_ERR_NO_BIAS_FRAME:
	    return cmpack_strdup("No bias frame");
    case CMPACK_ERR_NO_DARK_FRAME:
	    return cmpack_strdup("No dark frame");
    case CMPACK_ERR_NO_FLAT_FRAME:
	    return cmpack_strdup("No flat frame");
	case CMPACK_ERR_NO_OBS_COORDS:
		return cmpack_strdup("Missing observer's coordinates");
	case CMPACK_ERR_NO_OBJ_COORDS:
		return cmpack_strdup("Missing object's coordinates");
    case CMPACK_ERR_NO_OUTPUT_FILE:
	    return cmpack_strdup("Missing name of output file");
	case CMPACK_ERR_NO_REF_FILE:
		return cmpack_strdup("Missing name of the reference file");
	case CMPACK_ERR_MEAN_ZERO:
	    return cmpack_strdup("Mean value of flat frame is zero");
	case CMPACK_ERR_REF_NOT_FOUND:
	    return cmpack_strdup("Reference star was not found");
    case CMPACK_ERR_FEW_POINTS_REF:
	    return cmpack_strdup("Too few stars in the reference file");
    case CMPACK_ERR_FEW_POINTS_SRC:
	    return cmpack_strdup("Too few stars in the source file");
    case CMPACK_ERR_MATCH_NOT_FOUND:
	    return cmpack_strdup("Coincidences not found");
	case CMPACK_ERR_TARGET_NOT_FOUND:
		return cmpack_strdup("The target object was not identified");
	case CMPACK_ERR_ZERO_INTENSITY:
		return cmpack_strdup("The net intensity is zero");
	case CMPACK_ERR_INVALID_AP_SIZE:
		return cmpack_strdup("The aperture has invalid size");
	case CMPACK_ERR_CLOSE_TO_BORDER:
		return cmpack_strdup("The object is too close to the frame border");
	case CMPACK_ERR_OVEREXPOSED_PXLS:
		return cmpack_strdup("There are overexposed pixels in the aperture");
	case CMPACK_ERR_BAD_PIXELS:
		return cmpack_strdup("There are bad pixels in the aperture");
	case CMPACK_ERR_MANY_BAD_PXLS:
		return cmpack_strdup("The correction frame has got too many bad pixels");
	case CMPACK_ERR_SKY_NOT_MEASURED:
		return cmpack_strdup("Not enough valid pixels in the sky annulus");
    default:
		sprintf(buf, "Unknown error status %d", status);
		return cmpack_strdup(buf);
	}
}

/* Fill date */
void cmpack_date_set(CmpackDate *date, int year, int month, int day)
{
	date->year = year;
	date->month = month;
	date->day = day;
}

/* Fill time */
void cmpack_time_set(CmpackTime *time, int hour, int minute, int second, int milisecond)
{
	time->hour = hour;
	time->minute = minute;
	time->second = second;
	time->milisecond = milisecond;
}

/* Fill date and time */
void cmpack_datetime_set(CmpackDateTime *datetime, int year, int month, int day,
						 int hour, int minute, int second, int milisecond)
{
	cmpack_date_set(&datetime->date, year, month, day);
	cmpack_time_set(&datetime->time, hour, minute, second, milisecond);
}

/******************************   CmpackMatrix methods   ********************************/

/* Initialize border structure */
void cmpack_border_set(CmpackBorder *border, int left, int top, int right, int bottom)
{
	border->left = left;
	border->top = top;
	border->right = right;
	border->bottom = bottom;
}

/* Clear borders */
void cmpack_border_clear(CmpackBorder *border)
{
	memset(border, 0, sizeof(CmpackBorder));
}

/******************************   CmpackMatrix methods   ********************************/

/* Initialize the matrix */
void cmpack_matrix_init(CmpackMatrix *matrix, double xx, double yx, double xy, double yy,
	double x0, double y0)
{
	matrix->xx = xx;
	matrix->yx = yx;
	matrix->xy = xy;
	matrix->yy = yy;
	matrix->x0 = x0;
	matrix->y0 = y0;
}

/* Set matrix to identity transformation */
void cmpack_matrix_copy(CmpackMatrix *dst, const CmpackMatrix *src)
{
	memcpy(dst, src, sizeof(CmpackMatrix));
}

/* Set matrix to identity transformation */
void cmpack_matrix_identity(CmpackMatrix *matrix)
{
	memset(matrix, 0, sizeof(CmpackMatrix));
	matrix->xx = matrix->yy = 1;
}

/* Transform a point using given affine transformation */
void cmpack_matrix_transform_point(const CmpackMatrix *matrix, double *x, double *y)
{
	double sx = *x, sy = *y;
    *x = matrix->xx * sx + matrix->xy * sy + matrix->x0;
    *y = matrix->yx * sx + matrix->yy * sy + matrix->y0;
}

void cmpack_matrix_reverse_transform(const CmpackMatrix *matrix, double *u, double *v)
{
	double a[9], b[3], x[3];
	CmpackLinSolver *solver;

	a[0] = matrix->xx; a[1] = matrix->xy; a[2] = matrix->x0;
	a[3] = matrix->yx; a[4] = matrix->yy; a[5] = matrix->y0;
	a[6] = 0;          a[7] = 0;          a[8] = 1;
	solver = cmpack_linsolver_qr_create(3, a);
	
	b[0] = *u;         b[1] = *v;         b[2] = 1;
	cmpack_linsolver_solve(solver, b, x);
	cmpack_linsolver_destroy(solver);

	*u = x[0];         *v = x[1];
}

void cmpack_matrix_left_multiply(CmpackMatrix *X, const CmpackMatrix *A)
{
	double x[6], a[6];

	a[0] = A->xx;					a[1] = A->xy;					a[2] = A->x0;
	a[3] = A->yx;					a[4] = A->yy;					a[5] = A->y0;

	x[0] = X->xx;					x[1] = X->xy;					x[2] = X->x0;
	x[3] = X->yx;					x[4] = X->yy;					x[5] = X->y0;

	X->xx = a[0]*x[0]+a[1]*x[3];		X->xy = a[0]*x[1]+a[1]*x[4];		X->x0 = a[0]*x[2]+a[1]*x[5]+a[2];
	X->yx = a[3]*x[0]+a[4]*x[3];		X->yy = a[3]*x[1]+a[4]*x[4];		X->y0 = a[3]*x[2]+a[4]*x[5]+a[5];
}

/******************************   Regressions   ********************************/

void cmpack_quadratic_fit(int len, const double *jd, const double *yval, double *coeff)
{
	int i;
	double den, m_S10 = 0, m_S20 = 0, m_S30 = 0, m_S40 = 0, m_S01 = 0, m_S11 = 0, m_S21 = 0;
	
	for (i = 0; i < len; i++) {
		double x = jd[i], y = yval[i];
		m_S10 += x;
		m_S20 += x * x;
		m_S30 += x * x * x;
		m_S40 += x * x * x * x;
		m_S01 += y;
		m_S11 += x * y;
		m_S21 += x * x * y;
	}

	den = len*m_S20*m_S40 - m_S10*m_S10*m_S40 - len*m_S30*m_S30 + 2*m_S10*m_S20*m_S30 - m_S20*m_S20*m_S20;
	if (den != 0.0) {
		coeff[0] = (m_S01*m_S20*m_S40 - m_S11*m_S10*m_S40 - m_S01*m_S30*m_S30 + m_S11*m_S20*m_S30 + m_S21*m_S10*m_S30 - m_S21*m_S20*m_S20) / den;
		coeff[1] = (m_S11*len*m_S40 - m_S01*m_S10*m_S40 + m_S01*m_S20*m_S30 - m_S21*len*m_S30 - m_S11*m_S20*m_S20 + m_S21*m_S10*m_S20) / den;
		coeff[2] = (m_S01*m_S10*m_S30 - m_S11*len*m_S30 - m_S01*m_S20*m_S20 + m_S11*m_S10*m_S20 + m_S21*len*m_S20 - m_S21*m_S10*m_S10) / den;
	} else {
		coeff[0] = coeff[1] = coeff[2] = 0;
	}
}
