//..............................................................................
//
// Generic file session
//

import "doc_Session.jnc"
import "io_base.jncx"
import "io_FileStream.jnc"
import "ui_History.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 FileSession: doc.Session {
protected:
	enum State {
		Closed,
		Opened,
	}

	enum Access {
		ReadWrite,
		ReadOnly,
		WriteOnly,
	}

	enum ActionId {
		Open,
		Clear,
		_Count,
	}

	enum IconId {
		Open,
		Close,
		Clear,
		_Count,
	}

	enum StatusPaneId {
		State,
		_Count,
	}

protected:
	// file

	State bindable m_state;
	io.FileStream m_file;
	std.Buffer m_readBuffer;
	uint_t m_syncId;

	// 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_fileNameCombo;
	ui.ComboBox* m_accessCombo;

	// property grid

	ui.StringProperty* m_fileNameProp;
	ui.EnumProperty* m_accessProp;
	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 open();
	void close();
	void clear();

	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 : open();
	}

	override void disconnectSession() {
		close();
	}

protected:
	void onOpen() {
		m_state ? close() : try open();
	}

	void onFileNameComboEnter() {
		try open();
	}

	void onFileStreamEvent(
		uint_t syncId,
		io.FileStreamEvents triggeredEvents
	);

	io.FileStreamOptions getFileStreamOptions();
	void waitFileStreamEvents();
	void createUi();
}

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

FileSession.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));
}

void FileSession.createUi() {
	static ui.EnumPropertyOption accessModeTable[] = {
		{ "Read/Write",  Access.ReadWrite },
		{ "Read-only",   Access.ReadOnly  },
		{ "Write-only",  Access.WriteOnly },
	}

	// toolbar

	m_pluginHost.m_toolBar.addLabel("File:");
	m_fileNameCombo = m_pluginHost.m_toolBar.addComboBox(-1);
	m_fileNameCombo.m_isEditable = true;
	m_fileNameCombo.m_onEnter += onFileNameComboEnter;
	m_fileNameCombo.m_placeholderText = "Enter a file or device name...";

	m_accessCombo = m_pluginHost.m_toolBar.addComboBox("Access:", accessModeTable, countof(accessModeTable));

	m_iconTable[IconId.Open] = new ui.Icon("images/open.png");
	m_iconTable[IconId.Close] = new ui.Icon("images/close.png");
	m_iconTable[IconId.Clear] = new ui.Icon("images/clear.png");

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

	m_actionTable[ActionId.Clear] = createAction("Clear", m_iconTable[IconId.Clear]);
	m_actionTable[ActionId.Clear].m_onTriggered += clear;
	m_actionTable[ActionId.Clear].m_isEnabled = false;

	// status bar

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

	// property grid

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

	m_fileNameProp = m_pluginHost.m_propertyGrid.createStringProperty(
		group,,
		"File name",
		"Enter a file name...",
		"Specify a file name to open"
	);

	m_accessProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Access mode",
		"Access mode for the file",
		accessModeTable,
		countof(accessModeTable)
	);

	m_bufferPropertySet.createPropertiesGrouped(m_pluginHost.m_propertyGrid);

	// information grid

	ui.InformationGroup* infoGroup = m_pluginHost.m_informationGrid.createGroup("File session");

	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();
}

bool errorcode FileSession.open() {
	close();
	startSession();

	m_fileNameCombo.addEditTextToHistory();

	char const* fileName = m_fileNameCombo.m_editText;

	Access access = m_accessCombo.m_currentData;

	io.FileOpenFlags openFlags =
		access == Access.ReadOnly ? io.FileOpenFlags.ReadOnly :
		access == Access.WriteOnly ? io.FileOpenFlags.WriteOnly : 0;

	m_file.m_readParallelism = m_bufferPropertySet.m_readParallelismProp.m_value;
	m_file.m_readBlockSize = m_bufferPropertySet.m_readBlockSizeProp.m_value;
	m_file.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;
	m_file.m_writeBufferSize = m_bufferPropertySet.m_writeBufferSizeProp.m_value;
	m_file.m_options = getFileStreamOptions();
	m_file.open(fileName, openFlags);

	FileOpenLogRecordParams	params;
	params.m_kind = m_file.m_kind;
	params.m_openFlags = openFlags;

	m_logWriter.write(
		FileLogRecordCode.Opened,
		&params,
		sizeof(params),
		fileName,
		strlen(fileName) + 1
	);

	m_state = State.Opened;
	m_sessionConnectionState = SessionConnectionState.Connected;
	m_isTransmitEnabled = access != m_state == State.Opened;

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

catch:
	m_logWriter.writeLastError(FileLogRecordCode.OpenError);
	m_file.close();
	return false;
}

void FileSession.close() {
	if (m_state != State.Opened)
		return;

	m_logWriter.write(FileLogRecordCode.Closed);
	m_file.close();
	m_state = State.Closed;
	m_sessionConnectionState = SessionConnectionState.Disconnected;
	m_isTransmitEnabled = false;
	m_syncId++;
	m_stdSessionInfoSet.endSession();
}

void FileSession.clear() {
	if (m_state != State.Opened)
		return;

	try m_file.clear();
	m_logWriter.write(FileLogRecordCode.Cleared);
}

io.FileStreamOptions FileSession.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;
}

void FileSession.waitFileStreamEvents() {
	io.FileStreamEvents eventMask =
		io.FileStreamEvents.IoError |
		io.FileStreamEvents.ReadBufferFull |
		io.FileStreamEvents.IncomingData |
		io.FileStreamEvents.Eof;

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

	m_file.wait(eventMask, onFileStreamEvent~(++m_syncId) @ m_pluginHost.m_mainThreadScheduler);
}

void FileSession.load(doc.Storage* storage) {
	m_fileNameCombo.loadHistory(storage, "fileNameHistory");
	m_fileNameCombo.m_editText = storage.readString("fileName");
	m_accessCombo.m_currentData = storage.readInt("access");

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

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

	m_fileNameCombo.saveHistory(storage, "fileNameHistory");
	storage.writeString("fileName", m_fileNameCombo.m_editText);
	storage.writeInt("access", m_accessCombo.m_currentData);
}

void FileSession.updateProperties() {
	m_fileNameProp.m_value = m_fileNameCombo.m_editText;
	m_accessProp.m_value = m_accessCombo.m_currentData;
}

bool errorcode FileSession.applyProperties() {
	m_fileNameCombo.m_editText = m_fileNameProp.m_value;
	m_accessCombo.m_currentData = m_accessProp.m_value;
	m_readBuffer.setSize(m_bufferPropertySet.m_readBufferSizeProp.m_value);

	if (!m_file.m_isOpen)
		return true;

	m_file.m_readParallelism = m_bufferPropertySet.m_readParallelismProp.m_value;
	m_file.m_readBlockSize = m_bufferPropertySet.m_readBlockSizeProp.m_value;
	m_file.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;
	m_file.m_writeBufferSize = m_bufferPropertySet.m_writeBufferSizeProp.m_value;
	m_file.m_options = getFileStreamOptions();
	return true;
}

void FileSession.restoreDefaultProperties() {
	m_accessProp.m_value = Access.ReadWrite;
	m_bufferPropertySet.restoreDefaults();
}

size_t errorcode FileSession.transmit(
	void const* p,
	size_t size
) {
	if (m_state != State.Opened) {
		std.setError("file stream not opened");
		return -1;
	}

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

	return result;
}

void FileSession.onFileStreamEvent(
	uint_t syncId,
	io.FileStreamEvents triggeredEvents
) {
	if (syncId != m_syncId)
		return;

	if (triggeredEvents & io.FileStreamEvents.IoError) {
		m_logWriter.write(log.StdRecordCode.Error, m_file.m_ioError);
		close();
		return;
	}

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

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

	if (triggeredEvents & io.FileStreamEvents.Eof) {
		for (;;) { // read all the buffered data
			size_t result = m_file.read(m_readBuffer.m_p, m_readBuffer.m_size);
			if (!result)
				break;

			m_logWriter.write(log.StdRecordCode.Rx, m_readBuffer.m_p, result);
		}

		m_logWriter.write(FileLogRecordCode.Eof);
		close();
		return;
	}

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

	waitFileStreamEvents();
}

reactor FileSession.m_uiReactor {
	m_title = $"File $(m_fileNameCombo.m_editText)";
	m_accessCombo.m_isEnabled = !m_state;
	m_accessProp.m_isEnabled = !m_state;
	m_actionTable[ActionId.Open].m_text = m_state ? "Close" : "Open";
	m_actionTable[ActionId.Open].m_icon = m_iconTable[m_state ? IconId.Close : IconId.Open];
	m_actionTable[ActionId.Clear].m_isEnabled = m_state && m_file.m_kind == io.FileKind.Disk;
	m_statusPaneTable[StatusPaneId.State].m_text = m_state ? "Opened" : "Closed";
}

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