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

catfile_class.cpp (C-Munipack project)
Catalog file class interface
Copyright (C) 2011 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 <assert.h>

#include "catfile_class.h"
#include "main.h"

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

class tCatalogPrintData
{
public:
	tCatalogPrintData() {}
	virtual ~tCatalogPrintData() {}
};

static void DestroyPrintData(tCatalogPrintData *data)
{
	delete data;
}

class tCatalogPrintIntData:public tCatalogPrintData
{
public:
	CCatalog::tColumnId m_col;
	int m_min, m_max;
	tCatalogPrintIntData(CCatalog::tColumnId col, int min, int max):m_col(col), 
		m_min(min), m_max(max) {}
};

class tCatalogPrintDblData:public tCatalogPrintData
{
public:
	CCatalog::tColumnId m_col;
	double	m_min, m_max;
	int		m_prec;
	tCatalogPrintDblData(CCatalog::tColumnId col, double min, double max, int prec):m_col(col), 
		m_min(min), m_max(max), m_prec(prec) {}
};

class tCatalogPrintStrData:public tCatalogPrintData
{
public:
	CCatalog::tColumnId m_col;
	tCatalogPrintStrData(CCatalog::tColumnId col):m_col(col) {}
};

class tCatalogPrintWcsData:public tCatalogPrintData
{
public:
	CWcs *m_wcs;
	tCatalogPrintWcsData(CWcs *wcs):m_wcs(wcs) {}
};

static void SetCellRenderer(GtkCellRenderer *cell, const gchar *text, CmpackColor fg_color)
{
	switch (fg_color) 
	{
	case CMPACK_COLOR_RED:
		g_object_set(cell, "text", text, "foreground", "red", NULL);
		break;
	case CMPACK_COLOR_BLUE:
		g_object_set(cell, "text", text, "foreground", "blue", NULL);
		break;
	case CMPACK_COLOR_GREEN:
		g_object_set(cell, "text", text, "foreground", "green", NULL);
		break;
	case CMPACK_COLOR_YELLOW:
		g_object_set(cell, "text", text, "foreground", "yellow", NULL);
		break;
	case CMPACK_COLOR_GRAY:
		g_object_set(cell, "text", text, "foreground", "gray", NULL);
		break;
	default:
		g_object_set(cell, "text", text, "foreground", NULL, NULL);
	}
}

static void PrintStr(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
	GtkTreeModel *tree_model, GtkTreeIter *iter, tCatalogPrintStrData *data)
{
	gchar *text;
	int fg_color;

	gtk_tree_model_get(tree_model, iter, data->m_col, &text, CCatalog::COL_FG_COLOR, &fg_color, -1);
	SetCellRenderer(cell, text, (CmpackColor)fg_color);
	g_free(text);
}

static void PrintInt(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
	GtkTreeModel *tree_model, GtkTreeIter *iter, tCatalogPrintIntData *data)
{
	int value, fg_color;

	gtk_tree_model_get(tree_model, iter, data->m_col, &value, CCatalog::COL_FG_COLOR, &fg_color, -1);
	if (value>=data->m_min && value<=data->m_max) {
		gchar buf[256];
		sprintf(buf, "%d", value);
		SetCellRenderer(cell, buf, (CmpackColor)fg_color);
	} else 
		SetCellRenderer(cell, "--", (CmpackColor)fg_color);
}

static void PrintDouble(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
	GtkTreeModel *tree_model, GtkTreeIter *iter, tCatalogPrintDblData *data)
{
	int fg_color;
	double value;

	gtk_tree_model_get(tree_model, iter, data->m_col, &value, CCatalog::COL_FG_COLOR, &fg_color, -1);
	if (value>=data->m_min && value<=data->m_max) {
		gchar buf[512];
		sprintf(buf, "%.*f", data->m_prec, value);
		SetCellRenderer(cell, buf, (CmpackColor)fg_color);
	} else 
		SetCellRenderer(cell, "------", (CmpackColor)fg_color);
}

static void PrintBrightness(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
	GtkTreeModel *tree_model, GtkTreeIter *iter, tCatalogPrintData *data)
{
	int mag_valid, fg_color;
	double mag;

	gtk_tree_model_get(tree_model, iter, CCatalog::COL_MAG_VALID, &mag_valid, CCatalog::COL_MAG, &mag, 
		CCatalog::COL_FG_COLOR, &fg_color, -1);
	if (mag_valid) {
		gchar buf[512];
		sprintf(buf, "%.4f", mag);
		SetCellRenderer(cell, buf, (CmpackColor)fg_color);
	} else 
		SetCellRenderer(cell, "------", (CmpackColor)fg_color);
}

static gint CompareBrightness(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, 
	tCatalogPrintData *data)
{
	int mag_valid_a, mag_valid_b;
	double mag_a, mag_b;

	gtk_tree_model_get(tree_model, a, CCatalog::COL_MAG_VALID, &mag_valid_a, CCatalog::COL_MAG, &mag_a, -1);
	gtk_tree_model_get(tree_model, b, CCatalog::COL_MAG_VALID, &mag_valid_b, CCatalog::COL_MAG, &mag_b, -1);
	if (!mag_valid_a && !mag_valid_b)
		return 0;
	else if (!mag_valid_a)
		return 1;
	else if (!mag_valid_b)
		return -1;
	else if (mag_a < mag_b)
		return -1;
	else if (mag_a == mag_b)
		return 0;
	else
		return 1;
}

static void AddViewCol(GtkTreeView *view, GtkTreeCellDataFunc print_fn, tCatalogPrintData *print_data, 
	const gchar *name, double xalign, int sort_column_id)
{
	GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
	g_object_set(renderer, "xalign", xalign, NULL);
	int index = gtk_tree_view_insert_column_with_data_func(view, -1, name, renderer, print_fn, print_data, (GDestroyNotify)DestroyPrintData)-1;
	GtkTreeViewColumn *col = gtk_tree_view_get_column(view, index);
	g_object_set_data(G_OBJECT(col), "sort-column-id", GINT_TO_POINTER(sort_column_id));
}

static void AddViewCol_Text(GtkTreeView *view, CCatalog::tColumnId column, const gchar *name, 
	int sort_column_id)
{
	AddViewCol(view, (GtkTreeCellDataFunc)PrintStr, new tCatalogPrintStrData(column), name, 
		0.0, sort_column_id);
}

static void AddViewCol_Int(GtkTreeView *view, CCatalog::tColumnId column, int min, int max, 
	const gchar *name, int sort_column_id)
{
	AddViewCol(view, (GtkTreeCellDataFunc)PrintInt, new tCatalogPrintIntData(column, min, max), name, 
		1.0, sort_column_id);
}

static void AddViewCol_Dbl(GtkTreeView *view, CCatalog::tColumnId column, double min, double max, int prec, 
	const gchar *name, int sort_column_id)
{
	AddViewCol(view, (GtkTreeCellDataFunc)PrintDouble, new tCatalogPrintDblData(column, min, max, prec), name, 
		1.0, sort_column_id);
}

static void AddSortCol(GtkTreeSortable *sortable, int sort_column_id, GtkTreeIterCompareFunc compare_fn, tCatalogPrintData *data = NULL)
{
	gtk_tree_sortable_set_sort_func(sortable, sort_column_id, compare_fn, data, (GDestroyNotify)DestroyPrintData);
}

static void PrintLongitude(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
	GtkTreeModel *tree_model, GtkTreeIter *iter, tCatalogPrintWcsData *data)
{
	int wcs_valid;
	double lng;
	char buf[512];

	gtk_tree_model_get(tree_model, iter, CCatalog::COL_WCS_VALID, &wcs_valid, CCatalog::COL_WCS_LNG, &lng, -1);
	if (wcs_valid) {
		data->m_wcs->printLongitude(lng, buf, 256, false);
		g_object_set(cell, "text", buf, NULL);
	} else 
		g_object_set(cell, "text", "------", NULL);
}

static void PrintLatitude(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
	GtkTreeModel *tree_model, GtkTreeIter *iter, tCatalogPrintWcsData *data)
{
	int wcs_valid;
	double lat;
	char buf[512];

	gtk_tree_model_get(tree_model, iter, CCatalog::COL_WCS_VALID, &wcs_valid, CCatalog::COL_WCS_LAT, &lat, -1);
	if (wcs_valid) {
		data->m_wcs->printLatitude(lat, buf, 256, false);
		g_object_set(cell, "text", buf, NULL);
	} else 
		g_object_set(cell, "text", "------", NULL);
}

static gint CompareLongitude(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, 
	gpointer *data)
{
	int wcs_valid_a, wcs_valid_b;
	double lng_a, lng_b;

	gtk_tree_model_get(tree_model, a, CCatalog::COL_WCS_VALID, &wcs_valid_a, CCatalog::COL_WCS_LNG, &lng_a, -1);
	gtk_tree_model_get(tree_model, b, CCatalog::COL_WCS_VALID, &wcs_valid_b, CCatalog::COL_WCS_LNG, &lng_b, -1);
	if (!wcs_valid_a && !wcs_valid_b)
		return 0;
	else if (!wcs_valid_a)
		return 1;
	else if (!wcs_valid_b)
		return -1;
	else if (lng_a < lng_b)
		return -1;
	else if (lng_a == lng_b)
		return 0;
	else
		return 1;
}

static gint CompareLatitude(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, 
	gpointer *data)
{
	int wcs_valid_a, wcs_valid_b;
	double lat_a, lat_b;

	gtk_tree_model_get(tree_model, a, CCatalog::COL_WCS_VALID, &wcs_valid_a, CCatalog::COL_WCS_LAT, &lat_a, -1);
	gtk_tree_model_get(tree_model, b, CCatalog::COL_WCS_VALID, &wcs_valid_b, CCatalog::COL_WCS_LAT, &lat_b, -1);
	if (!wcs_valid_a && !wcs_valid_b)
		return 0;
	else if (!wcs_valid_a)
		return 1;
	else if (!wcs_valid_b)
		return -1;
	else if (lat_a < lat_b)
		return -1;
	else if (lat_a == lat_b)
		return 0;
	else
		return 1;
}

//-------------------   Catalogue file class interface  -------------------------------------


//
// Constructor with intialization (steals the reference)
//
CCatalog::CCatalog(CmpackCatFile *handle):m_Handle(NULL), m_CacheFlags(0), m_RefMag(0), 
	m_CurrentSelection(-1), m_Wcs(NULL)
{
	if (handle)
		m_Handle = cmpack_cat_reference(handle);
}


//
// Destructor
//
CCatalog::~CCatalog()
{
	if (m_Handle)
		cmpack_cat_destroy(m_Handle);
	delete m_Wcs;
}

//
// Create memory only file
//
void CCatalog::Create(void)
{
	Clear();

	m_Handle = cmpack_cat_new();
	InvalidateCache();
}

//
// Copy data from a file
//
bool CCatalog::Load(const gchar *fpath, GError **error)
{
	g_assert(fpath != NULL);

	Clear();

	char *f = g_locale_from_utf8(fpath, -1, NULL, NULL, NULL);
	CmpackCatFile *handle;
	int res = cmpack_cat_open(&handle, f, CMPACK_OPEN_READONLY, 0);
	g_free(f);
	if (res!=0) {
		set_error(error, "Error when reading the file", fpath, res);
		return false;
	}

	m_Handle = cmpack_cat_new();
	cmpack_cat_copy(m_Handle, handle);
	cmpack_cat_close(handle);
	InvalidateCache();
	return true;
}

//
// Save catalog file 
//
bool CCatalog::SaveAs(const gchar *fpath, GError **error) const
{
	CmpackCatFile *file;

	g_assert (m_Handle!=NULL);

	char *f = g_locale_from_utf8(fpath, -1, NULL, NULL, NULL);
	int res = cmpack_cat_open(&file, f, CMPACK_OPEN_CREATE, 0);
	g_free(f);
	if (res==0)
		res = cmpack_cat_copy(file, m_Handle);
	if (res==0) 
		res = cmpack_cat_close(file);
	if (res!=0) {
		set_error(error, "Error when creating the file", fpath, res);
		cmpack_cat_destroy(file);
	}
	return res==0;
}

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

//
// Get selection
//
const CSelectionList *CCatalog::Selections(void)
{
	if (!(m_CacheFlags & CF_SELECTIONS)) {
		m_Selections.Clear();
		if (m_Handle) {
			int count = cmpack_cat_selection_set_count(m_Handle);
			for (int i=0; i<count; i++) {
				cmpack_cat_set_current_selection_set(m_Handle, i);
				CSelection sel;
				LoadSelection(sel, CMPACK_SELECT_VAR);
				LoadSelection(sel, CMPACK_SELECT_COMP);
				LoadSelection(sel, CMPACK_SELECT_CHECK);
				m_Selections.Set(cmpack_cat_get_selection_set_name(m_Handle), sel);
			}
		}
		m_CacheFlags |= CF_SELECTIONS;
	}
	return &m_Selections;
}

//
// Get selection
//
const CTags *CCatalog::Tags(void)
{
	int id;
	const char *tag;

	if ((m_CacheFlags & CF_TAGS) == 0) {
		m_Tags.clear();
		if (m_Handle) {
			int count = cmpack_cat_get_tag_count(m_Handle);
			for (int i = 0; i < count; i++) {
				cmpack_cat_get_tag(m_Handle, i, &id, &tag);
				m_Tags.insert(id, tag);
			}
		}
		m_CacheFlags |= CF_TAGS;
	}
	return &m_Tags;
}

//
// Get object coordinates
//
const CObjectCoords *CCatalog::Object(void)
{
	if (!(m_CacheFlags & CF_OBJECT)) {
		m_Object.Clear();
		if (m_Handle) {
			const gchar *name = cmpack_cat_gkys(m_Handle, "object");
			if (name) {
				gchar *aux = g_strdup(name);
				m_Object.SetName(g_strstrip(aux));
				g_free(aux);
			}
			const gchar *ra = cmpack_cat_gkys(m_Handle, "ra2000");
			if (ra) {
				gchar *aux = g_strdup(ra);
				m_Object.SetRA(g_strstrip(aux));
				g_free(aux);
			}
			const gchar *dec = cmpack_cat_gkys(m_Handle, "dec2000");
			if (dec) {
				gchar *aux = g_strdup(dec);
				m_Object.SetDec(g_strstrip(aux));
				g_free(aux);
			}
		}
		m_CacheFlags |= CF_OBJECT;
	}
	return &m_Object;
}

//
// Get location
//
const CLocation *CCatalog::Location(void)
{
	if (!(m_CacheFlags & CF_LOCATION)) {
		m_Location.Clear();
		if (m_Handle) {
			const gchar *name = cmpack_cat_gkys(m_Handle, "observatory");
			if (name) {
				gchar *aux = g_strdup(name);
				m_Location.SetName(g_strstrip(aux));
				g_free(aux);
			}
			const gchar *lon = cmpack_cat_gkys(m_Handle, "longitude");
			if (lon) {
				gchar *aux = g_strdup(lon);
				m_Location.SetLon(g_strstrip(aux));
				g_free(aux);
			}
			const gchar *lat = cmpack_cat_gkys(m_Handle, "latitude");
			if (lat) {
				gchar *aux = g_strdup(lat);
				m_Location.SetLat(g_strstrip(aux));
				g_free(aux);
			}
		}
		m_CacheFlags |= CF_LOCATION;
	}
	return &m_Location;
}

//
// Clear data
//
void CCatalog::Clear()
{
	if (m_Handle) {
		cmpack_cat_destroy(m_Handle);
		m_Handle = NULL;
	}
	m_Selections.Clear();
	m_Object.Clear();
	m_Location.Clear();
	m_Tags.clear();
	delete m_Wcs;
	m_Wcs = NULL;
	m_RefMag = 0;
	m_CurrentSelection = -1;
	InvalidateCache();
}

//
// Date of observation
//
double CCatalog::JulianDate(void) const
{
	double jd;
	if (m_Handle && cmpack_cat_gkyd(m_Handle, "jd", &jd)==0)
		return jd;
	return 0.0;
}

//
// Exposure duration
//
double CCatalog::ExposureDuration(void) const
{
	double exptime;
	if (m_Handle && cmpack_cat_gkyd(m_Handle, "exptime", &exptime)==0)
		return exptime;
	return 0.0;
}

//
// Number of stars
//
int CCatalog::ObjectCount(void) const
{
	if (m_Handle)
		return cmpack_cat_nstar(m_Handle);
	return 0;
}

//
// Find object
//
int CCatalog::FindObject(int id)
{
	if (m_Handle)
		return cmpack_cat_find_star(m_Handle, id);
	return -1;
}


//
// Get star identifier
//
int CCatalog::GetObjectID(int index) const
{
	CmpackCatObject obj;
	if (m_Handle && cmpack_cat_get_star(m_Handle, index, CMPACK_OM_ID, &obj)==0)
		return obj.id;
	return -1;
}

//
// Get star identifier
//
bool CCatalog::GetObjectParams(int star_index, unsigned mask, CmpackCatObject *obj) const
{
	if (m_Handle)
		return cmpack_cat_get_star(m_Handle, star_index, mask, obj)==0;
	return false;
}

//
// Load selection (one type)
//
void CCatalog::LoadSelection(CSelection &selection, CmpackSelectionType type1)
{
	if (m_Handle) {
		int nstars = cmpack_cat_get_selection_count(m_Handle);
		int count = 0;
		for (int i=0; i<nstars; i++) {
			CmpackSelectionType type;
			cmpack_cat_get_selection(m_Handle, i, NULL, &type);
			if (type == type1) 
				count++;
		}
		if (count>0) {
			int *buf = (int*)g_malloc(count*sizeof(int));
			int j = 0;
			for (int i = 0; i<nstars; i++) {
				CmpackSelectionType type;
				int id;
				cmpack_cat_get_selection(m_Handle, i, &id, &type);
				if (type == type1)
					buf[j++] = id;
			}
			selection.SetStarList(type1, buf, j);
			g_free(buf);
		}
	}
}

//
// Save selection
//
void CCatalog::SaveSelection(const CSelection &selection, CmpackSelectionType type) const
{
	int i, count, *buf;

	g_assert (m_Handle!=NULL);

	count = selection.CountStars(type);
	if (count>0) {
		buf = (int*)g_malloc(count*sizeof(int));
		selection.GetStarList(type, buf, count);
		for (i=0; i<count; i++) 
			cmpack_cat_update_selection(m_Handle, buf[i], type);
		g_free(buf);
	}
}

//
// Remove all selections
//
void CCatalog::RemoveAllSelections(void)
{
	if (m_Handle) {
		m_Selections.Clear();
		if (m_Handle)
			cmpack_cat_clear_all_selections(m_Handle);
		m_CacheFlags |= CF_SELECTIONS;
	}
}

//
// Add one selection
//
void CCatalog::AddSelection(const gchar *name, const CSelection &selection)
{
	g_assert (m_Handle!=NULL);

	Selections();
	m_Selections.Set(name, selection);
	if (cmpack_cat_selection_set_new(m_Handle, name, -1)==0) {
		SaveSelection(selection, CMPACK_SELECT_VAR);
		SaveSelection(selection, CMPACK_SELECT_COMP);
		SaveSelection(selection, CMPACK_SELECT_CHECK);
	}
}

//
// Save tags to a file
//
void CCatalog::SetTags(const CTags &tags)
{
	g_assert(m_Handle != NULL);

	m_Tags = tags;
	cmpack_cat_clear_tags(m_Handle);
	for (int i = 0; i < m_Tags.count(); i++) {
		const CTags::tData &tag = m_Tags[i];
		cmpack_cat_update_tag(m_Handle, tag.id, tag.tag);
	}
	m_CacheFlags |= CF_TAGS;
}

//
// Chart width in pixels
//
int CCatalog::Width(void) const
{
	if (m_Handle)
		return cmpack_cat_get_width(m_Handle);
	return 0;
}

//
// Chart height in pixels
//
int CCatalog::Height(void) const
{
	if (m_Handle)
		return cmpack_cat_get_height(m_Handle);
	return 0;
}

//
// Date of observation
//
bool CCatalog::DateTime(CmpackDateTime *dt) const
{
	double jd = JulianDate();
	if (jd>0.0)
		return cmpack_decodejd(jd, dt)==0;
	return false;
}

//
// Filter name
//
const char *CCatalog::Filter(void) const
{
	if (m_Handle)
		return cmpack_cat_gkys(m_Handle, "filter");
	return NULL;
}
void CCatalog::SetFilter(const gchar *filter)
{
	g_assert (m_Handle!=NULL);

	cmpack_cat_pkys(m_Handle, "filter", filter, NULL);
}

//
// Observer's name
//
const gchar *CCatalog::Observer(void) const
{
	if (m_Handle)
		return cmpack_cat_gkys(m_Handle, "observer");
	return NULL;
}
void CCatalog::SetObserver(const gchar *observer)
{
	g_assert (m_Handle!=NULL);

	cmpack_cat_pkys(m_Handle, "observer", observer, NULL);
}

//
// Telescope
//
const gchar *CCatalog::Telescope(void) const
{
	if (m_Handle)
		return cmpack_cat_gkys(m_Handle, "telescope");
	return NULL;
}
void CCatalog::SetTelescope(const gchar *telescope)
{
	g_assert (m_Handle!=NULL);

	cmpack_cat_pkys(m_Handle, "telescope", telescope, NULL);
}

//
// Camera
//
const gchar *CCatalog::Instrument(void) const
{
	if (m_Handle)
		return cmpack_cat_gkys(m_Handle, "camera");
	return NULL;
}
void CCatalog::SetInstrument(const gchar *camera)
{
	g_assert (m_Handle!=NULL);

	cmpack_cat_pkys(m_Handle, "camera", camera, NULL);
}

//
// Field of view
//
const gchar *CCatalog::FieldOfView(void) const
{
	if (m_Handle)
		return cmpack_cat_gkys(m_Handle, "fov");
	return NULL;
}
void CCatalog::SetFieldOfView(const gchar *fov)
{
	g_assert (m_Handle!=NULL);

	cmpack_cat_pkys(m_Handle, "fov", fov, NULL);
}

//
// Orientation
//
const gchar *CCatalog::Orientation(void) const
{
	if (m_Handle)
		return cmpack_cat_gkys(m_Handle, "orientation");
	return NULL;
}
void CCatalog::SetOrientation(const gchar *orientation)
{
	g_assert (m_Handle!=NULL);

	cmpack_cat_pkys(m_Handle, "orientation", orientation, NULL);
}

//
// Notes
//
const gchar *CCatalog::Notes(void) const
{
	if (m_Handle)
		return cmpack_cat_gkys(m_Handle, "notes");
	return NULL;
}
void CCatalog::SetNotes(const gchar *notes)
{
	g_assert (m_Handle!=NULL);

	cmpack_cat_pkys(m_Handle, "notes", notes, NULL);
}

//
// Get header field by index
//
bool CCatalog::GetParam(int index, const char **key, const char **val, const char **com) const
{
	if (m_Handle)
		return cmpack_cat_gkyn(m_Handle, index, key, val, com)!=0;
	return false;
}

//
// Update reference magnitudes
//
double CCatalog::GetRefMag(void)
{
	int i, count;
	CmpackCatObject obj;

	if (!(m_CacheFlags & CF_REF_MAG)) {
		m_RefMag = 0.0;
		if (m_Handle) {
			int nstars = cmpack_cat_nstar(m_Handle);
			if (nstars>0) {
				double *maglist = (double*)g_malloc(nstars*sizeof(double));
				count = 0;
				for (i=0; i<nstars; i++) {
					if (GetObjectParams(i, CMPACK_OM_MAGNITUDE, &obj) && obj.refmag_valid) 
						maglist[count++] = obj.refmagnitude;
				}
				if (count>0) 
					cmpack_robustmean(count, maglist, &m_RefMag, NULL);
				g_free(maglist);
			}
		}
		m_CacheFlags |= CF_REF_MAG;
	}
	return m_RefMag;
}

//
// Set location
//
void CCatalog::SetLocation(const CLocation &loc)
{
	g_assert (m_Handle!=NULL);

	if (m_Location!=loc) {
		m_Location = loc;
		if (m_Location.Name())
			cmpack_cat_pkys(m_Handle, "observatory", m_Location.Name(), NULL);
		else
			cmpack_cat_dkey(m_Handle, "observatory");
		if (m_Location.Lon()) 
			cmpack_cat_pkys(m_Handle, "longitude", m_Location.Lon(), NULL);
		else
			cmpack_cat_dkey(m_Handle, "longitude");
		if (m_Location.Lat()) 
			cmpack_cat_pkys(m_Handle, "latitude", m_Location.Lat(), NULL);
		else
			cmpack_cat_dkey(m_Handle, "latitude");
		m_CacheFlags |= CF_LOCATION;
	}
}

//
// Set object coordinates
//
void CCatalog::SetObject(const CObjectCoords &obj)
{
	g_assert (m_Handle!=NULL);

	if (m_Object!=obj) {
		m_Object = obj;
		if (m_Object.Name())
			cmpack_cat_pkys(m_Handle, "object", m_Object.Name(), NULL);
		else
			cmpack_cat_dkey(m_Handle, "object");
		if (m_Object.RA())
			cmpack_cat_pkys(m_Handle, "ra2000", m_Object.RA(), NULL);
		else
			cmpack_cat_dkey(m_Handle, "ra2000");
		if (m_Object.Dec())
			cmpack_cat_pkys(m_Handle, "dec2000", m_Object.Dec(), NULL);
		else
			cmpack_cat_dkey(m_Handle, "dec2000");
		m_CacheFlags |= CF_OBJECT;
	}
}

//
// Set current selection
//
void CCatalog::SelectSelection(int index)
{
	m_CurrentSelection = index;
}

//
// Make catalogue file from photometry file
//
bool CCatalog::Create(const CPhot &pht, GError **error)
{
	assert (pht.m_Handle != NULL);

	CmpackCatFile *handle = cmpack_cat_new();
	int res = cmpack_cat_make(handle, pht.m_Handle, pht.SelectedAperture());
	if (res!=0) {
		set_error(error, res);
		cmpack_cat_destroy(handle);
		return false;
	}

	Clear();
	m_Handle = handle;
	InvalidateCache();
	return true;
}

//
// Make deep copy of a catalogue file
//
bool CCatalog::MakeCopy(const CCatalog &cat, GError **error)
{
	if (cat.m_Handle) {
		CmpackCatFile *handle = cmpack_cat_new();
		int res = cmpack_cat_copy(handle, cat.m_Handle);
		if (res!=0) {
			set_error(error, res);
			cmpack_cat_destroy(handle);
			return false;
		}
		Clear();
		m_Handle = handle;
		m_CurrentSelection = cat.CurrentSelectionIndex();
	} else 
		Clear();
	
	InvalidateCache();
	return true;
}


//
// Make chart data
//
CmpackChartData *CCatalog::ToChartData(bool show_selection, bool show_tags, bool transparent)
{
	static const unsigned mask = CMPACK_OM_ID | CMPACK_OM_CENTER | CMPACK_OM_MAGNITUDE;

	gint count, width, height;
	CmpackCatObject obj;
	CmpackChartItem item;

	if (!m_Handle)
		return NULL;

	memset(&item, 0, sizeof(CmpackChartItem));
	
	CmpackChartData *data = cmpack_chart_data_new();
	width = cmpack_cat_get_width(m_Handle);
	height = cmpack_cat_get_height(m_Handle);
	cmpack_chart_data_set_dimensions(data, width, height);
	count = cmpack_cat_nstar(m_Handle);
	cmpack_chart_data_alloc(data, count);
	gdouble ref = GetRefMag();
	for (int i=0; i<count; i++) {
		cmpack_cat_get_star(m_Handle, i, mask, &obj);
		item.x = obj.center_x;
		item.y = obj.center_y;
		item.outline = !obj.refmag_valid;
		item.param = obj.id;
		if (!transparent)
			item.d = 2.0 + MAX(0, (obj.refmag_valid ? 1.0 + 0.75*(ref - obj.refmagnitude) : 1.0));
		else
			item.d = 0;
		cmpack_chart_data_add(data, &item, sizeof(item));
	}
	const CTags *tags = NULL;
	if (show_tags)
		tags = Tags();
	CSelection selection;
	if (show_selection && m_CurrentSelection>=0)
		selection = Selections()->At(m_CurrentSelection);
	count = cmpack_chart_data_count(data);
	for (int row=0; row<count; row++) {
		int star_id = (int)cmpack_chart_data_get_param(data, row);
		const gchar *tag = (tags ? tags->caption(star_id) : NULL);
		const gchar *cap = selection.Caption(star_id);
		if (cap) {
			if (tag) {
				gchar *buf = (gchar*)g_malloc((strlen(cap)+strlen(tag)+2)*sizeof(gchar));
				sprintf(buf, "%s\n%s", cap, tag);
				cmpack_chart_data_set_tag(data, row, buf);
				g_free(buf);
			} else {
				cmpack_chart_data_set_tag(data, row, cap);
			}
			cmpack_chart_data_set_color(data, row, selection.Color(star_id));
			cmpack_chart_data_set_topmost(data, row, TRUE);
			if (transparent)
				cmpack_chart_data_set_diameter(data, row, 4.0);
		} else {
			if (tag) {
				cmpack_chart_data_set_tag(data, row, tag);
				cmpack_chart_data_set_color(data, row, CMPACK_COLOR_YELLOW);
				cmpack_chart_data_set_topmost(data, row, TRUE);
				if (transparent)
					cmpack_chart_data_set_diameter(data, row, 4.0);
			}
		}
	}
	return data;
}

//
// Make table model
//
GtkTreeModel *CCatalog::ToTreeModel(bool show_selection, bool show_tags)
{
	static const int mask = CMPACK_OM_ID | CMPACK_OM_MAGNITUDE | CMPACK_OM_CENTER;

	if (!m_Handle)
		return NULL;

	GtkTreeIter iter;

	const CTags *tags = NULL;
	if (show_tags)
		tags = Tags();
	CSelection selection;
	if (show_selection && m_CurrentSelection>=0)
		selection = Selections()->At(m_CurrentSelection);

	GtkListStore *data = gtk_list_store_new(COL_COUNT, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, 
		G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_STRING, 
		G_TYPE_ULONG, G_TYPE_STRING, G_TYPE_INT);
	int count = cmpack_cat_nstar(m_Handle);
	for (int i=0; i<count; i++) {
		CmpackCatObject obj;
		if (cmpack_cat_get_star(m_Handle, i, mask, &obj)==0) {
			gtk_list_store_append(data, &iter);
			const gchar *cap = selection.Caption(obj.id);
			gtk_list_store_set(data, &iter, COL_ID, obj.id, COL_POS_X, obj.center_x, 
				COL_POS_Y, obj.center_y, COL_MAG_VALID, obj.refmag_valid,
				COL_MAG, obj.refmagnitude, COL_SEL_TYPIDX, selection.SortKey(obj.id),
				COL_SEL_CAPTION, cap, COL_FG_COLOR, (int)selection.Color(obj.id), -1);
			double lat, lng;
			if (Wcs() && m_Wcs->pixelToWorld(obj.center_x, obj.center_y, lng, lat)) {
				int wcs_valid = 1;
				gtk_list_store_set(data, &iter, COL_WCS_VALID, wcs_valid, COL_WCS_LNG, lng, COL_WCS_LAT, lat, -1);
			}
			if (tags) 
				gtk_list_store_set(data, &iter, COL_TAGS, tags->caption(obj.id), -1);
		}
	}
	GtkTreeSortable *sortable = GTK_TREE_SORTABLE(data);
	AddSortCol(sortable, COL_MAG, (GtkTreeIterCompareFunc)CompareBrightness);
	AddSortCol(sortable, COL_WCS_LNG, (GtkTreeIterCompareFunc)CompareLongitude, new tCatalogPrintWcsData(Wcs()));
	AddSortCol(sortable, COL_WCS_LAT, (GtkTreeIterCompareFunc)CompareLatitude, new tCatalogPrintWcsData(Wcs()));
	return GTK_TREE_MODEL(data);
}

//
// Set graph view parameters
//
void CCatalog::SetView(GtkTreeView *view)
{
	// Delete old columns
	GList *list = gtk_tree_view_get_columns(view);
	for (GList *ptr=list; ptr!=NULL; ptr=ptr->next) 
		gtk_tree_view_remove_column(view, (GtkTreeViewColumn*)(ptr->data));
	g_list_free(list);

	// Create new columns
	if (m_Handle) {
		AddViewCol_Int(view, COL_ID, 0, INT_MAX, "Obj. #", COL_ID);
		AddViewCol_Dbl(view, COL_POS_X, INT_MIN, INT_MAX, 2, "X", COL_POS_X);
		AddViewCol_Dbl(view, COL_POS_Y, INT_MIN, INT_MAX, 2, "Y", COL_POS_Y);
		if (Wcs()) {
			AddViewCol(view, (GtkTreeCellDataFunc)PrintLongitude, new tCatalogPrintWcsData(Wcs()), m_Wcs->AxisName(CWcs::LNG), 1.0, COL_WCS_LNG);
			AddViewCol(view, (GtkTreeCellDataFunc)PrintLatitude, new tCatalogPrintWcsData(Wcs()), m_Wcs->AxisName(CWcs::LAT), 1.0, COL_WCS_LAT);
		}
		AddViewCol(view, (GtkTreeCellDataFunc)PrintBrightness, NULL, "Brightness [mag]", 1.0, COL_MAG);
		AddViewCol_Text(view, COL_SEL_CAPTION, "Name", COL_SEL_TYPIDX);
		AddViewCol_Text(view, COL_TAGS, "Tag", COL_TAGS);
	}

	// Insert last empty column that will stretch to the rest of the table
	gtk_tree_view_insert_column_with_data_func(view, -1, NULL, gtk_cell_renderer_text_new(), NULL, NULL, NULL);
}

//
// Export list of objects to a file
//
bool CCatalog::ExportTable(const gchar *filepath, const gchar *format, unsigned flags, int sort_column_id,
	GtkSortType sort_order, GError **error)
{
	g_assert(m_Handle != NULL);
	g_assert(format != NULL);
	g_assert(filepath != NULL);

	if (strcmp(format, "text/csv")) {
		set_error(error, "Error when creating the file", filepath, CMPACK_ERR_UNKNOWN_FORMAT);
		return false;
	}

	FILE *f = open_file(filepath, "w");
	if (!f) {
		set_error(error, "Error when creating the file", filepath, CMPACK_ERR_OPEN_ERROR);
		return false;
	}

	CCSVWriter *writer = new CCSVWriter(f);
	if (writer) {
		ExportTable(*writer, flags, sort_column_id, sort_order);
		delete writer;
	}

	fclose(f);
	return true;
}

void CCatalog::ExportTable(CCSVWriter &writer, unsigned flags, int sort_column_id, GtkSortType sort_order)
{
	g_assert(m_Handle != NULL);

	CmpackCatObject obj;
	double lng, lat;

	// Table header
	writer.SetSaveHeader((flags & EXPORT_NO_HEADER) == 0);
	writer.AddColumn("OBJ_ID");
	writer.AddColumn("CENTER_X");
	writer.AddColumn("CENTER_Y");
	if (Wcs()) {
		writer.AddColumn(m_Wcs->AxisName(CWcs::LNG));
		writer.AddColumn(m_Wcs->AxisName(CWcs::LAT));
	}
	writer.AddColumn("MAG");
	writer.AddColumn("SELECTION");
	writer.AddColumn("TAG");

	const CTags *tags = Tags();
	CSelection selection;
	if (m_CurrentSelection>=0)
		selection = Selections()->At(m_CurrentSelection);

	// Make list of indices and sort them using sort_column_id and sort_order
	int count = cmpack_cat_nstar(m_Handle);
	if (count>0) {
		tSortItem *items = (tSortItem*)g_malloc(count*sizeof(tSortItem));
		tSortType type = SORT_TYPE_INT;
		if (items) {
			switch (sort_column_id) 
			{
			case COL_ID:
			default:
				type = SORT_TYPE_INT;
				for (int i=0; i<count; i++) {
					items[i].row = i;
					if (cmpack_cat_get_star(m_Handle, i, CMPACK_OM_ID, &obj)==0) 
						items[i].value.i = obj.id;
					else
						items[i].value.i = -1;
				}
				break;
			case COL_POS_X:
				type = SORT_TYPE_DOUBLE;
				for (int i=0; i<count; i++) {
					items[i].row = i;
					if (cmpack_cat_get_star(m_Handle, i, CMPACK_OM_CENTER, &obj)==0) 
						items[i].value.d = obj.center_x;
					else
						items[i].value.d = DBL_MAX;
				}
				break;
			case COL_POS_Y:
				type = SORT_TYPE_DOUBLE;
				for (int i=0; i<count; i++) {
					items[i].row = i;
					if (cmpack_cat_get_star(m_Handle, i, CMPACK_OM_CENTER, &obj)==0) 
						items[i].value.d = obj.center_y;
					else
						items[i].value.d = DBL_MAX;
				}
				break;
			case COL_WCS_LNG:
				type = SORT_TYPE_DOUBLE;
				for (int i=0; i<count; i++) {
					items[i].row = i;
					if (cmpack_cat_get_star(m_Handle, i, CMPACK_OM_CENTER, &obj)==0 && Wcs()->pixelToWorld(obj.center_x, obj.center_y, lng, lat)) 
						items[i].value.d = lng;
					else
						items[i].value.d = DBL_MAX;
				}
				break;
			case COL_WCS_LAT:
				type = SORT_TYPE_DOUBLE;
				for (int i=0; i<count; i++) {
					items[i].row = i;
					if (cmpack_cat_get_star(m_Handle, i, CMPACK_OM_CENTER, &obj)==0 && Wcs()->pixelToWorld(obj.center_x, obj.center_y, lng, lat)) 
						items[i].value.d = lat;
					else
						items[i].value.d = DBL_MAX;
				}
				break;
			case COL_MAG:
				type = SORT_TYPE_DOUBLE;
				for (int i=0; i<count; i++) {
					items[i].row = i;
					if (cmpack_cat_get_star(m_Handle, i, CMPACK_OM_MAGNITUDE, &obj)==0 && obj.refmag_valid) 
						items[i].value.d = obj.refmagnitude;
					else
						items[i].value.d = DBL_MAX;
				}
				break;
			}
		}

		if (items && count > 1)
			SortItems(items, count, type, sort_order);

		// Go through row indices and export them to the file
		if (items) {
			for (int i = 0; i < count; i++) {
				static const int mask = CMPACK_OM_ID | CMPACK_OM_CENTER | CMPACK_OM_MAGNITUDE;
				if (cmpack_cat_get_star(m_Handle, items[i].row, mask, &obj) == 0) {
					bool show = 1;
					if (flags & EXPORT_SKIP_INVALID)
						show &= (obj.refmag_valid != 0);
					if (show) {
						int col = 0;
						writer.Append();
						writer.SetInt(col++, obj.id);
						writer.SetDbl(col++, obj.center_x, 2);
						writer.SetDbl(col++, obj.center_y, 2);
						if (Wcs()) {
							double lng, lat;
							if (m_Wcs->pixelToWorld(obj.center_x, obj.center_y, lng, lat)) {
								writer.SetDbl(col, lng, 6);
								writer.SetDbl(col + 1, lat, 6);
							}
							col += 2;
						}
						if (obj.refmag_valid)
							writer.SetDbl(col, obj.refmagnitude, 4);
						col++;
						const gchar* cap = selection.Caption(obj.id);
						if (cap)
							writer.SetStr(col, cap);
						col++;
						const gchar* tag = (tags ? tags->caption(obj.id) : NULL);
						if (tag)
							writer.SetStr(col, tag);
					}
				}
			}
			g_free(items);
		}
	}
}

//
// Export list of objects to a file
//
bool CCatalog::ExportHeader(const gchar *filepath, const gchar *format, unsigned flags, GError **error) const
{
	g_assert(m_Handle != NULL);
	g_assert(format != NULL);
	g_assert(filepath != NULL);

	if (strcmp(format, "text/csv")) {
		set_error(error, "Error when creating the file", filepath, CMPACK_ERR_UNKNOWN_FORMAT);
		return false;
	}

	FILE *f = open_file(filepath, "w");
	if (!f) {
		set_error(error, "Error when creating the file", filepath, CMPACK_ERR_OPEN_ERROR);
		return false;
	}

	CCSVWriter *writer = new CCSVWriter(f);
	if (writer) {
		ExportHeader(*writer, flags);
		delete writer;
	}

	fclose(f);
	return true;
}

//
// Export file header
//
void CCatalog::ExportHeader(CCSVWriter &writer, unsigned flags) const
{
	g_assert(m_Handle != NULL);

	// Table header
	writer.SetSaveHeader((flags & EXPORT_NO_HEADER) == 0);
	writer.AddColumn("KEY");
	writer.AddColumn("VALUE");
	writer.AddColumn("COMMENT");

	const char *key, *val, *com;
	for (int i=0; GetParam(i, &key, &val, &com); i++) {
		writer.Append();
		if (key)
			writer.SetStr(0, key);
		if (val)
			writer.SetStr(1, val);
		if (com)
			writer.SetStr(2, com);
	}
}

// Get WCS data
CWcs *CCatalog::Wcs(void)
{
	if (!(m_CacheFlags & CF_WCS)) {
		delete m_Wcs;
		m_Wcs = NULL;
		if (m_Handle) {
			CmpackWcs *wcs = NULL;
			if (cmpack_cat_get_wcs(m_Handle, &wcs)==0) 
				m_Wcs = new CWcs(cmpack_wcs_reference(wcs));
		}
		m_CacheFlags |= CF_WCS;
	}

	return m_Wcs;
}
