// 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. 

// Copyright 2003 Liam Girdwood  

#include "catalog.hh"
#include "deep_object.hh"
#include "star_object.hh"
#include "skymap_object.hh"
#include "galaxy.hh"
#include "nebula.hh"
#include "asteroid.hh"
#include "comet.hh"
#include <iostream>

namespace Pollux
{
	
/*! \fn Catalog::Catalog ()
*
* Constructor
*/
Catalog::Catalog ()
{
	m_size = 0;
	m_progress = 0;
	m_ra_div_size = 360 / RA_DIV;
	m_dec_div_size = 180 / DEC_DIV;
	unclip();
}

/*! \fn Catalog::Catalog(std::string& name, std::string& description, 
				std::string& maintainer, std::string& type, 
				std::string& created, std::string& cat_version,
				std::string& cat_history, std::string& cat_ack)
* \param name Catalog name
* \param description Catalog description
* \param maintainer Catalog maintainer
* \param type Catalog type
* \param created Catalog creation date
* \param version catalog version
* \param history catalog history
* \param ack catalog acknowledgements
*
* Constructor
*/
Catalog::Catalog(std::string& name, std::string& description, 
				std::string& maintainer, std::string& type, 
				std::string& created, std::string& version,
				std::string& history, std::string& ack)
{
	// init catalog details
	m_name = name;
	m_description = description;
	m_maintainer = maintainer;
	m_type = type;
	m_version = version;
	m_history = history;
	m_acknowledge = ack;
	m_size = 0;
	m_add_size = 0;
	m_date = created;
	m_ra_div_size = 360 / RA_DIV;
	m_dec_div_size = 180 / DEC_DIV;
	
	// init catalog config
	unclip();
}

/*! \fn Catalog::~Catalog()
*
* Free all memory
*/
Catalog::~Catalog()
{
	for (int ra = 0; ra < RA_DIV; ra++) {
		for (int dec = 0; dec < DEC_DIV; dec++) {
			for (int mag = 0; mag < MAG_MAX - MAG_MIN; mag++) {
				m_objects[ra][dec][mag].clear();
			}
		}
	}
}

/*! \fn double Catalog::get_progress()
* \return Catalog loading progresss 0..1
*
* get the catalog load progress
*/
double Catalog::get_progress()
{
	return m_progress;
}

/*! \fn bool Catalog::add (Castor::AstroObject* object)
* \param object Pointer to object to be added
* \return true if successfully added.
*
* Add an object to the catalog. This will fail if any object
* properties are out of bounds. i.e. RA > 360
*/
bool Catalog::add (Castor::AstroObject* object)
{
	// init any object properties
	object->init();
	
	// calculate offset into catalog
	double ra, dec;
	std::string id;
	object->get_equ_posn(0, ra, dec);
	object->get_id(id);
	int ra_offset = (int)(ra / m_ra_div_size);
	int dec_offset = ((int)dec + 90) / m_dec_div_size;
	int mag_offset = (int)object->get_mag();
	
	// throw out bad values
	// i.e. no ID, mag, ra or dec out of range
	if ((mag_offset < MAG_MIN || mag_offset > MAG_MAX) ||
		(ra_offset > RA_DIV || ra_offset < 0) ||
		(dec_offset > DEC_DIV || dec_offset < 0) || id == "") {
			std::cerr << "Catalog:: value out of range " << ra_offset <<" " << dec_offset <<
			" " << mag_offset << " id " << id << std::endl;
			return false;
	}
	
	m_objects[ra_offset][dec_offset][mag_offset - MAG_MIN].push_back(object);
	m_add_size++;
	return true;
}


/*! \fn void Catalog::clip (double min_ra, double min_dec, double max_ra, double max_dec)
* \param min_ra Minimum RA
* \param min_dec Minimum DEC
* \param max_ra Maximum RA
* \param max_dec Maximum DEC
* \param min_mag Minimum magnitude
* \param max_mag Maximum magnitude
*
* Set the catalog clipping area
*/
void Catalog::clip (double min_ra, double min_dec, double max_ra, double max_dec, double min_mag, double max_mag)
{
	m_clip_min_ra = (int)min_ra / m_ra_div_size;
	m_clip_min_dec = ((int)min_dec + 90) / m_dec_div_size;
	
	m_clip_max_ra = ((int)max_ra / m_ra_div_size) + 1;
	if (m_clip_max_ra > RA_DIV)
		m_clip_max_ra = RA_DIV;

	m_clip_max_dec = (((int)max_dec + 90) / m_dec_div_size) + 1;
	if (m_clip_max_dec > DEC_DIV)
		m_clip_max_dec = DEC_DIV;
	
	m_clip_min_mag = (int)min_mag;
	if (m_clip_min_mag < MAG_MIN)
		m_clip_min_mag = MAG_MIN;
	
	m_clip_max_mag = (int)max_mag;
	if (m_clip_max_mag > MAG_MAX)
		m_clip_max_mag = MAG_MAX;
}

/*! \fn void Catalog::unclip ()
* 
* Reset the catalog clipping area, i.e. ra = 0..360, dec = -90..90
*/
void Catalog::unclip ()
{
	m_clip_min_ra = 0;
	m_clip_min_dec = 0;
	m_clip_max_ra = RA_DIV;
	m_clip_max_dec = DEC_DIV;
	m_clip_min_mag = MAG_MIN;
	m_clip_max_mag = MAG_MAX;
}

/*! \fn int Catalog::get_objects (std::vector<Castor::AstroObject*>& result); 
* \param result Catalog objects
* \return Number of objects found.
*
* Get all catalog objects within clipping area.
*/
int Catalog::get_objects(std::vector<Castor::AstroObject*>& result)
{
	int count = 0;
	std::vector<Castor::AstroObject*>::iterator i;

	for (int ra = m_clip_min_ra; ra < m_clip_max_ra; ra++) {
		for (int dec = m_clip_min_dec; dec < m_clip_max_dec; dec++) {
			for (int mag = m_clip_min_mag; mag < m_clip_max_mag; mag++) {
				for (i = m_objects[ra][dec][mag - MAG_MIN].begin(); i != m_objects[ra][dec][mag - MAG_MIN].end(); i++) {
					result.push_back(*i);
					count ++;
				}
			}
		}
	}
	return count;
}


/*! \fn int Catalog::load (std::string& name, std::string& path, Glib::Dispatcher* signal)
* \param name Catalog name
* \param path Catalog path
* \param signal Pointer to update thread signal
* \return Number of catalog objects loaded.
*
* Load an astro catalog from disk into memory.
*/
int Catalog::load (std::string& name, std::string& path, Glib::Dispatcher* signal)
{
	std::ifstream* file;
	int count = 0;
	
	// load the catalog descriptor file
	std::string file_name = path + name + ".ncd";
	file = new std::ifstream(file_name.c_str());
	if (file->is_open()) { 
		// get the file header
		file_header* header = new file_header();
		file->read((char*)header, sizeof(file_header));
		m_name = header->cat_name;
		m_description = header->cat_description;
		m_type = header->cat_type;
		m_date = header->cat_date;
		m_size = header->cat_size;
		std::cout << "cat size " << m_size << std::endl;
		unclip();
	
		// get any unknown element descriptors
		extra_element e;
		for (int i = 0; i < header->unknown_elements; i++) {
			file_extra_element element;
			file->read ((char*)&element, sizeof(file_extra_element));
			e.name = element.name;
			e.units = element.units;
			e.description = element.description;
			m_extra_info.push_back(e);
		}
		file->close();
	} else {
		std::cerr << __FILE__ << " " << __LINE__ << " could not load " << file_name << std::endl;
		delete file;
		return 0;
	}
	delete file;
	
	// load the objects
	std::string lfile = path + name;
	count = load_sectors (lfile, signal);

	// update the catalog with the unknown element names
	if (count) {
		std::vector<extra_element>::iterator i;
		
		// object unknown name is static, so pos does not matter
		for (i=m_extra_info.begin(); i != m_extra_info.end(); ++i) {
			m_unknown_names.push_back((*i).name);
		}
	}
	
	// did we load the correct amount of objects
	if (m_add_size != m_size) {
		std::cerr << __FILE__ << " " << __LINE__ << " error catalog size mismatch." <<
			" Loaded " << m_add_size << " expected " << m_size << std::endl;
	}
	
	return count;
}

/*! \fn int Catalog::save (std::string& name, std::string& path, Glib::Dispatcher* signal)
* \param name Catalog file name
* \param path Catalog path
* \return number of objects saved to file
*
* Save catalog from memory to file.
*/
int Catalog::save (std::string& name, std::string& path, Glib::Dispatcher* signal)
{
	std::ofstream* file;
	int count = 0;
	
	std::cout << path << " " << m_name << " " << std::endl;
	std::string file_name = path + m_name + ".ncd";
	file = new std::ofstream(file_name.c_str());
	
	if (file->is_open()) { 
		// write the file header
		file_header* header = new file_header();
		strncpy (header->cat_name, m_name.c_str(), CAT_NAME_SIZE);
		strncpy (header->cat_description, m_description.c_str(), CAT_DESCRIPTION_SIZE);
		strncpy (header->cat_type, m_type.c_str(), CAT_TYPE_SIZE);
		strncpy (header->cat_date, m_date.c_str(), CAT_DATE_SIZE);
		strncpy (header->cat_maintainer, m_maintainer.c_str(), CAT_MAINTAINER_SIZE);
		header->cat_size = m_size;
		header->unknown_elements = m_extra_info.size();
		file->write ((char*)header, sizeof(file_header));
		
		//write the extra elements
		std::vector<extra_element>::iterator i;
		for (i = m_extra_info.begin(); i != m_extra_info.end(); i++) {
			file_extra_element e;
			strncpy (e.name, (*i).name.c_str(), ELEMENT_NAME_SIZE);
			strncpy (e.units, (*i).units.c_str(), ELEMENT_UNIT_SIZE);
			strncpy (e.description, (*i).description.c_str(), ELEMENT_DESCRIPTION_SIZE);
			file->write ((char*)&e, sizeof (file_extra_element));
		}
		file->close();
	} else {
		std::cerr << __FILE__ << "could not save " << file_name << std::endl;
	}
	
	delete file;
	
	// save the objects
	std::string sfile = path + m_name;
	return save_sectors (sfile, signal);
}

/*! \fn bool Catalog::add_description (std::string& name, std::string& units,
					std::string& description)
* \param name Parameter name
* \param units Parameter units
* \param description Parameter description
*
* Add a new object parameter description to the catalog
*/
void Catalog::add_description (std::string& name, std::string& units,
					std::string& description)
{
	extra_element element;
	element.name = name;
	element.units = units;
	element.description = description;

	m_extra_info.push_back(element);
}

/*! \fn int Catalog::save_sectors (std::string& file, Glib::Dispatcher* signal)
* \param file filename
* \return number of objects saved
*
* Save catalog sectors to file. Sectors are based on 
* magnitude bands for deep objects and semi-major axis
* for near sky objects.
*
*/
int Catalog::save_sectors (std::string& file, Glib::Dispatcher* signal)
{
	int count = 0, num_objects;
	char buffer[256];
	std::vector<Castor::AstroObject*> list;
	std::vector<Castor::AstroObject*>::iterator i;
	
	// save naked eye objects < mag 6
	sprintf(buffer, "%s-m6.noc",file.c_str());
	std::ofstream* ofile = new std::ofstream (buffer);
	if (ofile->is_open()) {
		clip (0, -90, 360, 90, MAG_MIN, MAG_EYE);
		count += get_objects (list);
		for (i = list.begin(); i != list.end(); i++)
			(*i)->save(ofile);
	}
	list.clear();
	ofile->close();
	delete ofile;
	
	// save faint magnitude divisions > mag 6
	for (int mag_idx = MAG_EYE; mag_idx < MAG_MAX; mag_idx++) {
		sprintf(buffer, "%s-m%d.noc", file.c_str(), mag_idx + 1);
			
		ofile = new std::ofstream (buffer);
		if (ofile->is_open()) {
			clip (0, -90, 360, 90, mag_idx, mag_idx + 1);
			num_objects = get_objects (list);
			std::cout << __FILE__ << " saving " << mag_idx << " " << num_objects << std::endl;
			if (num_objects	> 0 ) {
				count += num_objects;
				for (i = list.begin(); i != list.end(); i++)
					(*i)->save(ofile);
			}
			ofile->close();
			list.clear();
		}
		delete ofile;
	}
	
	unclip();
	return count;
}


/*! \fn int Catalog::load_file (char* file)
* \return number of objects loaded.
*
* Load catalog file.
*/
int Catalog::load_file (char* file)
{
	int count = 0;
	
	std::ifstream* ifile = new std::ifstream (file);
	if (ifile->is_open()) {
		std::cout << "opening " << file << std::endl;
		while (!(ifile->eof())) {
			// create new object
			Castor::AstroObject* object = 0;
			if (m_type == "skymap")
				object = new Castor::SkymapObject();
			else if (m_type == "star")
				object = new Castor::StarObject();
			else if (m_type == "galaxy")
				object = new Castor::Galaxy();
			else if (m_type == "asteroid")
				object = new Castor::Asteroid();
			else if (m_type == "comet")
				object = new Castor::Comet();
			else {	
				// error, unsupported type 
				std::cerr << __FILE__ << "unsupported type" << std::endl;
				ifile->close();
				delete ifile;
				return 0;
			}
		
			// load in the object data
			if (object) {
				object->load(ifile);
				if (!(ifile->eof())) {
					add (object);
					count++;
				} else
					delete object;
			}
		}
	} else {
		std::cerr << __FILE__ << " could not load " << file << std::endl;
	}
	ifile->close();
	delete ifile;
	
	return count;
}


/*! \fn int Catalog::load_sectors (std::string& file, Glib::Dispatcher* signal)
* 
* Load catalog sector into memory
*/
int Catalog::load_sectors (std::string& file, Glib::Dispatcher* signal)
{
	int count = 0;
	char buffer[256];

	// set progressbar
	if (signal) {
		m_progress = 0.0;
		(*signal)();
	}
	
	// mag division < mag 6
	sprintf(buffer, "%s/%s-m6.noc",file.c_str(), m_name.c_str());
	count += load_file(buffer);
	if (signal) {
		m_progress = (double)count / m_size;
		(*signal)();
	}
	
	// faint objects mag > 6
	for (int i = MAG_EYE; i < MAG_MAX; i++) {
		sprintf(buffer, "%s/%s-m%d.noc",file.c_str(), m_name.c_str(),i+1);
		count += load_file(buffer);
		if (!count)
			break;
		if (signal) {
			m_progress = (double)count / m_size;
			(*signal)();
		}
	}
	m_progress = 0;
	
	return count;
}

/*! \fn void Catalog::get_name (std::string& name);
*
* Get catalog name
*/
void Catalog::get_name (std::string& name)
{
	name = m_name;
}

/*! \fn void Catalog::get_description (std::string& desc);
*
* Get catalog description
*/
void Catalog::get_description (std::string& desc)
{
	desc = m_description;
}

/*! \fn void Catalog::get_type (std::string& type);
*
* Get catalog type
*/
void Catalog::get_type (std::string& type)
{
	type = m_type;
}

/*! \fn void Catalog::get_maintainer (std::string& maintainer);
*
* Get catalog maintainer
*/
void Catalog::get_maintainer (std::string& maintainer)
{
	maintainer = m_maintainer;
}

/*! \fn void Catalog::get_date (std::string& date);
* 
* Get catalog creation date
*/
void Catalog::get_date (std::string& date)
{
	date = m_date;
}

/*! \fn int Catalog::get_size ();
*
* Get catalog size
*/
int Catalog::get_size ()
{
	return m_size;
}

/*! \fn void Catalog::get_ack (std::string& date);
* 
* Get catalog creation date
*/
void Catalog::get_ack (std::string& ack)
{
	ack = m_acknowledge;
}

/*! \fn void Catalog::get_history (std::string& history);
*
* Get catalog history
*/
void Catalog::get_history (std::string& history)
{
	history = m_history;
}

/*! \fn void Catalog::get_version (std::string& version);
*
* Get catalog version
*/
void Catalog::get_version (std::string& version)
{
	version = m_version;
}

}


syntax highlighted by Code2HTML, v. 0.9.1