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

project.cpp (C-Munipack project)
Table of input frames
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 <assert.h>
#include <glib/gstdio.h>

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

//-------------------------------   PRIVATE DATA TYPES   -----------------------------------

struct tGetFileInfo
{
	int			id;
	GtkTreePath *path;
};

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

static gboolean FindFile(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
	int fileid;
	tGetFileInfo *pData = (tGetFileInfo*)data;

	gtk_tree_model_get(model, iter, FRAME_ID, &fileid, -1);
	if (fileid == pData->id) {
		pData->path = gtk_tree_path_copy(path);
		return TRUE;
	}
	return FALSE;
}

static gboolean MakeStatus(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, CProject::tStatus *st)
{
	unsigned state;

	gtk_tree_model_get(model, iter, FRAME_STATE, &state, -1);
	if (state & CFILE_CONVERSION)
		st->converted++;
	if (state & CFILE_PHOTOMETRY)
		st->photometred++;
	if (state & CFILE_MATCHING)
		st->matched++;
	st->files++;

	return FALSE;
}

// Return true if the given buffer can be a project file
static bool checkProjectFile(GKeyFile *file)
{
	if (g_key_file_has_group(file, "CMUNIPACK-PROJECT")) {
		int revision = g_key_file_get_integer(file, "CMUNIPACK-PROJECT", "Revision", NULL);
		return (revision==1);
	}
	return false;
}

// Check that the file is writable
static bool checkWritable(const gchar *fpath)
{
	FILE *f = open_file(fpath, "a");
	if (f) {
		fclose(f);
		return true;
	}
	return false;
}

// Ask user if we can open project in read-only mode
static bool confirmReadOnly(GtkWindow *parent, const gchar *fpath)
{
	GtkWidget *dialog = gtk_message_dialog_new(parent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
		GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "A project is already opened in another application. Do you want to open it anyway?");
	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "The project file \"%s\" is already opened in another application. "
		"You can answer yes to open the project in the read-only mode. You will be able to make outputs (i.e. a light curve), but you won't be allowed to do any action that would "
		"change the frames or the photometry files.", fpath);
	gtk_window_set_title(GTK_WINDOW (dialog), "Question");
	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
	gtk_dialog_add_button(GTK_DIALOG (dialog), "_No, thanks.", GTK_RESPONSE_CANCEL);
	gtk_dialog_add_button(GTK_DIALOG (dialog), "_Yes, open in read-only mode", GTK_RESPONSE_ACCEPT);
	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, -1);
	gtk_dialog_set_default_response(GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
	int response = gtk_dialog_run(GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
	return (response == GTK_RESPONSE_ACCEPT);
}

static gchar *FromLocale(const char *str)
{
	if (str)
		return g_locale_to_utf8(str, -1, NULL, NULL, NULL);
	return NULL;
}

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

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

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

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

static gint CompareFrameId(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_ID, &id_b, -1);
	if (id_a == id_b)
		return 0;
	else if (id_a < id_b)
		return -1;
	else
		return 1;
}

static gint CompareJulDat(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gdouble jd_a, jd_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_JULDAT, &jd_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_JULDAT, &jd_b, FRAME_ID, &id_b, -1);
	if (jd_a < jd_b) 
		return -1;
	else if (jd_a > jd_b)
		return 1;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareExpTime(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gdouble val_a, val_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_EXPTIME, &val_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_EXPTIME, &val_b, FRAME_ID, &id_b, -1);
	if (val_a < val_b)
		return -1;
	else if (val_a > val_b)
		return 1;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareCcdTemp(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gdouble val_a, val_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_CCDTEMP, &val_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_CCDTEMP, &val_b, FRAME_ID, &id_b, -1);
	if (val_a < val_b)
		return -1;
	else if (val_a > val_b)
		return 1;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareOffsetX(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gdouble val_a, val_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_OFFSET_X, &val_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_OFFSET_X, &val_b, FRAME_ID, &id_b, -1);
	if (val_a < val_b)
		return -1;
	else if (val_a > val_b)
		return 1;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareOffsetY(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gdouble val_a, val_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_OFFSET_Y, &val_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_OFFSET_Y, &val_b, FRAME_ID, &id_b, -1);
	if (val_a < val_b)
		return -1;
	else if (val_a > val_b)
		return 1;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareStarsFound(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gint val_a, val_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_STARS, &val_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_STARS, &val_b, FRAME_ID, &id_b, -1);
	if (val_a < val_b)
		return -1;
	else if (val_a > val_b)
		return 1;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareStarsMatched(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gint val_a, val_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_MSTARS, &val_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_MSTARS, &val_b, FRAME_ID, &id_b, -1);
	if (val_a < val_b)
		return -1;
	else if (val_a > val_b)
		return 1;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareReports(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gchar *str_a, *str_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_REPORT, &str_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_REPORT, &str_b, FRAME_ID, &id_b, -1);
	if (!str_a && !str_b) {
		if (id_a < id_b)
			return -1;
		else if (id_a > id_b)
			return 1;
	}
	else if (!str_a && str_b)
		return -1;
	else if (str_a && !str_b)
		return 1;
	else {
		int retval = g_strcmp0(str_a, str_b);
		if (retval != 0)
			return retval;
		else if (id_a < id_b)
			return -1;
		else if (id_a > id_b)
			return 1;
	}
	return 0;
}

static gint CompareFilter(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gchar *filter_a, *filter_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_FILTER, &filter_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_FILTER, &filter_b, FRAME_ID, &id_b, -1);
	int retval = StrCmp0(filter_a, filter_b);
	g_free(filter_a);
	g_free(filter_b);
	if (retval != 0)
		return retval;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareOrigFile(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gchar *fpath_a, *fpath_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_ORIGFILE, &fpath_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_ORIGFILE, &fpath_b, FRAME_ID, &id_b, -1);
	int retval = ComparePaths(fpath_a, fpath_b);
	g_free(fpath_a);
	g_free(fpath_b);
	if (retval != 0)
		return retval;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

static gint CompareTempFile(GtkTreeModel *tree_model, GtkTreeIter *a, GtkTreeIter *b, tProjectPrintData *data)
{
	gchar *fpath_a, *fpath_b;
	gint id_a, id_b;
	gtk_tree_model_get(tree_model, a, FRAME_TEMPFILE, &fpath_a, FRAME_ID, &id_a, -1);
	gtk_tree_model_get(tree_model, b, FRAME_TEMPFILE, &fpath_b, FRAME_ID, &id_b, -1);
	int retval = ComparePaths(fpath_a, fpath_b);
	g_free(fpath_a);
	g_free(fpath_b);
	if (retval != 0)
		return retval;
	else if (id_a < id_b)
		return -1;
	else if (id_a > id_b)
		return 1;
	else
		return 0;
}

//--------------------------------   UPDATE QUEUE ITEM   ------------------------------

struct tQueueItem
{
	// Frame reference
	GtkTreeRowReference *frameRef;

	// Field identifier
	tFrameListColumns fieldId;

	// False = set value, True = reset value
	bool reset;

	// New value
	unsigned uvalue;
	int ivalue;
	gchar *svalue;
	double dvalue;

	// Constructor
	tQueueItem(GtkTreeRowReference *ref, tFrameListColumns field) : frameRef(ref), fieldId(field), reset(true), uvalue(0), ivalue(0), svalue(NULL), dvalue(0) {}

	// Destructor
	~tQueueItem() { g_free(svalue); gtk_tree_row_reference_free(frameRef); }
};

//-------------------------------   PROJECT CLASS   -----------------------------------

// Constructor
CProject::CProject(void):m_Name(NULL), m_Path(NULL), m_DataDir(NULL), m_ReadOnly(true),
	m_FreeID(1), m_TargetType(STATIONARY_TARGET), m_RefType(REF_UNDEFINED), m_RefFrameID(-1), 
	m_Ref(NULL), m_RefMBObjID(0), m_Observer(NULL), m_Telescope(NULL), m_Instrument(NULL),
	m_blockUpdates(true)
{
	m_Frames = gtk_list_store_new(FRAME_NCOLS, G_TYPE_INT, G_TYPE_UINT, G_TYPE_UINT, 
		G_TYPE_DOUBLE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, 
		G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE, 
		G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
		G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_INT, 
		G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE, 
		G_TYPE_DOUBLE, G_TYPE_DOUBLE);
	GtkTreeSortable *sortable = GTK_TREE_SORTABLE(m_Frames);
	AddSortCol(sortable, FRAME_ID, (GtkTreeIterCompareFunc)CompareFrameId);
	AddSortCol(sortable, FRAME_JULDAT, (GtkTreeIterCompareFunc)CompareJulDat);
	AddSortCol(sortable, FRAME_REPORT, (GtkTreeIterCompareFunc)CompareReports);
	AddSortCol(sortable, FRAME_ORIGFILE, (GtkTreeIterCompareFunc)CompareOrigFile);
	AddSortCol(sortable, FRAME_TEMPFILE, (GtkTreeIterCompareFunc)CompareTempFile);
	AddSortCol(sortable, FRAME_FILTER, (GtkTreeIterCompareFunc)CompareFilter);
	AddSortCol(sortable, FRAME_EXPTIME, (GtkTreeIterCompareFunc)CompareExpTime);
	AddSortCol(sortable, FRAME_CCDTEMP, (GtkTreeIterCompareFunc)CompareCcdTemp);
	AddSortCol(sortable, FRAME_STARS, (GtkTreeIterCompareFunc)CompareStarsFound);
	AddSortCol(sortable, FRAME_MSTARS, (GtkTreeIterCompareFunc)CompareStarsMatched);
	AddSortCol(sortable, FRAME_OFFSET_X, (GtkTreeIterCompareFunc)CompareOffsetX);
	AddSortCol(sortable, FRAME_OFFSET_Y, (GtkTreeIterCompareFunc)CompareOffsetY);
	
	m_Params = g_key_file_new();
	m_FileLock = new FileLock();
	m_updateQueue = g_queue_new();
}

// Destructor
CProject::~CProject(void)
{
	if (m_Ref)
		gtk_tree_row_reference_free(m_Ref);
	g_free(m_Name);
	g_free(m_Path);
	g_free(m_DataDir);
	g_object_unref(m_Frames);
	g_key_file_free(m_Params);
	g_free(m_Observer);
	g_free(m_Telescope);
	g_free(m_Instrument);
	delete m_FileLock;

	tQueueItem *item = (tQueueItem*)g_queue_pop_head(m_updateQueue);
	while (item) {
		delete item;
		item = (tQueueItem*)g_queue_pop_head(m_updateQueue);
	}
	g_queue_free(m_updateQueue);
}

// Lock data
void CProject::Lock(void) const
{
	gdk_threads_lock();
}

// Unlock data
void CProject::Unlock(void) const
{
	gdk_threads_unlock();
}

void CProject::SetTags(const CTags &tags)
{
	Lock();
	m_Tags = tags;
	Unlock();
}

void CProject::SetObjectList(const CObjectList &objs)
{
	Lock();
	m_Objects = objs;
	Unlock();
}

void CProject::SetLastSelection(const CSelection &sel)
{
	Lock();
	m_Selection = sel;
	Unlock();
}

// Set obejct's coordinates
void CProject::SetObjectCoords(const CObjectCoords &obj)
{
	Lock();
	m_Coords = obj;
	Unlock();
}

// Set observer's name
void CProject::SetObserver(const gchar *name)
{
	Lock();
	g_free(m_Observer);
	m_Observer = (name!=NULL ? g_strdup(name) : NULL);
	Unlock();
}

// Set acquisition telescope designation
void CProject::SetTelescope(const gchar *name)
{
	Lock();
	g_free(m_Telescope);
	m_Telescope = (name!=NULL ? g_strdup(name) : NULL);
	Unlock();
}

// Set acquisition instrument designation
void CProject::SetInstrument(const gchar *name)
{
	Lock();
	g_free(m_Instrument);
	m_Instrument = (name!=NULL ? g_strdup(name) : NULL);
	Unlock();
}

// Set reference observer's coordinates
void CProject::SetLocation(const CLocation &obs)
{
	Lock();
	m_Location = obs;
	Unlock();
}

// Set reference observer's coordinates
void CProject::SetApertures(const CApertures &list)
{
	Lock();
	m_Apertures = list;
	Unlock();
}

// Set reference observer's coordinates
void CProject::SetTrackingData(const CTrackingData &data)
{
	Lock();
	m_TrackingData = data;
	Unlock();
}

// Close project
void CProject::Close(void)
{
	Lock();
	if (m_Path) 
		Save();
	m_FileLock->Release();
	reset();
	m_Profile.Clear();
	g_free(m_Name);
	m_Name = NULL;
	g_free(m_Path);
	m_Path = NULL;
	g_free(m_DataDir);
	m_DataDir = NULL;
	Unlock();
}

// Remove and delete all items from the update queue
void CProject::ClearUpdateQueue(void)
{
	if (m_updateQueue) {
		tQueueItem *item = (tQueueItem*)g_queue_pop_head(m_updateQueue);
		while (item) {
			delete item;
			item = (tQueueItem*)g_queue_pop_head(m_updateQueue);
		}
		g_queue_clear(m_updateQueue);
	}
}

// Open existing project
bool CProject::Open(const gchar *path, tOpenMode mode, GError **error, GtkWindow *parent)
{
	Lock();

	Close();

	// Acquire file lock
	m_ReadOnly = true;
	if (mode!=OPEN_READONLY) {
		if (m_FileLock->Acquire(path)) {
			m_ReadOnly = !checkWritable(path);
			if (m_ReadOnly && mode==OPEN_CREATE) {
				gchar *dirpath = g_path_get_dirname(path);
				set_error(error, "Error when creating the file", path, CMPACK_ERR_OPEN_ERROR);
				g_free(dirpath);
				Unlock();
				return false;
			} 
		} else {
			if (mode==OPEN_CREATE || !parent || !confirmReadOnly(parent, path)) {
				set_error(error, "Error when creating the file", path, CMPACK_ERR_ACCESS_DENIED);
				Unlock();
				return false;
			}
		}
	}
	if (mode!=OPEN_CREATE) {
		// Read a project file
		if (!read(path, false, error)) {
			m_FileLock->Release();
			Unlock();
			return false;
		}
	} else {
		// Set parameters to default values
		reset();
		m_Profile.Clear();
	}

	// Make sure that data directory exists
	gchar *datadir = g_strdup_printf("%s-files", path);
	if (!datadir || !force_directory(datadir)) {
		set_error(error, "Error when creating the directory", datadir);
		g_free(datadir);
		m_FileLock->Release();
		Unlock();
		return false;
	}

	if (mode==OPEN_CREATE) {
		// Write a new project file
		if (!write(path, error)) {
			m_FileLock->Release();
			Unlock();
			return false;
		}
	}

	// Set name, file name and path
	gchar *basename = g_path_get_basename(path);
	m_Name = StripFileExtension(basename);
	g_free(basename);
	m_DataDir = datadir;
	m_Path = g_strdup(path);

	CConfig::SetStr("Projects", "Last", m_Path);
	CConfig::AddProjectToRecentList(ProjectType(), m_Path);
	Unlock();
	return true;
}

// Rename project
bool CProject::Rename(const gchar *newname, GError **error)
{
	char buf[MAX_PROJECT_NAME+32];
	gchar *oldpath;

	assert(newname && newname[0]!=0);

	Lock();

	assert(m_Path != NULL && m_Name != NULL);

	sprintf(buf, "%s.%s", newname, FILE_EXTENSION_PROJECT);
	gchar *basedir = g_path_get_dirname(m_Path);
	gchar *fpath = g_build_filename(basedir, buf, NULL);
	g_free(basedir);

	if (g_file_test(fpath, G_FILE_TEST_EXISTS)) {
		// Project exists already
		set_error(error, "The file or the directory exists already. Please delete it first.");
		g_free(fpath);
		Unlock();
		return false;
	}
	gchar *datadir = g_strdup_printf("%s-files", fpath);
	if (g_file_test(datadir, G_FILE_TEST_EXISTS)) {
		// Data directory already exists
		set_error(error, "The file or the directory exists already. Please delete it first.");
		g_free(datadir);
		g_free(fpath);
		Unlock();
		return false;
	}

	// Acquire new lock
	FileLock *dstlock = new FileLock();
	if (!dstlock->Acquire(fpath)) {
		set_error(error, "Error when creating the file", fpath, CMPACK_ERR_ACCESS_DENIED);
		g_free(fpath);
		g_free(datadir);
		delete dstlock;
		Unlock();
		return false;
	}

	// Move project file
	if (g_rename(m_Path, fpath)!=0) {
		char msg[256+2*FILENAME_MAX];
		sprintf(msg, "Failed to rename the project file \"%s\" to \"%s\"", m_Path, fpath);
		set_error(error, msg);
		g_free(fpath);
		g_free(datadir);
		delete dstlock;
		Unlock();
		return false;
	}
	// Move data directory
	if (g_rename(m_DataDir, datadir)!=0) {
		char msg[256+2*FILENAME_MAX];
		sprintf(msg, "Failed to rename the directory \"%s\" to \"%s\"", m_DataDir, datadir);
		set_error(error, msg);
		g_free(fpath);
		g_free(datadir);
		delete dstlock;
		Unlock();
		return false;
	}

	// Set name, file name and path
	oldpath = g_strdup(m_Path);
	g_free(m_Name);
	gchar *basename = g_path_get_basename(fpath);
	m_Name = StripFileExtension(basename);
	g_free(basename);
	g_free(m_DataDir);
	m_DataDir = datadir;
	g_free(m_Path);
	m_Path = fpath;
	delete m_FileLock;
	m_FileLock = dstlock;

	CConfig::SetStr("Projects", "Last", m_Path);
	CConfig::RenameProjectInRecentList(ProjectType(), oldpath, m_Path);
	Unlock();

	g_free(oldpath);
	return true;
}

// Save project as
bool CProject::Export(const gchar *dstpath, GError **error)
{
	Lock();

	assert (m_Path != NULL && m_Name != NULL);

	if (SamePath(dstpath, m_Path)) {
		// Special case: same path
		Unlock();
		return false;
	}

	// Aquire new lock
	FileLock dstlock;
	if (!dstlock.Acquire(dstpath)) {
		set_error(error, "Error when creating the file", dstpath, CMPACK_ERR_ACCESS_DENIED);
		Unlock();
		return false;
	}
	
	// Copy project file
	if (!write(dstpath, error)) {
		Unlock();
		return false;
	}

	// Copy data files
	gchar *datadir = g_strdup_printf("%s-files", dstpath);
	if (!CopyFiles(datadir, error)) {
		g_free(datadir);
		Unlock();
		return false;
	}

	CConfig::SetStr("Projects", "Last", m_Path);
	CConfig::AddProjectToRecentList(ProjectType(), m_Path);
	Unlock();
	return true;
}

// Flush project to disk
void CProject::Save(void) const
{
	Lock();
	if (!m_ReadOnly && m_Path) {
		// Save project file
		write(m_Path, NULL);
	}
	Unlock();
}

// Import data from old C-Munipack
bool CProject::Import(const gchar *srcdir, GError **error)
{
	bool retval = true;
	int state;
	gchar *ftemp, *fphot;
	GtkTreeIter iter;

	Lock();

	assert (m_Path != NULL && m_Name != NULL);

	reset();

	// Read a project file
	gchar *fpath = g_build_filename(srcdir, "project.ini", NULL);
	if (!read(fpath, true, error)) {
		g_free(fpath);
		Unlock();
		return false;
	}
	g_free(fpath);

	// Make sure that the data directory exists
	if (!force_directory(m_DataDir)) {
		set_error(error, "Error when creating the directory", m_DataDir);
		Unlock();
		return false;
	}

	// Copy images and photometry files
	gboolean ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter);
	while (ok && retval) {
		gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter,	FRAME_STATE, &state, 
			FRAME_TEMPFILE, &ftemp, FRAME_PHOTFILE, &fphot, -1);
		if (retval && ftemp && (state & CFILE_CONVERSION)!=0) {
			gchar *srcpath = g_build_filename(srcdir, ftemp, NULL);
			gchar *dstpath = g_build_filename(m_DataDir, ftemp, NULL);
			retval = copy_file(srcpath, dstpath, false, error);
			g_free(srcpath);
			g_free(dstpath);
		}
		if (retval && fphot && (state & CFILE_PHOTOMETRY)!=0) {
			gchar *srcpath = g_build_filename(srcdir, fphot, NULL);
			gchar *dstpath = g_build_filename(m_DataDir, fphot, NULL);
			retval = copy_file(srcpath, dstpath, false, error);
			g_free(srcpath);
			g_free(dstpath);
		}
		g_free(ftemp);
		g_free(fphot);
		ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter);
	}

	// Copy temporary correction and reference files
	if (retval && m_TempBiasFile.Valid()) {
		gchar *basename = g_path_get_basename(m_TempBiasFile.FullPath());
		gchar *srcpath = g_build_filename(srcdir, basename, NULL);
		gchar *dstpath = g_build_filename(m_DataDir, basename, NULL);
		if (copy_file(srcpath, dstpath, false, error))
			m_TempBiasFile = CFileInfo(dstpath);
		else
			m_TempBiasFile.Clear();
		g_free(dstpath);
		g_free(srcpath);
		g_free(basename);
	}
	if (retval && m_TempDarkFile.Valid()) {
		gchar *basename = g_path_get_basename(m_TempDarkFile.FullPath());
		gchar *srcpath = g_build_filename(srcdir, basename, NULL);
		gchar *dstpath = g_build_filename(m_DataDir, basename, NULL);
		if (copy_file(srcpath, dstpath, false, error)) 
			m_TempDarkFile = CFileInfo(dstpath);
		else
			m_TempDarkFile.Clear();
		g_free(dstpath);
		g_free(srcpath);
		g_free(basename);
	}
	if (retval && m_OrigFlatFile.Valid() && m_TempFlatFile.Valid()) {
		gchar *basename = g_path_get_basename(m_TempFlatFile.FullPath());
		gchar *srcpath = g_build_filename(srcdir, basename, NULL);
		gchar *dstpath = g_build_filename(m_DataDir, basename, NULL);
		if (copy_file(srcpath, dstpath, false, error)) 
			m_TempFlatFile = CFileInfo(dstpath);
		else
			m_TempFlatFile.Clear();
		g_free(dstpath);
		g_free(srcpath);
		g_free(basename);
	}
	if (retval && m_RefType==REF_CATALOG_FILE && m_TempCatFile.Valid()) {
		gchar *basename = g_path_get_basename(m_TempCatFile.FullPath());
		gchar *srcpath = g_build_filename(srcdir, basename, NULL);
		gchar *dstpath = g_build_filename(m_DataDir, basename, NULL);
		if (copy_file(srcpath, dstpath, false, error))
			m_TempCatFile = CFileInfo(dstpath);
		else
			m_TempCatFile.Clear();
		g_free(dstpath);
		g_free(srcpath);
		g_free(basename);
	}

	// If fails, clear project
	if (!retval)
		reset();

	Unlock();
	return retval;
}

// Load project file
bool CProject::read(const gchar *cfg_path, bool import, GError **error)
{
	int i, count, state, id, nstars, mstars, avg_frames, sum_frames, mb_obj, mb_key;
	double juldat, exptime, ccdtemp, tcorr, radius, offset_x, offset_y;
	unsigned flags;
	char *str, file[64], key[64], val[64], *ftemp, *fpath, tag[MAX_TAG_SIZE+1];
	char *filter, *result, *fphot, *fbias, *fdark, *fflat;
	GtkTreeIter iter;
	GError *err = NULL;
	CmpackMatrix matrix;

	reset();
	m_Profile.Clear();

	// Load file
	GKeyFile *cfg = g_key_file_new();
	if (!g_key_file_load_from_file(cfg, cfg_path, G_KEY_FILE_NONE, error)) {
		g_key_file_free(cfg);
		return false;
	}

	if (!import) {
		// Check file format
		gchar *start = g_key_file_get_start_group(cfg);
		if (StrCmp0(start, "CMUNIPACK-PROJECT")!=0) {
			set_error(error, "Error when reading the file", cfg_path, CMPACK_ERR_UNKNOWN_FORMAT);
			g_free(start);
			g_key_file_free(cfg);
			return false;
		}
		g_free(start);
		// Check file version
		int revision = g_key_file_get_integer(cfg, "CMUNIPACK-PROJECT", "Revision", NULL);
		if (revision!=1) {
			set_error(error, "Error when reading the file", cfg_path, CMPACK_ERR_UNKNOWN_FORMAT);
			g_key_file_free(cfg);
			return false;
		}
	} else {
		// Old project does not support revisions, use one of mandatory keys
		GError *e = NULL;
		g_key_file_get_integer(cfg, "PROJECT", "files", &e);
		if (e) {
			set_error(error, "Error when reading the file", cfg_path, CMPACK_ERR_UNKNOWN_FORMAT);
			g_error_free(e);
		}
	}

	// Input files
	bool found_ref = false;
	count = g_key_file_get_integer(cfg, "PROJECT", "files", NULL);
	for (i=1; i<=count; i++) {
		sprintf(file, "FILE%d", i);
		fpath = g_key_file_get_string(cfg, file, "path", NULL);
		id = g_key_file_get_integer(cfg, file, "fileid", NULL);
		if (fpath && id>0) {
			gtk_list_store_append(m_Frames, &iter);
			juldat = g_key_file_get_double(cfg, file, "juldat", NULL);
			state = g_key_file_get_integer(cfg, file, "state", NULL);
			flags = (unsigned)g_key_file_get_integer(cfg, file, "flags", NULL);
			tcorr = g_key_file_get_double(cfg, file, "tcorr", NULL);
			exptime = g_key_file_get_double(cfg, file, "exptime", &err);
			if (err) {
				exptime = -1.0;
				g_error_free(err);
				err = NULL;
			}
			ccdtemp = g_key_file_get_double(cfg, file, "ccdtemp", &err);
			if (err) {
				ccdtemp = -999.9;
				g_error_free(err);
				err = NULL;
			}
			avg_frames = g_key_file_get_integer(cfg, file, "avg_frames", NULL);
			avg_frames = MIN(1, avg_frames);
			sum_frames = g_key_file_get_integer(cfg, file, "sum_frames", NULL);
			sum_frames = MIN(1, sum_frames);
			ftemp = g_key_file_get_string(cfg, file, "temp", NULL);
			fphot = g_key_file_get_string(cfg, file, "phot", NULL);
			fbias = g_key_file_get_string(cfg, file, "bias", NULL);
			fdark = g_key_file_get_string(cfg, file, "dark", NULL);
			fflat = g_key_file_get_string(cfg, file, "flat", NULL);
			filter = g_key_file_get_string(cfg, file, "filter", NULL);
			result = g_key_file_get_string(cfg, file, "result", NULL);
			nstars = g_key_file_get_integer(cfg, file, "nstars", NULL);
			mstars = g_key_file_get_integer(cfg, file, "mstars", NULL);
			offset_x = g_key_file_get_double(cfg, file, "offsetx", NULL);
			offset_y = g_key_file_get_double(cfg, file, "offsety", NULL);
			mb_obj = g_key_file_get_integer(cfg, file, "mbobj", NULL);
			mb_key = g_key_file_get_integer(cfg, file, "mbkey", NULL);
			matrix.xx = g_key_file_get_double(cfg, file, "tr_xx", NULL);
			matrix.yx = g_key_file_get_double(cfg, file, "tr_yx", NULL);
			matrix.xy = g_key_file_get_double(cfg, file, "tr_xy", NULL);
			matrix.yy = g_key_file_get_double(cfg, file, "tr_yy", NULL);
			matrix.x0 = g_key_file_get_double(cfg, file, "tr_x0", NULL);
			matrix.y0 = g_key_file_get_double(cfg, file, "tr_y0", NULL);
			if (mb_key >= 1 && mb_key <= 2) {
				if (mb_key == 1) {
					if (!found_ref) 
						found_ref = true;
					else 
						mb_key = 2;
				}
			} else
				mb_key = 0;
			state &= (~CFILE_THUMBNAIL);
			sprintf(val, "%d", id);
			gtk_list_store_set(m_Frames, &iter, FRAME_STATE, state,
				FRAME_FLAGS, flags, FRAME_ID, id, FRAME_JULDAT, juldat, 
				FRAME_REPORT, result, FRAME_ORIGFILE, fpath, FRAME_BIASFILE, fbias, 
				FRAME_DARKFILE, fdark, FRAME_FLATFILE, fflat, FRAME_FILTER, filter, 
				FRAME_TIMECORR, tcorr, FRAME_EXPTIME, exptime, FRAME_CCDTEMP, ccdtemp, 
				FRAME_STARS, nstars, FRAME_MSTARS, mstars, FRAME_TEMPFILE, ftemp, 
				FRAME_OFFSET_X, offset_x, FRAME_OFFSET_Y, offset_y, 
				FRAME_TR_XX, matrix.xx, FRAME_TR_YX, matrix.yx, FRAME_TR_XY, matrix.xy, 
				FRAME_TR_YY, matrix.yy, FRAME_TR_X0, matrix.x0, FRAME_TR_Y0, matrix.y0, 
				FRAME_PHOTFILE, fphot, FRAME_AVGFRAMES, avg_frames, 
				FRAME_SUMFRAMES, sum_frames, FRAME_STRINGID, val, 
				FRAME_MB_OBJ, mb_obj, FRAME_MB_KEY, mb_key, -1);
			if (id>=m_FreeID)
				m_FreeID = id+1;
			g_free(fflat);
			g_free(fdark);
			g_free(fbias);
			g_free(filter);
			g_free(result);
			g_free(ftemp);
			g_free(fphot);
		}
		g_free(fpath);
	}

	// Apertures
	m_Apertures.Clear();
	count = g_key_file_get_integer(cfg, "APERTURES", "count", NULL);
	for (int i=1; i<=count; i++) {
		sprintf(key, "aperture%d", i);
		str = g_key_file_get_string(cfg, "APERTURES", key, NULL);
		if (str && *str!='\0' && sscanf(str, " %9d; %32lf ", &id, &radius)==2)
			m_Apertures.Add(CAperture(id, radius));
		g_free(str);
	}
	
	// Selected stars
	m_Selection.Clear();
	str = g_key_file_get_string(cfg, "STARS", "var", NULL);
	if (str && *str!='\0')
		m_Selection.SetStarList(CMPACK_SELECT_VAR, str);
	g_free(str);
	str = g_key_file_get_string(cfg, "STARS", "comp", NULL);
	if (str && *str!='\0')
		m_Selection.SetStarList(CMPACK_SELECT_COMP, str);
	g_free(str);
	str = g_key_file_get_string(cfg, "STARS", "check", NULL);
	if (str && *str!='\0')
		m_Selection.SetStarList(CMPACK_SELECT_CHECK, str);
	g_free(str);

	// Saved object selections
	m_SelectionList.Clear();
	count = g_key_file_get_integer(cfg, "SelectionList", "count", NULL);
	for (int i=1, index=1; i<=count; i++) {
		sprintf(key, "item%d", i);
		gchar *str = g_key_file_get_string(cfg, "SelectionList", key, NULL);
		if (str && *str!='\0') {
			sprintf(key, "name%d", i);
			gchar *name = g_key_file_get_string(cfg, "SelectionList", key, NULL);
			if (name)
				name = g_strstrip(name);
			CSelection stars(str);
			if (stars.Count()>0 && m_SelectionList.IndexOf(name)<0) {
				while (!name) {
					gchar aux[128];
					sprintf(aux, "Selection %d", index);
					if (m_SelectionList.IndexOf(aux)<0) 
						name = g_strstrip(g_strdup(aux));
					index++;
				}
				m_SelectionList.Set(name, stars);
			}
			g_free(name);
		}
		g_free(str);
	}
	
	// Object tags
	m_Tags.clear();
	count = g_key_file_get_integer(cfg, "TAGS", "count", NULL);
	for (int i = 1; i <= count; i++) {
		sprintf(key, "tag%d", i);
		str = g_key_file_get_string(cfg, "TAGS", key, NULL);
		if (str && *str != '\0' && sscanf(str, " %9d; %1023[^\n] ", &id, tag) == 2) {
			if (id >= 0 && *tag != '\0') {
				gchar *aux = g_strdup(tag);
				m_Tags.insert(id, g_strstrip(aux));
				g_free(aux);
			}
		}
		g_free(str);
	}

	// List of objects
	m_Objects.clear();
	count = g_key_file_get_integer(cfg, "OBJECT_LIST", "count", NULL);
	for (int i = 1, j = 0; i <= count; i++) {
		sprintf(key, "obj%d", i);
		str = g_key_file_get_string(cfg, "OBJECT_LIST", key, NULL);
		double x, y;
		if (str && *str != '\0' && sscanf(str, " %lf; %lf; ", &x, &y) == 2)
			m_Objects.insert(x, y, ++j);
		g_free(str);
	}

	// Object coordinates
	m_Coords.Clear();
	str = g_key_file_get_string(cfg, "OBJECT", "name", NULL);
	if (str && *str!='\0')
		m_Coords.SetName(g_strstrip(str));
	g_free(str);
	str = g_key_file_get_string(cfg, "OBJECT", "ra", NULL);
	if (str && *str!='\0')
		m_Coords.SetRA(g_strstrip(str));
	g_free(str);
	str = g_key_file_get_string(cfg, "OBJECT", "dec", NULL);
	if (str && *str!='\0')
		m_Coords.SetDec(g_strstrip(str));
	g_free(str);
	str = g_key_file_get_string(cfg, "OBJECT", "source", NULL);
	if (str && *str!='\0')
		m_Coords.SetSource(g_strstrip(str));
	g_free(str);
	str = g_key_file_get_string(cfg, "OBJECT", "remarks", NULL);
	if (str && *str!='\0')
		m_Coords.SetRemarks(g_strstrip(str));
	g_free(str);

	// Observer's name, telescope, instrument
	m_Observer = g_key_file_get_string(cfg, "OBSERVER", "name", NULL);
	if (m_Observer)
		m_Observer = g_strstrip(m_Observer);
	m_Telescope = g_key_file_get_string(cfg, "TELESCOPE", "name", NULL);
	if (m_Telescope)
		m_Telescope = g_strstrip(m_Telescope);
	m_Instrument = g_key_file_get_string(cfg, "INSTRUMENT", "name", NULL);
	if (m_Instrument)
		m_Instrument = g_strstrip(m_Instrument);

	// Reference location
	str = g_key_file_get_string(cfg, "LOCATION", "name", NULL);
	if (str && *str!='\0')
		m_Location.SetName(g_strstrip(str));
	g_free(str);
	str = g_key_file_get_string(cfg, "LOCATION", "lon", NULL);
	if (str && *str!='\0')
		m_Location.SetLon(g_strstrip(str));
	g_free(str);
	str = g_key_file_get_string(cfg, "LOCATION", "lat", NULL);
	if (str && *str!='\0')
		m_Location.SetLat(g_strstrip(str));
	g_free(str);
	str = g_key_file_get_string(cfg, "LOCATION", "remarks", NULL);
	if (str && *str!='\0')
		m_Location.SetComment(g_strstrip(str));
	g_free(str);

	// Common
	m_TargetType = (tTargetType)LimitValue(g_key_file_get_integer(cfg, "PROJECT", "trgtype", NULL), 0, TARGET_TYPE_COUNT-1);
	m_RefType = (tReferenceType)LimitValue(g_key_file_get_integer(cfg, "PROJECT", "reftype", NULL), 0, REF_TYPE_COUNT-1);

	// Reference frame
	m_RefFrameID = g_key_file_get_integer(cfg, "PROJECT", "refid", NULL);
	GtkTreePath *path = GetPath(m_RefFrameID);
	if (path) {
		m_Ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), path);
		gtk_tree_path_free(path);
	}

	// Minor body - object ID
	m_RefMBObjID = g_key_file_get_integer(cfg, "PROJECT", "mb_object", NULL);

	// Tracking data
	m_TrackingData.load(cfg);

	// Correction frames
	m_OrigBiasFile.Load(cfg, "BIAS", "orig");
	m_TempBiasFile.Load(cfg, "BIAS", "temp");
	m_OrigDarkFile.Load(cfg, "DARK", "orig");
	m_TempDarkFile.Load(cfg, "DARK", "temp");
	m_OrigFlatFile.Load(cfg, "FLAT", "orig");
	m_TempFlatFile.Load(cfg, "FLAT", "temp");
	m_OrigCatFile.Load(cfg, "REF", "orig");
	m_TempCatFile.Load(cfg, "REF", "temp");

	// Profile
	m_Profile.Load(cfg);

	// User settings
	gchar **keys = g_key_file_get_keys(cfg, "ProjectSettings", NULL, NULL);
	if (keys) {
		for (int j=0; keys[j]!=NULL; j++) {
			gchar *value = g_key_file_get_value(cfg, "ProjectSettings", keys[j], NULL);
			if (value && value[0]!='\0') {
				char *key = strchr(keys[j], '.');
				if (key) {
					*key++ = '\0';
					g_key_file_set_value(m_Params, keys[j], key, value);
				}
			}
			g_free(value);
		}
		g_strfreev(keys);
	}
	
	g_key_file_free(cfg);
	return true;
}

// Save project to file
bool CProject::write(const gchar *cfg_path, GError **error) const
{
	gboolean ok;
	gint i, id, count, avg_frames, sum_frames, nstars, mstars, state, mb_obj, mb_key;
	gchar file[64], *fpath, *ftemp, *fbias, *fdark, *fflat;
	gchar *str, *filter, *result, *fphot, *data;
	unsigned flags;
	const gchar *cstr;
	gsize datalen;
	GtkTreeIter iter;
	gdouble juldat, ccdtemp, exptime, tcorr, offset_x, offset_y;
	GKeyFile *cfg = g_key_file_new();
	CmpackMatrix matrix;

	// Identification
	g_key_file_set_integer(cfg, "CMUNIPACK-PROJECT", "Revision", 1);

	// Profile
	m_Profile.Save(cfg);

	// Dialog settings
	gchar **grps = g_key_file_get_groups(m_Params, NULL);
	if (grps) {
		for (int i=0; grps[i]!=NULL; i++) {
			gchar **keys = g_key_file_get_keys(m_Params, grps[i], NULL, NULL);
			if (keys) {
				for (int j=0; keys[j]!=NULL; j++) {
					gchar *value = g_key_file_get_value(m_Params, grps[i], keys[j], NULL);
					if (value && value[0]!='\0') {
						char key[256];
						sprintf(key, "%s.%s", grps[i], keys[j]);
						g_key_file_set_value(cfg, "ProjectSettings", key, value);
					}
					g_free(value);
				}
				g_strfreev(keys);
			}
		}
		g_strfreev(grps);
	}

	// Input files
	count = 0;
	ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter);
	while (ok) {
		sprintf(file, "FILE%d", ++count);
		gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, FRAME_ORIGFILE, &fpath, FRAME_ID, &id, 
			FRAME_STATE, &state, FRAME_FLAGS, &flags, FRAME_JULDAT, &juldat,
			FRAME_REPORT, &result, FRAME_BIASFILE, &fbias, FRAME_DARKFILE, &fdark, 
			FRAME_FLATFILE, &fflat, FRAME_FILTER, &filter, FRAME_TIMECORR, &tcorr, 
			FRAME_EXPTIME, &exptime, FRAME_CCDTEMP, &ccdtemp, FRAME_STARS, &nstars, 
			FRAME_MSTARS, &mstars, FRAME_TEMPFILE, &ftemp, FRAME_OFFSET_X, &offset_x, 
			FRAME_OFFSET_Y, &offset_y, FRAME_PHOTFILE, &fphot, FRAME_AVGFRAMES, &avg_frames, 
			FRAME_SUMFRAMES, &sum_frames, FRAME_MB_OBJ, &mb_obj, FRAME_MB_KEY, &mb_key, 
			FRAME_TR_XX, &matrix.xx, FRAME_TR_YX, &matrix.yx, FRAME_TR_XY, &matrix.xy, 
			FRAME_TR_YY, &matrix.yy, FRAME_TR_X0, &matrix.x0, FRAME_TR_Y0, &matrix.y0, -1);
		g_key_file_set_string(cfg, file, "path", fpath);
		g_key_file_set_integer(cfg, file, "fileid", id);
		g_key_file_set_integer(cfg, file, "state", state & (~CFILE_THUMBNAIL));
		g_key_file_set_integer(cfg, file, "flags", (int)flags);
		if (ftemp)
			g_key_file_set_string(cfg, file, "temp", ftemp);
		if (fphot)
			g_key_file_set_string(cfg, file, "phot", fphot);
		if (juldat>0)
			g_key_file_set_double(cfg, file, "juldat", juldat);
		if (tcorr>0)
			g_key_file_set_double(cfg, file, "tcorr", tcorr);
		if (filter)
			g_key_file_set_string(cfg, file, "filter", filter);
		if (exptime>=0)
			g_key_file_set_double(cfg, file, "exptime", exptime);
		if (ccdtemp>-999.0)
			g_key_file_set_double(cfg, file, "ccdtemp", ccdtemp);
		if (avg_frames>1)
			g_key_file_set_integer(cfg, file, "avg_frames", avg_frames);
		if (sum_frames>1)
			g_key_file_set_integer(cfg, file, "sum_frames", sum_frames);
		if (fbias)
			g_key_file_set_string(cfg, file, "bias", fbias);
		if (fdark)
			g_key_file_set_string(cfg, file, "dark", fdark);
		if (fflat)
			g_key_file_set_string(cfg, file, "flat", fflat);
		if (nstars>0)
			g_key_file_set_integer(cfg, file, "nstars", nstars);
		if (mstars>0)
			g_key_file_set_integer(cfg, file, "mstars", mstars);
		if (offset_x!=0)
			g_key_file_set_double(cfg, file, "offsetx", offset_x);
		if (offset_y!=0)
			g_key_file_set_double(cfg, file, "offsety", offset_y);
		if (mb_obj>0)
			g_key_file_set_integer(cfg, file, "mbobj", mb_obj);
		if (mb_key>0)
			g_key_file_set_integer(cfg, file, "mbkey", mb_key);
		if (result) 
			g_key_file_set_string(cfg, file, "result", result);
		if (matrix.xx!=0) 
			g_key_file_set_double(cfg, file, "tr_xx", matrix.xx);
		if (matrix.yx!=0) 
			g_key_file_set_double(cfg, file, "tr_yx", matrix.yx);
		if (matrix.xy!=0) 
			g_key_file_set_double(cfg, file, "tr_xy", matrix.xy);
		if (matrix.yy!=0) 
			g_key_file_set_double(cfg, file, "tr_yy", matrix.yy);
		if (matrix.x0!=0) 
			g_key_file_set_double(cfg, file, "tr_x0", matrix.x0);
		if (matrix.y0!=0) 
			g_key_file_set_double(cfg, file, "tr_y0", matrix.y0);
		g_free(filter);
		g_free(ftemp);
		g_free(fbias);
		g_free(fdark);
		g_free(fflat);
		g_free(fpath);
		g_free(fphot);
		g_free(result);
		ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter);
	}
	g_key_file_set_integer(cfg, "PROJECT", "files", count);

	// Apertures
	g_key_file_set_integer(cfg, "APERTURES", "count", m_Apertures.Count());
	for (i=0; i<m_Apertures.Count(); i++) {
		const CAperture *aper = m_Apertures.Get(i);
		gchar key[64], val[64];
		sprintf(key, "aperture%d", i+1);
		sprintf(val, "%d;%.5f", aper->Id(), aper->Radius());
		g_key_file_set_string(cfg, "APERTURES", key, val);
	}

	// Selected stars
	str = m_Selection.GetStarList(CMPACK_SELECT_VAR);
	if (str)
		g_key_file_set_string(cfg, "STARS", "var", str);
	g_free(str);
	str = m_Selection.GetStarList(CMPACK_SELECT_COMP);
	if (str)
		g_key_file_set_string(cfg, "STARS", "comp", str);
	g_free(str);
	str = m_Selection.GetStarList(CMPACK_SELECT_CHECK);
	if (str)
		g_key_file_set_string(cfg, "STARS", "check", str);
	g_free(str);

	// Selected stars
	count = m_SelectionList.Count();
	g_key_file_set_integer(cfg, "SelectionList", "count", count);
	for (int i=0; i<count; i++) {
		gchar key[64], *str = m_SelectionList.At(i).toString();
		sprintf(key, "name%d", i+1);
		g_key_file_set_string(cfg, "SelectionList", key, m_SelectionList.Name(i));
		sprintf(key, "item%d", i+1);
		g_key_file_set_string(cfg, "SelectionList", key, str);
		g_free(str);
	}

	// Tags
	g_key_file_set_integer(cfg, "TAGS", "count", m_Tags.count());
	for (int i = 0; i < m_Tags.count(); i++) {
		const CTags::tData &tag = m_Tags[i];
		gchar key[64], val[MAX_TAG_SIZE + 64];
		sprintf(key, "tag%d", i + 1);
		sprintf(val, "%d;%s", tag.id, tag.tag);
		g_key_file_set_string(cfg, "TAGS", key, val);
	}

	// Objects
	g_key_file_set_integer(cfg, "OBJECT_LIST", "count", m_Objects.count());
	for (int i = 0; i < m_Objects.count(); i++) {
		gchar key[64], val[1024];
		const CObjectList::tObject &obj = m_Objects[i];
		sprintf(key, "obj%d", i + 1);
		sprintf(val, "%lf;%lf", obj.x, obj.y);
		g_key_file_set_string(cfg, "OBJECT_LIST", key, val);
	}

	// Object coordinates
	cstr = m_Coords.Name();
	if (cstr)
		g_key_file_set_string(cfg, "OBJECT", "name", cstr);
	cstr = m_Coords.RA();
	if (cstr)
		g_key_file_set_string(cfg, "OBJECT", "ra", cstr);
	cstr = m_Coords.Dec();
	if (cstr)
		g_key_file_set_string(cfg, "OBJECT", "dec", cstr);
	cstr = m_Coords.Source();
	if (cstr)
		g_key_file_set_string(cfg, "OBJECT", "source", cstr);
	cstr = m_Coords.Remarks();
	if (cstr)
		g_key_file_set_string(cfg, "OBJECT", "remarks", cstr);

	// Observer's name, telescope, instrument
	if (m_Observer)
		g_key_file_set_string(cfg, "OBSERVER", "name", m_Observer);
	if (m_Telescope)
		g_key_file_set_string(cfg, "TELESCOPE", "name", m_Telescope);
	if (m_Instrument)
		g_key_file_set_string(cfg, "INSTRUMENT", "name", m_Instrument);

	// Reference location
	cstr = m_Location.Name();
	if (cstr)
		g_key_file_set_string(cfg, "LOCATION", "name", cstr);
	cstr = m_Location.Lon();
	if (cstr)
		g_key_file_set_string(cfg, "LOCATION", "lon", cstr);
	cstr = m_Location.Lat();
	if (cstr)
		g_key_file_set_string(cfg, "LOCATION", "lat", cstr);
	cstr = m_Location.Comment();
	if (cstr)
		g_key_file_set_string(cfg, "LOCATION", "remarks", cstr);

	// Tracking data
	if (m_TrackingData.valid())
		m_TrackingData.save(cfg);

	// Common
	g_key_file_set_integer(cfg, "PROJECT", "refid", m_RefFrameID);
	g_key_file_set_integer(cfg, "PROJECT", "reftype", (int)m_RefType);
	g_key_file_set_integer(cfg, "PROJECT", "trgtype", (int)m_TargetType);
	g_key_file_set_integer(cfg, "PROJECT", "mb_object", m_RefMBObjID);
	m_OrigBiasFile.Save(cfg, "BIAS", "orig");
	m_TempBiasFile.Save(cfg, "BIAS", "temp");
	m_OrigDarkFile.Save(cfg, "DARK", "orig");
	m_TempDarkFile.Save(cfg, "DARK", "temp");
	m_OrigFlatFile.Save(cfg, "FLAT", "orig");
	m_TempFlatFile.Save(cfg, "FLAT", "temp");
	m_OrigCatFile.Save(cfg, "REF", "orig");
	m_TempCatFile.Save(cfg, "REF", "temp");

	// Save content to file
	ok = FALSE;
	data = g_key_file_to_data(cfg, &datalen, error);
	if (data) {
		GIOChannel *pFile = g_io_channel_new_file(cfg_path, "w+", error);
		if (pFile) {
			g_io_channel_write_chars(pFile, data, datalen, NULL, error);
			g_io_channel_unref(pFile);
			ok = TRUE;
		}
		g_free(data);
	}

	g_key_file_free(cfg);
	return ok!=0;
}

// Return true if the given buffer can be a project file
bool CProject::isProjectFile(const gchar *filepath)
{
	bool retval = false;
	GKeyFile *f = g_key_file_new();
	if (g_key_file_load_from_file(f, filepath, G_KEY_FILE_NONE, NULL)) 
		retval = checkProjectFile(f);
	g_key_file_free(f);
	return retval;
}

// Get number of files
int CProject::GetFileCount(void)
{ 
	int retval;
	Lock();
	retval = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_Frames), NULL);
	Unlock();
	return retval;
}

// Remove all frames from the project
void CProject::RemoveAllFrames(void)
{
	Lock();
	if (m_DataDir && !m_ReadOnly) {
		ClearTempFiles();
		ClearReference();
		ClearCorrections();
		ClearUpdateQueue();
		gtk_list_store_clear(m_Frames);
		m_FreeID = 1;
		m_Apertures.Clear();
	}
	Unlock();
}

// Clear all internal parameters
void CProject::reset(void)
{
	Lock();
	ClearReference();
	ClearCorrections();
	ClearUpdateQueue();
	gtk_list_store_clear(m_Frames);
	m_FreeID = 1;
	m_Apertures.Clear();
	g_key_file_free(m_Params);
	m_Params = g_key_file_new();
	Unlock();
}

void CProject::ClearCorrections(void)
{
	Lock();
	SetDbl("TimeCorr", "Seconds", 0);
	m_OrigBiasFile.Clear();
	m_TempBiasFile.Clear();
	m_OrigDarkFile.Clear();
	m_TempDarkFile.Clear();
	m_OrigFlatFile.Clear();
	m_TempFlatFile.Clear();
	Unlock();
}

void CProject::ClearObject(void)
{
	Lock();
	m_Coords.Clear();
	Unlock();
}

GtkTreePath *CProject::AddFile(const CFrameInfo &info)
{
	GtkTreePath *path = NULL;
	char buf[128];
	GtkTreeIter iter;

	Lock();
	if (info.Valid() && !m_ReadOnly) {
		int fileid = m_FreeID++;
		sprintf(buf, "%d", fileid);
		gtk_list_store_append(m_Frames, &iter);
		gtk_list_store_set(m_Frames, &iter, FRAME_ID, fileid, 
			FRAME_STRINGID, buf, FRAME_ORIGFILE, info.FullPath(), 
			FRAME_JULDAT, info.JulDat(), FRAME_EXPTIME, info.ExpTime(),
			FRAME_CCDTEMP, info.CCDTemp(), FRAME_AVGFRAMES, info.AvgFrames(),
			FRAME_SUMFRAMES, info.SumFrames(), FRAME_FILTER, info.Filter(), -1);
		path = gtk_tree_model_get_path(GTK_TREE_MODEL(m_Frames), &iter);
	}
	Unlock();
	return path;
}

// Returns true if the file is already in the project
bool CProject::FileInProject(const char *filepath)
{
	bool ok, retval = false;
	gchar *path;
	GtkTreeIter iter;

	Lock();
	ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter)!=0;
	while (ok && !retval) {
		gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, FRAME_ORIGFILE, &path, -1);
		if (SamePath(filepath, path))
			retval = true;
		g_free(path);
		ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter)!=0;
	}
	Unlock();
	return retval;
}

//
// Returns true if the given frame is a reference frame
//
bool CProject::IsReferenceFrame(GtkTreePath *path)
{
	bool retval = false;

	Lock();
	GtkTreePath *refpath = GetReferencePath();
	if (refpath) {
		retval = gtk_tree_path_compare(refpath, path)==0;
		gtk_tree_path_free(refpath);
	}
	Unlock();
	return retval;
}

//
// Returns true if the given list of tree paths contains a reference frame
//
bool CProject::ContainsReferenceFrame(GList *list)
{
	bool retval = false;

	Lock();
	if (m_RefType==REF_FRAME) {
		GtkTreePath *refpath = GetReferencePath();
		if (refpath) {
			for (GList *ptr=list; ptr!=NULL; ptr=ptr->next) {
				if (gtk_tree_path_compare(refpath, (GtkTreePath*)ptr->data)==0) {
					retval = true;
					break;
				}
			}
			gtk_tree_path_free(refpath);
		}
	}
	Unlock();
	return retval;
}

//
// Remove single file
//
void CProject::RemovePath(GtkTreePath *pPath)
{
	Lock();
	if (m_DataDir && !m_ReadOnly) {
		int state = GetState(pPath);
		if (IsReferenceFrame(pPath))
			ClearReference();
		// Delete temporary CCD image and photometry file
		if ((state & CFILE_CONVERSION) != 0) {
			gchar *ftemp = GetImageFileName(pPath);
			if (ftemp) {
				gchar *path = g_build_filename(m_DataDir, ftemp, NULL);
				g_remove(path);
				g_free(path);
				g_free(ftemp);
			}
		}
		if ((state & CFILE_PHOTOMETRY) != 0) {
			gchar *fphot = GetPhotFileName(pPath);
			if (fphot) {
				gchar *path = g_build_filename(m_DataDir, fphot, NULL);
				g_remove(path);
				g_free(path);
				g_free(fphot);
			}
		}
		// Remove the frame from the tree model
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
			gtk_list_store_remove(m_Frames, &iter);
		// If this was the last frame, reset internal parameters
		if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_Frames), NULL) == 0) {
			ClearTempFiles();
			ClearReference();
			ClearCorrections();
			ClearUpdateQueue();
			m_Apertures.Clear();
			m_FreeID = 1;
		}
		// Remove pending update items associated with this frame
		PruneUpdateItems();
	}
	Unlock();
}

void CProject::RemoveFrame(int frame_id)
{
	Lock();
	GtkTreePath *path = GetPath(frame_id);
	if (path) {
		RemovePath(path);
		gtk_tree_path_free(path);
	}
	Unlock();
}

void CProject::RemoveFiles(GList *pPaths)
{
	Lock();
	if (pPaths && !m_ReadOnly) {
		GSList *rr_list = NULL;
		for (GList *ptr = pPaths; ptr!=NULL; ptr=ptr->next) {
			GtkTreeRowReference *ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), (GtkTreePath*)ptr->data);
			rr_list = g_slist_prepend(rr_list, ref);
		}
		for (GSList *node = rr_list; node != NULL; node = node->next) {
			GtkTreePath *path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data);
			if (path) {
				RemovePath(path);
				gtk_tree_path_free(path);
			}
		}
		g_slist_foreach(rr_list, (GFunc)gtk_tree_row_reference_free, NULL);
		g_slist_free(rr_list);
	}
	Unlock();
}


//
// Clear the reference file
//
void CProject::ClearReference(void)
{
	gboolean ok;
	GtkTreeIter iter;

	Lock();
	if (!m_ReadOnly) {
		m_RefFrameID = -1;
		gtk_tree_row_reference_free(m_Ref);
		m_Ref = NULL;
		m_RefType = REF_UNDEFINED;
		m_RefMBObjID = 0;
		m_TrackingData.clear();
		m_OrigCatFile.Clear();
		m_Selection.Clear();
		m_SelectionList.Clear();
		m_Tags.clear();
		m_Objects.clear();
		m_Location.Clear();
		m_Coords.Clear();
		g_free(m_Observer);
		m_Observer = NULL;
		g_free(m_Telescope);
		m_Telescope = NULL;
		g_free(m_Instrument);
		m_Instrument = NULL;

		ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter);
		while (ok) {
			GtkTreePath *pPath = gtk_tree_model_get_path(GTK_TREE_MODEL(m_Frames), &iter);
			if (pPath) {
				int state = GetState(pPath);
				if ((state & CFILE_MATCHING) != 0)
					setField(pPath, FRAME_STATE, (state & ~CFILE_MATCHING), FRAME_REPORT, NULL, -1);
				gtk_tree_path_free(pPath);
			}
			ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter);
		}
	}
	Unlock();
}


//
// Delete all temporary files
//
void CProject::ClearTempFiles(void)
{
	gboolean ok;
	GtkTreeIter iter;

	Lock();
	if (m_DataDir && !m_ReadOnly) {
		ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter);
		while (ok) {
			GtkTreePath *pPath = gtk_tree_model_get_path(GTK_TREE_MODEL(m_Frames), &iter);
			if (pPath) {
				int state = GetState(pPath);
				// Delete temporary CCD image and photometry file
				if ((state & CFILE_CONVERSION) != 0) {
					gchar *ftemp = GetImageFileName(pPath);
					if (ftemp) {
						gchar *path = g_build_filename(m_DataDir, ftemp, NULL);
						g_remove(path);
						g_free(path);
						g_free(ftemp);
					}
				}
				if ((state & CFILE_PHOTOMETRY) != 0) {
					gchar *fphot = GetPhotFileName(pPath);
					if (fphot) {
						gchar *path = g_build_filename(m_DataDir, fphot, NULL);
						g_remove(path);
						g_free(path);
						g_free(fphot);
					}
				}
				// We have to mark all files as new
				resetFields(pPath, FRAME_STATE, FRAME_JULDAT, FRAME_TEMPFILE, FRAME_PHOTFILE,
					FRAME_REPORT, FRAME_BIASFILE, FRAME_DARKFILE, FRAME_FLATFILE, FRAME_FILTER, FRAME_TIMECORR, 
					FRAME_EXPTIME, FRAME_AVGFRAMES, FRAME_SUMFRAMES, FRAME_CCDTEMP, FRAME_STARS, FRAME_MSTARS, 
					FRAME_OFFSET_X, FRAME_OFFSET_Y, FRAME_THUMBNAIL, FRAME_MB_OBJ, FRAME_MB_KEY, FRAME_TR_XX, 
					FRAME_TR_YX, FRAME_TR_XY, FRAME_TR_YY, FRAME_TR_X0, FRAME_TR_Y0, -1);
				gtk_tree_path_free(pPath);
			}
			ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter);
		}
		// Copy temporary correction and reference files
		if (m_TempBiasFile.Valid()) {
			g_remove(m_TempBiasFile.FullPath());
			m_TempBiasFile.Clear();
		}
		if (m_TempDarkFile.Valid()) {
			g_remove(m_TempDarkFile.FullPath());
			m_TempDarkFile.Clear();
		}
		if (m_TempFlatFile.Valid()) {
			g_remove(m_TempFlatFile.FullPath());
			m_TempFlatFile.Clear();
		}
		if (m_RefType==REF_CATALOG_FILE && m_TempCatFile.Valid()) {
			g_remove(m_TempCatFile.FullPath());
			m_TempCatFile.Clear();
		}
	}
	Unlock();
}


//
// Clear thumbnails
//
void CProject::ClearThumbnails(void)
{
	gboolean ok;
	GtkTreeIter iter;

	Lock();
	ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter);
	while (ok) {
		GtkTreePath *pPath = gtk_tree_model_get_path(GTK_TREE_MODEL(m_Frames), &iter);
		if (pPath) {
			setField(pPath, FRAME_STATE, GetState(pPath) & ~CFILE_THUMBNAIL, -1);
			resetFields(pPath, FRAME_THUMBNAIL, -1);
			gtk_tree_path_free(pPath);
		}
		ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter);
	}
	Unlock();
}


//
// Remove all key frames (set all FRAME_MB_KEY to null)
//
void CProject::ClearKeyFrames(void)
{
	gboolean ok;
	GtkTreeIter iter;

	Lock();
	ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter);
	while (ok) {
		gtk_list_store_set(m_Frames, &iter, FRAME_MB_KEY, 0, -1);
		ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter);
	}
	Unlock();
}

//
// Set reference frame
//
void CProject::SetTargetType(tTargetType type)
{
	Lock();
	m_TargetType = type;
	Unlock();
}

//
// Set reference frame
//
void CProject::SetReferenceFrame(GtkTreePath *path, int mb_object)
{
	Lock();
	gtk_tree_row_reference_free(m_Ref);
	m_RefFrameID = -1;
	m_Ref = NULL;
	m_RefType = REF_UNDEFINED;
	m_RefMBObjID = 0;
	m_OrigCatFile.Clear();
	if (path) {
		m_RefType = REF_FRAME;
		m_RefFrameID = GetFrameID(path);
		m_Ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), path);
		m_RefMBObjID = mb_object;
	}
	Unlock();
}

//
// Set reference frame
//
void CProject::SetReferenceFile(const gchar *cat_file)
{
	Lock();
	gtk_tree_row_reference_free(m_Ref);
	m_RefFrameID = -1;
	m_Ref = NULL;
	m_RefType = REF_UNDEFINED;
	m_RefMBObjID = 0;
	m_OrigCatFile.Clear();
	if (cat_file) {
		m_RefType = REF_CATALOG_FILE;
		m_OrigCatFile = CFileInfo(cat_file);
	}
	Unlock();
}

//
// Get reference frame
//
GtkTreePath *CProject::GetReferencePath(void)
{
	GtkTreePath *path;
	Lock();
	path = (m_Ref ? gtk_tree_row_reference_get_path(m_Ref) : NULL);
	Unlock();
	return path;
}

//
// Get tree path to frame 
//
GtkTreePath *CProject::GetPath(int id)
{
	tGetFileInfo info;

	if (id>0) {
		info.id = id;
		info.path = NULL;
		Lock();
		gtk_tree_model_foreach(GTK_TREE_MODEL(m_Frames), FindFile, &info);
		Unlock();
		return info.path;
	}
	return NULL;
}

// Get tree path to the first file
GtkTreePath *CProject::GetFirstFile(void)
{
	GtkTreePath *path;
	GtkTreeIter iter;

	Lock();
	if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter))
		path = gtk_tree_model_get_path(GTK_TREE_MODEL(m_Frames), &iter);
	else
		path = NULL;
	Unlock();

	return path;
}

// Get tree path to the last file
GtkTreePath *CProject::GetLastFile(void)
{
	int count;
	GtkTreePath *path = NULL;
	GtkTreeIter iter;

	Lock();
	count = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_Frames), NULL);
	if (count>0) {
		gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(m_Frames), &iter, NULL, count-1);
		path = gtk_tree_model_get_path(GTK_TREE_MODEL(m_Frames), &iter);
	} 
	Unlock();
	return path;
}

// Get path to the previous file
GtkTreePath *CProject::GetPreviousFile(GtkTreePath *path)
{
	GtkTreePath *retval;

	Lock();
	GtkTreePath *pPrev = gtk_tree_path_copy(path);
	if (gtk_tree_path_prev(pPrev))
		retval = pPrev;
	else
		retval = NULL;
	Unlock();

	return retval;
}

// Get path to the next file
GtkTreePath *CProject::GetNextFile(GtkTreePath *path)
{
	GtkTreePath *retval = NULL;
	GtkTreeIter iter;

	Lock();
	if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, path) && gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter))
		retval = gtk_tree_model_get_path(GTK_TREE_MODEL(m_Frames), &iter);
	Unlock();
	return retval;
}

// Get frame state
unsigned CProject::GetState(GtkTreePath *pPath)
{
	unsigned state = 0;
	GetValueUInt(pPath, FRAME_STATE, &state);
	return state;
}

// Get frame flags
unsigned CProject::GetFlags(GtkTreePath *pPath)
{
	unsigned flags = 0;
	GetValueUInt(pPath, FRAME_FLAGS, &flags);
	return flags;
}

// Get date of observation
double CProject::GetJulDate(GtkTreePath *pPath)
{
	double jd = 0;
	GetValueDouble(pPath, FRAME_JULDAT, &jd);
	return jd;
}

// Get time correction
double CProject::GetTimeCorrection(GtkTreePath *pPath)
{
	double time_corr = 0;
	GetValueDouble(pPath, FRAME_TIMECORR, &time_corr);
	return time_corr;
}

// Get date of observation
double CProject::GetCCDTemperature(GtkTreePath *pPath)
{
	double ccd_temp = -999.9;
	GetValueDouble(pPath, FRAME_CCDTEMP, &ccd_temp);
	return ccd_temp;
}

// Get number of detected stars
int CProject::GetStars(GtkTreePath *pPath)
{
	int nstars = 0;
	GetValueInt(pPath, FRAME_STARS, &nstars);
	return nstars;
}

// Get number of detected stars
int CProject::GetMatchedStars(GtkTreePath *pPath)
{
	int mstars = 0;
	GetValueInt(pPath, FRAME_MSTARS, &mstars);
	return mstars;
}

// Get color filter
gchar *CProject::GetColorFilter(GtkTreePath *pPath)
{
	gchar *retval = NULL;
	GetValueText(pPath, FRAME_FILTER, &retval);
	return retval;
}

// Get last message
gchar *CProject::GetLastMessage(GtkTreePath *pPath)
{
	gchar *retval = NULL;
	GetValueText(pPath, FRAME_REPORT, &retval);
	return retval;
}

// Get bias frame file 
gchar *CProject::GetBiasFrameFile(GtkTreePath *pPath)
{
	gchar *retval = NULL;
	GetValueText(pPath, FRAME_BIASFILE, &retval);
	return retval;
}

// Get dark frame file
gchar *CProject::GetDarkFrameFile(GtkTreePath *pPath)
{
	gchar *retval = NULL;
	GetValueText(pPath, FRAME_DARKFILE, &retval);
	return retval;
}

// Get flat frame file
gchar *CProject::GetFlatFrameFile(GtkTreePath *pPath)
{
	gchar *retval = NULL;
	GetValueText(pPath, FRAME_FLATFILE, &retval);
	return retval;
}

// Get exposure duration
double CProject::GetExposure(GtkTreePath *pPath)
{
	double exptime = -1.0;
	GetValueDouble(pPath, FRAME_EXPTIME, &exptime);
	return exptime;
}

// Get frame ID
int CProject::GetFrameID(GtkTreePath *pPath)
{
	int frame_id = 0;
	if (pPath) {
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
			gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, FRAME_ID, &frame_id, -1);
	}
	return frame_id;
}

// Get full path to the original file
gchar *CProject::GetSourceFile(GtkTreePath *pPath)
{
	gchar *fpath = NULL;
	if (pPath) {
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
			gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, FRAME_ORIGFILE, &fpath, -1);
	}
	return fpath;
}

// Get name of the temporary file
gchar *CProject::GetImageFileName(GtkTreePath *pPath)
{
	gchar *file = NULL;
	GetValueText(pPath, FRAME_TEMPFILE, &file);
	return file;
}

// Get full path to the temporary file
gchar *CProject::GetImageFile(GtkTreePath *pPath)
{
	gchar *name, *file;

	Lock();
	name = GetImageFileName(pPath);
	Unlock();
	if (name) {
		file = g_build_filename(m_DataDir, name, NULL);
		g_free(name);
		return file;
	}
	return NULL;
}

// Get name of the phootmetry file
gchar *CProject::GetPhotFileName(GtkTreePath *pPath)
{
	gchar *file = NULL;
	GetValueText(pPath, FRAME_PHOTFILE, &file);
	return file;
}

// Get full path to the photometry file
gchar *CProject::GetPhotFile(GtkTreePath *pPath)
{
	gchar *name, *file;
	
	Lock();
	name = GetPhotFileName(pPath);
	Unlock();
	if (name) {
		file = g_build_filename(m_DataDir, name, NULL);
		g_free(name);
		return file;
	}
	return NULL;
}

// Get exposure duration
int CProject::GetMBKey(GtkTreePath *pPath)
{
	int mb_key = -1;
	GetValueInt(pPath, FRAME_MB_KEY, &mb_key);
	return mb_key;
}

// Get exposure duration
int CProject::GetMBObject(GtkTreePath *pPath)
{
	int mb_object = 0;
	GetValueInt(pPath, FRAME_MB_OBJ, &mb_object);
	return mb_object;
}

// Get number of averaged frames
int CProject::GetAveragedFrames(GtkTreePath *pPath)
{
	int avg_frames = 0;
	GetValueInt(pPath, FRAME_AVGFRAMES, &avg_frames);
	return avg_frames;
}

// Get number of accumulated frames
int CProject::GetAccumulatedFrames(GtkTreePath *pPath)
{
	int sum_frames = 0;
	GetValueInt(pPath, FRAME_SUMFRAMES, &sum_frames);
	return sum_frames;
}

void CProject::SetErrorMessage(GtkTreePath *pPath, const char *message)
{
	Lock();
	if (pPath) {
		if (!m_blockUpdates) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
				gtk_list_store_set(m_Frames, &iter, FRAME_REPORT, message, -1);
		}
		else {
			PushToQueueText(pPath, FRAME_REPORT, message);
		}
	}
	Unlock();
}

void CProject::SetErrorCode(GtkTreePath *pPath, int errcode)
{
	if (pPath) {
		char *buf = cmpack_formaterror(errcode);
		if (buf) {
			gchar *msg = FromLocale(buf);
			if (msg) {
				Lock();
				if (!m_blockUpdates) {
					GtkTreeIter iter;
					if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
						gtk_list_store_set(m_Frames, &iter, FRAME_REPORT, msg, -1);
					g_free(msg);
				}
				else {
					PushToQueueText(pPath, FRAME_REPORT, msg);
				}
				Unlock();
			}
			cmpack_free(buf);
		}
	}
}

// Set error message, change the state and set the error flag
void CProject::SetError(GtkTreePath *pPath, const char *message, int state_mask, ...)
{
	va_list ap;
	va_start(ap, state_mask);

	if (pPath) {
		Lock();
		unsigned state = (GetState(pPath) & ~state_mask) | CFILE_ERROR;
		if (!m_blockUpdates) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				gtk_list_store_set(m_Frames, &iter, FRAME_STATE, state, FRAME_REPORT, message, -1);
				tFrameListColumns fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
				while (fieldId >= 0) {
					assert(fieldId != FRAME_ID && fieldId != FRAME_STRINGID && fieldId != FRAME_ORIGFILE);
					resetField(m_Frames, &iter, fieldId);
					fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
				}
			}
		}
		else {
			GtkTreeRowReference *ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), pPath);

			tQueueItem *item1 = new tQueueItem(gtk_tree_row_reference_copy(ref), FRAME_STATE);
			item1->reset = false;
			item1->uvalue = state;
			g_queue_push_tail(m_updateQueue, item1);

			tQueueItem *item2 = new tQueueItem(gtk_tree_row_reference_copy(ref), FRAME_REPORT);
			if (message) {
				item2->reset = false;
				item2->svalue = g_strdup(message);
			}
			g_queue_push_tail(m_updateQueue, item2);

			tFrameListColumns fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
			while (fieldId >= 0) {
				assert(fieldId != FRAME_ID && fieldId != FRAME_STRINGID && fieldId != FRAME_ORIGFILE);
				g_queue_push_tail(m_updateQueue, new tQueueItem(gtk_tree_row_reference_copy(ref), fieldId));
				fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
			}

			gtk_tree_row_reference_free(ref);
		}
		Unlock();
	}

	va_end(ap);
}

void CProject::SetError(GtkTreePath *pPath, int errcode, int state_mask, ...)
{
	va_list ap;
	va_start(ap, state_mask);

	if (pPath) {
		char *buf = cmpack_formaterror(errcode);
		if (buf) {
			gchar *message = FromLocale(buf);
			if (message) {
				Lock();
				unsigned state = (GetState(pPath) & ~state_mask) | CFILE_ERROR;
				if (!m_blockUpdates) {
					GtkTreeIter iter;
					if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
						gtk_list_store_set(m_Frames, &iter, FRAME_STATE, state, FRAME_REPORT, message, -1);
						tFrameListColumns fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
						while (fieldId >= 0) {
							assert(fieldId != FRAME_ID && fieldId != FRAME_STRINGID && fieldId != FRAME_ORIGFILE);
							resetField(m_Frames, &iter, fieldId);
							fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
						}
					}
					g_free(message);
				}
				else {
					GtkTreeRowReference *ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), pPath);

					tQueueItem *item1 = new tQueueItem(gtk_tree_row_reference_copy(ref), FRAME_STATE);
					item1->reset = false;
					item1->uvalue = state;
					g_queue_push_tail(m_updateQueue, item1);

					tQueueItem *item2 = new tQueueItem(gtk_tree_row_reference_copy(ref), FRAME_REPORT);
					item2->reset = false;
					item2->svalue = message;
					g_queue_push_tail(m_updateQueue, item2);

					tFrameListColumns fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
					while (fieldId >= 0) {
						assert(fieldId != FRAME_ID && fieldId != FRAME_STRINGID && fieldId != FRAME_ORIGFILE);
						g_queue_push_tail(m_updateQueue, new tQueueItem(gtk_tree_row_reference_copy(ref), fieldId));
						fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
					}

					gtk_tree_row_reference_free(ref);
				}
				Unlock();
			}
			cmpack_free(buf);
		}
	}

	va_end(ap);
}

// Set report, change the state and clear the error flag
void CProject::SetResult(GtkTreePath *pPath, const char *message, int state_mask, int state_value, ...)
{
	va_list ap;
	va_start(ap, state_value);

	if (pPath) {
		Lock();
		unsigned state = (GetState(pPath) & ~(CFILE_ERROR | state_mask)) | state_value;
		if (!m_blockUpdates) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				gtk_list_store_set(m_Frames, &iter, FRAME_STATE, state, FRAME_REPORT, message, -1);
				tFrameListColumns fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
				while (fieldId >= 0) {
					assert(fieldId != FRAME_ID && fieldId != FRAME_STRINGID && fieldId != FRAME_ORIGFILE);
					switch (fieldId)
					{
					case FRAME_JULDAT:
					case FRAME_TIMECORR:
					case FRAME_OFFSET_X:
					case FRAME_OFFSET_Y:
					case FRAME_TR_XX:
					case FRAME_TR_XY:
					case FRAME_TR_YX:
					case FRAME_TR_YY:
					case FRAME_TR_X0:
					case FRAME_TR_Y0:
					case FRAME_CCDTEMP:
					case FRAME_EXPTIME:
					{
						double value = va_arg(ap, double);
						gtk_list_store_set(m_Frames, &iter, fieldId, value, -1);
						break;
					}

					case FRAME_STATE:
					case FRAME_FLAGS:
					{
						unsigned value = va_arg(ap, unsigned);
						gtk_list_store_set(m_Frames, &iter, fieldId, value, -1);
						break;
					}

					case FRAME_AVGFRAMES:
					case FRAME_SUMFRAMES:
					case FRAME_STARS:
					case FRAME_MSTARS:
					case FRAME_MB_OBJ:
					case FRAME_MB_KEY:
					{
						int value = va_arg(ap, int);
						gtk_list_store_set(m_Frames, &iter, fieldId, value, -1);
						break;
					}

					case FRAME_TEMPFILE:
					case FRAME_PHOTFILE:
					case FRAME_REPORT:
					case FRAME_BIASFILE:
					case FRAME_DARKFILE:
					case FRAME_FLATFILE:
					case FRAME_FILTER:
					{
						const char *value = va_arg(ap, const char*);
						gtk_list_store_set(m_Frames, &iter, fieldId, value, -1);
						break;
					}
					}
					fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
				}
			}
		}
		else {
			GtkTreeRowReference *ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), pPath);

			tQueueItem *item1 = new tQueueItem(gtk_tree_row_reference_copy(ref), FRAME_STATE);
			item1->reset = false;
			item1->uvalue = state;
			g_queue_push_tail(m_updateQueue, item1);

			tQueueItem *item2 = new tQueueItem(gtk_tree_row_reference_copy(ref), FRAME_REPORT);
			if (message) {
				item2->reset = false;
				item2->svalue = g_strdup(message);
			}
			g_queue_push_tail(m_updateQueue, item2);

			tFrameListColumns fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
			while (fieldId >= 0) {
				assert(fieldId != FRAME_ID && fieldId != FRAME_STRINGID && fieldId != FRAME_ORIGFILE);
				tQueueItem *item3 = new tQueueItem(gtk_tree_row_reference_copy(ref), fieldId);
				item3->reset = false;
				switch (fieldId)
				{
				case FRAME_JULDAT:
				case FRAME_TIMECORR:
				case FRAME_OFFSET_X:
				case FRAME_OFFSET_Y:
				case FRAME_TR_XX:
				case FRAME_TR_XY:
				case FRAME_TR_YX:
				case FRAME_TR_YY:
				case FRAME_TR_X0:
				case FRAME_TR_Y0:
				case FRAME_CCDTEMP:
				case FRAME_EXPTIME:
					item3->dvalue = va_arg(ap, double);
					break;

				case FRAME_STATE:
				case FRAME_FLAGS:
					item3->uvalue = va_arg(ap, unsigned);
					break;

				case FRAME_AVGFRAMES:
				case FRAME_SUMFRAMES:
				case FRAME_STARS:
				case FRAME_MSTARS:
				case FRAME_MB_OBJ:
				case FRAME_MB_KEY:
					item3->ivalue = va_arg(ap, int);
					break;

				case FRAME_TEMPFILE:
				case FRAME_PHOTFILE:
				case FRAME_REPORT:
				case FRAME_BIASFILE:
				case FRAME_DARKFILE:
				case FRAME_FLATFILE:
				case FRAME_FILTER:
				{
					const char *value = va_arg(ap, const char*);
					if (value)
						item3->svalue = g_strdup(value);
					break;
				}
				}
				g_queue_push_tail(m_updateQueue, item3);
				fieldId = static_cast<tFrameListColumns>(va_arg(ap, int));
			}

			gtk_tree_row_reference_free(ref);
		}
		Unlock();
	}

	va_end(ap);
}

// Set report, change the state and clear the error flag
void CProject::SetThumbnail(GtkTreePath *pPath, GdkPixbuf *pImage)
{
	if (pPath) {
		Lock();
		if (pImage)
			setField(pPath, FRAME_STATE, GetState(pPath) | CFILE_THUMBNAIL, -1);
		else
			setField(pPath, FRAME_STATE, GetState(pPath) & ~CFILE_THUMBNAIL, -1);
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
			gtk_list_store_set(m_Frames, &iter, FRAME_THUMBNAIL, pImage, -1);
		Unlock();
	}
}

void CProject::SetTrafo(GtkTreePath *pPath, const CmpackMatrix &pMatrix)
{
	GtkTreeIter iter;
	if (pPath) {
		Lock();
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
			gtk_list_store_set(m_Frames, &iter, FRAME_TR_XX, pMatrix.xx,
				FRAME_TR_YX, pMatrix.yx, FRAME_TR_XY, pMatrix.xy, FRAME_TR_YY, pMatrix.yy,
				FRAME_TR_X0, pMatrix.x0, FRAME_TR_Y0, pMatrix.y0, -1);
		}
		Unlock();
	}
}

void CProject::SetOffset(GtkTreePath *pPath, double offset_x, double offset_y)
{
	if (pPath) {
		Lock();
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
			gtk_list_store_set(m_Frames, &iter, FRAME_OFFSET_X, offset_x, FRAME_OFFSET_Y, offset_y, -1);
		Unlock();
	}
}

// Get number of accumulated frames
double CProject::GetOffsetX(GtkTreePath *pPath)
{
	double offset_x = 0;
	if (pPath) {
		Lock();
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
			gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, FRAME_OFFSET_X, &offset_x, -1);
		Unlock();
	}
	return offset_x;
}

// Get number of accumulated frames
double CProject::GetOffsetY(GtkTreePath *pPath)
{
	double offset_y = 0;
	if (pPath) {
		Lock();
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath))
			gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, FRAME_OFFSET_Y, &offset_y, -1);
		Unlock();
	}
	return offset_y;
}

bool CProject::GetTrafo(GtkTreePath *pPath, CmpackMatrix &pMatrix)
{
	pMatrix = CmpackMatrix();
	if (pPath) {
		Lock();
		GtkTreeIter iter;
		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
			gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, FRAME_TR_XX, &pMatrix.xx,
				FRAME_TR_YX, &pMatrix.yx, FRAME_TR_XY, &pMatrix.xy, FRAME_TR_YY, &pMatrix.yy,
				FRAME_TR_X0, &pMatrix.x0, FRAME_TR_Y0, &pMatrix.y0, -1);
		}
		Unlock();
		return (pMatrix.xx != 0 || pMatrix.yx != 0 || pMatrix.xy != 0 || pMatrix.yy != 0);
	}
	return false;
}

// Get Julian date range of frames
bool CProject::GetRange(double &jdmin, double &jdmax)
{
	GtkTreeIter iter;

	jdmin = jdmax = 0;

	// Copy images and photometry files
	gboolean ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter);
	while (ok) {
		gdouble jd;
		gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter,	FRAME_JULDAT, &jd, -1);
		if (jd>0) {
			if (jdmin == 0) {
				jdmin = jdmax = jd;
			} else {
				if (jd < jdmin)
					jdmin = jd;
				if (jd > jdmax)
					jdmax = jd;
			}
		}
		ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter);
	}
	return jdmin>0 && jdmax>0;
}

void CProject::SetOrigBiasFile(const CFileInfo &info)
{
	Lock();
	m_OrigBiasFile = info;
	Unlock();
}

void CProject::SetTempBiasFile(const CFileInfo &info)
{
	Lock();
	m_TempBiasFile = info;
	Unlock();
}

void CProject::SetOrigDarkFile(const CFileInfo &info)
{
	Lock();
	m_OrigDarkFile = info;
	Unlock();
}

void CProject::SetTempDarkFile(const CFileInfo &info)
{
	Lock();
	m_TempDarkFile = info;
	Unlock();
}

void CProject::SetOrigFlatFile(const CFileInfo &info)
{
	Lock();
	m_OrigFlatFile = info;
	Unlock();
}

void CProject::SetTempFlatFile(const CFileInfo &info)
{
	Lock();
	m_TempFlatFile = info;
	Unlock();
}

void CProject::SetTempCatFile(const CFileInfo &info)
{
	Lock();
	m_TempCatFile = info;
	Unlock();
}

void CProject::GetStatus(tStatus *status) const
{
	memset(status, 0, sizeof(tStatus));
	Lock();
	gtk_tree_model_foreach(GTK_TREE_MODEL(m_Frames),
		(GtkTreeModelForeachFunc)MakeStatus, status);
	status->open = isOpen();
	status->readonly = isReadOnly();
	Unlock();
}

tProjectType CProject::ProjectType(void) const
{
	tProjectType retval;
	Lock();
	if (isOpen())
		retval = m_Profile.ProjectType();
	else
		retval = EndOfProjectTypes;
	Unlock();
	return retval;
}

// Copy data files to given folder
bool CProject::CopyFiles(const gchar *datadir, GError **error) 
{
	bool ok = true;
	GtkTreeIter iter;

	if (!force_directory(datadir)) {
		set_error(error, "Failed to create the directory", datadir);
		return false;
	}

	Lock();

	if (m_OrigBiasFile.Valid() && m_TempBiasFile.Valid()) {
		gchar *basename = g_path_get_basename(m_TempBiasFile.FullPath());
		gchar *dstpath = g_build_filename(datadir, basename, NULL);
		ok = copy_file(m_TempBiasFile.FullPath(), dstpath, false, error);
		g_free(dstpath);
		g_free(basename);
	}
	if (ok && m_OrigDarkFile.Valid() && m_TempDarkFile.Valid()) {
		gchar *basename = g_path_get_basename(m_TempDarkFile.FullPath());
		gchar *dstpath = g_build_filename(datadir, basename, NULL);
		ok = copy_file(m_TempDarkFile.FullPath(), dstpath, false, error);
		g_free(dstpath);
		g_free(basename);
	}
	if (ok && m_OrigFlatFile.Valid() && m_TempFlatFile.Valid()) {
		gchar *basename = g_path_get_basename(m_TempFlatFile.FullPath());
		gchar *dstpath = g_build_filename(datadir, basename, NULL);
		ok = copy_file(m_TempFlatFile.FullPath(), dstpath, false, error);
		g_free(dstpath);
		g_free(basename);
	}
	if (ok && m_RefType==REF_CATALOG_FILE && m_OrigCatFile.Valid() && m_TempCatFile.Valid()) {
		gchar *basename = g_path_get_basename(m_TempCatFile.FullPath());
		gchar *dstpath = g_build_filename(datadir, basename, NULL);
		ok = copy_file(m_TempCatFile.FullPath(), dstpath, false, error);
		g_free(dstpath);
		g_free(basename);
	}

	gboolean iterok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_Frames), &iter);
	while (iterok && ok) {
		GtkTreePath *pPath = gtk_tree_model_get_path(GTK_TREE_MODEL(m_Frames), &iter);
		if (pPath) {
			int state = GetState(pPath);
			if ((state & CFILE_CONVERSION) != 0) {
				gchar *ftemp = GetImageFileName(pPath);
				if (ftemp) {
					gchar *srcpath = g_build_filename(m_DataDir, ftemp, NULL);
					gchar *dstpath = g_build_filename(datadir, ftemp, NULL);
					ok = copy_file(srcpath, dstpath, false, error);
					g_free(srcpath);
					g_free(dstpath);
					g_free(ftemp);
				}
			}
			if (ok && (state & CFILE_PHOTOMETRY) != 0) {
				gchar *fphot = GetPhotFileName(pPath);
				if (fphot) {
					gchar *srcpath = g_build_filename(m_DataDir, fphot, NULL);
					gchar *dstpath = g_build_filename(datadir, fphot, NULL);
					ok = copy_file(srcpath, dstpath, false, error);
					g_free(srcpath);
					g_free(dstpath);
					g_free(fphot);
				}
			}
			gtk_tree_path_free(pPath);
		}
		iterok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_Frames), &iter);
	}

	Unlock();

	return ok;
}

//
// Set configuration parameters
//
void CProject::SetProfile(const CProfile &profile)
{
	Lock();
	m_Profile = profile;
	Unlock();
}

//
// Set dialog settings
//
void CProject::SetStr(const char *group, const char *key, const char *val)
{
	Lock();
	g_key_file_set_string(m_Params, group, key, (val!=NULL ? val : ""));
	Unlock();
}
void CProject::SetDbl(const char *group, const char *key, const double val)
{
	Lock();
	g_key_file_set_double(m_Params, group, key, val);
	Unlock();
}
void CProject::SetInt(const char *group, const char *key, const int val)
{
	Lock();
	g_key_file_set_integer(m_Params, group, key, val);
	Unlock();
}
void CProject::SetBool(const char *group, const char *key, bool val)
{
	Lock();
	g_key_file_set_boolean(m_Params, group, key, val);
	Unlock();
}

//
// Get dialog settings
//
char *CProject::GetStr(const char *group, const char *key, const char *defval) const
{
	char *res;
	GError *error = NULL;

	Lock();
	res = g_key_file_get_string(m_Params, group, key, &error);
	if (error) {
		res = (defval ? g_strdup(defval) : NULL);
		g_error_free(error);
	}
	Unlock();
	return res;
}
double CProject::GetDbl(const char *group, const char *key, double defval) const
{
	double res;
	GError *error = NULL;

	Lock();
	res = g_key_file_get_double(m_Params, group, key, &error);
	if (error) {
		res = defval;
		g_error_free(error);
	}
	Unlock();
	return res;
}
int CProject::GetInt(const char *group, const char *key, int defval, int minval, int maxval) const
{
	int res;
	GError *error = NULL;

	Lock();
	res = g_key_file_get_integer(m_Params, group, key, &error);
	if (error) {
		res = defval;
		g_error_free(error);
	}
	Unlock();
	return LimitValue(res, minval, maxval);
}
int CProject::GetInt(const char *group, const char *key, const int defval) const
{
	int res;
	GError *error = NULL;

	Lock();
	res = g_key_file_get_integer(m_Params, group, key, &error);
	if (error) {
		res = defval;
		g_error_free(error);
	}
	Unlock();
	return res;
}
bool CProject::GetBool(const char *group, const char *key, const bool defval) const
{
	bool res;
	GError *error = NULL;

	Lock();
	res = g_key_file_get_boolean(m_Params, group, key, &error)!=0;
	if (error) {
		res = defval;
		g_error_free(error);
	}
	Unlock();
	return res;
}

// Enlist a request for resetting the frame properies
void CProject::resetFields(GtkTreePath *pPath, ...)
{
	va_list ap;
	va_start(ap, pPath);

	Lock();
	if (pPath && m_DataDir && !m_ReadOnly) {
		if (!m_blockUpdates) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				tFrameListColumns field = static_cast<tFrameListColumns>(va_arg(ap, int));
				while (field >= 0) {
					assert(field != FRAME_ID && field != FRAME_STRINGID && field != FRAME_ORIGFILE);
					resetField(m_Frames, &iter, field);
					field = static_cast<tFrameListColumns>(va_arg(ap, int));
				}
			}
		}
		else {
			GtkTreeRowReference *ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), pPath);
			if (ref) {
				tFrameListColumns field = static_cast<tFrameListColumns>(va_arg(ap, int));
				while (field >= 0) {
					assert(field != FRAME_ID && field != FRAME_STRINGID && field != FRAME_ORIGFILE);
					g_queue_push_tail(m_updateQueue, new tQueueItem(gtk_tree_row_reference_copy(ref), field));
					field = static_cast<tFrameListColumns>(va_arg(ap, int));
				}
				gtk_tree_row_reference_free(ref);
			}
		}
	}
	Unlock();

	va_end(ap);
}

void CProject::setField(GtkTreePath *pPath, ...)
{
	va_list ap;
	va_start(ap, pPath);

	if (pPath) {
		Lock();

		if (!m_blockUpdates) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				tFrameListColumns field = static_cast<tFrameListColumns>(va_arg(ap, int));
				while (field >= 0) {
					assert(field != FRAME_ID && field != FRAME_STRINGID && field != FRAME_ORIGFILE);
					assert(field != FRAME_ID && field != FRAME_STRINGID && field != FRAME_ORIGFILE);
					switch (field)
					{
					case FRAME_STATE:
					case FRAME_FLAGS:
						gtk_list_store_set(m_Frames, &iter, field, va_arg(ap, unsigned), -1);
						break;

					case FRAME_JULDAT:
					case FRAME_TIMECORR:
					case FRAME_OFFSET_X:
					case FRAME_OFFSET_Y:
					case FRAME_TR_XX:
					case FRAME_TR_XY:
					case FRAME_TR_YX:
					case FRAME_TR_YY:
					case FRAME_TR_X0:
					case FRAME_TR_Y0:
					case FRAME_EXPTIME:
					case FRAME_CCDTEMP:
						gtk_list_store_set(m_Frames, &iter, field, va_arg(ap, double), -1);
						break;

					case FRAME_AVGFRAMES:
					case FRAME_SUMFRAMES:
					case FRAME_STARS:
					case FRAME_MSTARS:
					case FRAME_MB_OBJ:
					case FRAME_MB_KEY:
						gtk_list_store_set(m_Frames, &iter, field, va_arg(ap, int), -1);
						break;

					case FRAME_TEMPFILE:
					case FRAME_PHOTFILE:
					case FRAME_REPORT:
					case FRAME_BIASFILE:
					case FRAME_DARKFILE:
					case FRAME_FLATFILE:
					case FRAME_FILTER:
						gtk_list_store_set(m_Frames, &iter, field, va_arg(ap, const char*), -1);
						break;
					}
					field = static_cast<tFrameListColumns>(va_arg(ap, int));
				}
			}
		}
		else {
			GtkTreeRowReference *ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), pPath);
			if (ref) {
				tFrameListColumns field = static_cast<tFrameListColumns>(va_arg(ap, int));
				while (field >= 0) {
					assert(field != FRAME_ID && field != FRAME_STRINGID && field != FRAME_ORIGFILE);
					switch (field)
					{
					case FRAME_STATE:
					case FRAME_FLAGS:
					{
						tQueueItem *item = new tQueueItem(gtk_tree_row_reference_copy(ref), field);
						item->reset = false;
						item->uvalue = va_arg(ap, unsigned);
						g_queue_push_tail(m_updateQueue, item);
						break;
					}

					case FRAME_JULDAT:
					case FRAME_TIMECORR:
					case FRAME_OFFSET_X:
					case FRAME_OFFSET_Y:
					case FRAME_TR_XX:
					case FRAME_TR_XY:
					case FRAME_TR_YX:
					case FRAME_TR_YY:
					case FRAME_TR_X0:
					case FRAME_TR_Y0:
					case FRAME_EXPTIME:
					case FRAME_CCDTEMP:
					{
						tQueueItem *item = new tQueueItem(gtk_tree_row_reference_copy(ref), field);
						item->reset = false;
						item->dvalue = va_arg(ap, double);
						g_queue_push_tail(m_updateQueue, item);
						break;
					}

					case FRAME_AVGFRAMES:
					case FRAME_SUMFRAMES:
					case FRAME_STARS:
					case FRAME_MSTARS:
					case FRAME_MB_OBJ:
					case FRAME_MB_KEY:
					{
						tQueueItem *item = new tQueueItem(gtk_tree_row_reference_copy(ref), field);
						item->reset = false;
						item->ivalue = va_arg(ap, int);
						g_queue_push_tail(m_updateQueue, item);
						break;
					}

					case FRAME_TEMPFILE:
					case FRAME_PHOTFILE:
					case FRAME_REPORT:
					case FRAME_BIASFILE:
					case FRAME_DARKFILE:
					case FRAME_FLATFILE:
					case FRAME_FILTER:
					{
						const char *message = va_arg(ap, const char*);
						tQueueItem *item = new tQueueItem(gtk_tree_row_reference_copy(ref), field);
						if (message) {
							item->reset = false;
							item->svalue = g_strdup(message);
						}
						g_queue_push_tail(m_updateQueue, item);
						break;
					}
					}
					field = static_cast<tFrameListColumns>(va_arg(ap, int));
				}
				gtk_tree_row_reference_free(ref);
			}
		}

		Unlock();
	}

	va_end(ap);
}

void CProject::PushToQueueText(GtkTreePath *pPath, tFrameListColumns fieldId, const gchar *message)
{
	tQueueItem *item = new tQueueItem(gtk_tree_row_reference_new(GTK_TREE_MODEL(m_Frames), pPath), fieldId);
	if (message) {
		item->reset = false;
		item->svalue = g_strdup(message);
	}
	g_queue_push_tail(m_updateQueue, item);
}


//
// Get value of unsigned int type
//
bool CProject::GetValueUInt(GtkTreePath *pPath, tFrameListColumns fieldId, unsigned *value)
{
	bool ok = false;
	if (fieldId == FRAME_STATE || fieldId == FRAME_FLAGS) {
		for (GList *qptr = m_updateQueue->tail; qptr != NULL; qptr = qptr->prev) {
			tQueueItem *qitem = (tQueueItem*)(qptr->data);
			if (qitem->fieldId == fieldId) {
				GtkTreePath *qPath = gtk_tree_row_reference_get_path(qitem->frameRef);
				if (qPath) {
					if (gtk_tree_path_compare(pPath, qPath) == 0) {
						if (qitem->reset)
							*value = 0;
						else
							*value = qitem->uvalue;
						ok = true;
						break;
					}
					gtk_tree_path_free(qPath);
				}
			}
		}
		if (!ok) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, fieldId, value, -1);
				ok = true;
			}
		}
	}
	return ok;
}


//
// Get value of unsigned int type
//
bool CProject::GetValueInt(GtkTreePath *pPath, tFrameListColumns fieldId, int *value)
{
	bool ok = false;
	if (fieldId == FRAME_STARS || fieldId == FRAME_MSTARS || fieldId == FRAME_AVGFRAMES || fieldId == FRAME_SUMFRAMES || fieldId == FRAME_MB_OBJ || fieldId == FRAME_MB_KEY) {
		for (GList *qptr = m_updateQueue->tail; qptr != NULL; qptr = qptr->prev) {
			tQueueItem *qitem = (tQueueItem*)(qptr->data);
			if (qitem->fieldId == fieldId) {
				GtkTreePath *qPath = gtk_tree_row_reference_get_path(qitem->frameRef);
				if (qPath) {
					if (gtk_tree_path_compare(pPath, qPath) == 0) {
						if (qitem->reset)
							*value = 0;
						else
							*value = qitem->ivalue;
						ok = true;
						break;
					}
					gtk_tree_path_free(qPath);
				}
			}
		}
		if (!ok) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, fieldId, value, -1);
				ok = true;
			}
		}
	}
	return ok;
}


//
// Get value of unsigned int type
//
bool CProject::GetValueDouble(GtkTreePath *pPath, tFrameListColumns fieldId, double *value)
{
	bool ok = false;
	if (fieldId == FRAME_JULDAT || fieldId == FRAME_TIMECORR || fieldId == FRAME_OFFSET_X || fieldId == FRAME_OFFSET_Y || fieldId == FRAME_TR_XX || fieldId == FRAME_TR_XY || 
		fieldId == FRAME_TR_YX || fieldId == FRAME_TR_YY || fieldId == FRAME_TR_X0 || fieldId == FRAME_TR_Y0 || fieldId == FRAME_CCDTEMP || fieldId == FRAME_EXPTIME) {
		for (GList *qptr = m_updateQueue->tail; qptr != NULL; qptr = qptr->prev) {
			tQueueItem *qitem = (tQueueItem*)(qptr->data);
			if (qitem->fieldId == fieldId) {
				GtkTreePath *qPath = gtk_tree_row_reference_get_path(qitem->frameRef);
				if (qPath) {
					if (gtk_tree_path_compare(pPath, qPath) == 0) {
						if (qitem->reset)
							*value = getDefaultValue(fieldId);
						else
							*value = qitem->dvalue;
						ok = true;
						break;
					}
					gtk_tree_path_free(qPath);
				}
			}
		}
		if (!ok) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, fieldId, value, -1);
				ok = true;
			}
		}
	}
	return ok;
}


//
// Get value of unsigned int type
//
bool CProject::GetValueText(GtkTreePath *pPath, tFrameListColumns fieldId, gchar **value)
{
	bool ok = false;
	if (fieldId == FRAME_TEMPFILE || fieldId == FRAME_PHOTFILE || fieldId == FRAME_REPORT || fieldId == FRAME_BIASFILE || fieldId == FRAME_DARKFILE || fieldId == FRAME_FLATFILE || fieldId == FRAME_FILTER) {
		for (GList *qptr = m_updateQueue->tail; qptr != NULL; qptr = qptr->prev) {
			tQueueItem *qitem = (tQueueItem*)(qptr->data);
			if (qitem->fieldId == fieldId) {
				GtkTreePath *qPath = gtk_tree_row_reference_get_path(qitem->frameRef);
				if (qPath) {
					if (gtk_tree_path_compare(pPath, qPath) == 0) {
						if (qitem->reset || !qitem->svalue)
							*value = NULL;
						else 
							*value = g_strdup(qitem->svalue);
						ok = true;
						break;
					}
					gtk_tree_path_free(qPath);
				}
			}
		}
		if (!ok) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				gtk_tree_model_get(GTK_TREE_MODEL(m_Frames), &iter, fieldId, value, -1);
				ok = true;
			}
		}
	}
	return ok;
}


//
// Reset field value
//
double CProject::getDefaultValue(tFrameListColumns fieldId)
{
	switch (fieldId)
	{
	case FRAME_CCDTEMP:
		return -999.9;
	case FRAME_EXPTIME:
		return -1.0;
	default:
		return 0;
	}
}


//
// Reset field value
//
void CProject::resetField(GtkListStore *model, GtkTreeIter *pIter, tFrameListColumns fieldId)
{
	switch (fieldId)
	{
	case FRAME_JULDAT:
	case FRAME_TIMECORR:
	case FRAME_OFFSET_X:
	case FRAME_OFFSET_Y:
	case FRAME_TR_XX:
	case FRAME_TR_XY:
	case FRAME_TR_YX:
	case FRAME_TR_YY:
	case FRAME_TR_X0:
	case FRAME_TR_Y0:
	case FRAME_CCDTEMP:
	case FRAME_EXPTIME:
	{
		double value = getDefaultValue(fieldId);
		gtk_list_store_set(model, pIter, fieldId, value, -1);
		break;
	}

	case FRAME_STATE:
	case FRAME_FLAGS:
	case FRAME_AVGFRAMES:
	case FRAME_SUMFRAMES:
	case FRAME_STARS:
	case FRAME_MSTARS:
	case FRAME_MB_OBJ:
	case FRAME_MB_KEY:
		gtk_list_store_set(model, pIter, fieldId, 0, -1);
		break;

	case FRAME_TEMPFILE:
	case FRAME_PHOTFILE:
	case FRAME_REPORT:
	case FRAME_BIASFILE:
	case FRAME_DARKFILE:
	case FRAME_FLATFILE:
	case FRAME_FILTER:
		gtk_list_store_set(model, pIter, fieldId, NULL, -1);
		break;
	}
}

void CProject::blockUpdates(bool enable)
{
	Lock();
	m_blockUpdates = enable;
	if (!m_blockUpdates)
		applyPendingUpdates();
	Unlock();
}

void CProject::applyPendingUpdates(void)
{
	Lock();

	tQueueItem *item = (tQueueItem*)g_queue_pop_head(m_updateQueue);
	while (item) {
		GtkTreePath *pPath = gtk_tree_row_reference_get_path(item->frameRef);
		if (pPath) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(GTK_TREE_MODEL(m_Frames), &iter, pPath)) {
				if (item->reset) {
					resetField(m_Frames, &iter, item->fieldId);
				}
				else {
					switch (item->fieldId)
					{
					case FRAME_STATE:
					case FRAME_FLAGS:
						gtk_list_store_set(m_Frames, &iter, item->fieldId, item->uvalue, -1);
						break;

					case FRAME_JULDAT:
					case FRAME_TIMECORR:
					case FRAME_OFFSET_X:
					case FRAME_OFFSET_Y:
					case FRAME_TR_XX:
					case FRAME_TR_XY:
					case FRAME_TR_YX:
					case FRAME_TR_YY:
					case FRAME_TR_X0:
					case FRAME_TR_Y0:
					case FRAME_CCDTEMP:
					case FRAME_EXPTIME:
						gtk_list_store_set(m_Frames, &iter, item->fieldId, item->dvalue, -1);
						break;

					case FRAME_AVGFRAMES:
					case FRAME_SUMFRAMES:
					case FRAME_STARS:
					case FRAME_MSTARS:
					case FRAME_MB_OBJ:
					case FRAME_MB_KEY:
						gtk_list_store_set(m_Frames, &iter, item->fieldId, item->ivalue, -1);
						break;

					case FRAME_TEMPFILE:
					case FRAME_PHOTFILE:
					case FRAME_REPORT:
					case FRAME_BIASFILE:
					case FRAME_DARKFILE:
					case FRAME_FLATFILE:
					case FRAME_FILTER:
						gtk_list_store_set(m_Frames, &iter, item->fieldId, item->svalue, -1);
						break;
					}
				}
			}
			gtk_tree_path_free(pPath);
		}
		delete item;
		item = (tQueueItem*)g_queue_pop_head(m_updateQueue);
	}
	Unlock();
}

void CProject::PruneUpdateItems(void)
{
	Lock();

	if (!g_queue_is_empty(m_updateQueue)) {
		GQueue *newQueue = g_queue_new();
		tQueueItem *item = (tQueueItem*)g_queue_pop_head(m_updateQueue);
		while (item) {
			GtkTreePath *itemPath = gtk_tree_row_reference_get_path(item->frameRef);
			if (itemPath) {
				// Push update item to the new queue
				g_queue_push_tail(newQueue, item);
				gtk_tree_path_free(itemPath);
			}
			else {
				// Remove update items that does not refer to a valid frame anymore
				delete item;
			}
			item = (tQueueItem*)g_queue_pop_head(m_updateQueue);
		}
		// The old queue is empty, delete it and replace with a new queue
		g_queue_free(m_updateQueue);
		m_updateQueue = newQueue;
	}
	Unlock();
}
