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

varcat.h (C-Munipack project)
Catalogs of variable stars
Copyright (C) 2010 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 <stdlib.h>
#include <string.h>

#include "varcat.h"
#include "utils.h"
#include "configuration.h"
#include "file_classes.h"
#include "progress_dlg.h"

#define MAXLINE 1024
#define PROGRESS_LINES 10000

static long fileSize(FILE *f)
{
	if (fseek(f, 0, SEEK_END) == 0)
		return ftell(f);
	return 0;
}

static void InsertString(char *buf, size_t buflen, const char *substr, size_t index)
{
	size_t slen = strlen(buf), sublen = strlen(substr);
	if (sublen>0 && index<=slen) {
		if (slen+sublen+1 <= buflen) {
			memmove(buf+index+sublen, buf+index, slen-index+1);
			memmove(buf+index, substr, sublen);
		} else 
		if (index+sublen+1 < buflen) {
			memmove(buf+index+sublen, buf+index, buflen-index-sublen-1);
			memmove(buf+index, substr, sublen);
			buf[buflen-1] = '\0';
		} else {
			memmove(buf+index, substr, buflen-index-1);
			buf[buflen-1] = '\0';
		}
	}
}

static char *MakePattern(const char *searchString)
{
	if (searchString && *searchString != '\0') {
		int state;
		const char *sptr;
		char *buf, *dptr;
		size_t len = strlen(searchString);
		if (len > 0) {
			buf = (char*)g_malloc(len + 1);
			for (sptr = searchString, dptr = buf, state = 0; *sptr != '\0'; sptr++) {
				if (state == 0) {
					if (*sptr >= '1' && *sptr <= '9') {
						*dptr++ = *sptr;
						state = 1;
					}
					else {
						if (*sptr >= 'A' && *sptr <= 'Z')
							*dptr++ = *sptr;
						else if (*sptr >= 'a' && *sptr <= 'z')
							*dptr++ = *sptr - 'a' + 'A';
					}
				}
				else {
					if (*sptr >= '0' && *sptr <= '9') {
						*dptr++ = *sptr;
					}
					else {
						if (*sptr >= 'A' && *sptr <= 'Z')
							*dptr++ = *sptr;
						else if (*sptr >= 'a' && *sptr <= 'z')
							*dptr++ = *sptr - 'a' + 'A';
						state = 0;
					}
				}
			}
			*dptr = '\0';
			if (*buf != '\0')
				return buf;
			else
				g_free(buf);
		}
	}
	return NULL;
}

static bool TestName(const char *pattern, const char *name)
{
	if (pattern) {
		char *buf = MakePattern(name);
		if (buf) {
			int res = strcmp(buf, pattern);
			g_free(buf);
			return res == 0;
		}
		return false;
	}
	return true;
}

static bool TestPos(const VarCat::tPosFilter *filter, double ra, double dec)
{
	if (filter) {
		double d = angular_distance(filter->ra / 180.0 * M_PI, filter->dec / 180.0 * M_PI, ra / 180.0 * M_PI, dec / 180.0 * M_PI) / 180.0 * M_PI;
		return (d <= filter->radius);
	}
	return true;
}

static void StripLeadingZeros(char *buf)
{
	if (strlen(buf)>1) {
		char *sptr = buf;
		while ((*sptr=='0' || *sptr==' ') && *(sptr+1)!='\0')
			sptr++;
		if (sptr!=buf)
			memmove(buf, sptr, strlen(sptr)+1);
	}
}

void VarCat::Search_GCVS(const char *searchstr, const tPosFilter *posfilter, tCallbacks *cb_struct)
{
	char name[MAXLINE], aux[MAXLINE], objra[MAXLINE], objdec[MAXLINE];
	int line = 0;

	if (cb_struct->cb_setFileName)
		cb_struct->cb_setFileName("GCVS", cb_struct->cb_struct_data);

	if (CConfig::GetBool(CConfig::SEARCH_GCVS)) {
		char *fpath = CConfig::GetStr(CConfig::GCVS_PATH);
		if (fpath) {
			FILE *f = open_file(fpath, "r");
			if (f) {
				if (cb_struct->cb_setSize) {
					cb_struct->cb_setSize(fileSize(f), cb_struct->cb_struct_data);
					fseek(f, 0, SEEK_SET);
				}

				CTextReader reader(f);
				char *pattern = MakePattern(searchstr);
				while (reader.ReadLine()) {
					if ((line++ % PROGRESS_LINES) == 0) {
						if (cb_struct->cb_cancelled && cb_struct->cb_cancelled(cb_struct->cb_struct_data))
							break;
						if (cb_struct->cb_setPosition)
							cb_struct->cb_setPosition(ftell(f), cb_struct->cb_struct_data);
					}

					if (reader.Length() > 53 && reader.Length() < MAXLINE &&
						reader.Col(7) == '|' && reader.Col(19) == '|' && reader.Col(53) == '|') {
						// GCVS I-III 2006
						reader.GetField(8, 5, name, MAXLINE);
						strcat(name, " ");
						reader.GetField(14, 3, aux, MAXLINE);
						strcat(name, aux);
						if (TestName(pattern, name)) {
							double ra, dec;
							reader.GetField(37, 8, objra, MAXLINE);
							InsertString(objra, MAXLINE, " ", 2);
							InsertString(objra, MAXLINE, " ", 5);
							reader.GetField(45, 7, objdec, MAXLINE);
							InsertString(objdec, MAXLINE, " ", 3);
							InsertString(objdec, MAXLINE, " ", 6);
							if (cmpack_strtora(objra, &ra) == 0 && cmpack_strtodec(objdec, &dec) == 0 && TestPos(posfilter, ra * 15, dec)) {
								if (cb_struct->cb_proc)
									cb_struct->cb_proc(name, ra * 15, dec, "GCVS I-III", NULL, cb_struct->cb_proc_data);
							}
						}
					}
					else if (reader.Length() > 53 && reader.Length() < MAXLINE && reader.Col(7) == '|' && reader.Col(19) == '|' && reader.Col(47) == '|') {
						// GCVS I-III 2007 - 2009
						reader.GetField(8, 5, name, MAXLINE);
						strcat(name, " ");
						reader.GetField(14, 3, aux, MAXLINE);
						strcat(name, aux);
						if (TestName(pattern, name)) {
							double ra, dec;
							reader.GetField(20, 8, objra, MAXLINE);
							InsertString(objra, MAXLINE, " ", 2);
							InsertString(objra, MAXLINE, " ", 5);
							reader.GetField(28, 7, objdec, MAXLINE);
							InsertString(objdec, MAXLINE, " ", 3);
							InsertString(objdec, MAXLINE, " ", 6);
							if (cmpack_strtora(objra, &ra) == 0 && cmpack_strtodec(objdec, &dec) == 0 && TestPos(posfilter, ra * 15, dec)) {
								if (cb_struct->cb_proc)
									cb_struct->cb_proc(name, ra * 15, dec, "GCVS I-III", NULL, cb_struct->cb_proc_data);
							}
						}
					}
				}
				fclose(f);
				g_free(pattern);
			}
			g_free(fpath);
		}
	}
}

void VarCat::Search_NSV(const char *searchstr, const tPosFilter *posfilter, tCallbacks *cb_struct)
{
	char name[MAXLINE], objra[MAXLINE], objdec[MAXLINE], comment[MAXLINE];
	char *pattern, *fpath;
	int line = 0;

	if (cb_struct->cb_setFileName)
		cb_struct->cb_setFileName("NSV", cb_struct->cb_struct_data);

	if (CConfig::GetBool(CConfig::SEARCH_NSV)) {
		fpath = CConfig::GetStr(CConfig::NSV_PATH);
		pattern = MakePattern(searchstr);
		if (pattern && fpath) {
			FILE *f = open_file(fpath, "r");
			if (f) {
				if (cb_struct->cb_setSize) {
					cb_struct->cb_setSize(fileSize(f), cb_struct->cb_struct_data);
					fseek(f, 0, SEEK_SET);
				}

				CTextReader reader(f);
				while (reader.ReadLine()) {
					if ((line++ % PROGRESS_LINES) == 0) {
						if (cb_struct->cb_cancelled && cb_struct->cb_cancelled(cb_struct->cb_struct_data))
							break;
						if (cb_struct->cb_setPosition)
							cb_struct->cb_setPosition(ftell(f), cb_struct->cb_struct_data);
					}

					if (reader.Length() > 108 && reader.Length() < MAXLINE) {
						if (reader.Col(8) == '|' && reader.Col(26) == '|' && (reader.Col(108) == '|' || reader.Col(108) == '=') && (reader.Col(17) == '+' || reader.Col(17) == '-')) {
							// NSV
							reader.GetField(0, 5, name, MAXLINE);
							StripLeadingZeros(name);
							InsertString(name, MAXLINE, "NSV ", 0);
							if (TestName(pattern, name)) {
								double ra, dec;
								reader.GetField(27, 8, objra, MAXLINE);
								InsertString(objra, MAXLINE, " ", 2);
								InsertString(objra, MAXLINE, " ", 5);
								reader.GetField(35, 7, objdec, MAXLINE);
								InsertString(objdec, MAXLINE, " ", 3);
								InsertString(objdec, MAXLINE, " ", 6);
								reader.GetField(81, 13, comment, MAXLINE);
								if (cmpack_strtora(objra, &ra) == 0 && cmpack_strtodec(objdec, &dec) == 0 && TestPos(posfilter, ra * 15, dec)) {
									if (cb_struct->cb_proc)
										cb_struct->cb_proc(name, ra * 15, dec, "NSV", comment, cb_struct->cb_proc_data);
								}
								if (reader.Col(108) == '=') {
									reader.GetField(109, 10, name, MAXLINE);
									Search_GCVS(name, posfilter, cb_struct);
								}
							}
						}
						else if (reader.Col(8) == ' ' && reader.Col(26) == ' ' && reader.Col(108) == '=') {
							// Cross reference to another star
							reader.GetField(0, 5, name, MAXLINE);
							StripLeadingZeros(name);
							InsertString(name, MAXLINE, "NSV ", 0);
							if (TestName(pattern, name)) {
								reader.GetField(109, 10, name, MAXLINE);
								Search_GCVS(name, posfilter, cb_struct);
							}
						}
					}
				}
				fclose(f);
			}
		}
		g_free(pattern);
		g_free(fpath);
	}
}

void VarCat::Search_NSVS(const char *searchstr, const tPosFilter *posfilter, tCallbacks *cb_struct)
{
	char name[MAXLINE], objra[MAXLINE], objdec[MAXLINE];
	char *pattern, *fpath;
	int line = 0;

	if (cb_struct->cb_setFileName)
		cb_struct->cb_setFileName("NSVS", cb_struct->cb_struct_data);

	if (CConfig::GetBool(CConfig::SEARCH_NSVS)) {
		fpath = CConfig::GetStr(CConfig::NSVS_PATH);
		pattern = MakePattern(searchstr);
		if (pattern && fpath) {
			FILE *f = open_file(fpath, "r");
			if (f) {
				if (cb_struct->cb_setSize) {
					cb_struct->cb_setSize(fileSize(f), cb_struct->cb_struct_data);
					fseek(f, 0, SEEK_SET);
				}

				CTextReader reader(f);
				while (reader.ReadLine()) {
					if ((line++ % PROGRESS_LINES) == 0) {
						if (cb_struct->cb_cancelled && cb_struct->cb_cancelled(cb_struct->cb_struct_data))
							break;
						if (cb_struct->cb_setPosition)
							cb_struct->cb_setPosition(ftell(f), cb_struct->cb_struct_data);
					}

					if (reader.Length() > 104 && reader.Length() < MAXLINE) {
						if (reader.Col(6) == '|' && reader.Col(23) == '|' && (reader.Col(104) == '|' || reader.Col(104) == '=') && (reader.Col(15) == '+' || reader.Col(15) == '-')) {
							// NSV Supplement
							reader.GetField(0, 5, name, MAXLINE);
							StripLeadingZeros(name);
							InsertString(name, MAXLINE, "NSV ", 0);
							if (TestName(pattern, name)) {
								double ra, dec;
								reader.GetField(24, 8, objra, MAXLINE);
								InsertString(objra, MAXLINE, " ", 2);
								InsertString(objra, MAXLINE, " ", 5);
								reader.GetField(32, 7, objdec, MAXLINE);
								InsertString(objdec, MAXLINE, " ", 3);
								InsertString(objdec, MAXLINE, " ", 6);
								if (cmpack_strtora(objra, &ra) == 0 && cmpack_strtodec(objdec, &dec) == 0 && TestPos(posfilter, ra * 15, dec)) {
									if (cb_struct->cb_proc)
										cb_struct->cb_proc(name, ra * 15, dec, "NSVS", NULL, cb_struct->cb_proc_data);
								}
								if (reader.Col(104) == '=') {
									reader.GetField(105, 10, name, MAXLINE);
									Search_GCVS(name, posfilter, cb_struct);
								}
							}
						}
					}
				}
				fclose(f);
			}
		}
		g_free(pattern);
		g_free(fpath);
	}
}

void VarCat::Search_VSX(const char *searchstr, const tPosFilter *posfilter, tCallbacks *cb_struct)
{
	//  133087 MACHO 311.37389.3983           0 274.60808 -23.92522 UG: 

	char name[MAXLINE], objra[MAXLINE], objdec[MAXLINE];
	char *pattern, *fpath;
	int line = 0;

	if (cb_struct->cb_setFileName)
		cb_struct->cb_setFileName("VSX", cb_struct->cb_struct_data);

	if (CConfig::GetBool(CConfig::SEARCH_VSX)) {
		fpath = CConfig::GetStr(CConfig::VSX_PATH);
		pattern = MakePattern(searchstr);
		if (fpath) {
			FILE *f = open_file(fpath, "r");
			if (f) {
				if (cb_struct->cb_setSize) {
					cb_struct->cb_setSize(fileSize(f), cb_struct->cb_struct_data);
					fseek(f, 0, SEEK_SET);
				}

				CTextReader reader(f);
				while (reader.ReadLine()) {
					if ((line++ % PROGRESS_LINES) == 0) {
						if (cb_struct->cb_cancelled && cb_struct->cb_cancelled(cb_struct->cb_struct_data))
							break;
						if (cb_struct->cb_setPosition)
							cb_struct->cb_setPosition(ftell(f), cb_struct->cb_struct_data);
					}

					if (reader.Length() >= 60 && reader.Length() < MAXLINE) {
						if (reader.Col(7) == ' ' && reader.Col(38) == ' ' && (reader.Col(39) >= '0' || reader.Col(108) <= '3') && reader.Col(40) == ' ' && reader.Col(44) == '.' && reader.Col(50) == ' ' && reader.Col(54) == '.') {
							reader.GetField(8, 30, name, MAXLINE);
							StripLeadingZeros(name);
							if (TestName(pattern, name)) {
								reader.GetField(41, 9, objra, MAXLINE);
								reader.GetField(51, 9, objdec, MAXLINE);
								char *endptr;
								double ra = strtod(objra, &endptr), dec = strtod(objdec, &endptr);
								if (TestPos(posfilter, ra, dec)) {
									if (cb_struct->cb_proc)
										cb_struct->cb_proc(name, ra, dec, "VSX", NULL, cb_struct->cb_proc_data);
								}
							}
						}
					}
				}
				fclose(f);
			}
		}
		g_free(pattern);
		g_free(fpath);
	}
}

bool VarCat::Test(void)
{
	gboolean ok = false;
	char *fpath;

	if (!ok && CConfig::GetBool(CConfig::SEARCH_GCVS)) {
		fpath = CConfig::GetStr(CConfig::GCVS_PATH);
		ok = g_file_test(fpath, G_FILE_TEST_IS_REGULAR);
		g_free(fpath);
	}
	if (!ok && CConfig::GetBool(CConfig::SEARCH_NSV)) {
		fpath = CConfig::GetStr(CConfig::NSV_PATH);
		ok = g_file_test(fpath, G_FILE_TEST_IS_REGULAR);
		g_free(fpath);
	}
	if (!ok && CConfig::GetBool(CConfig::SEARCH_NSVS)) {
		fpath = CConfig::GetStr(CConfig::NSVS_PATH);
		ok = g_file_test(fpath, G_FILE_TEST_IS_REGULAR);
		g_free(fpath);
	}
	if (!ok && CConfig::GetBool(CConfig::SEARCH_VSX)) {
		fpath = CConfig::GetStr(CConfig::VSX_PATH);
		ok = g_file_test(fpath, G_FILE_TEST_IS_REGULAR);
		g_free(fpath);
	}
	return ok!=0;
}

void VarCat::Search(int catalogFilter, const char *searchstr, const tPosFilter *posfilter, tCallbacks *cb_struct)
{
	if (catalogFilter & GCVS)
		Search_GCVS(searchstr, posfilter, cb_struct);
	if (catalogFilter & NSV)
		Search_NSV(searchstr, posfilter, cb_struct);
	if (catalogFilter & NSVS)
		Search_NSVS(searchstr, posfilter, cb_struct);
	if (catalogFilter & VSX)
		Search_VSX(searchstr, posfilter, cb_struct);
}

//--------------------------------------------------------------------------------

class CVarCatDialog
{
public:
	// Constructor
	CVarCatDialog(GtkWindow *parent);

	// Destructor
	virtual ~CVarCatDialog();

	// Execute the dialog
	void Execute(int catalogFilter, const gchar *searchstr, const VarCat::tPosFilter *posfilter, VarCat::tVarCatProc *cb_proc, void *cb_data);

private:
	GtkWindow					*m_pParent;
	CProgressDlg				*m_pDlg;
	const gchar					*m_searchStr;
	const VarCat::tPosFilter	*m_posFilter;
	VarCat::tVarCatProc			*m_cbProc;
	void						*m_cbData;
	int							m_catalogFilter;

	static int ExecProc(CProgressDlg *sender, void *user_data);
	static bool Cancelled(void *data);
	static void SetFileName(const gchar *name, void *data);
	static void SetSize(long size, void *data);
	static void SetPosition(long pos, void *data);

private:
	// Disable copy constructor and assignment operator
	CVarCatDialog(const CVarCatDialog&);
	CVarCatDialog &operator=(const CVarCatDialog&);
};

//
// Constructor
//
CVarCatDialog::CVarCatDialog(GtkWindow *parent) : m_pParent(parent), m_searchStr(NULL), m_posFilter(NULL), m_cbProc(NULL), m_cbData(NULL),
m_catalogFilter(0)
{
	m_pDlg = new CProgressDlg(m_pParent, "Searching catalog files");
}


//
// Destructor
//
CVarCatDialog::~CVarCatDialog()
{
	delete m_pDlg;
}


//
// Execute the dialog
//
void CVarCatDialog::Execute(int catalogFilter, const char *searchStr, const VarCat::tPosFilter *posFilter, VarCat::tVarCatProc *cb_proc, void *cb_data)
{
	m_catalogFilter = catalogFilter;
	m_searchStr = searchStr;
	m_posFilter = posFilter;
	m_cbProc = cb_proc;
	m_cbData = cb_data;

	GError *error = NULL;
	m_pDlg->Execute(ExecProc, this, &error);
	if (error) {
		ShowError(m_pParent, error->message);
		g_error_free(error);
	}
}


//
// Worker procedure
//
int CVarCatDialog::ExecProc(CProgressDlg *sender, void *user_data)
{
	CVarCatDialog *pMe = reinterpret_cast<CVarCatDialog*>(user_data);

	VarCat::tCallbacks cb;
	cb.cb_proc = pMe->m_cbProc;
	cb.cb_proc_data = pMe->m_cbData;

	cb.cb_cancelled = Cancelled;
	cb.cb_setFileName = SetFileName;
	cb.cb_setSize = SetSize;
	cb.cb_setPosition = SetPosition;
	cb.cb_struct_data = pMe;

	VarCat::Search(pMe->m_catalogFilter, pMe->m_searchStr, pMe->m_posFilter, &cb);
	return 0;
}


//
// Execute the dialog
//
void VarCat::SearchEx(GtkWindow *pParent, int catalogFilter, const char *searchStr, const tPosFilter *posFilter, tVarCatProc *cb_proc, void *cb_data)
{
	CVarCatDialog pDlg(pParent);

	pDlg.Execute(catalogFilter, searchStr, posFilter, cb_proc, cb_data);
}

bool CVarCatDialog::Cancelled(void *user_data)
{
	CVarCatDialog *pMe = reinterpret_cast<CVarCatDialog*>(user_data);
	return pMe->m_pDlg->Cancelled();
}

void CVarCatDialog::SetFileName(const gchar *name, void *user_data)
{
	CVarCatDialog *pMe = reinterpret_cast<CVarCatDialog*>(user_data);
	return pMe->m_pDlg->SetFileName(name);
}

void CVarCatDialog::SetSize(long size, void *user_data)
{
	CVarCatDialog *pMe = reinterpret_cast<CVarCatDialog*>(user_data);
	return pMe->m_pDlg->SetMinMax(0, size);
}

void CVarCatDialog::SetPosition(long pos, void *user_data)
{
	CVarCatDialog *pMe = reinterpret_cast<CVarCatDialog*>(user_data);
	return pMe->m_pDlg->SetProgress(pos);
}
