//..............................................................................
//
// Pipe Listener session
//

import "io_base.jncx"
import "io_NamedPipe.jnc"
import "io_FileStream.jnc"
import "io_BufferUi.jnc"
import "std_Buffer.jnc"
import "doc_Session.jnc"
import "gui_History.jnc"
import "gui_StdSessionInfo.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.jnc"

using extension gui.ComboBoxHistory;

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

class PipeListenerSession: doc.Session
{
protected:
	enum Defaults
	{
		MessageMode        = false,
		OverrideConnection = true,
		ConnectParallelism = 2,
	}

	enum State
	{
		Closed,
		Opened,
	}

	enum ActionId
	{
		Listen,
		Disconnect,
		_Count,
	}

	enum IconId
	{
		Listen,
		StopListen,
		Disconnect,
		_Count,
	}

	enum StatusPaneId
	{
		ConnectionState,
		ListenerState,
		_Count,
	}

protected:
	// listener

	State bindable m_listenerState;
	io.NamedPipe m_listenerPipe;
	char const* m_pipeName;
	uint_t m_listenerSyncId;

	// connection

	State bindable m_connectionState;
	io.FileStream* m_connectionPipe;
	std.Buffer m_readBuffer;
	uint_t m_connectionSyncId;

	// toolbar, status bar & menu

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

	// property grid

	gui.StringProperty* m_pipeNameProp;
	gui.BoolProperty* m_messageModeProp;
	gui.BoolProperty* m_overrideOldConnectionProp;
	io.BufferPropertySet m_bufferPropertySet;

	// information grid

	gui.StdSessionInfoSet m_stdSessionInfoSet;
	log.ThroughputInfoSet m_throughputInfoSet;
	log.ChecksumInfoSet m_checksumInfoSet;

	// ui reactor

	reactor m_uiReactor;

public:
	construct (doc.PluginHost* pluginHost);

	bool errorcode listen ();
	stopListen ();
	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
		);

protected:
	onListen ()
	{
		m_listenerState ? stopListen () : try listen ();
	}

	onPipeNameComboEnter ()
	{
		try listen ();
	}

	onListenerPipeEvent (
		uint_t syncId,
		io.NamedPipeEvents triggeredEvents
		);

	onConnectionPipeEvent (
		uint_t syncId,
		io.FileStreamEvents triggeredEvents
		);

	io.FileStreamOptions getFileStreamOptions ();
	waitListenerPipeEvents ();
	waitConnectionPipeEvents ();
	createUi ();
}

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

PipeListenerSession.construct (doc.PluginHost* pluginHost)
{
	basetype.construct (pluginHost);

	createUi ();

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

PipeListenerSession.createUi ()
{
	// toolbar

	m_pluginHost.m_toolBar.addLabel ("Name:");
	m_pipeNameCombo = m_pluginHost.m_toolBar.addComboBox (-1);
	m_pipeNameCombo.m_isEditable = true;
	m_pipeNameCombo.m_onEnter += onPipeNameComboEnter;

	m_iconTable [IconId.Listen] = new gui.Icon ("images/listen.png");
	m_iconTable [IconId.StopListen] = new gui.Icon ("images/pause.png");
	m_iconTable [IconId.Disconnect] = new gui.Icon ("images/disconnect.png");

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

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

	// status bar

	m_statusPaneTable [StatusPaneId.ConnectionState] = m_pluginHost.m_statusBar.addPane (, 80);
	m_statusPaneTable [StatusPaneId.ListenerState] = m_pluginHost.m_statusBar.addPane (, 80);

	// property grid

	gui.GroupProperty* group = m_pluginHost.m_propertyGrid.createGroupProperty (
		"Connection",
		"Named pipe-specific connection settings"
		);

	m_pipeNameProp = m_pluginHost.m_propertyGrid.createStringProperty (
		group,,
		"Pipe name",
		"Specify pipe name to listen on"
		);

	m_messageModeProp =  m_pluginHost.m_propertyGrid.createBoolProperty (
		group,,
		"Message mode",
		"Read and write using the named pipe message mode"
		);

	m_overrideOldConnectionProp = m_pluginHost.m_propertyGrid.createBoolProperty (
		group,,
		"Override connections",
		"Accept incoming connection even if already connected"
		);

	m_bufferPropertySet.createPropertiesGrouped (m_pluginHost.m_propertyGrid);

	// information grid

	gui.InformationGroup* infoGroup = m_pluginHost.m_informationGrid.createGroup ("Pipe listener");

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

	m_pluginHost.m_informationGrid.resizeNameColumnToContents ();

	m_uiReactor.start ();
}

bool errorcode PipeListenerSession.listen ()
{
	stopListen ();
	startSession ();

	m_pipeNameCombo.addEditTextToHistory ();

	char const* name = m_pipeNameCombo.m_editText;

	io.FileStreamOptions options = getFileStreamOptions ();

	if (m_messageModeProp.m_value)
		options |= io.FileStreamOptions.MessageNamedPipe;

	m_listenerPipe.m_readParallelism = m_bufferPropertySet.m_readParallelismProp.m_value;
	m_listenerPipe.m_readBlockSize = m_bufferPropertySet.m_readBlockSizeProp.m_value;
	m_listenerPipe.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;
	m_listenerPipe.m_writeBufferSize = m_bufferPropertySet.m_writeBufferSizeProp.m_value;
	m_listenerPipe.m_options = options;
	m_listenerPipe.open (name);

	writeLog (
		PipeListenerLogRecordCode.Listening,
		name,
		strlen (name)
		);

	m_pipeName = name;
	m_listenerState = State.Opened;

	waitListenerPipeEvents ();
	m_stdSessionInfoSet.startSession (m_pluginHost.m_mainThreadScheduler);
	return true;

catch:
	std.Error const* error = std.getLastError ();
	writeLog (
		PipeListenerLogRecordCode.ListenError,
		error,
		error.m_size
		);

	m_listenerPipe.close ();
	return false;
}

PipeListenerSession.stopListen ()
{
	if (m_listenerState != State.Opened)
		return;

	writeLog (
		PipeListenerLogRecordCode.ListenStopped,
		m_pipeName,
		strlen (m_pipeName)
		);

	m_listenerPipe.close ();
	m_listenerState = State.Closed;
	m_listenerSyncId++;
	m_stdSessionInfoSet.endSession ();
}

PipeListenerSession.disconnect ()
{
	if (m_connectionState != State.Opened)
		return;

	writeLog (PipeListenerLogRecordCode.ClientDisconnected);
	m_connectionPipe.close ();
	m_connectionPipe = null;
	m_connectionState = State.Closed;
	m_connectionSyncId++;
}

io.FileStreamOptions PipeListenerSession.getFileStreamOptions ()
{
	io.FileStreamOptions options;

	if (m_bufferPropertySet.m_keepReadBlockSizeProp.m_value)
		options |= io.FileStreamOptions.KeepReadBlockSize;

	if (m_bufferPropertySet.m_keepWriteBlockSizeProp.m_value)
		options |= io.FileStreamOptions.KeepWriteBlockSize;

	return options;
}

PipeListenerSession.waitListenerPipeEvents ()
{
	io.NamedPipeEvents eventMask =
		io.NamedPipeEvents.IoError |
		io.NamedPipeEvents.IncomingConnection;

	m_listenerPipe.wait (eventMask, onListenerPipeEvent ~(++m_listenerSyncId) @ m_pluginHost.m_mainThreadScheduler);
}

PipeListenerSession.waitConnectionPipeEvents ()
{
	io.FileStreamEvents eventMask =
		io.FileStreamEvents.IoError |
		io.FileStreamEvents.ReadBufferFull |
		io.FileStreamEvents.IncomingData |
		io.FileStreamEvents.Eof;

	if (!m_isTransmitEnabled)
		eventMask |= io.FileStreamEvents.WriteBufferReady;

	m_connectionPipe.wait (eventMask, onConnectionPipeEvent ~(++m_connectionSyncId) @ m_pluginHost.m_mainThreadScheduler);
}

void PipeListenerSession.load (doc.Storage* storage)
{
	m_pipeNameCombo.loadHistory (storage, "pipeNameHistory");
	m_pipeNameCombo.m_editText = storage.readString ("pipeName");
	m_messageModeProp.m_value = storage.readBool ("messageMode", Defaults.MessageMode);
	m_overrideOldConnectionProp.m_value = storage.readBool ("overrideOldConnection", Defaults.OverrideConnection);

	m_bufferPropertySet.load (storage);
	m_readBuffer.setSize (m_bufferPropertySet.m_readBufferSizeProp.m_value);
}

void PipeListenerSession.save (doc.Storage* storage)
{
	m_bufferPropertySet.save (storage);

	m_pipeNameCombo.saveHistory (storage, "pipeNameHistory");
	storage.writeString ("pipeName", m_pipeNameCombo.m_editText);
	storage.writeBool ("messageMode", m_messageModeProp.m_value);
	storage.writeBool ("overrideOldConnection", m_overrideOldConnectionProp.m_value);
}

void PipeListenerSession.updateProperties ()
{
	m_pipeNameProp.m_value = m_pipeNameCombo.m_editText;
}

bool errorcode PipeListenerSession.applyProperties ()
{
	m_pipeNameCombo.m_editText = m_pipeNameProp.m_value;
	m_readBuffer.setSize (m_bufferPropertySet.m_readBufferSizeProp.m_value);

	io.FileStreamOptions options = getFileStreamOptions ();

	if (m_listenerPipe.m_isOpen)
	{
		m_listenerPipe.m_readParallelism = m_bufferPropertySet.m_readParallelismProp.m_value;
		m_listenerPipe.m_readBlockSize = m_bufferPropertySet.m_readBlockSizeProp.m_value;
		m_listenerPipe.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;
		m_listenerPipe.m_writeBufferSize = m_bufferPropertySet.m_writeBufferSizeProp.m_value;
		m_listenerPipe.m_options = options;
	}

	if (m_connectionPipe)
	{
		m_connectionPipe.m_readParallelism = m_bufferPropertySet.m_readParallelismProp.m_value;
		m_connectionPipe.m_readBlockSize = m_bufferPropertySet.m_readBlockSizeProp.m_value;
		m_connectionPipe.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;
		m_connectionPipe.m_writeBufferSize = m_bufferPropertySet.m_writeBufferSizeProp.m_value;
		m_connectionPipe.m_options = options;
	}

	return true;
}

void PipeListenerSession.restoreDefaultProperties ()
{
	m_overrideOldConnectionProp.m_value = Defaults.OverrideConnection;
	m_bufferPropertySet.restoreDefaults ();
}

size_t errorcode PipeListenerSession.transmit (
	void const* p,
	size_t size
	)
{
	if (m_connectionState != State.Opened)
		return -1;

	size_t result = m_connectionPipe.write (p, size);
	if (result)
		writeLog (log.StdRecordCode.Tx, p, result);

	return result;
}

PipeListenerSession.onListenerPipeEvent (
	uint_t syncId,
	io.NamedPipeEvents triggeredEvents
	)
{
	if (syncId != m_listenerSyncId)
		return;

	if (triggeredEvents & io.NamedPipeEvents.IoError)
	{
		writeLog (log.StdRecordCode.Error, m_listenerPipe.m_ioError, m_listenerPipe.m_ioError.m_size);
		stopListen ();
		return;
	}

	if (triggeredEvents & io.NamedPipeEvents.IncomingConnection)
	{
		io.FileStream* pipe = m_listenerPipe.accept ();

		if (m_connectionState == State.Opened && !m_overrideOldConnectionProp.m_value)
		{
			pipe.close ();
			writeLog (PipeListenerLogRecordCode.ClientRejected);
		}
		else
		{
			disconnect ();

			writeLog (PipeListenerLogRecordCode.ClientConnected);

			m_connectionPipe = pipe;
			m_connectionState = State.Opened;
			waitConnectionPipeEvents ();
		}
	}

	waitListenerPipeEvents ();
}

PipeListenerSession.onConnectionPipeEvent (
	uint_t syncId,
	io.FileStreamEvents triggeredEvents
	)
{
	if (syncId != m_connectionSyncId)
		return;

	if (triggeredEvents & io.FileStreamEvents.IoError)
	{
		writeLog (log.StdRecordCode.Error, m_connectionPipe.m_ioError, m_connectionPipe.m_ioError.m_size);
		disconnect ();
		return;
	}

	if ((triggeredEvents & io.FileStreamEvents.ReadBufferFull) &&
		m_bufferPropertySet.m_notifyReadBufferFullProp.m_value)
		writeLog (log.StdRecordCode.RxBufferFull);

	if (triggeredEvents & io.FileStreamEvents.IncomingData)
	{
		size_t result = m_connectionPipe.read (m_readBuffer.m_p, m_readBuffer.m_size);
		if (result)
			writeLog (log.StdRecordCode.Rx, m_readBuffer.m_p, result);
	}

	if (triggeredEvents & io.FileStreamEvents.Eof)
	{
		disconnect ();
		return;
	}

	if (io.FileStreamEvents.WriteBufferReady)
		m_isTransmitEnabled = true;

	waitConnectionPipeEvents ();
}

reactor PipeListenerSession.m_uiReactor
{
	m_title = $"Pipe $(m_pipeNameCombo.m_editText)";
	m_isTransmitEnabled = m_connectionState == State.Opened;
	m_actionTable [ActionId.Listen].m_text = m_listenerState ? "Stop Listening" : "Listen";
	m_actionTable [ActionId.Listen].m_icon = m_iconTable [m_listenerState ? IconId.StopListen : IconId.Listen];
	m_actionTable [ActionId.Disconnect].m_isEnabled = m_connectionState == State.Opened;
	m_statusPaneTable [StatusPaneId.ListenerState].m_text = m_listenerState  ? "Listening" : "Not listening";
	m_statusPaneTable [StatusPaneId.ConnectionState].m_text = m_connectionState  ? "Connected" : "Not connected";
}

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