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

selection.cpp (C-Munipack project)
Table of selected stars
Copyright (C) 2008 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 <glib/gstdio.h>
#include <assert.h>

#include "helper_classes.h"
#include "ccdfile_class.h"
#include "utils.h"
#include "configuration.h"
#include "main.h"

#define ALLOC_BY 64

#if !GLIB_CHECK_VERSION(2,26,0)
typedef struct stat GStatBuf;
#endif

// File types
static const struct {
	tFileType type;
	const gchar *rc_group;
} FileTypes[] = {
	{ TYPE_IMAGE, "CCD image" },
	{ TYPE_PHOT, "Photometry file" },
	{ TYPE_CAT, "Catalog file" },
	{ TYPE_TABLE, "Table" },
	{ TYPE_VARFIND, "Frame set" },
	{ TYPE_PROJECT, RECENT_GROUP_PROJECT },
	{ TYPE_PROFILE, NULL },
	{ TYPE_UNKNOWN, NULL }
};

// Selection types
static const gchar *typeLabel[CMPACK_SELECT_COUNT] = {
	NULL, "var", "comp", "check"
};

// Table types
static struct {
	CmpackTableType type;
	const gchar *str;
} TableTypes[] = {
	{ CMPACK_TABLE_UNSPECIFIED,	"Other" },
	{ CMPACK_TABLE_LCURVE_DIFF,	"LCD" },
	{ CMPACK_TABLE_LCURVE_INST,	"LCI" },
	{ CMPACK_TABLE_MAGDEV,		"MAGDEV" },
	{ CMPACK_TABLE_TRACKLIST,	"TRACK" },
	{ CMPACK_TABLE_APERTURES,	"APER" },
	{ CMPACK_TABLE_AIRMASS,		"AIRMAS" },
	{ CMPACK_TABLE_CCD_TEMP,	"CCDTEMP" },
	{ CMPACK_TABLE_OBJ_PROP,	"OBJPROP" },
	{ (CmpackTableType)(-1),		NULL }
};

//--------------------------   HELPER FUNCTIONS   ----------------------------------

//
// Make copy of a string
//
static gchar *CopyString(const gchar *str)
{
	return (str ? g_strdup(str) : NULL);
}

//
// Convert from current locale
//
static gchar *FromLocale(const char *str)
{
	if (str)
		return g_locale_to_utf8(str, -1, NULL, NULL, NULL);
	return NULL;
}


// Autodetect file type
tFileType FileType(const char *fpath)
{
	const int BLOCKSIZE = 2048;

	tFileType type = TYPE_UNKNOWN;
	FILE *f = open_file(fpath, "rb");
	if (f) {
		char buffer[BLOCKSIZE];
		fseek(f, 0, SEEK_END);
		int filesize = ftell(f);
		fseek(f, 0, SEEK_SET);
		int bytes = (int)fread(buffer, 1, BLOCKSIZE, f);
		if (cmpack_pht_test_buffer(buffer, bytes, filesize))
			type = TYPE_PHOT;
		else if (cmpack_cat_test_buffer(buffer, bytes, filesize))
			type = TYPE_CAT;
		else if (cmpack_fset_test_buffer(buffer, bytes, filesize))
			type = TYPE_VARFIND;
		else if (cmpack_tab_test_buffer(buffer, bytes, filesize))
			type = TYPE_TABLE;
		else if (cmpack_ccd_test_buffer(buffer, bytes, filesize))
			type = TYPE_IMAGE;
		fclose(f);
	}
	return type;
}

// Project type to recent group
const gchar *FileTypeRecentGroup(tFileType type)
{
	for (int i=0; FileTypes[i].type!=TYPE_UNKNOWN; i++)
		if (FileTypes[i].type == type)
			return FileTypes[i].rc_group;
	return NULL;
}

// String representing table type
const gchar *TableTypeToStr(CmpackTableType type)
{
	for (int i=0; TableTypes[i].str!=NULL; i++) {
		if (TableTypes[i].type == type)
			return TableTypes[i].str;
	}
	return TableTypes[0].str;
}

//----------------------   CONSOLE   -------------------------

//
// Call OnPrint
//
void CConsole::OutputProc(const char *text, void *user_data)
{
	char *buffer = g_locale_to_utf8(text, -1, NULL, NULL, NULL);
	((CConsole*)user_data)->OnPrint(buffer);
	g_free(buffer);
}

//
// Call OnPrint
//
void CConsole::Print(const char *text)
{
	OnPrint(text); 
}

//----------------------   SELECTION OF STARS   -------------------------

//
// Default constructor
//
CSelection::CSelection():m_Count(0), m_Capacity(0), m_List(NULL), 
	m_Changed(false), m_LockCount(0), m_LabelsOther(NULL)
{
	memset(m_Modes, 0, CMPACK_SELECT_COUNT*sizeof(tSelectionMode));
}

//
// Constructor from string
//
CSelection::CSelection(const gchar *string):m_Count(0), m_Capacity(0), m_List(NULL), 
	m_Changed(false), m_LockCount(0), m_LabelsOther(NULL)
{
	const gchar *ptr = string;

	memset(m_Modes, 0, CMPACK_SELECT_COUNT*sizeof(tSelectionMode));

	BeginUpdate();
	while (*ptr!='\0') {
		gchar *endptr;
		int id = strtol(ptr, &endptr, 10);
		if (endptr!=ptr && *endptr==':') {
			endptr++;
			for (int i=CMPACK_SELECT_VAR; i<CMPACK_SELECT_COUNT; i++) {
				if (memcmp(endptr, typeLabel[i], strlen(typeLabel[i]))==0) {
					Select(id, (CmpackSelectionType)i);
					break;
				}
			}
		}
		// Find start of the next piece
		ptr += strcspn(ptr, ";");
		if (*ptr == ';')
			ptr++;
	}
	EndUpdate();
}


//
// Copy constructor
//
CSelection::CSelection(const CSelection &orig):m_Count(0), m_Capacity(0), 
	m_List(NULL), m_Changed(false), m_LockCount(0), m_LabelsOther(NULL)
{
	memcpy(m_Modes, orig.m_Modes, CMPACK_SELECT_COUNT*sizeof(tSelectionMode));
	if (orig.m_Count>0) {
		m_Count = m_Capacity = orig.m_Count;
		m_List = (tStarRec*)g_malloc0(m_Capacity*sizeof(tStarRec));
		for (int i=0; i<m_Count; i++) {
			m_List[i].id = orig.m_List[i].id;
			m_List[i].type = orig.m_List[i].type;
			m_List[i].index = orig.m_List[i].index;
		}
	}
}


//
// Destructor
//
CSelection::~CSelection()
{
	for (int i=0; i<m_Count; i++) {
		g_free(m_List[i].label);
		g_free(m_List[i].caption);
		g_free(m_List[i].description);
	}
	g_free(m_List);

	tLabelOther *ptr = m_LabelsOther;
	while (ptr) {
		tLabelOther *next = ptr->next;
		g_free(ptr->label);
		g_free(ptr);
		ptr = next;
	}
}


//
// assignment operator
//
CSelection &CSelection::operator=(const CSelection &orig)
{
	if (&orig!=this) {
		BeginUpdate();
		memcpy(m_Modes, orig.m_Modes, CMPACK_SELECT_COUNT*sizeof(tSelectionMode));
		for (int i=0; i<m_Count; i++) {
			g_free(m_List[i].label);
			g_free(m_List[i].caption);
			g_free(m_List[i].description);
		}
		g_free(m_List);
		m_List = NULL;
		tLabelOther *ptr = m_LabelsOther;
		while (ptr) {
			tLabelOther *next = ptr->next;
			g_free(ptr->label);
			g_free(ptr);
			ptr = next;
		}
		m_LabelsOther = NULL;
		m_Count = m_Capacity = orig.m_Count;
		if (m_Count>0) {
			m_List = (tStarRec*)g_malloc0(m_Capacity*sizeof(tStarRec));
			for (int i=0; i<m_Count; i++) {
				m_List[i].id = orig.m_List[i].id;
				m_List[i].type = orig.m_List[i].type;
				m_List[i].index = orig.m_List[i].index;
			}
		}
		Changed();
		EndUpdate();
	}
	return *this;
}

//
// Comparison operators
//
bool operator==(const CSelection &a, const CSelection &b)
{
	if (a.Count() == b.Count()) {
		for (int i=0; i<a.Count(); i++) {
			if (a.m_List[i].id != b.m_List[i].id || a.m_List[i].type != b.m_List[i].type)
				return false;
		}
		return true;
	}
	return false;
}

//
// Increment update lock
//
void CSelection::BeginUpdate()
{
	g_return_if_fail(m_LockCount >= 0);
	m_LockCount++;
	m_Changed = false;
}

//
// Decrement update lock
//
void CSelection::EndUpdate()
{
	g_return_if_fail(m_LockCount > 0);
	m_LockCount--;
	if (m_LockCount==0) {
		if (m_Changed) {
			Reindex();
			m_Changed = false;
		}
	} else {
		m_Changed = true;
	}
}

//
// Postpone callbacks if locked
//
void CSelection::Changed(void)
{
	if (m_LockCount>0) {
		m_Changed = true;
	} else {
		Reindex();
		m_Changed = false;
	}
}

//
// Clear data
//
void CSelection::Clear()
{
	BeginUpdate();
	m_Count = m_Capacity = 0;
	for (int i=0; i<m_Count; i++) {
		g_free(m_List[i].label);
		g_free(m_List[i].caption);
		g_free(m_List[i].description);
	}
	g_free(m_List);
	m_List = NULL;
	tLabelOther *ptr = m_LabelsOther;
	while (ptr) {
		tLabelOther *next = ptr->next;
		g_free(ptr->label);
		g_free(ptr);
		ptr = next;
	}
	m_LabelsOther = NULL;
	Changed();
	EndUpdate();
}

// 
// Change selection modes for all types
//
void CSelection::setSelectionModes(tSelectionMode mode)
{
	BeginUpdate();
	for (int i=0; i<CMPACK_SELECT_COUNT; i++)
		setSelectionMode((CmpackSelectionType)i, mode);
	EndUpdate();
}

//
// Change selection mode for type
void CSelection::setSelectionMode(CmpackSelectionType type, tSelectionMode mode)
{
	BeginUpdate();
	if (type>=CMPACK_SELECT_VAR && type<=CMPACK_SELECT_CHECK && m_Modes[type] != mode) {
		if (mode == SINGLE) {
			int firstIndex = -1;
			for (int j=0; j<m_Count; j++) {
				if (m_List[j].type == type) {
					firstIndex = j;
					break;
				}
			}
			for (int j=m_Count-1; j>firstIndex; --j) {
				if (m_List[j].type == type) 
					RemoveAt(j);
			}
		} else
		if (mode == NONE)
			Unselect(type);
		m_Modes[type] = mode;
	}
	EndUpdate();
}

//
// Select a star
//
void CSelection::Select(int id, CmpackSelectionType type)
{
	BeginUpdate();
	if (m_Modes[type] != NONE) {
		int index = Search(id);
		if (index<0 || m_List[index].type != type) {
			if (index>=0)
				RemoveAt(index);
			if (type!=CMPACK_SELECT_NONE) {
				if (m_Modes[type] == SINGLE) 
					Unselect(type);
				Append(id, type);
			}
		}
	}
	EndUpdate();
}

//
// Select a star
//
void CSelection::Append(int id, CmpackSelectionType type)
{
	if (m_Count >= m_Capacity) {
		int old_size = m_Capacity;
		m_Capacity += 64;
		m_List = (tStarRec*)g_realloc(m_List, m_Capacity*sizeof(tStarRec));
		memset(m_List+old_size, 0, (m_Capacity-old_size)*sizeof(tStarRec));
	}
	// Append the object to the end of the list
	m_List[m_Count].id = id;
	m_List[m_Count].type = type;
	m_List[m_Count].index = 0;
	m_Count++;
	Changed();
}

//
// Update item in the list
//
void CSelection::Update(int index, int id, CmpackSelectionType type)
{
	if (id != m_List[index].id || type != m_List[index].type) {
		m_List[index].id = id;
		m_List[index].type = type;
		m_List[index].index = 0;
		g_free(m_List[index].label);
		m_List[index].label = NULL;
		g_free(m_List[index].caption);
		m_List[index].caption = NULL;
		g_free(m_List[index].description);
		m_List[index].description = NULL;
		Changed();
	}
}

//
// Unselect a star
//
void CSelection::RemoveAt(int index)
{
	g_free(m_List[index].label);
	g_free(m_List[index].caption);
	g_free(m_List[index].description);
	for (int i=index+1; i<m_Count; i++)
		m_List[i-1] = m_List[i];
	m_List[m_Count-1].label = NULL;
	m_List[m_Count-1].caption = NULL;
	m_List[m_Count-1].description = NULL;
	m_Count--;
}


//
// Unselect a star
//
void CSelection::Remove(int id)
{
	for (int j=0; j<m_Count; j++) {
		if (m_List[j].id == id) {
			RemoveAt(j);
			break;
		}
	}
}


//
// Unselect a star
//
void CSelection::Unselect(CmpackSelectionType type)
{
	BeginUpdate();
	for (int j=m_Count-1; j>=0; --j) {
		if (m_List[j].type == type) 
			RemoveAt(j);
	}
	EndUpdate();
}

//
// Get star identifier
//
int CSelection::GetId(int i) const
{
	if (i>=0 && i<m_Count) 
		return m_List[i].id;
	return 0;
}

//
// Get star type
//
CmpackSelectionType CSelection::Type(int id) const
{
	int index = Search(id);
	if (index>=0)
		return m_List[index].type;
	return CMPACK_SELECT_NONE;
}

//
// Search record by its id
//
int CSelection::Search(int id) const
{
	int i;

	for (i=0; i<m_Count; i++) {
		if (m_List[i].id == id)
			return i;
	}
	return -1;
}

//
// Reindex stars
//
void CSelection::Reindex(void)
{
	int count[CMPACK_SELECT_COUNT];

	memset(count, 0, CMPACK_SELECT_COUNT*sizeof(int));
	for (int i=0; i<m_Count; i++) {
		m_List[i].index = ++count[m_List[i].type];
		g_free(m_List[i].label);
		m_List[i].label = NULL;
		g_free(m_List[i].caption);
		m_List[i].caption = NULL;
		g_free(m_List[i].description);
		m_List[i].description = NULL;
	}

	tLabelOther *ptr = m_LabelsOther;
	while (ptr) {
		tLabelOther *next = ptr->next;
		g_free(ptr->label);
		g_free(ptr);
		ptr = next;
	}
}

//
// Set selection
//
void CSelection::SetStarList(CmpackSelectionType type, const char *string)
{
	BeginUpdate();

	// Delete all stars with specified type
	Unselect(type);

	// Parse string and add all stars
	if (string && m_Modes[type] != NONE) {
		int left = (m_Modes[type] == SINGLE ? 1 : INT_MAX);
		const char *str = string, *endptr;
		int id = strtol(str, (char**)&endptr, 10);
		while (endptr > str && left > 0) {
			int index = Search(id);
			if (index>=0) 
				Update(index, id, type);
			else
				Append(id, type);
			str = endptr + strcspn(endptr, "-+0123456789");
			id = strtol(str, (char**)&endptr, 10);
			left--;
		}
	}
	EndUpdate();
}

//
// Set selection
//
void CSelection::SetStarList(CmpackSelectionType type, const int *list, int count)
{
	BeginUpdate();

	// Delete all stars with specified type
	Unselect(type);

	if (list && count>0 && m_Modes[type] != NONE) {
		int left = (m_Modes[type] == SINGLE ? 1 : INT_MAX);
		// Parse string and add all stars
		for (int i=0; i<count && left>0; i++) {
			int index = Search(list[i]);
			if (index>=0) 
				Update(index, list[i], type);
			else
				Append(list[i], type);
			left--;
		}
	}
	EndUpdate();
}

//
// Get number of stars of specified type
//
int CSelection::CountStars(CmpackSelectionType type) const
{
	int i, count;

	count = 0;
	for (i=0; i<m_Count; i++) {
		if (m_List[i].type == type) 
			count++;
	}
	return count;
}

//
// Get selection
//
int CSelection::GetStarList(CmpackSelectionType type, int *list, int maxlen) const
{
	int i, count;

	count = 0;
	for (i=0; i<m_Count && count<maxlen; i++) {
		if (m_List[i].type == type) 
			list[count++] = m_List[i].id;
	}
	return count;
}

//
// Get selection
//
char *CSelection::GetStarList(CmpackSelectionType type) const
{
	int i, count;
	char aux[12];

	count = 0;
	for (i=0; i<m_Count; i++) {
		if (m_List[i].type == type) 
			count++;
	}

	if (count>0) {
		char *buf = (char*)g_malloc((count*12+1)*sizeof(char));
		if (buf) {
			buf[0] = '\0';
			for (i=0; i<m_Count; i++) {
				if (m_List[i].type == type) {
					if (buf[0]!='\0')
						strcat(buf, ",");
					sprintf(aux, "%d", m_List[i].id);
					strcat(buf, aux);
				}
			}
		}
		return buf;
	}
	return NULL;
}

//
// Convert to string
//
gchar *CSelection::toString(void) const
{
	gchar *buf = (gchar*)g_malloc((m_Count*16+1)*sizeof(gchar)), *ptr = buf;
	for (int i=0; i<m_Count; i++) {
		if (m_List[i].type>CMPACK_SELECT_NONE && m_List[i].type<CMPACK_SELECT_COUNT) 
			ptr += sprintf(ptr, ";%d:%s", m_List[i].id, typeLabel[m_List[i].type]);
	}
	*ptr = '\0';
	return buf;
}

CmpackColor CSelection::Color(int obj_id) const
{
	switch (Type(obj_id))
	{
	case CMPACK_SELECT_VAR:
		return CMPACK_COLOR_RED;
	case CMPACK_SELECT_COMP:
		return CMPACK_COLOR_GREEN;
	case CMPACK_SELECT_CHECK:
		return CMPACK_COLOR_BLUE;
	default:
		return CMPACK_COLOR_DEFAULT;
	}
}

gulong CSelection::SortKey(int obj_id) const
{
	if (m_Modes[CMPACK_SELECT_VAR] != NONE) {
		int i = Search(obj_id);
		if (i>=0) 
			return ((gulong)m_List[i].type << 16) | (m_List[i].index & 0xFFFF);
	} else {
		int i = Search(obj_id);
		if (i>=0 && m_List[i].type == CMPACK_SELECT_COMP) {
			if (CountStars(CMPACK_SELECT_COMP)==1) {
				if (m_List[i].index==1) 
					return ((gulong)CMPACK_SELECT_COMP << 16);
			} else {
				return ((gulong)CMPACK_SELECT_COMP << 16) | (m_List[i].index & 0xFFFF);
			}
		}
	}
	return ((gulong)CMPACK_SELECT_COUNT << 16) | obj_id;
}

const gchar *CSelection::Label(int obj_id)
{
	char buf[256];

	if (m_Modes[CMPACK_SELECT_VAR] != NONE) {
		int i = Search(obj_id);
		if (i>=0) {
			if (!m_List[i].label) {
				// Selected objects
				switch (m_List[i].type)
				{
				case CMPACK_SELECT_VAR:
					if (CountStars(CMPACK_SELECT_VAR)==1) {
						if (m_List[i].index==1) 
							UpdateLabel(i, "V");
						else
							UpdateLabel(i, NULL);
					} else {
						sprintf(buf, "V%d", m_List[i].index);
						UpdateLabel(i, buf);
					}
					break;
				case CMPACK_SELECT_COMP:
					if (CountStars(CMPACK_SELECT_COMP)==1) {
						if (m_List[i].index==1) 
							UpdateLabel(i, "C");
						else
							UpdateLabel(i, NULL);
					} else {
						sprintf(buf, "C%d", m_List[i].index);
						UpdateLabel(i, buf);
					}
					break;
				case CMPACK_SELECT_CHECK:
					sprintf(buf, "K%d", m_List[i].index);
					UpdateLabel(i, buf);
					break;
				default:
					break;
				}
			}
			return m_List[i].label;
		} else {
			UpdateLabelOther(obj_id, NULL);
			return NULL;
		}
	} else {
		// All objects
		int i = Search(obj_id);
		if (i>=0 && m_List[i].type == CMPACK_SELECT_COMP) {
			if (!m_List[i].label) {
				if (CountStars(CMPACK_SELECT_COMP)==1) {
					if (m_List[i].index==1) 
						UpdateLabel(i, "C");
					else {
						sprintf(buf, "V%d", obj_id);
						UpdateLabel(i, buf);
					}
				} else {
					sprintf(buf, "C%d", obj_id);
					UpdateLabel(i, buf);
				}
			}
			return m_List[i].label;
		} else {
			gchar *label = GetLabelOther(obj_id);
			if (!label) {
				sprintf(buf, "V%d", obj_id);
				UpdateLabelOther(obj_id, buf);
			}
			return GetLabelOther(obj_id);
		}
	}
}

const gchar *CSelection::Caption(int obj_id)
{
	char buf[256];

	if (m_Modes[CMPACK_SELECT_VAR] != NONE) {
		int i = Search(obj_id);
		if (i>=0) {
			if (!m_List[i].caption) {
				switch (m_List[i].type)
				{
				case CMPACK_SELECT_VAR:
					if (CountStars(CMPACK_SELECT_VAR)==1) {
						if (m_List[i].index==1) 
							UpdateCaption(i, "var");
						else
							UpdateCaption(i, NULL);
					} else {
						sprintf(buf, "var #%d", m_List[i].index);
						UpdateCaption(i, buf);
					}
					break;
				case CMPACK_SELECT_COMP:
					if (CountStars(CMPACK_SELECT_COMP)==1) {
						if (m_List[i].index==1) 
							UpdateCaption(i, "comp");
						else
							UpdateCaption(i, NULL);
					} else {
						sprintf(buf, "comp #%d", m_List[i].index);
						UpdateCaption(i, buf);
					}
					break;
				case CMPACK_SELECT_CHECK:
					sprintf(buf, "check #%d", m_List[i].index);
					UpdateCaption(i, buf);
					break;
				default:
					break;
				}
			}
			return m_List[i].caption;
		}
	} else {
		int i = Search(obj_id);
		if (i>=0 && m_List[i].type == CMPACK_SELECT_COMP) {
			if (!m_List[i].caption) {
				if (CountStars(CMPACK_SELECT_COMP)==1) {
					if (m_List[i].index==1) 
						UpdateCaption(i, "comp");
					else 
						UpdateCaption(i, NULL);
				} else {
					sprintf(buf, "comp #%d", obj_id);
					UpdateCaption(i, buf);
				}
			}
			return m_List[i].caption;
		}
	}
	return NULL;
}

const gchar *CSelection::Description(int obj_id)
{
	char buf[256];

	if (m_Modes[CMPACK_SELECT_VAR] != NONE) {
		int i = Search(obj_id);
		if (i>=0) {
			if (!m_List[i].description) {
				switch (m_List[i].type)
				{
				case CMPACK_SELECT_VAR:
					if (CountStars(CMPACK_SELECT_VAR)==1) {
						if (m_List[i].index==1) 
							UpdateDescription(i, "var");
						else
							UpdateDescription(i, NULL);
					} else {
						sprintf(buf, "var #%d", m_List[i].index);
						UpdateDescription(i, buf);
					}
					break;
				case CMPACK_SELECT_COMP:
					if (CountStars(CMPACK_SELECT_COMP)==1) {
						if (m_List[i].index==1) 
							UpdateDescription(i, "comp");
						else
							UpdateDescription(i, NULL);
					} else {
						sprintf(buf, "comp #%d", m_List[i].index);
						UpdateDescription(i, buf);
					}
					break;
				case CMPACK_SELECT_CHECK:
					sprintf(buf, "check #%d", m_List[i].index);
					UpdateDescription(i, buf);
					break;
				default:
					break;
				}
			}
			return m_List[i].description;
		}
	} else {
		int i = Search(obj_id);
		if (i>=0 && m_List[i].type == CMPACK_SELECT_COMP) {
			if (!m_List[i].description) {
				if (CountStars(CMPACK_SELECT_COMP)==1) {
					if (m_List[i].index==1) 
						UpdateDescription(i, "comp");
					else
						UpdateDescription(i, NULL);
				} else {
					sprintf(buf, "comp #%d", obj_id);
					UpdateDescription(i, buf);
				}
			}
			return m_List[i].description;
		}
	}
	return NULL;
}

// Update label for a selected object
void CSelection::UpdateLabel(int index, const gchar *buf)
{
	tStarRec *rec = m_List+index;
	if (buf) {
		if (!rec->label || strcmp(rec->label, buf)!=0) {
			g_free(rec->label);
			rec->label = g_strdup(buf);
		}
	} else {
		if (rec->label) {
			g_free(rec->label);
			rec->label = NULL;
		}
	}
}

// Update caption for a selected object
void CSelection::UpdateCaption(int index, const gchar *buf)
{
	tStarRec *rec = m_List+index;
	if (buf) {
		if (!rec->caption || strcmp(rec->caption, buf)!=0) {
			g_free(rec->caption);
			rec->caption = g_strdup(buf);
		}
	} else {
		if (rec->caption) {
			g_free(rec->caption);
			rec->caption = NULL;
		}
	}
}

// Update caption for a selected object
void CSelection::UpdateDescription(int index, const gchar *buf)
{
	tStarRec *rec = m_List+index;
	if (buf) {
		if (!rec->description || strcmp(rec->description, buf)!=0) {
			g_free(rec->description);
			rec->description = g_strdup(buf);
		}
	} else {
		if (rec->description) {
			g_free(rec->description);
			rec->description = NULL;
		}
	}
}

// Update label for un-selected object
void CSelection::UpdateLabelOther(int objId, const gchar *buf)
{
	tLabelOther *ptr = m_LabelsOther, *prev = NULL;
	while (ptr) {
		if (ptr->id == objId) {
			if (buf) {
				if (!ptr->label || strcmp(ptr->label, buf)!=0) {
					// Update existing label
					g_free(ptr->label);
					ptr->label = g_strdup(buf);
				}
			} else {
				// Remove label
				if (!prev) 
					m_LabelsOther = ptr->next;
				else
					prev->next = ptr->next;
				g_free(ptr->label);
				g_free(ptr);
			}
			return;
		}
		prev = ptr;
		ptr = ptr->next;
	}
	if (buf) {
		// Insert label
		ptr = (tLabelOther*)g_malloc(sizeof(tLabelOther));
		ptr->id = objId;
		ptr->label = g_strdup(buf);
		ptr->next = m_LabelsOther;
		m_LabelsOther = ptr;
	}
}

// Get label for un-selected object
gchar *CSelection::GetLabelOther(int objId)
{
	tLabelOther *ptr = m_LabelsOther;
	while (ptr) {
		if (ptr->id == objId)
			return ptr->label;
		ptr = ptr->next;
	}
	return NULL;
}

//----------------------   LIST OF TAGS   -------------------------

// Constructor(s)
CTags::CTags(const CTags &orig):m_List(NULL)
{
	int id;
	const gchar *tag;

	CTags::tIter iter;
	bool ok = orig.First(&iter, &id, &tag);
	while (ok) {
		if (tag && *tag!='\0')
			Set(id, tag);
		ok = orig.Next(&iter, &id, &tag);
	}
}
	
// Destructor
CTags::~CTags()
{
	Clear();
}

// Clear data
void CTags::Clear()
{
	tData *ptr = m_List, *next;
	while (ptr) {
		next = ptr->next;
		g_free(ptr->tag);
		g_free(ptr);
		ptr = next;
	}
	m_List = NULL;
}

// Assignment operator
CTags &CTags::operator=(const CTags &orig)
{
	int id;
	const gchar *tag;

	if (&orig!=this) {
		Clear();
		CTags::tIter iter;
		bool ok = orig.First(&iter, &id, &tag);
		while (ok) {
			if (tag && *tag!='\0')
				Set(id, tag);
			ok = orig.Next(&iter, &id, &tag);
		}
	}
	return *this;
}

// Set a tag
void CTags::Set(int id, const gchar *tag)
{
	if (tag && *tag!='\0') {
		tData *ptr=m_List, *prev=NULL;
		// Sort items by id
		while (ptr && ptr->id<id) {
			prev = ptr;
			ptr = ptr->next;
		}
		if (ptr && ptr->id == id) {
			// Change existing tag
			if (strcmp(ptr->tag, tag)!=0) {
				g_free(ptr->tag);
				ptr->tag = g_strdup(tag);
			}
		} else {
			// Create a new tag before ptr
			tData *it = (tData*)g_malloc0(sizeof(tData));
			it->id = id;
			it->tag = g_strdup(tag);
			it->next = ptr;
			if (prev)
				prev->next = it;
			else
				m_List = it;
		}
	} else
		Remove(id);
}

// Remove a tag
void CTags::Remove(int id)
{
	tData *prev=NULL, *next;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (ptr->id == id) {
			next = ptr->next;
			g_free(ptr->tag);
			g_free(ptr);
			if (prev)
				prev->next = next;
			else
				m_List = next;
			break;
		}	
		prev = ptr;
	}
}

// Get a tag
const gchar *CTags::Get(int id) const
{
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (ptr->id == id) 
			return ptr->tag;
	}
	return NULL;
}

// Get number of tags
bool CTags::First(tIter *iter, int *pid, const gchar **ptag) const
{
	tData *tag = m_List;
	if (tag) {
		if (pid)
			*pid = tag->id;
		if (ptag)
			*ptag = tag->tag;
		iter->ptr = tag;
		return true;
	} else {
		iter->ptr = NULL;
		return false;
	}
}

// Get tag by index
bool CTags::Next(tIter *iter, int *pid, const gchar **ptag) const
{
	if (!iter->ptr)
		return false;

	tData *tag = iter->ptr->next;
	if (tag) {
		if (pid)
			*pid = tag->id;
		if (ptag)
			*ptag = tag->tag;
		iter->ptr = tag;
		return true;
	} else {
		iter->ptr = NULL;
		return false;
	}
}

// Number of tags
int CTags::Count(void) const
{
	int count = 0;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) 
		count++;
	return count;
}

//----------------------   OBJECT COORDINATES   -----------------------------

//
// Constructor
// 
CObjectCoords::CObjectCoords(const CmpackObjCoords *c):m_Name(NULL), m_RA(NULL), 
	m_Dec(NULL), m_Source(NULL), m_Remarks(NULL)
{
	if (c) {
		m_Name = FromLocale(c->designation);
		if (c->ra_valid) {
			char buf[256];
			cmpack_ratostr(c->ra, buf, 256);
			m_RA = CopyString(buf);
		}
		if (c->dec_valid) {
			char buf[256];
			cmpack_dectostr(c->dec, buf, 256);
			m_Dec = CopyString(buf);
		}
		m_Remarks = NULL;
		m_Source = NULL;
	}
}


//
// Copy constructor
//
CObjectCoords::CObjectCoords(const CObjectCoords &orig)
{
	m_Name = CopyString(orig.m_Name);
	m_RA = CopyString(orig.m_RA);
	m_Dec = CopyString(orig.m_Dec);
	m_Source = CopyString(orig.m_Source);
	m_Remarks = CopyString(orig.m_Remarks);
}


//
// Destructor
//
CObjectCoords::~CObjectCoords()
{
	g_free(m_Name);
	g_free(m_RA);
	g_free(m_Dec);
	g_free(m_Source);
	g_free(m_Remarks);
}


// assignment operator
//
CObjectCoords &CObjectCoords::operator=(const CObjectCoords &orig)
{
	if (&orig!=this) {
		g_free(m_Name);
		m_Name = CopyString(orig.m_Name);
		g_free(m_RA);
		m_RA = CopyString(orig.m_RA);
		g_free(m_Dec);
		m_Dec = CopyString(orig.m_Dec);
		g_free(m_Source);
		m_Source = CopyString(orig.m_Source);
		g_free(m_Remarks);
		m_Remarks = CopyString(orig.m_Remarks);
	}
	return *this;
}

//
// Set object designation
//
void CObjectCoords::SetName(const char *name)
{
	g_free(m_Name);
	m_Name = CopyString(name);
}

//
// Set source
//
void CObjectCoords::SetSource(const char *source)
{
	g_free(m_Source);
	m_Source = CopyString(source);
}

//
// Set remarks
//
void CObjectCoords::SetRemarks(const char *remarks)
{
	g_free(m_Remarks);
	m_Remarks = CopyString(remarks);
}

//
// Set object coordinates
//
void CObjectCoords::SetRA(const char *ra)
{
	g_free(m_RA);
	m_RA = CopyString(ra);
}
void CObjectCoords::SetDec(const char *dec)
{
	g_free(m_Dec);
	m_Dec = CopyString(dec);
}

//
// Clear data
//
void CObjectCoords::Clear()
{
	g_free(m_Name);
	m_Name = NULL;
	g_free(m_RA);
	m_RA = NULL;
	g_free(m_Dec);
	m_Dec = NULL;
	g_free(m_Source);
	m_Source = NULL;
	g_free(m_Remarks);
	m_Remarks = NULL;
}


//
// Check validity of coordinates
//
bool CObjectCoords::Valid(void) const
{
	double x, y;

	if (m_RA && m_Dec && *m_RA!='\0' && *m_Dec!='\0') 
		return cmpack_strtora(m_RA, &x)==0 && cmpack_strtodec(m_Dec, &y)==0;
	return false;
}

//
// Comparison operators
//
bool operator==(const CObjectCoords &a, const CObjectCoords &b)
{
	return StrCmp0(a.m_Name, b.m_Name)==0 &&
		StrCmp0(a.m_RA, b.m_RA)==0 && StrCmp0(a.m_Dec, b.m_Dec)==0;
}

//----------------------   OBSERVER COORDINATES   -----------------------------

//
// Constructor
// 
CLocation::CLocation(const CmpackLocation *c):m_Name(NULL), m_Lon(NULL), m_Lat(NULL), 
	m_Com(NULL)
{
	if (c) {
		m_Name = FromLocale(c->designation);
		if (c->lon_valid) {
			char buf[256];
			cmpack_lontostr(c->lon, buf, 256);
			m_Lon = CopyString(buf);
		}
		if (c->lat_valid) {
			char buf[256];
			cmpack_lattostr(c->lat, buf, 256);
			m_Lat = CopyString(buf);
		}
	}
}


//
// Copy constructor
//
CLocation::CLocation(const CLocation &orig)
{
	m_Name = CopyString(orig.m_Name);
	m_Lon = CopyString(orig.m_Lon);
	m_Lat = CopyString(orig.m_Lat);
	m_Com = CopyString(orig.m_Com);
}


//
// Destructor
//
CLocation::~CLocation()
{
	g_free(m_Name);
	g_free(m_Lon);
	g_free(m_Lat);
	g_free(m_Com);
}


// assignment operator
//
CLocation &CLocation::operator=(const CLocation &orig)
{
	if (&orig!=this) {
		g_free(m_Name);
		m_Name = CopyString(orig.m_Name);
		g_free(m_Lon);
		m_Lon = CopyString(orig.m_Lon);
		g_free(m_Lat);
		m_Lat = CopyString(orig.m_Lat);
		g_free(m_Com);
		m_Com = CopyString(orig.m_Com);
	}
	return *this;
}

//
// Set observer's designation
//
void CLocation::SetName(const char *name)
{
	g_free(m_Name);
	m_Name = CopyString(name);
}

//
// Set remarks
//
void CLocation::SetComment(const char *com)
{
	g_free(m_Com);
	m_Com = CopyString(com);
}

//
// Set observer's coordinates
//
void CLocation::SetLon(const char *lon)
{
	g_free(m_Lon);
	m_Lon = CopyString(lon);
}
void CLocation::SetLat(const char *lat)
{
	g_free(m_Lat);
	m_Lat = CopyString(lat);
}

//
// Clear data
//
void CLocation::Clear()
{
	g_free(m_Name);
	m_Name = NULL;
	g_free(m_Lon);
	m_Lon = NULL;
	g_free(m_Lat);
	m_Lat = NULL;
	g_free(m_Com);
	m_Com = NULL;
}

//
// Check validity of coordinates
//
bool CLocation::Valid(void) const
{
	double x, y;

	if (m_Lon && m_Lat && *m_Lon!='\0' && *m_Lat!='\0') 
		return cmpack_strtolon(m_Lon, &x)==0 && cmpack_strtolat(m_Lat, &y)==0;
	return false;
}

//
// Comparison operators
//
bool operator==(const CLocation &a, const CLocation &b)
{
	return StrCmp0(a.m_Name, b.m_Name)==0 &&
		StrCmp0(a.m_Lon, b.m_Lon)==0 && StrCmp0(a.m_Lat, b.m_Lat)==0;
}

//------------------------   FILE INFO   --------------------------

//
// Default constructor
//
CFileInfo::CFileInfo():m_FullPath(NULL)
{
	memset(&m_MTime, 0, sizeof(time_t));
}


//
// Constructor with initialization
//
CFileInfo::CFileInfo(const gchar *path):m_FullPath(NULL)
{
	char *fullpath;
	GStatBuf fs;

	memset(&m_MTime, 0, sizeof(time_t));
	if (path) {
		fullpath = g_strdup(path);
		if (g_stat(fullpath, &fs)==0) {
			m_MTime = fs.st_mtime;
			m_FullPath = fullpath;
		} else {
			g_free(fullpath);
		}
	}
}


//
// Copy constructor
//
CFileInfo::CFileInfo(const CFileInfo &orig)
{
	m_FullPath = CopyString(orig.m_FullPath);
	m_MTime = orig.m_MTime;
}


//
// Destructor
//
CFileInfo::~CFileInfo()
{
	g_free(m_FullPath);
}


//
// Assignment operator
//
CFileInfo &CFileInfo::operator=(const CFileInfo &orig)
{
	if (&orig!=this) {
		g_free(m_FullPath);
		m_FullPath = CopyString(orig.m_FullPath);
		m_MTime = orig.m_MTime;
	}
	return *this;
}


//
// Clear data
//
void CFileInfo::Clear(void)
{
	g_free(m_FullPath);
	m_FullPath = NULL;
	memset(&m_MTime, 0, sizeof(time_t));
}


//
// Load from project file
//
void CFileInfo::Load(GKeyFile *cfg, const gchar *section, const gchar *prefix)
{
	gchar key[128], *val;

	Clear();

	sprintf(key, "%s.path", prefix);
	val = g_key_file_get_string(cfg, section, key, NULL);
	if (val && *val!='\0')
		m_FullPath = g_strdup(val);
	g_free(val);

	sprintf(key, "%s.time", prefix);
	val = g_key_file_get_string(cfg, section, key, NULL);
	if (val && *val!='\0') {
		if (sizeof(time_t)==sizeof(int)) 
			sscanf(val, " %11d ", (int*)&m_MTime);
		else {
			int time[2];
			sscanf(val, " %11d %11d", time, time+1);
			memcpy(&m_MTime, time, 2*sizeof(int));
		}
	}
	g_free(val);
}


//
// Save to project file
//
void CFileInfo::Save(GKeyFile *cfg, const gchar *section, const gchar *prefix) const
{
	gchar key[128], val[128];

	sprintf(key, "%s.path", prefix);
	if (m_FullPath) 
		g_key_file_set_string(cfg, section, key, m_FullPath);
	else
		g_key_file_remove_key(cfg, section, key, NULL);

	sprintf(key, "%s.time", prefix);
	if (m_MTime!=0) {
		if (sizeof(time_t)==sizeof(int))
			sprintf(val, "%d", (int)m_MTime);
		else {
			int time[2];
			memcpy(time, &m_MTime, 2*sizeof(int));
			sprintf(val, "%d %d", time[0], time[1]);
		}
		g_key_file_set_string(cfg, section, key, val);
	} else {
		g_key_file_remove_key(cfg, section, key, NULL);
	}
}


//
// Equivalence operator
//
int CFileInfo::Compare(const CFileInfo &b) const
{
	if (!m_FullPath) 
		return (b.m_FullPath ? -1 : 0);
	if (!b.m_FullPath)
		return 1;
	
	int res = strcmp(m_FullPath, b.m_FullPath);
	if (res!=0)
		return res;

	if (m_MTime < b.m_MTime)
		return -1;
	if (m_MTime > b.m_MTime)
		return 1;

	return 0;
}

//-------------------------   TABLE OF APERTURES   ------------------------

//
// Copy constructor
//
CApertures::CApertures(const CApertures &orig):m_Count(0), m_Capacity(0), 
	m_List(NULL)
{
	int i, count;

	count = orig.Count();
	if (count>0) {
		m_Capacity = count;
		m_List = (CAperture**)g_malloc(m_Capacity*sizeof(CAperture*));
		for (i=0; i<count; i++) {
			const CAperture *ap = orig.Get(i);
			m_List[i] = (ap!=NULL ? new CAperture(*ap) : NULL);
		}
		m_Count = count;
	}
}

// Destructor
CApertures::~CApertures()
{
	for (int i=0; i<m_Count; i++)
		delete m_List[i];
	g_free(m_List);
}

// assignment operator
CApertures &CApertures::operator=(const CApertures &orig)
{
	int i, count;

	if (&orig!=this) {
		Clear();
		count = orig.Count();
		if (count>0) {
			m_Capacity = count;
			m_List = (CAperture**)g_malloc(m_Capacity*sizeof(CAperture*));
			for (i=0; i<count; i++) {
				const CAperture *ap = orig.Get(i);
				m_List[i] = (ap!=NULL ? new CAperture(*ap) : NULL);
			}
			m_Count = count;
		}
	}
	return *this;
}

// Clear the table
void CApertures::Clear(void)
{
	for (int i=0; i<m_Count; i++)
		delete m_List[i];
	g_free(m_List);
	m_List = NULL;
	m_Count = m_Capacity = 0;
}

// Add an aperture, if the aperture with the same id is 
// in the table, it changes its parameters
void CApertures::Add(const CAperture &item)
{
	int index = Find(item.Id());
	if (index<0) {
		if (m_Count>=m_Capacity) {
			m_Capacity += ALLOC_BY;
			m_List = (CAperture**)g_realloc(m_List, m_Capacity*sizeof(CAperture*));
		}
		m_List[m_Count++] = new CAperture(item);
	} else 
		*m_List[index] = item;
}

// Find aperture by id 
int CApertures::Find(int id) const
{
	int i;

	for (i=0; i<m_Count; i++) {
		if (m_List[i]->Id() == id)
			return i;
	}
	return -1;
}

// Return index of smallest aperture
int CApertures::FindSmallest(void) const
{
	int i, imin = -1;
	double qmin = 1e99;

	for (i=0; i<m_Count; i++) {
		if (m_List[i]->Radius() < qmin) {
			imin = i;
			qmin = m_List[i]->Radius();
		}
	}
	return imin;
}

const CAperture *CApertures::Get(int index) const
{
	if (index>=0 && index<m_Count)
		return m_List[index];
	return NULL;
}

// Get aperture by index
int CApertures::GetId(int index) const
{
	if (index>=0 && index<m_Count)
		return m_List[index]->Id();
	return -1;
}

// Get aperture by index
double CApertures::GetRadius(int index) const
{
	if (index>=0 && index<m_Count)
		return m_List[index]->Radius();
	return 0.0;
}

//-----------------------   FRAME DESCRIPTOR   ------------------------------

//
// Default constructor
//
CFrameInfo::CFrameInfo(void):m_FullPath(NULL), m_Filter(NULL), m_Object(NULL),
	m_JulDat(0), m_ExpTime(-1.0), m_CCDTemp(-999.99), m_AvgFrames(0), m_SumFrames(0),
	m_Format(CMPACK_FORMAT_UNKNOWN)
{
}

//
// Copy constructor
//
CFrameInfo::CFrameInfo(const CFrameInfo &orig):m_JulDat(orig.m_JulDat), 
	m_ExpTime(orig.m_ExpTime), m_CCDTemp(orig.m_CCDTemp), m_AvgFrames(orig.m_AvgFrames),
	m_SumFrames(orig.m_SumFrames), m_Format(orig.m_Format)
{
	m_FullPath = CopyString(orig.m_FullPath);
	m_Object = CopyString(orig.m_Object);
	m_Filter = CopyString(orig.m_Filter);
}

//
// Destructor
//
CFrameInfo::~CFrameInfo(void)
{
	g_free(m_FullPath);
	g_free(m_Object);
	g_free(m_Filter);
}

//
// Initialization
//
bool CFrameInfo::Init(const gchar *filepath, GError **error)
{
	Clear();

	CCCDFile file;
	if (!file.Open(filepath, CMPACK_OPEN_READONLY, error)) 
		return false;

	m_FullPath = CopyString(filepath);
	m_Filter = CopyString(file.Filter());
	m_Object = CopyString(file.Object()->Name());
	m_JulDat = file.JulianDate();
	m_ExpTime = file.ExposureDuration();
	m_CCDTemp = file.CCDTemperature();
	m_AvgFrames = file.AvgFrames();
	m_SumFrames = file.SumFrames();
	m_Format = file.FileFormat();
	return true;
}

//
// Clear data
//
void CFrameInfo::Clear(void)
{
	g_free(m_FullPath);
	m_FullPath = NULL;
	g_free(m_Object);
	m_Object = NULL;
	g_free(m_Filter);
	m_Filter = NULL;
	m_JulDat = 0.0;
	m_ExpTime = -1.0;
	m_CCDTemp = -999.99;
	m_AvgFrames = m_SumFrames = 0;
	m_Format = CMPACK_FORMAT_UNKNOWN;
}

//
// Assignment operator
//
CFrameInfo &CFrameInfo::operator=(const CFrameInfo &orig)
{
	if (&orig!=this) {
		g_free(m_FullPath);
		m_FullPath = CopyString(orig.m_FullPath);
		g_free(m_Object);
		m_Object = CopyString(orig.m_Object);
		g_free(m_Filter);
		m_Filter = CopyString(orig.m_Filter);
		m_JulDat = orig.m_JulDat;
		m_ExpTime = orig.m_ExpTime;
		m_CCDTemp = orig.m_CCDTemp;
		m_AvgFrames = orig.m_AvgFrames;
		m_SumFrames = orig.m_SumFrames;
		m_Format = orig.m_Format;
	}
	return *this;
}

//------------------------   SELECTION LIST   -----------------------------

CSelectionList::CSelectionList(const CSelectionList &other):m_List(NULL)
{
	tData *last = NULL;
	for (const tData *ptr = other.m_List; ptr!=NULL; ptr=ptr->next) {
		tData *newData = (tData*)g_malloc0(sizeof(tData));
		if (ptr->name)
			newData->name = g_strdup(ptr->name);
		if (ptr->data)
			newData->data = new CSelection(*ptr->data);
		if (!last)
			m_List = newData;
		else
			last->next = newData;
		last = newData;
	}
}

CSelectionList::~CSelectionList(void)
{
	Clear();
}

void CSelectionList::Clear(void)
{
	tData *ptr = m_List;
	while (ptr!=NULL) {
		tData *next = ptr->next;
		g_free(ptr->name);
		delete ptr->data;
		g_free(ptr);
		ptr = next;
	}
	m_List = NULL;
}

// Add a new selection (takes ownership of the instance)
void CSelectionList::Set(const gchar *name, const CSelection &selection)
{
	tData *data = findData(name);
	if (data) {
		delete data->data;
		data->data = new CSelection(selection);
		g_free(data->name);
		data->name = (name ? g_strdup(name) : NULL);
	} else {
		tData *item = (tData*)g_malloc0(sizeof(tData));
		item->data = new CSelection(selection);
		item->name = (name ? g_strdup(name) : NULL);
		tData *insertPos = findInsertPos(name);
		if (insertPos) {
			// Insert before given item
			item->next = insertPos->next;
			insertPos->next = item;
		} else {
			// Insert before all other items
			item->next = m_List;
			m_List = item;
		}
	}
}

// Remove a selection from the list (caller must delete it)
void CSelectionList::RemoveAt(int index)
{
	int i = 0;
	tData *prevItem = NULL;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (i == index) {
			if (!prevItem) {
				// Remove the first item
				m_List = ptr->next;
			} else {
				// Remove item after prev
				prevItem->next = ptr->next;
			}
			g_free(ptr->name);
			delete ptr->data;
			g_free(ptr);
			break;
		}
		prevItem = ptr;
		i++;
	}
}

// Get number of records
int CSelectionList::Count(void) const
{
	int count = 0;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) 
		count++;
	return count;
}

const gchar *CSelectionList::Name(int index) const
{
	int i = 0;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (i == index) 
			return ptr->name;
		i++;
	}
	return NULL;
}

int CSelectionList::IndexOf(const gchar *name) const
{
	int i = 0;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (StrCmp0(ptr->name, name) == 0) 
			return i;
		i++;
	}
	return -1;
}

int CSelectionList::IndexOf(const CSelection &sel) const
{
	int i = 0;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (ptr->data && *ptr->data == sel) 
			return i;
		i++;
	}
	return -1;
}

CSelection CSelectionList::At(int index) const 
{ 
	int i = 0;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (i == index) 
			return (ptr->data ? *ptr->data : CSelection());
		i++;
	}
	return CSelection();
}

CSelectionList::tData *CSelectionList::findInsertPos(const gchar *name) const 
{ 
	tData *prevItem = NULL;
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (StrCmp0(ptr->name, name) > 0) 
			break;
		prevItem = ptr;
	}
	return prevItem;
}

CSelectionList::tData *CSelectionList::findData(const gchar *name) const
{
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (StrCmp0(ptr->name, name) == 0) 
			return ptr;
	}
	return NULL;
}

// Find selection by name
CSelection CSelectionList::Value(const gchar *name) const
{
	for (tData *ptr=m_List; ptr!=NULL; ptr=ptr->next) {
		if (StrCmp0(ptr->name, name) == 0)
			return (ptr->data ? *ptr->data : CSelection());
	}
	return CSelection();
}

// Assignment operator (makes deep copy)
CSelectionList &CSelectionList::operator=(const CSelectionList &other)
{
	if (this != &other) {
		Clear();

		tData *last = NULL;
		for (const tData *ptr = other.m_List; ptr!=NULL; ptr=ptr->next) {
			tData *newData = (tData*)g_malloc0(sizeof(tData));
			if (ptr->name)
				newData->name = g_strdup(ptr->name);
			if (ptr->data)
				newData->data = new CSelection(*ptr->data);
			if (!last)
				m_List = newData;
			else
				last->next = newData;
			last = newData;
		}
	}
	return *this;
}

//---------------------------------   WCS DATA   ----------------------------

//
// Copy constructor (deep copy)
//
CWcs::CWcs(const CWcs &orig):m_Handle(NULL), m_CacheFlags(0), m_Name(NULL)
{
	if (orig.m_Handle)
		m_Handle = cmpack_wcs_copy(orig.m_Handle);
}

//
// Destructor
//
CWcs::~CWcs()
{
	if (m_Handle)
		cmpack_wcs_destroy(m_Handle);
	g_free(m_Name);
}


//
// Assignment operator (deep copy)
//
CWcs &CWcs::operator=(const CWcs &orig)
{
	if (this != &orig) {
		if (m_Handle) {
			cmpack_wcs_destroy(m_Handle);
			m_Handle = NULL;
		}
		g_free(m_Name);
		m_Name = NULL;
		if (orig.m_Handle) 
			m_Handle = cmpack_wcs_copy(orig.m_Handle);
		InvalidateCache();
	}
	return *this;
}

//
// Update axis parameters
//
void CWcs::UpdateAxes(void)
{
	if (!(m_CacheFlags & CF_AXIS)) {
		memset(m_Axis, 0, 2*sizeof(CmpackWcsAxisParams));
		if (m_Handle) {
			for (int i=0; i<2; i++) 
				cmpack_wcs_get_axis_params(m_Handle, i, CMPACK_AP_NAME | CMPACK_AP_STYP | CMPACK_AP_UNIT, m_Axis+i);
		}
		m_CacheFlags |= CF_AXIS;
	}
}

//
// Get name of the axis
//
const gchar *CWcs::AxisName(tAxis axis)
{
	UpdateAxes();

	if (m_Axis[axis].name && m_Axis[axis].name[0])
		return m_Axis[axis].name;
	else if (m_Axis[axis].styp && m_Axis[axis].styp[0])
		return m_Axis[axis].styp;
	else 
		return "???";
}

//
// 2D pixel-to-world transformation
//
bool CWcs::pixelToWorld(double x, double y, double &r, double &d)
{
	if (m_Handle)
		return cmpack_wcs_p2w(m_Handle, x, y, &r, &d)==0;
	return false;
}

//
// Print data
//
void CWcs::Print(char **buf, int *len) const
{
	char *cbuf;
	int clen;

	if (m_Handle && cmpack_wcs_print(m_Handle, &cbuf, &clen)==0 && cbuf && clen>0) {
		*buf = g_strdup(cbuf);
		*len = clen;
	} else {
		*buf = NULL;
		*len = 0;
	}
}

//
// Refresh cached data
//
void CWcs::InvalidateCache()
{
	m_CacheFlags = 0;
}

//
// Get data set name (optional, NULL is valid value)
//
const char *CWcs::Name(void)
{
	if (!(m_CacheFlags & CF_NAME)) {
		if (m_Handle) {
			g_free(m_Name);
			const char *name = cmpack_wcs_get_name(m_Handle);
			if (name)
				m_Name = FromLocale(name);
			else
				m_Name = NULL;
		}
		m_CacheFlags |= CF_NAME;
	}
	return m_Name;
}

//
// Print 2D coordinates
//
void CWcs::print(double lng, double lat, char *buf, int buflen)
{
	UpdateAxes();

	const char *rname = m_Axis[LNG].name;
	if (!rname || !rname[0])
		rname = m_Axis[LNG].styp;
	if (!rname || !rname[0])
		rname = "???";
	const char *dname = m_Axis[LAT].name;
	if (!dname || !dname[0])
		dname = m_Axis[LAT].styp;
	if (!dname || !dname[0])
		dname = "???";
	char rval[256], dval[256];
	rval[0] = dval[0] = '\0';
	if (StrCmp0(m_Axis[LNG].styp, "RA")==0 && StrCmp0(m_Axis[LNG].unit, "deg")==0)
		lng /= 15.0;
	if (StrCmp0(m_Axis[LNG].styp, "RA")==0) {
		if (cmpack_ratostr(lng, rval, 256)!=0)
			strcpy(rval, "???");
	} else {
		sprintf(rval, "%.5f %s", lng, m_Axis[LNG].unit);
	}
	if (StrCmp0(m_Axis[LAT].styp, "DEC")==0) {
		if (cmpack_dectostr(lat, dval, 256)!=0)
			strcpy(dval, "???");
	} else {
		sprintf(dval, "%.5f %s", lat, m_Axis[LAT].unit);
	}
	sprintf(buf, "%s = %s, %s = %s", rname, rval, dname, dval);
}

// Print longitude / latitude only in following form: RA = lng or DEC = lat
void CWcs::printLongitude(double lng, char *buf, int buflen, bool includeUnit)
{
	UpdateAxes();

	if (includeUnit) {
		const char *rname = m_Axis[LNG].name;
		if (!rname || !rname[0])
			rname = m_Axis[LNG].styp;
		if (!rname || !rname[0])
			rname = "???";
		if (StrCmp0(m_Axis[LNG].styp, "RA")==0 && StrCmp0(m_Axis[LNG].unit, "deg")==0)
			lng /= 15.0;
		if (StrCmp0(m_Axis[LNG].styp, "RA")==0) {
			char rval[256];
			rval[0] = '\0';
			if (cmpack_ratostr(lng, rval, 256)!=0)
				strcpy(rval, "???");
			sprintf(buf, "%s = %s", rname, rval);
		} else
			sprintf(buf, "%s = %.5f %s", rname, lng, m_Axis[LNG].unit);
	} else {
		if (StrCmp0(m_Axis[LNG].styp, "RA")==0 && StrCmp0(m_Axis[LNG].unit, "deg")==0)
			lng /= 15.0;
		if (StrCmp0(m_Axis[LNG].styp, "RA")==0) {
			char rval[256];
			rval[0] = '\0';
			if (cmpack_ratostr(lng, rval, 256)!=0)
				strcpy(rval, "???");
			strcpy(buf, rval);
		} else
			sprintf(buf, "%.5f %s", lng, m_Axis[LNG].unit);
	}
}

void CWcs::printLatitude(double lat, char *buf, int buflen, bool includeUnit)
{
	UpdateAxes();

	if (includeUnit) {
		const char *dname = m_Axis[LAT].name;
		if (!dname || !dname[0])
			dname = m_Axis[LAT].styp;
		if (!dname || !dname[0])
			dname = "???";
		if (StrCmp0(m_Axis[LAT].styp, "DEC")==0) {
			char dval[256];
			dval[0] = '\0';
			if (cmpack_dectostr(lat, dval, 256)!=0)
				strcpy(dval, "???");
			sprintf(buf, "%s = %s", dname, dval);
		} else 
			sprintf(buf, "%s = %.5f %s", dname, lat, m_Axis[LAT].unit);
	} else {
		if (StrCmp0(m_Axis[LAT].styp, "DEC")==0) {
			char dval[256];
			dval[0] = '\0';
			if (cmpack_dectostr(lat, dval, 256)!=0)
				strcpy(dval, "???");
			strcpy(buf, dval);
		} else 
			sprintf(buf, "%.5f %s", lat, m_Axis[LAT].unit);
	}
}

//---------------------------------   TRACKING DATA   ----------------------------

//
// Constructor
//
CTrackingData::CTrackingData():m_valid(false), m_jd0(0), m_x0(0), m_y0(0), m_t0(0), m_t1(0)
{
	for (int i=0; i<6; i++)
		m_coeff[i] = 0;
}

//
// Constructor
//
CTrackingData::CTrackingData(double jd0, double x0, double y0, double t0, double t1, const double *coeff):m_valid(true), 
	m_jd0(jd0), m_x0(x0), m_y0(y0), m_t0(t0), m_t1(t1)
{
	memcpy(m_coeff, coeff, 6*sizeof(double));
}

//
// Clear data
//
void CTrackingData::clear()
{
	m_valid = false;
	m_jd0 = m_x0 = m_y0 = m_t0 = m_t1 = 0;
	for (int i=0; i<6; i++)
		m_coeff[i] = 0;
}

//
// Fit key frames and create tracking data
//
CTrackingData::CTrackingData(int count, const double *t, const double *x, const double *y):m_valid(true)
{ 
	assert(count > 0);

	double *dt = (double*)g_malloc(count*sizeof(double)), *dx = (double*)g_malloc(count*sizeof(double)), *dy = (double*)g_malloc(count*sizeof(double));
	double min_jd = t[0], max_jd = min_jd, min_x = x[0], max_x = min_x, min_y = y[0], max_y = min_y;
	for (int i = 0; i < count; i++) {
		dt[i] = t[i];				
		if (t[i] < min_jd)
			min_jd = t[i];
		if (t[i] > max_jd)
			max_jd = t[i];
		dx[i] = x[i];			
		if (x[i] < min_x)
			min_x = x[i];
		if (x[i] > max_x)
			max_x = x[i];
		dy[i] = y[i];			
		if (y[i] < min_y)
			min_y = y[i];
		if (y[i] > max_y)
			max_y = y[i];
	}
	m_jd0 = (min_jd + max_jd) / 2;
	m_x0 = (min_x + max_x) / 2;
	m_y0 = (min_y + max_y) / 2;
	for (int i = 0; i < count; i++) {
		dt[i] -= m_jd0; 
		dx[i] -= m_x0;
		dy[i] -= m_y0;
	}
	cmpack_quadratic_fit(count, dt, dx, m_coeff);
	cmpack_quadratic_fit(count, dt, dy, m_coeff+3);
	m_t0 = min_jd - m_jd0;
	m_t1 = max_jd - m_jd0;

	g_free(dx);
	g_free(dy);
	g_free(dt);
}

//
// Get tracked object position for given JD
//
void CTrackingData::track(double jd, double &x, double &y)
{
	if (m_valid) {
		x = m_x0 + m_coeff[0] + m_coeff[1] * (jd - m_jd0) + m_coeff[2] * (jd - m_jd0) * (jd - m_jd0);
		y = m_y0 + m_coeff[3] + m_coeff[4] * (jd - m_jd0) + m_coeff[5] * (jd - m_jd0) * (jd - m_jd0);
	} else
		x = y = 0;
}

// Transform the coordinate system
CTrackingData CTrackingData::transformed(const CmpackMatrix &matrix) const
{
	CmpackMatrix q;
	double coeff[6];

	if (m_valid) {
		cmpack_matrix_init(&q, m_coeff[2], m_coeff[5], m_coeff[1], m_coeff[4], m_x0 + m_coeff[0], m_y0 + m_coeff[3]);
		cmpack_matrix_left_multiply(&q, &matrix);
		coeff[0] = q.x0 - m_x0;	coeff[1] = q.xy; coeff[2] = q.xx;	
		coeff[3] = q.y0 - m_y0;	coeff[4] = q.yy; coeff[5] = q.yx;
		return CTrackingData(m_jd0, m_x0, m_y0, m_t0, m_t1, coeff);
	}
	return CTrackingData();
}


// Load data from a file
void CTrackingData::load(GKeyFile *file)
{
	int n = 0;

	clear();

	gchar *str = g_key_file_get_string(file, "TRACKING", "jd0", NULL);
	if (str) 
		m_jd0 = atof(str);
	g_free(str);

	str = g_key_file_get_string(file, "TRACKING", "x0", NULL);
	if (str) 
		m_x0 = atof(str);
	g_free(str);

	str = g_key_file_get_string(file, "TRACKING", "y0", NULL);
	if (str) 
		m_y0 = atof(str);
	g_free(str);

	str = g_key_file_get_string(file, "TRACKING", "t0", NULL);
	if (str) 
		m_t0 = atof(str);
	g_free(str);

	str = g_key_file_get_string(file, "TRACKING", "t1", NULL);
	if (str) 
		m_t1 = atof(str);
	g_free(str);

	str = g_key_file_get_string(file, "TRACKING", "coeff", NULL);
	if (str) 
		n = sscanf(str, " %lf; %lf; %lf; %lf; %lf; %lf ", m_coeff, m_coeff+1, m_coeff+2, m_coeff+3, m_coeff+4, m_coeff+5);
	g_free(str);

	m_valid = (n==6);
}

// Save data to a file
void CTrackingData::save(GKeyFile *file) const
{
	char val[6*128];

	sprintf(val, "%.8f", m_jd0);
	g_key_file_set_string(file, "TRACKING", "jd0", val);
	sprintf(val, "%.8f", m_x0);
	g_key_file_set_string(file, "TRACKING", "x0", val);
	sprintf(val, "%.8f", m_y0);
	g_key_file_set_string(file, "TRACKING", "y0", val);
	sprintf(val, "%.8f", m_t0);
	g_key_file_set_string(file, "TRACKING", "t0", val);
	sprintf(val, "%.8f", m_t1);
	g_key_file_set_string(file, "TRACKING", "t1", val);
	if (m_valid) {
		sprintf(val, "%.12g;%.12g;%.12g;%.12g;%.12g;%.12g", m_coeff[0], m_coeff[1], m_coeff[2], m_coeff[3], m_coeff[4], m_coeff[5]);
		g_key_file_set_string(file, "TRACKING", "coeff", val);
	} else 
		g_key_file_set_string(file, "TRACKING", "coeff", "");
}
