//..............................................................................
//
// SSH Connection session
//

import "doc_Session.jnc"
import "iox_SshChannel.jnc"
import "ui_Dialog.jnc"
import "ui_History.jnc"
import "ui_SocketUi.jnc"
import "ui_SshPropertySet.jnc"
import "ui_BufferPropertySet.jnc"
import "ui_StdSessionInfoSet.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.jnc"
import "std_Buffer.jnc"

using extension ui.ComboBoxHistory;

//..............................................................................

class SshChannelSession: doc.Session {
protected:
	alias State = iox.SshChannel.State;

	enum Defaults {
		ServerPort = 22,
	}

	enum ActionId {
		Connect,
		_Count,
	}

	enum IconId {
		Connect,
		Disconnect,
		_Count,
	}

	enum StatusPaneId {
		RemoteAddress,
		State,
		_Count,
	}

protected:
	// SSH channel

	iox.SshChannel m_ssh;

	// toolbar, status bar & menu

	ui.Action* m_actionTable[ActionId._Count];
	ui.Icon* m_iconTable[IconId._Count];
	ui.StatusPane* m_statusPaneTable[StatusPaneId._Count];
	ui.ComboBox* m_addressCombo;

	// property grid

	ui.SocketPropertySet m_socketPropertySet;
	ui.SshPropertySet m_sshPropertySet;
	ui.BufferPropertySet m_bufferPropertySet;

	// information grid

	ui.StdSessionInfoSet m_stdSessionInfoSet;
	ui.ThroughputInfoSet m_throughputInfoSet;
	ui.ChecksumInfoSet m_checksumInfoSet;

	// ui reactor

	reactor m_uiReactor;

public:
	construct(doc.PluginHost* pluginHost);

	bool errorcode connect();
	void disconnect();

	override void load(doc.Storage* storage);
	override void save(doc.Storage* storage);
	override void updateProperties();
	override bool errorcode applyProperties();
	override void restoreDefaultProperties();

	override size_t errorcode transmit(
		void const* p,
		size_t size
	);

	override bool errorcode connectSession() {
		return m_sessionConnectionState ? true : connect();
	}

	override void disconnectSession() {
		disconnect();
	}

protected:
	void onConnect() {
		m_ssh.m_state ? disconnect() : try connect();
	}

	void onAddressComboEnter() {
		try connect();
	}

	void onConnected() {
		m_stdSessionInfoSet.startSession(m_pluginHost.m_mainThreadScheduler);
	}

	void onDisconnected() {
		m_stdSessionInfoSet.endSession();
	}

	void onIncomingData(
		void const* p,
		size_t size
	) {
		m_logWriter.write(log.StdRecordCode.Rx, p, size);
	}

	void onLoginDlgCompleted(
		string_t userName,
		ui.AuthenticationMethod authenticationMethod,
		string_t privateKeyFileName
	);

	void createUi();
	iox.SshChannel.ConnectParams const* createConnectParams();
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

SshChannelSession.construct(doc.PluginHost* pluginHost) {
	basetype.construct(pluginHost);
	m_ssh.construct(pluginHost);

	m_ssh.m_onLoginDlgCompleted += onLoginDlgCompleted;
	m_ssh.m_onConnected += onConnected;
	m_ssh.m_onDisconnected += onDisconnected;
	m_ssh.m_onIncomingData += onIncomingData;

	createUi();

	pluginHost.m_log.addSelectionProcessor(log.ThroughputCalcRangeProcessor.factory~(m_throughputInfoSet));
	pluginHost.m_log.addSelectionProcessor(log.ChecksumCalcRangeProcessor.factory~(m_checksumInfoSet));
}

void SshChannelSession.createUi() {
	// toolbar

	m_pluginHost.m_toolBar.addLabel("Address:");
	m_addressCombo = m_pluginHost.m_toolBar.addComboBox(-1);
	m_addressCombo.m_isEditable = true;
	m_addressCombo.m_onReturnPressed += onAddressComboEnter;
	m_addressCombo.m_placeholderText = "Enter a remote IP or hostname...";

	m_iconTable[IconId.Connect] = new ui.Icon("images/connect.png");
	m_iconTable[IconId.Disconnect] = new ui.Icon("images/disconnect.png");

	m_actionTable[ActionId.Connect] = createAction("Connect", m_iconTable[IconId.Connect]);
	m_actionTable[ActionId.Connect].m_onTriggered += onConnect;

	// status bar

	m_statusPaneTable[StatusPaneId.RemoteAddress] = m_pluginHost.m_statusBar.addPane(, 100);
	m_statusPaneTable[StatusPaneId.RemoteAddress].m_color = ui.StdColor.Blue;
	m_statusPaneTable[StatusPaneId.State] = m_pluginHost.m_statusBar.addPane(, 80);

	// property grid

	ui.GroupProperty* group = m_pluginHost.m_propertyGrid.createGroupProperty(
		"TCP",
		"TCP-specific connection settings"
	);

	m_socketPropertySet.createProperties(
		m_pluginHost.m_propertyGrid,
		group,,
		ui.SocketPropertySet.PropertyId.AllTcpClient |
		ui.SocketPropertySet.PropertyId.Reconnect
	);

	m_socketPropertySet.m_defaultRemotePort = Defaults.ServerPort;

	group = m_pluginHost.m_propertyGrid.createGroupProperty(
		"SSH",
		"SSH-specific connection settings"
	);

	m_sshPropertySet.createProperties(m_pluginHost.m_propertyGrid, group);

	m_bufferPropertySet.createPropertiesGrouped(
		m_pluginHost.m_propertyGrid,,,
		ui.BufferPropertySet.PropertyId.All & ~ui.BufferPropertySet.PropertyId.ReadParallelism
	);

	// information grid

	ui.InformationGroup* infoGroup = m_pluginHost.m_informationGrid.createGroup("SSH channel");

	m_stdSessionInfoSet.create(m_pluginHost.m_txRxStats, m_pluginHost.m_informationGrid, infoGroup);
	m_throughputInfoSet.createInformationValuesGrouped(m_pluginHost.m_informationGrid);
	m_checksumInfoSet.createInformationValuesGrouped(m_pluginHost.m_informationGrid);

	m_pluginHost.m_propertyGrid.resizeNameColumnToContents();
	m_pluginHost.m_informationGrid.resizeNameColumnToContents();

	m_uiReactor.start();
	m_sshPropertySet.m_uiReactor.start();
}

bool errorcode SshChannelSession.connect() {
	disconnect();
	startSession();
	m_addressCombo.addEditTextToHistory();
	return m_ssh.connect(m_addressCombo.m_editText, createConnectParams());
}

void SshChannelSession.disconnect() {
	if (!m_ssh.m_state)
		return;

	m_ssh.close();
	m_stdSessionInfoSet.endSession();
}

iox.SshChannel.ConnectParams const* SshChannelSession.createConnectParams() {
	string_t processType = m_sshPropertySet.m_processTypeProp.m_value;
	string_t processExtra = processType != "shell" ? m_sshPropertySet.m_processParamProp.m_value : null;

	iox.SshChannel.ConnectParams params;
	iox.SshChannel.initConnectParams(&params, m_socketPropertySet, m_sshPropertySet, m_bufferPropertySet);
	params.m_sshParams.m_channelType = "session";
	params.m_sshParams.m_processType = processType;
	params.m_sshParams.m_processExtra = processExtra.m_p;
	params.m_sshParams.m_processExtraSize = processExtra.m_length;
	params.m_sshParams.m_ptyType = m_sshPropertySet.m_ptyTypeProp.m_value;
	params.m_sshParams.m_ptyHeight = m_pluginHost.m_terminal.m_consoleWidth;
	params.m_sshParams.m_ptyHeight = m_pluginHost.m_terminal.m_consoleHeight;
	return params;
}

void SshChannelSession.load(doc.Storage* storage) {
	m_addressCombo.loadHistory(storage, "addressHistory");
	m_addressCombo.m_editText = storage.readString("address");

	m_socketPropertySet.load(storage);
	m_sshPropertySet.load(storage);
	m_bufferPropertySet.load(storage);
	m_ssh.applyProperties(m_socketPropertySet, m_sshPropertySet, m_bufferPropertySet);
}

void SshChannelSession.save(doc.Storage* storage) {
	m_socketPropertySet.save(storage);
	m_sshPropertySet.save(storage);
	m_bufferPropertySet.save(storage);

	m_addressCombo.saveHistory(storage, "addressHistory");
	storage.writeString("address", m_addressCombo.m_editText);
}

void SshChannelSession.updateProperties() {
	m_socketPropertySet.m_remoteAddressProp.m_value = m_addressCombo.m_editText;
}

bool errorcode SshChannelSession.applyProperties() {
	m_addressCombo.m_editText = m_socketPropertySet.m_remoteAddressProp.m_value;
	m_ssh.applyProperties(m_socketPropertySet, m_sshPropertySet, m_bufferPropertySet);
	return true;
}

void SshChannelSession.restoreDefaultProperties() {
	m_socketPropertySet.restoreDefaults();
	m_sshPropertySet.restoreDefaults();
	m_bufferPropertySet.restoreDefaults();
}

size_t errorcode SshChannelSession.transmit(
	void const* p,
	size_t size
) {
	if (m_ssh.m_state != State.Connected) {
		std.setError("not connected");
		return -1;
	}

	size_t result = m_ssh.write(p, size);
	if (result)
		m_logWriter.write(log.StdRecordCode.Tx, p, result);

	return result;
}

void SshChannelSession.onLoginDlgCompleted(
	string_t userName,
	ui.AuthenticationMethod authenticationMethod,
	string_t privateKeyFileName
) {
	m_sshPropertySet.m_userNameProp.m_value = userName;
	m_sshPropertySet.m_authenticationMethodProp.m_value = authenticationMethod;
	m_sshPropertySet.m_privateKeyFileProp.m_value = privateKeyFileName;
}

reactor SshChannelSession.m_uiReactor {
	m_title = $"SSH $(m_addressCombo.m_currentText)";
	m_isTransmitEnabled = m_ssh.m_isTransmitEnabled;

	m_sessionConnectionState =
		m_ssh.m_state == State.Connected ? SessionConnectionState.Connected :
		m_ssh.m_state == State.Closed ? SessionConnectionState.Disconnected :
		SessionConnectionState.Connecting;

	m_actionTable[ActionId.Connect].m_text = m_ssh.m_state ? "Disconnect" : "Connect";
	m_actionTable[ActionId.Connect].m_icon = m_iconTable[m_ssh.m_state ? IconId.Disconnect : IconId.Connect];
	m_statusPaneTable[StatusPaneId.State].m_text = m_ssh.m_stateStringTable[m_ssh.m_state];
	m_statusPaneTable[StatusPaneId.RemoteAddress].m_text = m_ssh.m_state > State.Resolving ? m_ssh.m_remoteAddress.getString() : "<peer-address>";
	m_statusPaneTable[StatusPaneId.RemoteAddress].m_isVisible = m_ssh.m_state > State.Resolving;
}

//..............................................................................
