/*
    PIP - Platform Independent Primitives
    UDP file transfer (send /receive files) utility
    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 "picli.h"
#include "pidatatransfer.h"
#include "piethernet.h"
#include "pifiletransfer.h"
#include "piliterals_time.h"
#include "piscreen.h"
#include "piscreentiles.h"


TileSimple pmt;


using namespace PICoutManipulators;

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

public:
	UDPFileTransfer(const PIString & src_ip_port, const PIString & dst_ip_port, bool test = false) {
		quet_ = false;
		test_ = test;
		eth.setReadAddress(src_ip_port);
		eth.setSendAddress(dst_ip_port);
		if (test_) {
			testt.setCRCEnabled(false);
			testt.setName("TEST");
			CONNECT1(void, PIByteArray &, &testt, sendRequest, this, ftsend);
		} else {
			ft.setPacketSize(65000);
			ft.setName("PIFT");
			CONNECT1(void, PIByteArray &, &ft, sendRequest, this, ftsend);
			CONNECTU(&ft, sendFilesStarted, this, ftevent);
			CONNECTU(&ft, receiveFilesStarted, this, ftevent);
			CONNECTU(&ft, sendFilesFinished, this, ftevent);
			CONNECTU(&ft, receiveFilesFinished, this, ftevent);
		}
		CONNECT2(void, const uchar *, ssize_t, &eth, threadedReadEvent, this, received);
		start(20_Hz);
		eth.setParameter(PIEthernet::SeparateSockets);
		eth.startThreadedRead();
	}

	void setQuet(bool quet) { quet_ = quet; }

	void startSend(const PIStringList & file) { ft.send(file); }

	void startTest() {
		PIByteArray ba(1024 * 1024 * 8);
		testt.send(ba);
	}

	PIFileTransfer ft;
	PIDataTransfer testt;
	bool test_;

private:
	PIEthernet eth;
	bool quet_;

	void tick(int) override {
		if (ft.isStarted()) {
			ftevent();
			updatePMT();
			if (PIKbdListener::exiting) {
				ft.stopSend();
				ft.stopReceive();
			}
		}
		if (testt.isSending() || testt.isReceiving()) {
			ftevent();
			updatePMT();
		}
	}

	EVENT_HANDLER(void, ftevent) {
		if (test_) {
#ifdef WINDOWS
			piCout
#else
			PICout(AddSpaces) << ClearLine
#endif
				<< testt.stateString() << testt.diagnostic().receiveSpeed() << testt.diagnostic().sendSpeed() << "("
				<< PIString::readableSize(testt.bytesCur()) << "/" << PIString::readableSize(testt.bytesAll()) << ")"
#ifndef WINDOWS
				<< Flush
#endif
				;
			return;
		}
		if (quet_) return;

#ifdef WINDOWS
		piCout
#else
		PICout(AddSpaces)
			<< ClearLine
#endif
			<< ft.stateString() << ft.curFile() << ft.diagnostic().receiveSpeed() << ft.diagnostic().sendSpeed() << "("
			<< PIString::readableSize(ft.bytesFileCur()) << "/" << PIString::readableSize(ft.bytesFileAll()) << ", "
			<< PIString::readableSize(ft.bytesCur()) << "/" << PIString::readableSize(ft.bytesAll()) << ")"
			<< "ETA"
			<< (ft.diagnostic().state().received_bytes_per_sec > 0
		            ? PIString::fromNumber(
						  PISystemTime::fromSeconds((ft.bytesAll() - ft.bytesCur()) / ft.diagnostic().state().received_bytes_per_sec)
							  .toSeconds())
		            : PIString("unknown"))
#ifndef WINDOWS
			<< Flush
#endif
			;
	}

	EVENT_HANDLER1(void, ftsend, PIByteArray &, data) { eth.send(data); }

	EVENT_HANDLER2(void, received, const uchar *, readed, ssize_t, size) {
		PIByteArray ba(readed, size);
		if (test_) {
			testt.received(ba);
			return;
		}
		ft.received(ba);
	}

	void updatePMT() {
		PIString pm;
		if (test_)
			pm = testt.packetMap();
		else
			pm = ft.packetMap();
		int wd = 110;
		pmt.content.resize(pm.size() / wd + 1);
		for (int i = 0; i < pmt.content.size_s(); i++) {
			pmt.content[i].first = pm.mid(wd * i, piMini(pm.size() - i * wd, wd));
		}
	}
};


void usage() {
	piCout << Bold << "PIP UDP file transfer";
	piCout << Cyan << "Version" << Bold << PIPVersion() << NewLine;
	piCout << Green << Bold << "Usage:" << Default
		   << "\"pift [-hqf] -r <receive_ip> -s <send_ip> [-d <work_dir>] [-p <port>] [<path1>] [<path2>] [<path3>] [...]\"" << NewLine;
	piCout << Green << Bold << "Details:";
	piCout << "-f --fullpath              " << Green << "- full path in <receive_ip> and <send_ip>";
	piCout << "-h --help                  " << Green << "- display this message and exit";
	piCout << "-q --quet                  " << Green << "- quiet, no debug output to console";
	piCout << "-r --receive <receive_ip>  " << Green << "- set receive ip address, must be ip address of this computer";
	piCout << "-s --send <send_ip>        " << Green << "- set send ip address, address of computer which communicate with you";
	piCout << "-p --port  <port>          " << Green << "- UDP port for transfer, by default 50005";
	piCout << "-d --dir <work_dir>        " << Green << "- directory, where place received files";
	piCout << "<path>                     " << Green << "- add path to send, if no path, then \"pift\" working in receive mode";
	piCout << "-t --test                  " << Green << "- test mode";
}


int main(int argc, char * argv[]) {
	PICLI cli(argc, argv);
	cli.setOptionalArgumentsCount(-1);
	cli.addArgument("send", true);
	cli.addArgument("receive", true);
	cli.addArgument("dir", true);
	cli.addArgument("port", true);
	cli.addArgument("help");
	cli.addArgument("quet");
	cli.addArgument("fullpath");
	cli.addArgument("test", true);
	if (cli.hasArgument("test")) {
		PIString src;
		PIString dst;
		bool send_ = false;
		if (cli.argumentValue("test") == "1") {
			src   = "127.0.0.1:50005";
			dst   = "127.0.0.1:50006";
			send_ = true;
		} else {
			src = "127.0.0.1:50006";
			dst = "127.0.0.1:50005";
		}
		if (cli.hasArgument("send") && cli.hasArgument("receive") && cli.hasArgument("fullpath")) {
			src = cli.argumentValue("receive");
			dst = cli.argumentValue("send");
		}
		PIScreen screen(false);
		screen.enableExitCapture();
		screen.rootTile()->addTile(new TilePICout());
		screen.rootTile()->addTile(&pmt);
		UDPFileTransfer tuf(src, dst, true);
		screen.start();
		if (send_) tuf.startTest();
		screen.waitForFinish();
		return 0;
	}
	if ((!cli.hasArgument("send") || !cli.hasArgument("receive")) || cli.hasArgument("help")) {
		usage();
		return 0;
	}
	PIString src = cli.argumentValue("receive");
	PIString dst = cli.argumentValue("send");
	int port     = -1;
	if (cli.hasArgument("port")) port = cli.argumentValue("port").toInt();
	if (port <= 0) port = 50005;
	if (!cli.hasArgument("fullpath")) {
		src += ":" + PIString::fromNumber(port);
		dst += ":" + PIString::fromNumber(port);
	}
	PIScreen screen(false);
	screen.enableExitCapture();
	screen.rootTile()->addTile(new TilePICout());
	screen.rootTile()->addTile(&pmt);
	screen.start();
	UDPFileTransfer f(src, dst);
	if (cli.hasArgument("dir")) f.ft.setDirectory(cli.argumentValue("dir"));
	if (cli.hasArgument("quet")) f.setQuet(true);
	piCout << "work directory" << f.ft.directory().absolutePath() << ", listen on" << src << ", send to" << dst;
	PIStringList fls = cli.optionalArguments();
	if (fls.size() > 0) {
		piCout << "send files" << fls;
		f.startSend(fls);
		PICout(PICoutManipulators::AddNone) << "\n";
		return 0;
	} else {
		piCout << "wait for receiving";
	}
	PICout(PICoutManipulators::AddNone) << "\n";
	screen.waitForFinish();
	return 0;
}
