/*
    PIP - Platform Independent Primitives
    PIP System Daemon
    Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "daemon.h"
#include "file_manager.h"
#include "picli.h"
#include "piintrospection_server.h"
#include "piliterals_time.h"
#include "piprocess.h"
#include "pisingleapplication.h"
#include "pisysteminfo.h"
#include "pisystemmonitor.h"
#include "shared.h"

STATIC_INITIALIZER_BEGIN
	randomize();
STATIC_INITIALIZER_END

PISystemMonitor sys_mon;
PIScreen * screen = nullptr;


class MainMenu: public PITimer {
	PIOBJECT_SUBCLASS(MainMenu, PITimer)

public:
	MainMenu(Daemon & d): daemon_(d) {
		cur_peer = -1;
		title    = new TileSimple("title");
		updateTitle(title);
		title->back_format.color_back = Yellow;
		title->size_policy            = Fixed;
		screen->rootTile()->addTile(title);

		PIScreenTile * center          = new PIScreenTile("center");
		center->back_format.color_back = Cyan;
		center->size_policy            = Expanding;
		center->setMargins(2, 2, 1, 1);
		screen->rootTile()->addTile(center);

		PIScreenTile * mt = tmenu = menuTile();
		mt->show();
		mt->name() = "main menu";
		center->addTile(mt);
		mtiles << mt;

		mt = tinfo = infoTile();
		mt->hide();
		mt->name() = "local info";
		center->addTile(mt);
		mtiles << mt;

		mt = tdaemon = daemon_.tile();
		mt->hide();
		mt->name() = "daemon";
		center->addTile(mt);
		mtiles << mt;

		mt = tpeer = peerTile();
		mt->hide();
		mt->name() = "peer info";
		center->addTile(mt);
		mtiles << mt;

		mt = tpeerdiag = peerDiagTile();
		mt->hide();
		mt->name() = "peer diag";
		center->addTile(mt);
		mtiles << mt;

		tpicout = new TilePICout();
		tpicout->hide();
		tpicout->size_policy = PIScreenTypes::Expanding;
		screen->rootTile()->addTile(tpicout);

		CONNECTU(screen, tileEvent, this, tileEvent)
		CONNECTU(screen, keyPressed, this, keyEvent)
		CONNECTU(&daemon_, menuRequest, this, menuRequest)
		start(40_Hz);
	}
	PIScreenTile * menuTile() {
		TileList * ret = new TileList();
		ret->content << TileList::Row("Show local info", CellFormat());
		ret->content << TileList::Row("Local file manager", CellFormat());
		ret->content << TileList::Row("Connect to another daemon", CellFormat());
		ret->content << TileList::Row("Peer info", CellFormat());
		ret->content << TileList::Row("Peer reinit", CellFormat());
		ret->content << TileList::Row("Peer diagnostics", CellFormat());
		ret->content << TileList::Row("Peer change self name", CellFormat());
		ret->content << TileList::Row("Exit", CellFormat());
		ret->selection_mode = TileList::NoSelection;
		return ret;
	}
	PIScreenTile * infoTile() {
		TileList * ret = new TileList();
		local_info_base << TileList::Row("Exec command: " + PISystemInfo::instance()->execCommand, CellFormat());
		local_info_base << TileList::Row(" Executed on  " + PISystemInfo::instance()->execDateTime.toString(), CellFormat());
		local_info_base << TileList::Row("    Hostname: " + PISystemInfo::instance()->hostname, CellFormat());
		local_info_base << TileList::Row("    Username: " + PISystemInfo::instance()->user, CellFormat());
		local_info_base << TileList::Row("     OS name: " + PISystemInfo::instance()->OS_name, CellFormat());
		local_info_base << TileList::Row("  OS version: " + PISystemInfo::instance()->OS_version, CellFormat());
		local_info_base << TileList::Row("Architecture: " + PISystemInfo::instance()->architecture, CellFormat());
		local_info_base << TileList::Row("   CPU count: " + PIString::fromNumber(PISystemInfo::instance()->processorsCount), CellFormat());
		local_info_base << TileList::Row("", CellFormat());
		return ret;
	}
	PIScreenTile * peerDiagTile() {
		PIScreenTile * ret = new PIScreenTile();
		TileSimple * htl   = new TileSimple();
		htl->size_policy   = PIScreenTypes::Fixed;
		ret->direction     = PIScreenTypes::Vertical;
		htl->content << TileSimple::Row("Peer: " + daemon_.name() + " | " + daemon_.selfInfo().name,
		                                CellFormat(PIScreenTypes::Default, PIScreenTypes::Default, PIScreenTypes::Bold));
		PIScreenTile * diag = new PIScreenTile();
		diag->direction     = PIScreenTypes::Horizontal;
		peerdiagdata_tl     = new TileSimple();
		peerdiagservice_tl  = new TileSimple();
		ret->addTile(htl);
		ret->addTile(diag);
		diag->addTile(peerdiagdata_tl);
		diag->addTile(peerdiagservice_tl);
		return ret;
	}
	PIScreenTile * peerTile() {
		PIScreenTile * ret           = new PIScreenTile();
		ret->direction               = PIScreenTypes::Vertical;
		peerinfo_header              = new TileSimple();
		peerinfo_header->size_policy = PIScreenTypes::Fixed;
		peerinfo_header->content << TileSimple::Row("Peer: " + daemon_.name() + " | " + daemon_.selfInfo().name,
		                                            CellFormat(PIScreenTypes::Default, PIScreenTypes::Default, PIScreenTypes::Bold));
		addrs_tl                 = new TileList();
		peers_tl                 = new TileList();
		peerinfo_tl              = new TileSimple();
		peermap_tl               = new TileList();
		peerinfo_tl->size_policy = PIScreenTypes::Fixed;
		ret->addTile(peerinfo_header);
		ret->addTile(peers_tl);
		ret->addTile(peerinfo_tl);
		ret->addTile(addrs_tl);
		ret->addTile(peermap_tl);
		return ret;
	}

	void updateTitle(TileSimple * tl) {
		tl->content.clear();
		tl->content << TileSimple::Row("pisd (PI System Daemon, PIP version " + PIPVersion() + ")", CellFormat(Black, Transparent));
		tl->content << TileSimple::Row("This daemon: \"" + daemon_.thisDaemonName() + "\"", CellFormat(Black, Transparent));
	}
	void updatePeerDiag(TileSimple * tl, const PIDiagnostics & diag) {
		tl->content.clear();
		PIDiagnostics::State ds = diag.state();
		tl->content << TileSimple::Row(diag.name() + " diagnostics",
		                               CellFormat(PIScreenTypes::Default, PIScreenTypes::Default, PIScreenTypes::Bold));
		tl->content << TileSimple::Row("Received count: " + PIString::fromNumber(ds.received_packets), CellFormat());
		tl->content << TileSimple::Row("Invalid  count: " + PIString::fromNumber(ds.received_packets_wrong), CellFormat());
		tl->content << TileSimple::Row("Sended   count: " + PIString::fromNumber(ds.sended_packets), CellFormat());
		tl->content << TileSimple::Row("Immediate Frequency, Hz: " + PIString::fromNumber(ds.immediate_freq), CellFormat());
		tl->content << TileSimple::Row("Integral  Frequency, Hz: " + PIString::fromNumber(ds.integral_freq), CellFormat());
		tl->content << TileSimple::Row("Receive speed: " + ds.receive_speed, CellFormat());
		tl->content << TileSimple::Row("Send    speed: " + ds.send_speed, CellFormat());
		tl->content << TileSimple::Row("Quality: " + PIString::fromNumber((int)ds.quality), CellFormat());
	}
	void updatePeerInfo() {
		//		bool pm = daemon_.lockedPeers();
		screen->lock();
		daemon_.lock();
		peers_tl->content.clear();
		addrs_tl->content.clear();
		peerinfo_tl->content.clear();
		peermap_tl->content.clear();
		peers_tl->content << TileList::Row(
			"this | 0 | 0 | " + PIString::fromNumber(daemon_.allPeers().size_s())
			//										   +  " [em = " + PIString::fromBool(daemon_.lockedEth()) + ",
			//" 											"mm = " + PIString::fromBool(daemon_.lockedMBcasts()) + ", "
			//"sm
			//=
			//"
			//+ PIString::fromBool(daemon_.lockedSends()) + ", " 											"ms = " +
			// PIString::fromBool(daemon_.lockedMCSends()) + ", " 											"pm = " + PIString::fromBool(pm)
			// +
			// "]"
			,
			CellFormat());
		for (const auto & p: daemon_.allPeers())
			peers_tl->content << TileList::Row(p.name + " | d = " + PIString::fromNumber(p.dist) + " | p = " +
			                                       PIString::fromNumber(p.ping()) + " | n = " + PIString::fromBool(p.isNeighbour()),
			                                   CellFormat());
		PIPeer::PeerInfo pi = daemon_.selfInfo();
		if (cur_peer >= 0 && cur_peer < daemon_.allPeers().size_s()) pi = daemon_.allPeers()[cur_peer];
		peerinfo_tl->content << TileSimple::Row("Name: " + pi.name, CellFormat());
		peerinfo_tl->content << TileSimple::Row("Addreses: " + PIString::fromNumber(pi.addresses.size()), CellFormat());
		peerinfo_tl->content << TileSimple::Row("Neighbours: " + pi.neighbours.join(", "), CellFormat());
		for (const auto & a: pi.addresses)
			addrs_tl->content << TileList::Row(a.address.toString() + " | p = " + PIString::fromNumber(a.ping) +
			                                       " | a = " + PIString::fromBool(a.isAvailable()),
			                                   CellFormat());
		PIStringList peermap;
		for (auto p = daemon_._peerMap().begin(); p != daemon_._peerMap().end(); p++) {
			PIString s = p.key() + " | ";
			piForeachCR(PIPeer::PeerInfo * pp, p.value())
				s += " -> " + pp->name;
			peermap << s;
		}
		for (const auto & s: peermap)
			peermap_tl->content << TileList::Row(s, CellFormat());
		updatePeerDiag(peerdiagdata_tl, daemon_.diagnosticData());
		updatePeerDiag(peerdiagservice_tl, daemon_.diagnosticService());
		daemon_.unlock();
		screen->unlock();
	}
	void updateSysMon() {
		TileList * tile                           = (TileList *)tinfo;
		PIVector<PISystemMonitor::ThreadStats> ts = sys_mon.threadsStatistic();
		screen->lock();
		tile->content = local_info_base;
		int num = 0, maxlen = 0;
		PIString line = "Process load: k ";
		PIString ns   = PIString::fromNumber(sys_mon.statistic().cpu_load_system, 'f', 2);
		line += ns.expandLeftTo(5, ' ') + " %, u ";
		ns = PIString::fromNumber(sys_mon.statistic().cpu_load_user, 'f', 2);
		line += ns.expandLeftTo(5, ' ') + " %";
		tile->content << TileList::Row("PID: " + PIString::fromNumber(sys_mon.statistic().ID), CellFormat());
		tile->content << TileList::Row(line, CellFormat());
		tile->content << TileList::Row("Threads:", CellFormat());
		for (const auto & t: ts)
			maxlen = piMaxi(maxlen, t.name.length());
		for (const auto & t: ts) {
			line = PIString::fromNumber(++num).expandLeftTo(2, ' ') + ": ";
			line += PIString(t.name).expandRightTo(maxlen, ' ') + ": k ";
			PIString ns = PIString::fromNumber(t.cpu_load_kernel, 'f', 2);
			line += ns.expandLeftTo(5, ' ') + " %, u ";
			ns = PIString::fromNumber(t.cpu_load_user, 'f', 2);
			line += ns.expandLeftTo(5, ' ') + " %";
			tile->content << TileList::Row(line, CellFormat());
		}
		screen->unlock();
	}
	void tick(int delimiter) override {
		if (tpeerdiag->visible || tpeer->visible) updatePeerInfo();
		if (tinfo->visible) updateSysMon();
	}
	EVENT_HANDLER(void, menuRequest) {
		for (auto * t: mtiles)
			t->hide();
		daemon_.disconnect();
		tmenu->show();
		tmenu->setFocus();
	}
	EVENT_HANDLER2(void, tileEvent, PIScreenTile *, t, PIScreenTypes::TileEvent, e) {
		if (t == tmenu) {
			if (e.type == TileList::RowPressed) {
				for (auto * t: mtiles)
					t->hide();
				switch (e.data.toInt()) {
				case 0: tinfo->show(); break;
				case 1:
					daemon_.fm.setLocal();
					daemon_.showLocalFilemanager();
					tdaemon->show();
					break;
				case 2:
					daemon_.fm.setRemote();
					daemon_.showMainList();
					tdaemon->show();
					break;
				case 3:
					tpeer->show();
					peers_tl->setFocus();
					break;
				case 4:
					daemon_.reinit();
					tmenu->show();
					break;
				case 5: tpeerdiag->show(); break;
				case 6: {
					PIString nn = askUserInput("Peer name:");
					if (!nn.isEmpty()) {
						daemon_.changeName(pisd_prefix + nn);
						peerinfo_header->content.clear();
						peerinfo_header->content
							<< TileSimple::Row("Peer: " + daemon_.name() + " | " + daemon_.selfInfo().name,
						                       CellFormat(PIScreenTypes::Default, PIScreenTypes::Default, PIScreenTypes::Bold));
						updateTitle(title);
					}
					menuRequest();
				} break;
				case 7: PIKbdListener::exiting = true; break;
				}
			}
			return;
		}
		if (t == peers_tl) {
			if (e.type == TileList::RowPressed) {
				cur_peer = e.data.toInt() - 1;
				updatePeerInfo();
			}
			return;
		}
	}
	EVENT_HANDLER1(void, keyEvent, PIKbdListener::KeyEvent, e) {
		if (e.key == PIKbdListener::F9) {
			tpicout->visible = !tpicout->visible;
			return;
		}
		if (e.key == PIKbdListener::Esc && e.modifiers[PIKbdListener::Shift]) {
			PIKbdListener::exiting = true;
			return;
		}
		if (screen->dialogTile()) return;
		if (tpeer->visible || tinfo->visible || tpeerdiag->visible)
			if (e.key == PIKbdListener::Esc) menuRequest();
		// piCout << "key" << e.key;
	}
	EVENT_HANDLER1(void, messageFromApp, PIByteArray, m) {
		if (m[0] == 'k') PIKbdListener::exiting = true;
	}
	Daemon & daemon_;
	PIScreenTile *tmenu, *tinfo, *tfm, *tdaemon, *tpeer, *tpeerdiag;
	TileList *peers_tl, *addrs_tl, *peermap_tl;
	TilePICout * tpicout;
	TileSimple * title;
	TileSimple *peerinfo_tl, *peerinfo_header;
	TileSimple *peerdiagdata_tl, *peerdiagservice_tl;
	PIVector<PIScreenTile *> mtiles;
	PIDeque<TileList::Row> local_info_base;
	int cur_peer;
};


void usage() {
	piCout << PICoutManipulators::Bold << "PIP System Daemon";
	piCout << PICoutManipulators::Cyan << "Version" << PICoutManipulators::Bold << PIPVersion() << PICoutManipulators::NewLine;
	piCout << PICoutManipulators::Green << PICoutManipulators::Bold << "Usage:" << PICoutManipulators::Default
		   << "\"pisd [-1hdfk] [-n <name>] [-a <ip>]\"" << PICoutManipulators::NewLine;
	piCout << PICoutManipulators::Green << PICoutManipulators::Bold << "Details:";
	piCout << "-h --help        " << PICoutManipulators::Green << "- display this message and exit";
	piCout << "-d --daemon      " << PICoutManipulators::Green << "- start as daemon";
	piCout << "-k --kill        " << PICoutManipulators::Green << "- kill daemon";
	piCout << "-f --force       " << PICoutManipulators::Green << "- don`t check for another running instance";
	piCout << "-n --name <name> " << PICoutManipulators::Green << "- set daemon name";
	piCout << "-a --address <ip>" << PICoutManipulators::Green << "- connect to remote daemon via tcp";
	piCout << "-s --silent      " << PICoutManipulators::Green << "- run without user interfase";
}


int main(int argc, char * argv[]) {
	// piDebug = false;
	PICLI cli(argc, argv);
	cli.addArgument("help");
	cli.addArgument("daemon");
	cli.addArgument("force");
	cli.addArgument("kill");
	cli.addArgument("1");
	cli.addArgument("silent");
	cli.addArgument("name", true);
	cli.addArgument("address", true);
	if (cli.hasArgument("help")) {
		usage();
		return 0;
	}
	sys_mon.startOnSelf();
	PIString name              = cli.argumentValue("name");
	PIString sip               = cli.argumentValue("address");
	PISingleApplication * sapp = 0;
	if ((cli.hasArgument("1") && !cli.hasArgument("force")) || cli.hasArgument("kill")) {
		sapp = new PISingleApplication("pisd");
		if (cli.hasArgument("1")) {
			if (!sapp->isFirst()) {
				piCout << "Another pisd is running, exit";
				delete sapp;
				return 0;
			}
		}
		if (cli.hasArgument("kill")) {
			sapp->sendMessage(PIByteArray("k", 1));
			delete sapp;
			return 0;
		}
	}
	PIINTROSPECTION_START(pisd)
	if (cli.hasArgument("daemon")) {
		PIStringList args;
		args << "-1"
			 << "-s";
		if (cli.hasArgument("force")) args << "-f";
		if (cli.hasArgument("address")) args << "-a" << sip;
		if (!name.isEmpty()) args << "-n" << name;
		PIString exe;
#ifdef WINDOWS
		exe = PISystemInfo::instance()->execCommand;
#else
		exe = PIProcess::getEnvironmentVariable("_");
#endif
		piCout << "start in background:" << exe; // << "; with args" << args;
		PIProcess::execIndependent(exe, args);
		return 0;
	}
	screen = new PIScreen(false);
	screen->setMouseEnabled(true);
	Daemon * daemon = new Daemon();
	if (!sip.isEmpty()) daemon->setTcpServerIP(sip);
	screen->enableExitCapture(PIKbdListener::F10);
	if (!name.isEmpty()) daemon->changeName(pisd_prefix + name);
	MainMenu * menu = new MainMenu(*daemon);
	if (sapp) CONNECTU(sapp, messageReceived, menu, messageFromApp);
	if (cli.hasArgument("silent")) {
		PICout::setOutputDevices(PICout::Console);
		PIKbdListener ls(0, 0, false);
		ls.enableExitCapture(PIKbdListener::F10);
		ls.start();
		while (!PIKbdListener::exiting)
			piMSleep(PIP_MIN_MSLEEP * 5);
		if (!ls.stopAndWait(1_s)) ls.terminate();
	} else {
		screen->start();
		screen->waitForFinish();
		screen->stop(true);
	}
	sys_mon.stop();
	delete menu;
	delete daemon;
	delete screen;
	if (sapp) delete sapp;
	return 0;
}
