/*
    PIP - Platform Independent Primitives
    Object, base class of some PIP classes, provide EVENT -> EVENT_HANDLER mechanism
    Copyright (C) 2016  Ivan Pelipenko peri4ko@gmail.com

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "piobject.h"
#include "pifile.h"
#include "pisysteminfo.h"


/** \class PIObject
 * \brief This is base class for any classes which use events -> handlers mechanism.
 * \details
 * \section PIObject_sec0 Events and Event handlers
 * %PIObject provide notification mechanism similar Qt but implemented
 * on language capabilities without any special preprocessors or compilers.
 * Any class inherits PIObject should use macro \a PIOBJECT() immediate
 * after declaration to proper compile.
 * 
 * Event is a some abstract event that can be raised at any time.
 * Event is a function but declared with special macro \a EVENT().
 * To raise event simply execute event function.
 * 
 * Event handler is a function but declared with special macro
 * \a EVENT_HANDLER(). You can use event handlers as ordinary functions.
 * 
 * Main goal of this mechanism is perform abstract connections between
 * various objects. This functionality provide macro \a CONNECT() which
 * connect some event of first object to some event handler or event of
 * second object. Each event can be connected any times to any event handlers.
 * 
 * \image html events_handlers.png
 * 
 * Example: \snippet piobject.cpp main
 * Result:
\code{.cpp}
handler B: 2 , 0.5
handler A: event to handler
handler A: event to event
\endcode
 */


PIVector<PIObject * > PIObject::objects;
PIMutex PIObject::__eh_mutex;
PIMap<PIString, PIObject::__EHData> PIObject::__eh_data;


PIString PIObject::__EHFunc::fullFormat() const {
	PIString ret = type_ret + " " + scope + "::" + func_name +"(";
	for (int i = 0; i < types.size_s(); ++i) {
		if (i > 0) ret += ", ";
		ret += types[i] + " " + names[i];
	}
	ret += ")";
	return ret;
}


PIObject::PIObject(const PIString & name): _signature_(__PIOBJECT_SIGNATURE__), emitter_(0), thread_safe_(false) {
	piMonitor.objects++;
	//__PIVariantInitBuiltin__();
	setName(name);
	setDebug(true);
	objects << this;
	//piCout << "new" << this;
}


PIObject::~PIObject() {
	//piCout << "delete" << this;
	piMonitor.objects--;
	objects.removeAll(this);
	piDisconnect(this);
}


void PIObject::piConnect(const PIString & src, const PIString & sig, void * dest, void * ev_h) {
	PIObject * o = findByName(src);
	if (o == 0) {
		piCout << "[PIObject] Can`t find object with name \"" << src << "\"!";
		return;
	}
	PIMutexLocker _ml(o->mutex_connect);
	PIMutexLocker _mld(((PIObject*)dest)->mutex_connect, ((PIObject*)dest) != o);
	o->connections << Connection(ev_h, 0, sig, (PIObject*)dest, dest);
	((PIObject*)dest)->connectors << o;
}


void PIObject::piConnect(PIObject * src, const PIString & sig, const PIString & dest, void * ev_h) {
	PIObject * o = findByName(dest);
	if (o == 0) {
		piCout << "[PIObject] Can`t find object with name \"" << dest << "\"!";
		return;
	}
	PIMutexLocker _ml(src->mutex_connect);
	PIMutexLocker _mld(o->mutex_connect, src != o);
	src->connections << Connection(ev_h, 0, sig, o, o);
	((PIObject*)o)->connectors << src;
}


void PIObject::piConnect(const PIString & src, const PIString & sig, const PIString & dest, void * ev_h) {
	PIObject * s = findByName(src);
	if (s == 0) {
		piCout << "[PIObject] Can`t find object with name \"" << src << "\"!";
		return;
	}
	PIObject * d = findByName(dest);
	if (d == 0) {
		piCout << "[PIObject] Can`t find object with name \"" << dest << "\"!";
		return;
	}
	PIMutexLocker _ml(s->mutex_connect);
	PIMutexLocker _mld(d->mutex_connect, s != d);
	s->connections << Connection(ev_h, 0, sig, d, d);
	d->connectors << s;
}

/*
PIStringList PIObject::events() {
	PIStringList l;
	for (PIMap<NamedFunction, PIString>::const_iterator i = signals_.begin(); i != signals_.end(); i++)
		l << (*i).first;
	return l;
}
*/

PIStringList PIObject::methodsEH() {
	PIMutexLocker ml(__eh_mutex);
	PIStringList ret;
	__EHData & ehd(__eh_data[className()]);
	piForeachC (__EHPair & eh, ehd.eh_func)
		ret << eh.second.fullFormat();
	return ret;
}


bool PIObject::isMethodEHContains(const PIString & name) const {
	PIMutexLocker ml(__eh_mutex);
	__EHData & ehd(__eh_data[className()]);
	piForeachC (__EHPair & eh, ehd.eh_func)
		if (eh.second.func_name == name)
			return true;
	return false;
}


PIString PIObject::methodEHArguments(const PIString & name) const {
	PIMutexLocker ml(__eh_mutex);
	__EHData & ehd(__eh_data[className()]);
	piForeachC (__EHPair & eh, ehd.eh_func)
		if (eh.second.func_name == name)
			return eh.second.arguments();
	return PIString();
}


PIString PIObject::methodEHFullFormat(const PIString & name) const {
	PIMutexLocker ml(__eh_mutex);
	__EHData & ehd(__eh_data[className()]);
	piForeachC (__EHPair & eh, ehd.eh_func)
		if (eh.second.func_name == name)
			return eh.second.fullFormat();
	return PIString();
}


PIString PIObject::methodEHFromAddr(const void * addr) const {
	return methodEH(addr).func_name;
}


PIVector<PIObject::__EHFunc> PIObject::findEH(const PIString & name) const {
	PIVector<__EHFunc> ret;
	__EHData & ehd(__eh_data[className()]);
	piForeachC (__EHPair & eh, ehd.eh_func)
		if (eh.second.func_name == name)
			ret << eh.second;
	return ret;
}


PIObject::__EHFunc PIObject::methodEH(const void * addr) const {
	PIMutexLocker ml(__eh_mutex);
	return __eh_data[className()].eh_func.value(addr);
}


void PIObject::piConnect(PIObject * src, const PIString & sig, PIObject * dest_o, void * dest, void * ev_h, void * e_h, int args, const char * loc) {
	//piCout << "piConnect ...";
	//piCout << "piConnect" << src << (void*)(dest) << sig;
	//piCout << "piConnect" << src->className() << "->" << ((PIObject*)dest)->className();
	PIMutexLocker _ml(src->mutex_connect);
	PIMutexLocker _mld(dest_o->mutex_connect, src != dest_o);
	
	src->connections << Connection(ev_h, e_h, sig, dest_o, dest, args);
	//piCout << "piConnect" << ((PIObject*)dest) << sig << ((PIObject*)dest)->connectors.size_s() << "...";
	//piCout << "addConnector" << dest_o << src;
	dest_o->connectors << src;
	//piCout << "piConnect" << ((PIObject*)dest) << sig << ((PIObject*)dest)->connectors.size_s();
	//piCout << "piConnect ok";
}


bool PIObject::piConnectU(PIObject * src, const PIString & ename, PIObject * dest_o, void * dest, const PIString & hname, const char * loc) {
	if (src == 0 || dest_o == 0 || dest == 0) return false;
	if (!src->isPIObject()) {
		piCout << "[piConnectU] \"" << ename << "\" -> \"" << hname << "\" error: source object is not PIObject! (" << loc << ")";
		return false;
	}
	if (!dest_o->isPIObject()) {
		piCout << "[piConnectU] \"" << ename << "\" -> \"" << hname << "\" error: destination object is not PIObject! (" << loc << ")";
		return false;
	}
	PIMutexLocker ml(__eh_mutex);
	PIMutexLocker mls(src->mutex_connect);
	PIMutexLocker mld(dest_o->mutex_connect, src != dest_o);
	PIVector<__EHFunc> m_src = src->findEH(ename), m_dest = dest_o->findEH(hname);
	if (m_src.isEmpty()) {
		piCout << "[piConnectU] Error: can`t find event \"" << ename << "\" in class \"" << src->className() << "\"! (" << loc << ")";
		return false;
	}
	if (m_dest.isEmpty()) {
		piCout << "[piConnectU] Error: can`t find handler \"" << hname << "\" in class \"" << dest_o->className() << "\"! (" << loc << ")";
		return false;
	}
	void * addr_src(0), * addr_dest(0);
	int args(0);
	piForeachC (__EHFunc & fs, m_src) {
		if (addr_src != 0) break;
		piForeachC (__EHFunc & fd, m_dest) {
			if (addr_src != 0) break;
			if (fs.arguments().startsWith(fd.arguments()) || fd.arguments().isEmpty()) {
				addr_src = fs.addr;
				addr_dest = fd.addr;
				args = fd.names.size_s();
			}
		}
	}
	if (addr_src == 0) {
		piCout << "[piConnectU] Error: can`t find suitable pair of event \"" << ename << "\" in class \"" << src->className()
				<< "\" and handler \"" << hname << "\" in class \"" << dest_o->className() << "\"! (" << loc << ")";
		return false;
	}
	//piCout << "connect" << ename << "->" << hname << "with" << args << "args";
	src->connections << PIObject::Connection(addr_dest, addr_src, ename, dest_o, dest, args);
	dest_o->connectors << src;
	return true;
}


void PIObject::piDisconnect(PIObject * src, const PIString & sig, PIObject * dest, void * ev_h) {
	PIMutexLocker _ml(src->mutex_connect);
	PIMutexLocker _mld(dest->mutex_connect, src != dest);
	for (int i = 0; i < src->connections.size_s(); ++i) {
		Connection & cc(src->connections[i]);
		if (cc.event == sig && cc.dest_o == dest && cc.slot == ev_h) {
			src->connections.remove(i);
			i--;
		}
	}
	dest->updateConnectors();
}


void PIObject::piDisconnect(PIObject * src, const PIString & sig, PIObject * dest) {
	PIMutexLocker _ml(src->mutex_connect);
	PIMutexLocker _mld(dest->mutex_connect, src != dest);
	for (int i = 0; i < src->connections.size_s(); ++i) {
		Connection & cc(src->connections[i]);
		if (cc.event == sig && cc.dest_o == dest) {
			src->connections.remove(i);
			i--;
		}
	}
	dest->updateConnectors();
}


void PIObject::piDisconnect(PIObject * src, const PIString & sig) {
	PIMutexLocker _ml(src->mutex_connect);
	for (int i = 0; i < src->connections.size_s(); ++i) {
		Connection & cc(src->connections[i]);
		if (cc.event == sig) {
			PIObject * dest = cc.dest_o;
			src->connections.remove(i);
			i--;
#ifndef ANDROID
			PIMutexLocker _mld(dest->mutex_connect, src != dest);
#endif
			dest->updateConnectors();
		}
	}
}


void PIObject::piDisconnect(PIObject * src) {
	src->deleted();
	PIMutexLocker _ml(src->mutex_connect);
	PIVector<PIObject * > cv = src->connectors.toVector();
	piForeach (PIObject * o, cv) {
		if (o == src) continue;
#ifndef ANDROID
		PIMutexLocker _mld(o->mutex_connect, src != o);
#endif
		PIVector<Connection> & oc(o->connections);
		for (int i = 0; i < oc.size_s(); ++i) {
			//piCout << "    check" << (void*)(oc[i].dest_o) << "==" << (void*)(src);
			if (oc[i].dest_o == src) {
				oc.remove(i);
				--i;
			}
		}
	}
	piForeachC (PIObject::Connection & c, src->connections)
		c.dest_o->connectors.remove(src);
	src->connections.clear();
}


void PIObject::updateConnectors() {
	//piCout << "*** updateConnectors" << this;
	connectors.clear();
	piForeach (PIObject * o, objects) {
		if (o == this) continue;
		PIVector<Connection> & oc(o->connections);
		piForeach (Connection & c, oc)
			if (c.dest == this)
				connectors << o;
	}
}


bool PIObject::execute(const PIString & method) {
	if (method.isEmpty()) return false;
	PIVector<__EHFunc> ml = findEH(method);
	piForeachC (__EHFunc & m, ml) {
		if (!m.names.isEmpty()) continue;
		((void(*)(void*))m.addr)(this);
		return true;
	}
	piCoutObj << "Error: can`t find event or handler \"" << (method + "()") << "\" to execute!";
	return false;
}




void PIObject::dump(const PIString & line_prefix) const {
	//printf("dump %s \"%s\"\n", className(), name().data());
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "class " << className() << " (" << (const void*)this << ", \"" << name() << "\") {";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    properties {";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "        count: " << properties_.size_s();
	//printf("dump %d properties\n", properties_.size());
	piForeachC (Property p, properties_)
		if (p.first != "name")
			PICout(PICoutManipulators::AddNewLine) << line_prefix << "        " << p.first << ": " << p.second;
	//printf("dump %d properties ok\n", properties_.size());
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    }";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    methodsEH {";
	__EHData & ehd(__eh_data[className()]);
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "        count: " << ehd.eh_func.size_s();
	//printf("dump %d methods\n", ehd.eh_func.size());
	piForeachC (__EHPair & eh, ehd.eh_func) {
		PICout(PICoutManipulators::AddNewLine) << line_prefix << "        " << eh.second.fullFormat();
	}
	//printf("dump %d methods ok\n", ehd.eh_func.size());
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    }";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    connections {";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "        count: " << connections.size_s();
	//printf("dump %d connections\n",connections.size());
	piForeachC (Connection & c, connections) {
		PIObject * dst = c.dest_o;
		__EHFunc hf = dst->methodEH(c.slot);
		__EHFunc ef = methodEH(c.signal);
		if (hf.func_name.isEmpty()) hf.func_name = "[BROKEN]";
		else hf.func_name += "(" + hf.arguments() + ")";
		PIString src(c.event);
		if (!ef.func_name.isEmpty())
			src = ef.func_name + "(" + ef.arguments() + ")";
		PICout(PICoutManipulators::AddNewLine) << line_prefix << "        " << src << " -> " << dst->className() << " (" << c.dest << ", \"" << dst->name()  << "\")::" << hf.func_name;
	}
	//printf("dump %d connections ok\n",connections.size());
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "    }";
	PICout(PICoutManipulators::AddNewLine) << line_prefix << "}";
}




void dumpApplication() {
	//printf("dump application ...\n");
	PIDateTime cd = PIDateTime::current();
	PISystemInfo * pi = PISystemInfo::instance();
	PICout(PICoutManipulators::AddNewLine) << "application {";
	PICout(PICoutManipulators::AddNewLine) << "    PIP version: " << PIPVersion();
	PICout(PICoutManipulators::AddNewLine) << "    processors: " << pi->processorsCount;
	PICout(PICoutManipulators::AddNewLine) << "    hostname: \"" << pi->hostname << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    user: \"" << pi->user << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    exec command: \"" << pi->execCommand << "\"";
	PICout(PICoutManipulators::AddNewLine) << "    started: " << pi->execDateTime.toString();
	PICout(PICoutManipulators::AddNewLine) << "    uptime, s: " << (cd.toSystemTime() - pi->execDateTime.toSystemTime()).toSeconds();
	PICout(PICoutManipulators::AddNewLine) << "    PIObjects {";
	PICout(PICoutManipulators::AddNewLine) << "        count: " << PIObject::objects.size_s();
	piForeachC (PIObject * o, PIObject::objects)
		o->dump("        ");
	PICout(PICoutManipulators::AddNewLine) << "    }";
	PICout(PICoutManipulators::AddNewLine) << "}";
	//printf("dump application done\n");
}


bool dumpApplicationToFile(const PIString & path) {
	PIFile f(path + "_tmp");
	f.setName("__S__DumpFile");
	f.clear();
	if (!f.open(PIIODevice::WriteOnly)) return false;
	bool ba = PICout::isBufferActive();
	PICout::setBufferActive(true, true);
	dumpApplication();
	f << PICout::buffer();
	f.close();
	PICout::setBufferActive(ba, true);
	PIFile::rename(path + "_tmp", path);
	return true;
}
