#include "cdutils_core.h"
#include "cdutils_parser.h"
#include "piconfig.h"
#include "piiobytearray.h"
#include "piiostring.h"
#include "pifile.h"

using namespace CDUtils;

const char CDCore::app_config[] =
		"include = cd_ip.conf\n\
		port_rec = 2\n\
		port_send = 1\n\
		[connection]\n\
		device.cd = peer://cd_app:cd_pult #s\n\
		[]\n\
		connectionmodel = AAAM2Xja7VXLTttAFD12QpsikKjUSixYlLbKEpIUtVIlVEfqhk2FWuiGRRolUYtoHgrmpYiv6IItf8AveMMH9E/YsG6Ph3sde5hGoQoblLGuPHfunTPjc49nADxDA110+LTYC7FrPCAPeAO+vZu+aX7c/8PGd45WCJC0OGcfT6FDnmfSTPtwhZFt3HjgDs/Qtu5jPbZHtI/x50XfIzMQbdwEolbg9INP4ku++myPaUtCHYRaT2j1ldIh3VP60/Qff8vSfXLu9BP6JX9K/0TVH6jqVe22P1X/fao/oddWu/paDs1vBf9Jv/EZ91clbyHqv7BL6sscDOd4v4WTqs6jzaHGJ8QJerxlpJSpdZ7IWFJvDW7I2JxZqIM62k6A57RZmMQGmlyrxdV+WGBnmR01mXPI267hBKwp4FeBeo9VPtssxyb7rzHg1B7T9nCMU45U8BZlWuVWtIcD/CRGOqtsbW09851tXsHN0UTlLIAdASjSXnLyLn+H7L2+xbGYvC63Ezqg543egkLmn8qnRF6USbM4Qp9godkhzI777Ne5bCIt/5UtGz2o/yGby0nKpjqmbOa1ynkjmyzIrzvIZUeBPjvlUmbh32EFJbGyJZhR8YcvlS+3TpjhqeWSyvUkpbI9plSWtcKLcsK05beOJVEnhaEFfHEH+RwpeMcpn1JKGqWMNOL+G6wZyahlpdVOtufKfbDS+guLke9O\n\
		";

const char CDCore::pult_config[] =
		"include = cd_ip.conf\n\
		port_rec = 1\n\
		port_send = 2\n\
		[connection]\n\
		device.cd = peer://cd_pult:cd_app #s\n\
		[]\n\
		connectionmodel = AAAM2Xja7VXLTttAFD12QpsikKjUSixYlLbKEpIUtVIlVEfqhk2FWuiGRRolUYtoHgrmpYiv6IItf8AveMMH9E/YsG6Ph3sde5hGoQoblLGuPHfunTPjc49nADxDA110+LTYC7FrPCAPeAO+vZu+aX7c/8PGd45WCJC0OGcfT6FDnmfSTPtwhZFt3HjgDs/Qtu5jPbZHtI/x50XfIzMQbdwEolbg9INP4ku++myPaUtCHYRaT2j1ldIh3VP60/Qff8vSfXLu9BP6JX9K/0TVH6jqVe22P1X/fao/oddWu/paDs1vBf9Jv/EZ91clbyHqv7BL6sscDOd4v4WTqs6jzaHGJ8QJerxlpJSpdZ7IWFJvDW7I2JxZqIM62k6A57RZmMQGmlyrxdV+WGBnmR01mXPI267hBKwp4FeBeo9VPtssxyb7rzHg1B7T9nCMU45U8BZlWuVWtIcD/CRGOqtsbW09851tXsHN0UTlLIAdASjSXnLyLn+H7L2+xbGYvC63Ezqg543egkLmn8qnRF6USbM4Qp9godkhzI777Ne5bCIt/5UtGz2o/yGby0nKpjqmbOa1ynkjmyzIrzvIZUeBPjvlUmbh32EFJbGyJZhR8YcvlS+3TpjhqeWSyvUkpbI9plSWtcKLcsK05beOJVEnhaEFfHEH+RwpeMcpn1JKGqWMNOL+G6wZyahlpdVOtufKfbDS+guLke9O\n\
		";


int __Core_Initializer__::count_(0);
CDCore * __Core_Initializer__::__instance__(0);
const uchar header_direct   = 0x80;
const uchar header_transfer = 0x81;


__Core_Initializer__::__Core_Initializer__() {
	count_++;
	if (count_ > 1) return;
	__instance__ = new CDCore();
}


__Core_Initializer__::~__Core_Initializer__() {
	count_--;
	if (count_ < 0) {
		count_ = 0;
		return;
	}
	if (count_ > 0) return;
	if (__instance__) {
		delete __instance__;
		__instance__ = 0;
	}
}




CDCore::CDCore() {
	setName("CDCore");
	x_timer.setName("__S__.CDCore.x_timer");
	datatr.setPacketSize(960);
	CONNECTU(&connection, dataReceivedEvent, this, dataReceived);
	CONNECTU(PICout::Notifier::object(), finished, this, piCoutFinished);
	/*PIString s(app_config);
	connection.configureFromString(&s);
	connection.start();*/
	need_rebuild_x = x_pult_side = false;
	k_.cd_type_ = CDType::cdK;
	x_.cd_type_ = CDType::cdX;
	c_.cd_type_ = CDType::cdC;
	m_.cd_type_ = CDType::cdM;
	initRoot(&k_);
	initRoot(&x_);
	initRoot(&c_);
	initRoot(&m_);

	CONNECTU(&sendt, started, this, sendThread)
	CONNECTU(&datatr, sendRequest, this, dtSendRequest)
	CONNECTU(&datatr, receiveFinished, this, dtReceiveFinished)
	CONNECTU(&x_timer, tickEvent, this, xTimerTick)

	/*k_[1] = KType(1, "123", "120+3", "comment");
	k_[2] = KType(2, "1", "2", "comm");
	k_[4] = KType(4, "-0.6", "-6/10", "mment");
	k_.section(10)[5] = KType(5, "8", "2*2*2", "88");
	k_.section(10).section(50)[100] = KType(100, "8", "2*2*2", "88");
	k_.section(11)[3] = KType(3, "1", "1", "88");
	k_.section(11)[4] = KType(4, "0", "0", "88");
	k_.section(11)[6] = KType(6, "0", "0", "88");*/
	//piCout << s;
}


CDCore::~CDCore() {
	x_timer.stop(true);
	datatr.stop();
	sendt.stop();
	sendt.waitForFinish(10);
	connection.stop();
}


void CDCore::cd_write(CDSection * cd, PIIODevice * d) {
	cd->write(d, PIString());
}


void CDCore::cd_read(CDSection * cd, PIIODevice * d) {
	PIConfig conf(d, PIIODevice::ReadOnly);
	cd->read(&(conf.rootEntry()));
	if (cd->cd_type_ == CDType::cdX)
		x_selected = cd->collectX();
	initRoot(cd);
	raiseChangedGlobal(cd->cd_type_);
	/*PIVector<PIIODevice * > ds = connection.allDevices();
	piForeach(PIIODevice * d, ds) {
		if (d)
			piCoutObj << d->constructFullPath() << d->isOpened();
	}*/
}


void CDCore::cd_parse(CDSection * cd, PIIODevice * d) {
	*cd = CDParser::parse(d, cd->cd_type_);
	initRoot(cd);
	raiseChangedGlobal(cd->cd_type_);
}


void CDCore::cd_update(CDSection * cd, PIIODevice * d, UpdateModeFlags mode) {
	CDSection ucd = *cd;
	cd_parse(cd, d);
	/*bool kn = true;
	if (!ucd.isEmpty())
		if (!ucd.isSameStructure(k_)) {
			piCout << "ask for save names";
			K_KeepNamesRequest(&kn);
		}*/
	ucd.update(*cd, mode);
	//piCout << k_.count() << ucd.count();
	*cd = ucd;
	initRoot(cd);
	raiseChangedGlobal(cd->cd_type_);
}


void CDCore::cd_calculate(CDSection * cd) {
	cd->calculate();
	raiseChangedGlobal(cd->cd_type_);
}


void CDCore::cd_send(CDSection * cd, CDPacketType pt, bool direct) {
	if (!cd) return;
	PIByteArray ba, sba;
	PIIOByteArray iob(&ba, PIIODevice::ReadWrite);
	cd_write(cd, &iob);
	//piCoutObj << PIString(ba);
	sba = makeHeader(pt, 0);
	sba << ba;
	if (direct)
		sendDirect(sba);
	else
		sendThreaded(sba);
}


void CDCore::send(CDType::cdT cdt) {
	CDPacketType pt = CD_Ping;
	switch (cdt) {
	case CDType::cdK: pt = CD_KSend; break;
	case CDType::cdX: pt = CD_XSend; break;
	case CDType::cdC: pt = CD_CSend; break;
	case CDType::cdM: pt = CD_MSend; break;
	default: break;
	}
	piCoutObj << "send" << typeLetter(cdt);
	cd_send(root(cdt), pt);
}


void CDCore::request(CDType::cdT cdt) {
	CDPacketType pt = CD_Ping;
	switch (cdt) {
	case CDType::cdK: pt = CD_KQuery; break;
	case CDType::cdX: pt = CD_XQuery; break;
	case CDType::cdC: pt = CD_CQuery; break;
	case CDType::cdM: pt = CD_MQuery; break;
	default: break;
	}
	piCoutObj << "request" << typeLetter(cdt);
	PIByteArray sba = makeHeader(pt, 0);
	sendThreaded(sba);
}


void CDCore::initApp() {
	init(appConfig(), false);
}


void CDCore::initPult() {
	init(pultConfig(), true);
}


void CDCore::init(const PIString & configuration, bool pult) {
	PIString c = configuration;
	//piCoutObj << "init" << c;
	connection.stop();
	connection.removeAllDevices();
	connection.configureFromString(&c);
	connection.start();
	x_pult_side = pult;
}


void CDCore::stop() {
	x_timer.stop();
	x_timer.waitForFinish(1000);
	connection.stop();
}


void CDCore::release() {
	stop();
	connection.removeAllDevices();
}


void CDCore::startX(double freq) {
	//piCout << "start x" << x_timer.isRunning() << freq;
	if (!x_timer.isRunning())
		x_timer.start(1000. / piMaxd(freq, 0.01));
}


void CDCore::stopX() {
	x_timer.stop();
	x_timer.waitForFinish(1000);
}


void CDCore::sendCommand(const CDType & c) {
	//piCoutObj << "C_sendCommand" << c;
	PIByteArray sba = makeHeader(CD_Command, 0);
	sba << c.path();
	sendDirect(sba);
}


void CDCore::registerCHandler(const CDType & c, PIObject * o, Handler h) {
	PIString sp = pathToString(c.path());
	if (sp.isEmpty() || !h) return;
	//piCout << "register" << sp;
	c_handlers[sp] = OHPair(o, h);
}


void CDCore::sendMessage(const CDType & m, MessageType mt, const PIString & msg) {
	if (msg.isEmpty() || (m.cd_type() != CDType::cdM)) return;
	PIByteArray sba = makeHeader(CD_Message, 0);
	sba << m.path() << int(mt) << msg;
	sendDirect(sba);
}


CDSection * CDCore::root(CDType::cdT cdt) {
	switch (cdt) {
	case CDType::cdK: return &k_; break;
	case CDType::cdX: return &x_; break;
	case CDType::cdC: return &c_; break;
	case CDType::cdM: return &m_; break;
	default: break;
	}
	return 0;
}


PIString CDCore::typeLetter(CDType::cdT cdt) {
	switch (cdt) {
	case CDType::cdK: return PIStringAscii("k"); break;
	case CDType::cdX: return PIStringAscii("x"); break;
	case CDType::cdC: return PIStringAscii("c"); break;
	case CDType::cdM: return PIStringAscii("m"); break;
	default: break;
	}
	return PIString();
}


CDCore * CDCore::instance() {
	/*static CDCore * ret = new CDCore();
	return ret;*/
	return __Core_Initializer__::__instance__;
}


bool CDCore::destroy() {
	if (!__Core_Initializer__::__instance__) return false;
//	piCout << "delete Core ...";
	delete __Core_Initializer__::__instance__;
//	piCout << "delete Core ok";
	__Core_Initializer__::__instance__ = 0;
	__Core_Initializer__::count_ = 0;
	return true;
}


void CDUtils::CDCore::K_DirectChange(PIDeque<int> path, PIString value) {
//	piCoutObj << "K_DirectChange";
	PacketKDirectChange p;
	p.path = path;
	p.value = value;
	PIByteArray sba = makeHeader(CD_KDirectChange, 0);
	sba << p;
	sendDirect(sba);
}


void CDCore::sendThread() {
	if (send_data.size_s() < 4) return;
	PacketHeader h;
	memcpy(&h, send_data.data(), sizeof(h));
	bool ok = datatr.send(send_data);
	switch (h.type) {
	case CD_KSend:
		if (ok) K_Sended();
		else K_SendFail();
		break;
	case CD_KQuery:
		if (!ok) K_ReceiveFail();
		break;
	case CD_XSend:
		if (ok) X_Sended();
		else X_SendFail();
		break;
	case CD_XQuery:
		if (!ok) X_ReceiveFail();
		break;
	case CD_CSend:
		if (ok) C_Sended();
		else C_SendFail();
		break;
	case CD_CQuery:
		if (!ok) C_ReceiveFail();
		break;
	case CD_MSend:
		if (ok) M_Sended();
		else M_SendFail();
		break;
	case CD_MQuery:
		if (!ok) M_ReceiveFail();
		break;
	default: break;
	}
}


void CDCore::xTimerTick() {
	//piCout << "x tick" << x_pult_side;
	PIByteArray ba;
	x_mutex.lock();
	if (x_pult_side) {
		ba = makeHeader(CD_XRequest, 0);
		if (need_rebuild_x) {
			x_selected = x_.collectX();
			//piCout << "collectX" << x_selected.size();
			need_rebuild_x = false;
		}
		ba << x_selected;
		//piCout << "x pult send" << x_selected.size();
	} else {
		ba = makeHeader(CD_XValues, 0);
		ba << x_selected;
		piForeachC (PIDeque<int> & p, x_selected) {
			x_[p].writeX(ba);
		}
		//piCout << "x app" << x_selected.size();
	}
	x_mutex.unlock();
	sendDirect(ba);
}


void CDCore::piCoutFinished(int id, PIString * buffer) {
	if (!buffer || !(id == 1)) return;
	PIString sp = buffer->takeRange("[", "]");
	PIDeque<int> p = CDCore::stringToPath(sp);
	sendMessage(m_[p], Log, *buffer);
	delete buffer;
}


void CDCore::initRoot(CDSection * r) {
	r->name = "__root__";
	r->alias = "root";
	r->makePath();
	r->calculate();
}


PIByteArray CDCore::makeHeader(CDPacketType type, int session_id) const {
	PacketHeader h;
	h.type = type;
	h.session_id = session_id;
	PIByteArray ret; ret << h;
	return ret;
}


void CDCore::sendDirect(PIByteArray & ba) {
	ba.push_front(header_direct);
	connection.writeByName("cd", ba);
}


void CDCore::sendThreaded(PIByteArray & ba) {
	if (sendt.isRunning()) {
		piCoutObj << "Send in process, abort";
		return;
	}
	send_data = ba;
	sendt.startOnce();
}


void CDCore::procReceivedPacket(PIByteArray & ba) {
	PacketHeader h;
	ba >> h;
	switch(h.type) {
	case CD_Ping:
		//piCoutObj << "ping";
		break;
	case CD_KQuery:
		send(CDType::cdK);
		break;
	case CD_KSend: {
		PIByteArray k;
		ba >> k;
		k << uchar(0);
		PIString s = PIString::fromUTF8((const char *)k.data());
		PIIOString ios(&s);
		cd_read(&k_, &ios);
		K_Received();
		piCoutObj << "K received";
		} break;
	case CD_KDirectChange: {
		PacketKDirectChange p;
		ba >> p;
		k_[p.path].setValue(p.value);
		} break;
	case CD_XQuery:
		send(CDType::cdX);
		break;
	case CD_XSend: {
		PIByteArray x;
		ba >> x;
		x << uchar(0);
		PIString s = PIString::fromUTF8((const char *)x.data());
		PIIOString ios(&s);
		cd_read(&x_, &ios);
		x_selected = x_.collectX();
		X_Received();
		piCoutObj << "X received";
		} break;
	case CD_XRequest: {
		if (x_pult_side) break;
		//break;
		x_mutex.lock();
		x_selected.clear();
		ba >> x_selected;
		//piCout << "X req" << x_selected.size();
		x_.setSelectedX(false);
		piForeachC (PIDeque<int> & p, x_selected) {
			x_[p].x_enabled = true;
		}
		x_mutex.unlock();
		} break;
	case CD_XValues: {
		if (!x_pult_side) break;
		PIVector<PIDeque<int> > x_vals;
		ba >> x_vals;
		x_mutex.lock();
		piForeachC (PIDeque<int> & p, x_vals) {
			x_[p].readX(ba);
		}
		x_mutex.unlock();
		X_ReceivedX(x_vals);
		} break;
	case CD_CQuery:
		send(CDType::cdC);
		break;
	case CD_CSend: {
		piCoutObj << "C received";
		PIByteArray c;
		ba >> c;
		c << uchar(0);
		PIString s = PIString::fromUTF8((const char *)c.data());
		PIIOString ios(&s);
		cd_read(&c_, &ios);
		C_Received();
		} break;
	case CD_Command: {
		piCoutObj << "C command";
		PIDeque<int> p;
		ba >> p;
		if (p.isEmpty()) return;
		PIString sp = pathToString(p);
		OHPair h = c_handlers.value(sp, OHPair(0, 0));
		//piCoutObj << "found" << sp << h.first;
		if (h.first && h.second) h.second(h.first);
		} break;
	case CD_MQuery:
		send(CDType::cdM);
		break;
	case CD_MSend: {
		piCoutObj << "M received";
		PIByteArray c;
		ba >> c;
		c << uchar(0);
		PIString s = PIString::fromUTF8((const char *)c.data());
		PIIOString ios(&s);
		cd_read(&m_, &ios);
		M_Received();
		} break;
	case CD_Message: {
		PIDeque<int> p;
		ba >> p;
		piCoutObj << "M message" << p;
		if (p.isEmpty()) return;
		int t = 0;
		PIString msg;
		ba >> t >> msg;
		//piCoutObj << "found" << sp << h.first;
		//piCoutObj << "M message invoke";
		M_Message(p, t, msg);
		} break;
	default: break;
	}
}


void CDCore::raiseChangedGlobal(CDType::cdT cdt) {
	switch (cdt) {
	case CDType::cdK: K_ChangedGlobal(); break;
	case CDType::cdX: X_ChangedGlobal(); break;
	case CDType::cdC: C_ChangedGlobal(); break;
	case CDType::cdM: M_ChangedGlobal(); break;
	default: break;
	}
}


PIString CDCore::pathToString(const PIDeque<int> & p) {
	PIString ret;
	for (int i = 0; i < p.size_s(); ++i) {
		if (!ret.isEmpty()) ret += ".";
		ret << p[i];
	}
	return ret;
}


PIDeque<int> CDCore::stringToPath(const PIString & p) {
	PIDeque<int> ret;
	PIStringList sl = p.split(".");
	piForeachC (PIString & s, sl)
		ret << s.toInt();
	return ret;
}


void CDUtils::CDCore::dataReceived(const PIString & from, const PIByteArray & data) {
	//piCoutObj << "dataReceived" << from << data.size();
	PIIODevice * d = connection.deviceByName("cd");
	if (d && d == connection.deviceByFullPath(from)) {
		if (data.size() >= sizeof(4)) {
			PIByteArray ba = data;
			uchar header = ba.take_front();
			if (header == header_transfer) {
				datatr.received(ba);
			}
			if (header == header_direct) {
				procReceivedPacket(ba);
			}
		}
	}
}


void CDCore::dtSendRequest(PIByteArray & data) {
	data.push_front(header_transfer);
	connection.writeByName("cd", data);
	//piCoutObj << "send" << data.size() << ret;
}


void CDCore::dtReceiveFinished(bool ok) {
	if (!ok) return;
	PIByteArray ba = datatr.data();
	procReceivedPacket(ba);
}

