//..............................................................................
//
// USB Control Endpoint terminal session
//

import "doc_Session.jnc"
import "io_usb.jncx"
import "io_UsbDb.jnc"
import "ui_UsbUi.jnc"
import "ui_BufferPropertySet.jnc"
import "ui_StdSessionInfoSet.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.jnc"
import "parseInteger.jnc"
import "std_Buffer.jnc"

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

class UsbControlEndpointSession: doc.Session
{
protected:
	enum State
	{
		Closed,
		Opened,
	}

	enum ActionId
	{
		Refresh,
		Open,
		_Count,
	}

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

	enum StatusPaneId
	{
		State,
		_Count,
	}

protected:
	// usb

	State bindable m_state;

	io.UsbDevice* m_device;
	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_deviceCombo;
	ui.ComboBox* m_reqDirectionCombo;
	ui.ComboBox* m_reqRecipientCombo;
	ui.ComboBox* m_reqTypeCombo;
	ui.ComboBox* m_reqCodeCombo;
	ui.ComboBox* m_reqValueCombo;
	ui.ComboBox* m_reqIndexCombo;

	// property grid

	ui.EnumProperty* m_deviceProp;
	ui.BoolProperty* m_autoDetachKernelDriverProp;
	ui.EnumProperty* m_reqDirectionProp;
	ui.EnumProperty* m_reqRecipientProp;
	ui.EnumProperty* m_reqTypeProp;
	ui.ComboProperty* m_reqCodeProp;
	ui.StringProperty* m_reqValueProp;
	ui.StringProperty* m_reqIndexProp;

	// 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 openDevice();
	void closeDevice();

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

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

protected:
	onEnumerateDevices()
	{
		ui.refreshUsbDeviceListUi(m_deviceCombo, m_deviceProp);
	}

	onOpenDevice();

	onTransferCompleted(
		uint_t syncId,
		uint_t type,
		uint_t code,
		uint_t value,
		uint_t index,
		void const* p,
		size_t size,
		std.Error const* error
		);

	createUi();
}

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

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

UsbControlEndpointSession.createUi()
{
	static ui.EnumPropertyOption reqDirectionTable[] =
	{
		{ "Out", 0x00 },
		{ "In",  0x80 },
	}

	static ui.EnumPropertyOption reqTypeTable[] =
	{
		{ "Standard", 0x00 << 5 },
		{ "Class",    0x01 << 5 },
		{ "Vendor",   0x02 << 5 },
		{ "Reserved", 0x03 << 5 },
	}

	static ui.EnumPropertyOption reqRecipientTable[] =
	{
		{ "Device",    0x00 },
		{ "Interface", 0x01 },
		{ "Endpoint",  0x02 },
		{ "Other",     0x03 },
	}

	static ui.EnumPropertyOption reqIdTable[] =
	{
		{ "Get Status",        "0x00" },
		{ "Clear Feature",     "0x01" },
		{ "Set Feature",       "0x03" },
		{ "Set Address",       "0x05" },
		{ "Get Descriptor",    "0x06" },
		{ "Set Descriptor",    "0x07" },
		{ "Get Configuration", "0x08" },
		{ "Set Configuration", "0x09" },
		{ "Get Interface",     "0x0a" },
		{ "Set Interface",     "0x0b" },
	}

	// toolbar

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

	m_pluginHost.m_toolBar.addLabel("Device:");
	m_deviceCombo = m_pluginHost.m_toolBar.addComboBox(-1);

	m_actionTable[ActionId.Refresh]  = createAction("Re-enumerate devices", m_iconTable [IconId.Refresh]);
	m_actionTable[ActionId.Refresh].m_onTriggered += onEnumerateDevices;

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

	m_reqDirectionCombo = m_pluginHost.m_toolBar.addComboBox(
		"Direction:",
		reqDirectionTable,
		countof(reqDirectionTable)
		);

	m_reqRecipientCombo = m_pluginHost.m_toolBar.addComboBox(
		"Recipient:",
		reqRecipientTable,
		countof(reqRecipientTable)
		);

	m_reqTypeCombo = m_pluginHost.m_toolBar.addComboBox(
		"Type:",
		reqTypeTable,
		countof(reqTypeTable)
		);

	m_reqCodeCombo = m_pluginHost.m_toolBar.addComboBox(
		"Request:",
		reqIdTable,
		countof(reqIdTable)
		);

	m_reqCodeCombo.m_isEditable = true;

	m_pluginHost.m_toolBar.addLabel("Value:");
	m_reqValueCombo = m_pluginHost.m_toolBar.addComboBox();
	m_reqValueCombo.m_isEditable = true;

	m_pluginHost.m_toolBar.addLabel("Index:");
	m_reqIndexCombo = m_pluginHost.m_toolBar.addComboBox();
	m_reqIndexCombo.m_isEditable = true;

	// status bar

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

	// property grid

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

	m_deviceProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Device",
		"Target USB device",
		null, 0
		);

	m_autoDetachKernelDriverProp = m_pluginHost.m_propertyGrid.createBoolProperty(
		group,,
		"Auto-detach",
		"Auto detach kernel driver (if present)"
		);

	m_reqDirectionProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Direction",
		"Direction of the USB control transfer",
		reqDirectionTable,
		countof(reqDirectionTable)
		);

	m_reqRecipientProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Recipient",
		"Target of the USB control transfer",
		reqRecipientTable,
		countof(reqRecipientTable)
		);

	m_reqTypeProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"Type",
		"Class of the USB control request code",
		reqTypeTable,
		countof(reqTypeTable)
		);

	m_reqCodeProp = m_pluginHost.m_propertyGrid.createComboProperty(
		group,,
		"Request",
		"Enter a request code...",
		"Code of the USB control request",
		reqIdTable,
		countof(reqIdTable)
		);

	m_reqValueProp = m_pluginHost.m_propertyGrid.createStringProperty(
		group,,
		"Value",
		"Enter a request value...",
		"The 1st ('value') parameter of the USB control request"
		);

	m_reqIndexProp = m_pluginHost.m_propertyGrid.createStringProperty(
		group,,
		"Index",
		"Enter a request index...",
		"The 2nd ('index') parameter of the USB control request"
		);

	// information grid

	ui.InformationGroup* infoGroup = m_pluginHost.m_informationGrid.createGroup("USB connection");

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

	m_uiReactor.start();

	ui.refreshUsbDeviceListUi(m_deviceCombo, m_deviceProp);
}

UsbControlEndpointSession.onOpenDevice()
{
	if (m_state >= State.Opened)
		closeDevice();
	else
		openDevice();
}

bool UsbControlEndpointSession.openDevice()
{
	if (m_deviceCombo.m_currentIndex == -1)
		return false;

	closeDevice();
	startSession();

	m_device = m_deviceCombo.m_currentData;

	io.UsbDeviceDesc const* deviceDesc = m_device.m_deviceDesc;

	m_device.open();

	DeviceParams params;
	params.m_bus = m_device.m_bus;
	params.m_address = m_device.m_address;
	params.m_productId = deviceDesc.m_productId;
	params.m_vendorId = deviceDesc.m_vendorId;

	m_logWriter.write(UsbLogRecordCode.DeviceOpened, &params, sizeof(params));
	m_state = State.Opened;
	return true;

catch:
	m_logWriter.writeLastError(UsbLogRecordCode.DeviceOpenError);
	return false;
}

UsbControlEndpointSession.closeDevice()
{
	if (m_state == State.Closed)
		return;

	m_device.close();
	m_device = null;

	m_logWriter.write(UsbLogRecordCode.DeviceClosed);
	m_state = State.Closed;
	m_syncId++;
}

UsbControlEndpointSession.load(doc.Storage* storage)
{
	uint_t vid = storage.readInt("deviceVid");
	uint_t pid = storage.readInt("devicePid");
	uint_t bus = storage.readInt("deviceBus");
	uint_t address = storage.readInt("deviceAddress");

	size_t index = ui.findUsbDeviceComboIndex(m_deviceCombo, vid, pid, bus, address);
	if (index != -1)
		m_deviceCombo.m_currentIndex = index;

	m_autoDetachKernelDriverProp.m_value = storage.readBool("autoDetachKernelDriver");
	m_reqDirectionCombo.m_currentIndex = storage.readInt("reqDirection");
	m_reqRecipientCombo.m_currentIndex = storage.readInt("reqRecipient");
	m_reqTypeCombo.m_currentIndex = storage.readInt("reqType");
	m_reqCodeCombo.m_editText = storage.readString("reqCode", "0x00");
	m_reqValueCombo.m_editText = storage.readString("reqValue", "0x0000");
	m_reqIndexCombo.m_editText = storage.readString("reqIndex", "0x0000");
}

UsbControlEndpointSession.save(doc.Storage* storage)
{
	io.UsbDevice* device = m_deviceCombo.m_currentData;
	if (device)
	{
		storage.writeInt("deviceVid", device.m_deviceDesc.m_vendorId);
		storage.writeInt("devicePid", device.m_deviceDesc.m_productId);
		storage.writeInt("deviceBus", device.m_bus);
		storage.writeInt("deviceAddress", device.m_address);
	}

	storage.writeBool("autoDetachKernelDriver", m_autoDetachKernelDriverProp.m_value);
	storage.writeInt("reqDirection", m_reqDirectionCombo.m_currentIndex);
	storage.writeInt("reqRecipient", m_reqRecipientCombo.m_currentIndex);
	storage.writeInt("reqType", m_reqTypeCombo.m_currentIndex);
	storage.writeString("reqCode", m_reqCodeCombo.m_editText);
	storage.writeString("reqValue", m_reqValueCombo.m_editText);
	storage.writeString("reqIndex", m_reqIndexCombo.m_editText);
}

UsbControlEndpointSession.updateProperties()
{
	m_deviceProp.m_currentIndex = m_deviceCombo.m_currentIndex;
	m_reqDirectionProp.m_currentIndex = m_reqDirectionCombo.m_currentIndex;
	m_reqRecipientProp.m_currentIndex = m_reqRecipientCombo.m_currentIndex;
	m_reqTypeProp.m_currentIndex = m_reqTypeCombo.m_currentIndex;
	m_reqCodeProp.m_value = m_reqCodeCombo.m_editText;
	m_reqValueProp.m_value = m_reqValueCombo.m_editText;
	m_reqIndexProp.m_value = m_reqIndexCombo.m_editText;
}

bool errorcode UsbControlEndpointSession.applyProperties()
{
	m_deviceCombo.m_currentIndex = m_deviceProp.m_currentIndex;
	m_reqDirectionCombo.m_currentIndex = m_reqDirectionProp.m_currentIndex;
	m_reqRecipientCombo.m_currentIndex = m_reqRecipientProp.m_currentIndex;
	m_reqTypeCombo.m_currentIndex = m_reqTypeProp.m_currentIndex;
	m_reqCodeCombo.m_editText = m_reqCodeProp.m_value;
	m_reqValueCombo.m_editText = m_reqValueProp.m_value;
	m_reqIndexCombo.m_editText = m_reqIndexProp.m_value;

	return true;
}

UsbControlEndpointSession.restoreDefaultProperties()
{
	m_autoDetachKernelDriverProp.m_value = true;
	m_reqDirectionCombo.m_currentIndex = 0;
	m_reqRecipientCombo.m_currentIndex = 0;
	m_reqTypeCombo.m_currentIndex = 0;
	m_reqCodeCombo.m_editText = "0x00";
	m_reqValueCombo.m_editText = "0x0000";
	m_reqIndexCombo.m_editText = "0x0000";
}

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

	uint_t direction = m_reqDirectionCombo.m_currentData;
	uint_t recipient = m_reqRecipientCombo.m_currentData;
	uint_t type = m_reqTypeCombo.m_currentData | direction | recipient;
	uint_t code = parseInteger(m_reqCodeCombo.m_editText);;
	uint_t value = parseInteger(m_reqValueCombo.m_editText);
	uint_t index = parseInteger(m_reqIndexCombo.m_editText);

	char* buffer = new char[size];
	if (!direction)
		memcpy(buffer, p, size);

	m_device.controlTransfer(
		type,
		code,
		value,
		index,
		buffer,
		size,
		-1,
		onTransferCompleted~(
			m_syncId,
			type,
			code,
			value,
			index,
			buffer
			) @ m_pluginHost.m_mainThreadScheduler
		);

	return size;

catch:
	m_logWriter.writeLastError(log.StdRecordCode.Error);
	return -1;
}

UsbControlEndpointSession.onTransferCompleted(
	uint_t syncId,
	uint_t type,
	uint_t code,
	uint_t value,
	uint_t index,
	void const* p,
	size_t size,
	std.Error const* error
	)
{
	if (syncId != m_syncId)
		return;

	ControlTransferParams params;
	params.m_type = type;
	params.m_code = code;
	params.m_value = value;
	params.m_index = index;

	m_logWriter.write(UsbLogRecordCode.ControlTransfer, &params, sizeof(params));

	if (error)
		m_logWriter.write(log.StdRecordCode.Error, error);
	else
		m_logWriter.write((type & 0x80) ? log.StdRecordCode.Rx : log.StdRecordCode.Tx, p, size);
}

reactor UsbControlEndpointSession.m_uiReactor
{
	m_title = $"USB $(m_deviceCombo.m_currentText)";
	m_actionTable[ActionId.Open].m_text = m_state ? "Close Device" : "Open Device";
	m_actionTable[ActionId.Open].m_icon = m_iconTable[m_state];
	m_isTransmitEnabled = m_state;
	m_statusPaneTable[StatusPaneId.State].m_text = m_state ? "Opened" : "Closed";
}

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