#include "device_widget.h"
#include "ui_device_widget.h"
#include "ui_device_widget_dialog.h"
#include "SH_server_base.h"
#include "SH_ui_sync.h"
#include <QStyleFactory>
#include <QTimer>
#include <QMessageBox>
#include <QInputDialog>
#include <piserial.h>
#include <piqt.h>
#include "ccm_SHS_shared.h"


class __SerialProxy: public PIObject {
	PIOBJECT(__SerialProxy)
public:
	__SerialProxy(PISerial * ser, QObject * dw) {
		parent = dw;
		CONNECTU(ser, threadedReadEvent, this, readed);
		CONNECTU(ser, opened, this, opened);
		CONNECTU(ser, closed, this, closed);
	}
	EVENT_HANDLER2(void, readed, THREADED_READ_TYPE, data, ssize_t, size) {
		if (!data || size <= 0) return;
		QMetaObject::invokeMethod(parent, "readed", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray((const char *)data, size)));
	}
	EVENT_HANDLER(void, opened) {QMetaObject::invokeMethod(parent, "opened", Qt::QueuedConnection);}
	EVENT_HANDLER(void, closed) {QMetaObject::invokeMethod(parent, "closed", Qt::QueuedConnection);}
	QObject * parent;
};


DeviceWidget::DeviceWidget(QWidget * parent): QWidget(parent), channel(true) {
	Q_INIT_RESOURCE(designer_tools);
	ui_d = new Ui::DeviceWidgetDialog();
	ui_d->setupUi(&dialog);
	dialog.setParent(this);
	ui = new Ui::DeviceWidget();
	ui->setupUi(this);
	dialog.setParent(0);
	ui_d->stackedServer->setCurrentIndex(0);
	ui_d->tabWidget->setCurrentIndex(0);
	channel.setID(generateID());
	timeout_r = 150;
	ui_d->spinReadTimeout->setValue(timeout_r);
	ui_d->buttonDeviceDialogOpen->setStyle(QStyleFactory::create("Fusion"));
	started_ = false;
	icon_on    = QIcon(":/icons/led_on.png");
	icon_off   = QIcon(":/icons/led_off.png");
	icon_open  = QIcon(":/icons/media-playback-start.png");
	icon_close = QIcon(":/icons/media-playback-stop.png");
	ser = new PISerial();
	proxy = new __SerialProxy(ser, this);
	timer_ao = 0;
	timer_update = startTimer(200);
	timer_pip = startTimer(10);
	PIVector<int> spd = PISerial::availableSpeeds();
	piForeachC (int i, spd)
		ui_d->comboSpeed->addItem(QString::number(i), i);
	showDevices();
	CONNECTU_QUEUED(&channel, disconnected, this, disconnected, this);
	CONNECTU_QUEUED(&channel, connected, this, connected, this);
	CONNECTU(&channel, dataReceived, this, receiveEvent);
}


DeviceWidget::~DeviceWidget() {
	channel.stop();
	delete proxy;
	delete ser;
	delete ui;
}


void DeviceWidget::setReadTimeout(int to) {
	timeout_r = to;
}


int DeviceWidget::readTimeout() const {
	return timeout_r;
}


void DeviceWidget::hideServer() {
	ui_d->tabWidget->removeTab(ui_d->tabWidget->indexOf(ui_d->tabServer));
	ui_d->groupTimeout->hide();
}


void DeviceWidget::write(const PIByteArray & data) {
	if (isLocalDevice())
		ser->send(data);
	else {
		if (!channel.isConnected() || server_device.isEmpty())
			return;
		PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminDeviceForwardingWrite);
		ba << server_device << data;
		channel.dataSend(ba);
	}
}


PIByteArray DeviceWidget::read(int max_size, int timeout) {
	if (isLocalDevice())
		return ser->readData(max_size, timeout < 0 ? timeout_r : timeout);
	else {
		int t = timeout < 0 ? timeout_r : timeout;
		if (max_size < 0 && t < 0)
			return PIByteArray();
		PITimeMeasurer tm;
		PIByteArray ret;
		int ps = 0;
		if (t > 0) {
			tm.reset();
			if (max_size <= 0) {
				while (tm.elapsed_m() < t) {
					piMSleep(1);
				}
				srec_mutex.lock();
				ret = server_packet;
				server_packet.clear();
				srec_mutex.unlock();
			} else {
				while (ps < max_size && tm.elapsed_m() < t) {
					piMSleep(1);
					srec_mutex.lock();
					ps = server_packet.size_s();
					srec_mutex.unlock();
				}
				srec_mutex.lock();
				ret = PIByteArray(server_packet.data(), piMini(max_size, server_packet.size_s()));
				server_packet.remove(0, ret.size_s());
				srec_mutex.unlock();
			}
		} else {
			while (ps < max_size) {
				piMSleep(1);
				srec_mutex.lock();
				ps = server_packet.size_s();
				srec_mutex.unlock();
			}
			srec_mutex.lock();
			ret = PIByteArray(server_packet.data(), piMini(max_size, server_packet.size_s()));
			server_packet.remove(0, ret.size_s());
			srec_mutex.unlock();
		}
		return ret;
	}
	return PIByteArray();
}


bool DeviceWidget::isOpened() const {
	if (isLocalDevice()) return ser->isOpened();
	return channel.isConnected();
}


bool DeviceWidget::isLocalDevice() const {
	return ui_d->tabWidget->currentIndex() == 0;
}


void DeviceWidget::open() {
	if (!isOpened()) on_buttonDeviceOpen_clicked();
}


void DeviceWidget::open(const QString & com) {
	serverDisconnect();
	int ind = ui_d->comboDevice->findData(com);
	if (ind >= 0) {
		ui_d->comboDevice->setCurrentIndex(ind);
		on_buttonDeviceOpen_clicked();
	}
}


void DeviceWidget::close() {
	if (isOpened()) on_buttonDeviceOpen_clicked();
}


void DeviceWidget::startRead() {
	started_ = true;
	if (isLocalDevice())
		ser->startThreadedRead();
}


void DeviceWidget::stopRead() {
	ser->stopThreadedRead();
	started_ = false;
}


void DeviceWidget::timerEvent(QTimerEvent * e) {
	if (e->timerId() == timer_pip) {
		maybeCallQueuedEvents();
		return;
	}
	if ((e->timerId() == timer_ao) && isLocalDevice()) {
		PIStringList ndl(PISerial::availableDevices());
		if (ndl == last_devlist) return;
		showDevices();
		for (int i = ui_d->comboDevice->count() - 1; i >= 0; --i) {
			if (ui_d->comboDevice->itemData(i).toString().contains(ui_d->lineAutoOpen->text(), Qt::CaseInsensitive)) {
				ui_d->comboDevice->setCurrentIndex(i);
				ser->close();
				QTimer::singleShot(100, this, SLOT(on_buttonDeviceOpen_clicked()));
				return;
			}
		}
		on_buttonDeviceOpen_clicked();
		return;
	}
	if (e->timerId() == timer_update) {
		bool o = isOpened();
		ui->buttonDeviceOpen->setText(o ? tr("Close") : tr("Open"));
		ui->buttonDeviceOpen->setIcon(o ? icon_close : icon_open);
		ui->labelState->setText(o ? tr("Opened") : tr("Closed"));
		ui->labelState->setIcon(o ? icon_on : icon_off);
		QString dev = "-";
		if (isLocalDevice())
			dev = QString("%1:%2").arg(ui_d->comboDevice->currentData().toString(), ui_d->comboSpeed->currentText());
		else {
			PacketServerInfo info = ui_d->treeServers->currentServer();
			if (!info.isValid() || info.connect_address.isEmpty()) return;
			dev = QString("%1:%2").arg(PI2QString(info.name), PI2QString(server_device));
		}
		ui->labelDevice->setText(dev);
		return;
	}
	return QWidget::timerEvent(e);
}


void DeviceWidget::showDevices() {
	QString cd = ui_d->comboDevice->currentData().toString();
	last_devlist = PISerial::availableDevices();
	auto dl = PISerial::availableDevicesInfo();
	ui_d->comboDevice->clear();
	//ui_d->comboDevice->addItem("COM18");
	for (const auto & d: dl) {
		QString qd = PI2QString(d.path);
		ui_d->comboDevice->addItem(QString("%1 (%2)").arg(qd, PI2QString(d.description)), qd);
		if (qd == cd)
			ui_d->comboDevice->setCurrentIndex(ui_d->comboDevice->count() - 1);
	}
}


void DeviceWidget::showServerDevices() {
	ui_d->treeDevices->clear();
	piForeachC (ServerDevice & d, server_devices) {
		QTreeWidgetItem * ti = new QTreeWidgetItem();
		ti->setText(0, PI2QString(d.names.join(", ")));
		ti->setText(1, PI2QString(d.fullpath));
		ui_d->treeDevices->addTopLevelItem(ti);
	}
	setState(tr("Select device"));
}


void DeviceWidget::serverError(QString msg) {
	QMessageBox::critical(this, tr("Server device"), msg);
	serverDisconnect();
}


void DeviceWidget::selectServerDevice(int index) {
	server_device.clear();
	for (int i = 0; i < ui_d->treeDevices->topLevelItemCount(); ++i) {
		QTreeWidgetItem * ti = ui_d->treeDevices->topLevelItem(i);
		QFont f = ti->font(0);
		f.setBold(i == index);
		if (i == index)
			server_device = Q2PIString(ti->text(1));
		for (int j = 0; j < ui_d->treeDevices->columnCount(); ++j)
			ti->setFont(j, f);
	}
	if (!server_device.isEmpty()) {
		PIByteArray ba = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminDeviceForwardingSelect);
		ba << server_device;
		channel.dataSend(ba);
		setState(tr("Ready"));
	}
}


void DeviceWidget::setState(QString t) {
	ui_d->labelServerState->setText(t);
	QApplication::processEvents();
}


void DeviceWidget::serverDisconnect() {
	if (channel.isConnected()) {
		channel.dataStop();
		setState(tr("Select server"));
	}
	ui_d->stackedServer->setCurrentIndex(0);
}


void DeviceWidget::disconnected() {
	if (ui_d->treeServers->isRequesting()) return;
	channel.stop();
	ui_d->stackedServer->setCurrentIndex(0);
	if (!isLocalDevice())
		emit closed();
}


void DeviceWidget::connected() {
	if (ui_d->treeServers->isRequesting()) return;
	ui_d->stackedServer->setCurrentIndex(1);
	setState(tr("Send identification ..."));
	bool ok = false;
	QString pass = QInputDialog::getText(this, tr("Server device"), tr("Input admin password:"), QLineEdit::Password, "", &ok);
	if (!ok) return;
	QApplication::setOverrideCursor(Qt::WaitCursor);
	PIByteArray ph = passwordHash(Q2PIString(pass));
	QApplication::restoreOverrideCursor();
	PIByteArray ba;
	PacketIdentification p = PacketIdentification::makeLocal(SHServer::RoleDeviceForward, SHS_NETWORK_VERSION, SHS_UISYNC_VERSION);
	p.admin_password = ph;
	ba = SHNetworkTypes::makeHeader(SHNetworkTypes::Identification);
	ba << p;
	channel.dataSend(ba);
	ba = SHNetworkTypes::makeHeader(SHNetworkTypes::AdminDeviceForwardingRequest);
	channel.dataSend(ba);
	if (!isLocalDevice())
		emit opened();
}



void DeviceWidget::receiveEvent(PIByteArray data) {
	if (ui_d->treeServers->isRequesting()) return;
	PacketHeader hdr = SHNetworkTypes::takeHeader(data);
	if (hdr.type < 0) return;
	//qDebug() << "packet type" << type;
	switch ((SHNetworkTypes::PacketType)hdr.type) {
	case SHNetworkTypes::AdminDeviceForwardingPermit:
		data >> server_devices;
		QMetaObject::invokeMethod(this, "showServerDevices", Qt::QueuedConnection);
		break;
	case SHNetworkTypes::AdminDeviceForwardingRead:
		{
		PIByteArray ba;
		data >> ba;
		if (started_)
			QMetaObject::invokeMethod(this, "readed", Q_ARG(QByteArray, PI2QByteArray(ba)));
		else {
			srec_mutex.lock();
			server_packet.append(ba);
			srec_mutex.unlock();
		}
		}
		break;
	case SHNetworkTypes::NetworkVersionMismatch:
		QMetaObject::invokeMethod(this, "serverError", Q_ARG(QString, "Invalid network version"));
		break;
	case SHNetworkTypes::AdminPasswordFail:
		QMetaObject::invokeMethod(this, "serverError", Q_ARG(QString, "Invalid admin password"));
		break;
	default: break;
	}
}


void DeviceWidget::on_checkAutoOpen_toggled(bool on) {
	if (timer_ao != 0) killTimer(timer_ao);
	timer_ao = 0;
	if (on) timer_ao = startTimer(1000);
}


void DeviceWidget::on_buttonDisconnect_clicked() {
	serverDisconnect();
}


void DeviceWidget::on_buttonDeviceOpen_clicked() {
	if (isLocalDevice()) {
		if (ser->isOpened()) {
			ser->close();
			//emit closed();
			showDevices();
		} else {
			ser->setSpeed((PISerial::Speed)(ui_d->comboSpeed->itemData(ui_d->comboSpeed->currentIndex()).toInt()));
			if (ser->open(Q2PIString(ui_d->comboDevice->currentData().toString())))
				;//emit opened();
		}
	} else {
		if (channel.isConnected()) {
			channel.dataStop();
			setState(tr("Select server"));
		} else {
			server_device.clear();
			PacketServerInfo info = ui_d->treeServers->currentServer();
			if (!info.isValid() || info.connect_address.isEmpty()) return;
			channel.connectToServer(info);
		}
	}
}


void DeviceWidget::on_buttonDeviceConfigure_clicked() {
	dialog.exec();
}


void DeviceWidget::sessionSave(QByteArray * data) {
	ChunkStream cs;
	cs.add(1, ui_d->comboDevice->currentText()).add(2, ui_d->comboSpeed->currentIndex());
	cs.add(3, ui_d->checkAutoOpen->isChecked()).add(4, ui_d->lineAutoOpen->text());
	cs.add(5, ui_d->spinReadTimeout->value()).add(6, dialog.saveGeometry());
	*data = cs.data();
}


void DeviceWidget::sessionLoad(QByteArray * data) {
	ChunkStream cs(data);
	while (!cs.atEnd()) {
		switch (cs.read()) {
		case 1: ui_d->comboDevice->setCurrentText(cs.getData<QString>()); break;
		case 2: ui_d->comboSpeed->setCurrentIndex(cs.getData<int>()); break;
		case 3: ui_d->checkAutoOpen->setChecked(cs.getData<bool>()); break;
		case 4: ui_d->lineAutoOpen->setText(cs.getData<QString>()); break;
		case 5: ui_d->spinReadTimeout->setValue(cs.getData<int>()); break;
		case 6: dialog.restoreGeometry(cs.getData<QByteArray>()); break;
		}
	}
}


void DeviceWidget::on_tabWidget_currentChanged(int index) {
	if (index == 0) {
		if (channel.isConnected())
			emit closed();
		ui_d->treeServers->stopBroadcast();
	} else {
		setState(tr("Select server"));
		server_device.clear();
		ui_d->treeServers->startBroadcast();
	}
}


void DeviceWidget::on_treeServers_serverSelected(PacketServerInfo info) {
	ui->buttonDeviceOpen->click();
}


void DeviceWidget::on_treeDevices_itemDoubleClicked(QTreeWidgetItem * item, int column) {
	ui_d->buttonDeviceDialogOpen->blinkYes();
	selectServerDevice(ui_d->treeDevices->indexOfTopLevelItem(item));
	QTimer::singleShot(700, &dialog, SLOT(hide()));
}


void DeviceWidget::on_spinReadTimeout_valueChanged(int v) {
	setReadTimeout(v);
}


void DeviceWidget::on_buttonDeviceDialogOpen_clicked() {
	if (isOpened()) close();
	on_buttonDeviceOpen_clicked();
	ui_d->buttonDeviceDialogOpen->blinkYesOrNo(isOpened());
	if (isOpened())
		QTimer::singleShot(700, &dialog, SLOT(hide()));
}
