// This file is part of the Coral project.
// Please read the copyright notice in the LICENSE file.

#include <coral/core/Project.h>
#include <coral/core/Element.h>
#include <coral/lib/assert.h>
#include <coral/lib/struct_helpers.h>
#include <coral/core/Collection.h>
#include <coral/core/util/TransactionManager.h>
#include <coral/lib/debug.h>

#include <coral/core/util/uElement.h>

#include <coral/core/io/xmi_reader_common.h>

#include <map>
#include <sstream>

#include <functional>

NAMESPACE_BEGIN(coral);

typedef std::map<const Element*, Project*, std::less<const Element*> > storage_map_t;
static storage_map_t storage_map;

typedef std::set<Project*, std::less<Project*> > storages_t;
storages_t storages;

Project::Project() {
	m_device = 0;
	m_inode = 0;
	m_isinvalid = false;
	m_isUserProject = true;
	m_xmiformat="2.0";
	autounref<Sequence> _o(new Sequence());
	storages.insert(this);
	m_roots = _o.get();
	_o.release();

	/*
	 * _Always_ create the transaction, even though
	 * we're "paused" e.g. when loading. This enables
	 * the transaction to only include NewProjectCommands
	 * and enables e.g. the GUI display them.
	 */
	if (Transaction::theTM
	    && Transaction::theTM->isCreatingTransaction() ) {
		coral_assert(!Transaction::theTM->isUndoOrRedo());
		Transaction::NewProjectCommand* x;
		MUST_WORK(x = new Transaction::NewProjectCommand(this));
		Transaction::theTM->partialModifyCmd(x);
	}
}

Project::~Project() throw () {
	dlog << DEBUGLEVEL_DEBUG << "DEBUG Deleting Project "
	     << this << " "
	     << m_filename << std::endl;
	Iterator o(m_roots);
	ref_counted* t;
	while ((t = o.next()) != NULL) {
		Element* e = dynamic_cast<Element*>(t);
		if (!e) continue;
		storage_map.erase(e);
	}
	storages.erase(this);
	m_roots->unref();
}

std::string Project::__str__() const {
	std::ostringstream s;
	s << "Project " << this;
	return s.str();
}

std::vector<coral::Project*> getAllProjects() {
	std::vector<coral::Project*> s;
	for (storages_t::iterator i = storages.begin();
	     i != storages.end();
	     ++i) {
		Project* st = *i;
		if (st->isInvalid()) continue;
		s.push_back(*i);
	}
	return s;
}

void Project::fixLoadedRef() {
	dlog << DEBUGLEVEL_DEBUG << "DEBUG fixing ref information for Project "
	     << this << " " << this->getFilename() << std::endl;
	for (old_loaded_ref_info_t::iterator i
		     = m_old_loaded_ref_info.begin();
	     i != m_old_loaded_ref_info.end();
		) {
		(*i).first->unref();
		(*i).second->unref();
		m_old_loaded_ref_info.erase(i++);
	}

	Iterator i(m_roots);
	ref_counted* t;
	while ((t = i.next()) != NULL) {
		Element* root = dynamic_cast<Element*>(t);
		coral_assert(root);
		autounref<coral::Set> o(root->__getAllParts__());
		ref_counted* t2;
		Iterator i2(o.get());
		while ((t2 = i2.next()) != NULL) {
			Element* e = dynamic_cast<Element*>(t2);
			coral_assert(e);
			const char* uuid = e->__XMIid__().c_str();
			std::auto_ptr<coral::io::_uuidref> u
				(new coral::io::_uuidref(uuid));
			m_old_loaded_ref_info[u.get()] = e;
			e->ref();
			//dlog << DEBUGLEVEL_DEBUG << "DEBUG Adding " << u.get()->id << std::endl;
			u.release();
		}
	}
}

Element* findByNamesNext(Element* e,
			 std::vector<std::string>& names,
			 unsigned int index);

Element* findByNames(autounref<Iterator>& iter,
		     std::vector<std::string>& names,
		     unsigned int index) {
	for (; ref_counted* r = iter->next();) {
		Element* e = dynamic_cast<Element*>(r);
		if (!e) continue;
		const ElementString* es =
			coral::util::Element__getName(e);
		if (!es) continue;
		if (names[index].c_str()
		    != es->__getString__().toUTF8())
			continue;
		// Name matches!
		if (index + 1 == names.size())
			return e;
		Element* found = findByNamesNext(e, names, index+1);
		if (found) return found;
	}
	return NULL;
}

Element* findByNamesNext(Element* e,
			 std::vector<std::string>& names,
			 unsigned int index) {
	for (Element::slots_type::iterator allslots = e->begin();
	     allslots != e->end();
	     ++allslots) {
		const Property* pr = (*allslots).first;
		if (!pr->isComposition())
			continue;
		Slot* s = (*allslots).second;
		coral_assert(s);
		autounref<Iterator> iter(s->__iter__());
		coral_assert(iter.get());
		Element* found = findByNames(iter,
					     names,
					     index);
		if (found) return found;
	}
	return NULL;
}

Element* Project::findRef(const coral::io::_ref* ioref) {
	coral_assert(ioref);
	if (dynamic_cast<const coral::io::xpathref*>(ioref)) {
		const char* a = ioref->id.c_str();
		if (!strncmp("#//", a, 3)) {
			a += 3;
			std::vector<std::string> names;
			while (true) {
				const char* slash = strchr(a, '/');
				if (!slash) {
					names.push_back(a);
					break;
				}
				std::string s(a, slash);
				names.push_back(s);
				a = slash + 1;
			}

			for (Iterator i(m_roots);
			     ref_counted* r = i.next(); ) {
				Element* e = dynamic_cast<Element*>(r);
				if (!e) continue;
				Element* found = findByNamesNext(e,
								 names,
								 0);
				if (found) return found;
			}
		}
		return NULL;
	}
	old_loaded_ref_info_t::const_iterator i
		= m_old_loaded_ref_info.find(ioref);
	if (i != m_old_loaded_ref_info.end()) {
		return (*i).second;
	}
	return NULL;
}

void Project::invalidate() {
	for (old_loaded_ref_info_t::iterator i
		     = m_old_loaded_ref_info.begin();
	     i != m_old_loaded_ref_info.end();
		) {
		(*i).first->unref();
		(*i).second->unref();
		m_old_loaded_ref_info.erase(i++);
	}

	std::set<Element*> all_elems_BUG; // need two sets...
	Set all_elems;
	for (Iterator i(m_roots); ref_counted* r = i.next(); ) {
		Element* e = throwing_cast<Element*>(r);
		autounref<Set> allchildren(e->__getAllParts__());
		for (Iterator c(allchildren.get());
		     ref_counted* rc = c.next(); ) {
			Element* cc = throwing_cast<Element*>(rc);
			all_elems.insert(cc);
			all_elems_BUG.insert(cc);
		}
	}

	for (Iterator i(m_roots); ref_counted* r = i.next(); ) {
		Element* e = static_cast<Element*>(r);
		e->__detachUsFromEverybodyElse__(all_elems_BUG);
	}
	
	Transaction::PauseTransaction p(true);

	for (Iterator i(&all_elems); ref_counted* r = i.next(); ) {
		Element* e = static_cast<Element*>(r);
		e->__resetAttrs__();
	}

	m_isinvalid = true;

	coral_assert(m_roots->size() == 0);
}

Project* Project::getProject(const Element* root) {
	coral_assert(root);
	storage_map_t::const_iterator i = storage_map.find(root);
	if (i == storage_map.end()) return NULL;
	Project* s = (*i).second;
	if (s && s->isInvalid()) return NULL;
	return s;
}
void Project::setProject(const Element* root,
			 Project* info) {
	/*
	 * Note either info is NULL or you must have
	 * a reference on root before calling this
	 */
	coral_assert(root);
	temp_ref _t(info);
	storage_map_t::iterator i = storage_map.find(root);
	if (i != storage_map.end()) {
		Project* old = (*i).second;
		coral_assert(old);
		storage_map.erase(i);
		old->m_roots->drop(root);
		old->unref();
	}
	if (info) {
		storage_map[root] = info;
		info->m_roots->append(root);
		info->ref();
	}
}

void Project::setFilename(const std::string& f) {
	std::string name(getCanonicalFilename(f));

	if (f == m_filename && name == m_hreffilename)
		return;

	/*
	 * _Always_ create the transaction, even though
	 * we're "paused" e.g. when loading. This enables
	 * the transaction to only include NewProjectCommands
	 * and enables e.g. the GUI display them.
	 */
	if (Transaction::theTM
	    && Transaction::theTM->isCreatingTransaction() ) {
		coral_assert(!Transaction::theTM->isUndoOrRedo());
		Transaction::ProjectNameChangeCommand* x;
		MUST_WORK(x = new Transaction::ProjectNameChangeCommand(this, m_filename, m_hreffilename));
		Transaction::theTM->partialModifyCmd(x);
	}

	__setFilename_nocheck(f);
	__setHrefFilename(name);
	struct stat statbuf;
	int err = stat(f.c_str(), &statbuf);
	if (err == 0) {
		m_device = statbuf.st_dev;
		m_inode = statbuf.st_ino;
	} else {
		m_device = 0;
		m_inode = 0;
	}
}

void Project::__setFilename_nocheck(const std::string& f) {
	m_filename = f;
}

void Project::__setHrefFilename(const std::string& f) {
	m_hreffilename = f;
}


/*
 * CORALPATH
 */
typedef std::vector<std::string> env_coral_path_t;
env_coral_path_t env_coral_path;


autounref<coral::Project>
Project::getProjectByFilename(const std::string& filename,
			      const std::string& relative_path,
			      bool* did_create) {
	/*
	 * Note that we use three mechanisms to determine
	 * Project identity
	 * - pointer value
	 * - device/inode numbers
	 * - short-form names.
	 */

	if (filename.empty()) {
		if (did_create)
			*did_create = true;
		Project* s = new Project();
		dlog << DEBUGLEVEL_DEBUG << "Created new storage " << s << std::endl;
		return autounref<coral::Project>(s);
	}

	/*
	 * Pointer value?
	 */
	void* ptr = NULL;
	std::istringstream i(filename);
	if (i >> ptr && i.eof()) {
		dlog << DEBUGLEVEL_DEBUG << "DEBUG Matched pointer "
		     << ptr << " and "
		     << filename << std::endl;
		Project* s = reinterpret_cast<Project*>(ptr);
		storages_t::iterator j = storages.find(s);
		if (j == storages.end()) {
			dlog << DEBUGLEVEL_DEBUG << "DEBUG Didn't find a"
			     << " suitable Project!"
			     << std::endl;
			if (did_create) {
				*did_create = true;
			}
			return autounref<Project>(new Project());
		}
		dlog << DEBUGLEVEL_DEBUG << "DEBUG Found a suitable Project"
		     << std::endl;
		if (did_create) {
			*did_create = false;
		}
		autounref<Project> _s(s);
		s->ref();
		return _s;
	}

	/*
	 * Then the device/inode or href matching
	 */

	std::string truefilename = filename;

	dev_t device = 0;
	ino_t inode = 0;
	struct stat statbuf;
	int err = -1;
	/*
	 * Try relative path first
	 */
	if (!relative_path.empty()
	    && filename[0] != '/') {
		truefilename = relative_path + '/' + filename;
		err = stat(truefilename.c_str(), &statbuf);
	}
	/*
	 * Try current directory
	 */
	if (err) {
		truefilename = filename;
		err = stat(truefilename.c_str(), &statbuf);
	}
	/*
	 * Try CORALPATH
	 */
	if (err
	    && filename[0] != '/') {
		for (env_coral_path_t::const_iterator j =
			     env_coral_path.begin();
		     j != env_coral_path.end();
		     ++j) {
			truefilename = *j + filename;
			err = stat(truefilename.c_str(), &statbuf);
			if (!err) break;
		}
	}

	if (err == 0) {
		device = statbuf.st_dev;
		inode = statbuf.st_ino;
	} else {
		truefilename = filename;
	}

	/*
	 * The canonical filename is calculated here.
	 * This means users only have to care about the real filename
	 * and any shortforms ("href names") are dealt with here
	 * only.
	 */
	std::string hreffilename(getCanonicalFilename(truefilename));
		
	for (storages_t::const_iterator i = storages.begin();
	     i != storages.end();
	     ++i) {
		Project* s = *i;
		coral_assert(s);
		if (s->isInvalid()) continue;
		bool ok = false;
		if (device && device == s->m_device
		    && inode == s->m_inode) {
			ok = true;
		}
		if (!ok && s->m_hreffilename == hreffilename) {
			ok = true;
			/*
			 * Update device/inode information if
			 * we didn't have any.
			 */
			if (!s->m_device) {
				s->m_device = device;
				s->m_inode = inode;
			}
		}
		if (ok) {
			if (did_create) {
				*did_create = false;
			}
			autounref<Project> _s(s);
			s->ref();
			return _s;
		}
	}

#if 0
	dlog << DEBUGLEVEL_DEBUG << "DEBUG Didn't find Project "
	     << filename << std::endl;
#endif
	if (did_create) {
		*did_create = true;
	}
#if 0
	dlog << DEBUGLEVEL_DEBUG << "DEBUG Creating Project "
	     << filename << std::endl;
#endif

	autounref<Project> _s(new Project());
	_s.get()->setFilename(truefilename);
	return _s;
}

std::string getCanonicalFilename(const std::string& inp_) {
	std::string input(inp_);
	input.erase(0, input.find("/coral/core") + 1);
	input.erase(0, input.find("/coral/lang") + 1);

	for (env_coral_path_t::const_iterator i =
		     env_coral_path.begin();
	     i != env_coral_path.end();
	     ++i) {
		const std::string& prefix = *i;
		if (strncmp(input.c_str(),
			    prefix.c_str(),
			    prefix.size()) == 0) {
			input.erase(0, prefix.size());
			break;
		}
	}
	return input;
}

std::vector<std::string> getCoralPath() {
	return env_coral_path;
}

void setCoralPath(std::vector<std::string> v) {
	for (env_coral_path_t::iterator i =
		     v.begin();
	     i != v.end();
	     ++i) {
		std::string& s = *i;
		if (s[s.size()-1] == '/') continue;
		s.append("/");
	}

	env_coral_path.swap(v);

	//  Recalculate href names for projects
	coral::Transaction::MakeTransaction tr;
	for (storages_t::iterator i = storages.begin();
	     i != storages.end();
	     ++i) {
		Project* st = *i;
		if (st->isInvalid()) continue;
		st->setFilename(st->getFilename());
	}

	dlog << DEBUGLEVEL_INFO;
	dlog << "INFO Setting CORALPATH..." << std::endl;
	for (env_coral_path_t::const_iterator i =
		     env_coral_path.begin();
	     i != env_coral_path.end();
	     ++i) {
		dlog << "INFO " << *i << std::endl;
	}
	dlog << "INFO Done." << std::endl;
}

NAMESPACE_END();

