//..............................................................................
//
// Socket & network adapter user interface
//

import "ui_ToolBar.jnc"
import "ui_BufferPropertySet.jnc"
import "io_base.jncx"
import "io_Socket.jnc"
import "io_NetworkAdapter.jnc"

namespace ui {

//! \addtogroup common-ui
//! @{

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

enum NetworkAdapterUsage {
	Connection,
	Listener,
}

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

EnumPropertyOption const* createNetworkAdapterOptionArray(NetworkAdapterUsage usage) {
	size_t addressCount;
	io.NetworkAdapterDesc const* adapterList = io.enumerateNetworkAdapters(null, &addressCount);

	EnumPropertyOption* optionArray;
	EnumPropertyOption* option;

	switch (usage) {
	case NetworkAdapterUsage.Connection:
		addressCount++; // include 'Auto'
		optionArray = new EnumPropertyOption[addressCount];
		option = optionArray;

		option.m_text = "Auto";
		option.m_value = null;
		option++;
		break;

	case NetworkAdapterUsage.Listener:
		static io.SocketAddress autoAddress_ip4 = { m_family = io.AddressFamily.Ip4 }
		static io.SocketAddress autoAddress_ip6 = { m_family = io.AddressFamily.Ip6 }

		addressCount += 2; // include 'All IPv4/IPv6 adapters'
		optionArray = new EnumPropertyOption[addressCount];
		option = optionArray;

		option.m_text = "All IPv4 adapters";
		option.m_value = &autoAddress_ip4;
		option++;

		option.m_text = "All IPv6 adapters";
		option.m_value = &autoAddress_ip6;
		option++;
		break;
	}

	io.NetworkAdapterDesc const* adapter = adapterList;
	for (; adapter; adapter = adapter.m_next) {
		io.NetworkAdapterAddress const* address = adapter.m_address;
		for (; address; address = address.m_next, option++) {
			option.m_text = $"%1 - %2" (
				adapter.m_description,
				address.m_address.getString()
			);

			option.m_value = &address.m_address;
		}
	}

	return optionArray;
}

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

EnumProperty* createNetworkAdapterProperty(
	PropertyGrid* propertyGrid,
	Property* group = null,
	Property* insertBefore = null,
	NetworkAdapterUsage usage,
	string_t name,
	string_t toolTip
) {
	EnumPropertyOption const* optionArray = createNetworkAdapterOptionArray(usage);
	size_t optionCount = dynamic countof(optionArray);

	return propertyGrid.createEnumProperty(
		group,
		insertBefore,
		name,
		toolTip,
		optionArray,
		optionCount
	);
}

EnumProperty* createNetworkAdapterProperty(
	PropertyGrid* propertyGrid,
	NetworkAdapterUsage usage,
	string_t name,
	string_t toolTip
) {
	return createNetworkAdapterProperty(propertyGrid,,, usage, name, toolTip);
}

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

ComboBox* createNetworkAdapterComboBox(
	ToolBar* toolBar,
	NetworkAdapterUsage usage,
	uint_t width = -1
) {
	EnumPropertyOption const* optionArray = createNetworkAdapterOptionArray(usage);
	size_t optionCount = dynamic countof(optionArray);

	ComboBox* comboBox = toolBar.addComboBox(width);

	for (size_t i = 0; i < optionCount; i++)
		comboBox.addItem(
			optionArray[i].m_text,
			optionArray[i].m_value
		);

	return comboBox;
}

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

class SocketPropertySet {
	bitflag enum PropertyId {
		RemoteAddress,
		RemotePort,
		UseLocalAddress,
		ClientAdapter,
		ServerAdapter,
		LocalPort,
		ReuseLocalAddress,
		TcpOptions,
		UdpBroadcast,
		Reconnect,

		AllUdpClient =
			RemoteAddress |
			RemotePort |
			ServerAdapter |
			LocalPort |
			ReuseLocalAddress |
			UdpBroadcast,

		AllTcpClient =
			RemoteAddress |
			RemotePort |
			UseLocalAddress |
			ClientAdapter |
			LocalPort |
			ReuseLocalAddress |
			TcpOptions,

		AllTcpServer =
			ServerAdapter |
			LocalPort |
			ReuseLocalAddress |
			TcpOptions,
	}

	enum Defaults {
		RemotePort         = 80,
		UseReconnect       = false,
		ReconnectTimeout   = 3000,
		LocalPort          = 0,
		ReuseLocalAddress  = false,
		TcpKeepAlive       = false,
		TcpNagle           = false,
		TcpReset           = false,
		UdpBroadcast       = true,
	}

	static string_t const DefaultRemoteAddress = "ioninja.com";

	StringProperty* m_remoteAddressProp;
	IntProperty* m_remotePortProp;
	BoolProperty* m_useReconnectProp;
	IntProperty* m_reconnectTimeoutProp;
	BoolProperty* m_useLocalAddressProp;
	EnumProperty* m_adapterProp;
	IntProperty* m_localPortProp;
	BoolProperty* m_reuseLocalAddressProp;
	BoolProperty* m_tcpKeepAliveProp;
	BoolProperty* m_tcpNagleProp;
	BoolProperty* m_tcpResetProp;
	BoolProperty* m_udpBroadcastProp;

	string_t m_defaultRemoteAddress = DefaultRemoteAddress;
	uint16_t m_defaultRemotePort = Defaults.RemotePort;
	uint16_t m_defaultLocalPort = Defaults.LocalPort;
	uint_t m_defaultReconnectTimeout = Defaults.ReconnectTimeout;

	void createProperties(
		PropertyGrid* propertyGrid,
		Property* group = null,
		Property* insertBefore = null,
		PropertyId mask = PropertyId.AllTcpClient
	);

	void load(doc.Storage* storage);
	void save(doc.Storage* storage);
	void restoreDefaults();
}

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

void SocketPropertySet.createProperties(
	PropertyGrid* propertyGrid,
	Property* group,
	Property* insertBefore,
	PropertyId mask
) {
	if (mask & PropertyId.RemoteAddress)
		m_remoteAddressProp = propertyGrid.createStringProperty(
			group,
			insertBefore,
			"Remote address",
			"Enter a remote IP or hostname...",
			"Remote address to connect to (IP-address or hostname followed by ':' port)"
		);

	if (mask & PropertyId.RemotePort)
		m_remotePortProp = propertyGrid.createIntProperty(
			group,
			insertBefore,
			"Remote port (default)",
			"Remote port to connect to (if not specified in remote address)"
		);

	if (mask & PropertyId.Reconnect) {
		m_useReconnectProp = propertyGrid.createBoolProperty(
			group,
			insertBefore,
			"Reconnect",
			"Attempt to reconnect when connection is dropped by the remote node"
		);

		m_useReconnectProp.m_isGroupEnabler = true;

		m_reconnectTimeoutProp = propertyGrid.createIntProperty(
			m_useReconnectProp,,
			"Reconnect timeout",
			"Specify the delay between reconnect attempts"
		);
	}

	Property* localAddressGroup = group;
	Property* localAddressInsertBefore = insertBefore;

	if (mask & PropertyId.UseLocalAddress) {
		m_useLocalAddressProp = propertyGrid.createBoolProperty(
			group,
			insertBefore,
			"Use local address",
			"Bind socket to the specified local address"
		);

		m_useLocalAddressProp.m_isGroupEnabler = true;
		localAddressGroup = m_useLocalAddressProp;
		localAddressInsertBefore = null;
	}

	if (mask & (PropertyId.ClientAdapter | PropertyId.ServerAdapter))
		m_adapterProp = createNetworkAdapterProperty(
			propertyGrid,
			localAddressGroup,
			localAddressInsertBefore,
			(mask & PropertyId.ClientAdapter) ?
				NetworkAdapterUsage.Connection :
				NetworkAdapterUsage.Listener,
			"Adapter",
			"Network adapter to bind to"
		);

	if (mask & PropertyId.LocalPort)
		m_localPortProp = propertyGrid.createIntProperty(
			localAddressGroup,
			localAddressInsertBefore,
			"Local port",
			"Local port to bind to"
		);

	if (mask & PropertyId.ReuseLocalAddress)
		m_reuseLocalAddressProp = propertyGrid.createBoolProperty(
			localAddressGroup,
			localAddressInsertBefore,
			"Reuse address",
			"Allow multiple sockets to share the same local address"
		);

	if (mask & PropertyId.TcpOptions) {
		GroupProperty* tcpGroup = propertyGrid.createGroupProperty(
			group,
			insertBefore,
			"Advanced TCP options",
			"Specify advanced TCP flags"
		);

		m_tcpNagleProp = propertyGrid.createBoolProperty(
			tcpGroup,,
			"TCP Nagle algorithm",
			"Delay transmission to reduce the number of small TCP packets"
		);

		m_tcpResetProp = propertyGrid.createBoolProperty(
			tcpGroup,,
			"TCP reset",
			"Drop TCP connections abruptly with a TCP RST packet"
		);

		m_tcpKeepAliveProp = propertyGrid.createBoolProperty(
			tcpGroup,,
			"TCP keep-alives",
			"Detect connection loss with TCP keep-alive probes"
		);
	}

	if (mask & PropertyId.UdpBroadcast)
		m_udpBroadcastProp = propertyGrid.createBoolProperty(
			group,
			insertBefore,
			"UDP broadcast",
			"Allow broadcast packets to be transmitted"
		);
}

void SocketPropertySet.load(doc.Storage* storage) {
	if (m_remoteAddressProp)
		m_remoteAddressProp.m_value = storage.readString("address", m_defaultRemoteAddress);

	if (m_remotePortProp)
		m_remotePortProp.m_value = storage.readInt("remotePort", m_defaultRemotePort);

	if (m_useReconnectProp)
		m_useReconnectProp.m_value = storage.readBool("useReconnect");

	if (m_reconnectTimeoutProp)
		m_reconnectTimeoutProp.m_value = storage.readInt("reconnectTimeout", m_defaultReconnectTimeout);

	if (m_useLocalAddressProp)
		m_useLocalAddressProp.m_value = storage.readBool("useLocalAddress");

	if (m_adapterProp)
		m_adapterProp.m_currentIndex = storage.readInt("adapterIdx");

	if (m_localPortProp)
		m_localPortProp.m_value = storage.readInt("localPort", m_defaultLocalPort);

	if (m_reuseLocalAddressProp)
		m_reuseLocalAddressProp.m_value = storage.readBool("reuseLocalAddress", Defaults.ReuseLocalAddress);

	if (m_tcpKeepAliveProp)
		m_tcpKeepAliveProp.m_value = storage.readBool("tcpKeepAlive", Defaults.TcpKeepAlive);

	if (m_tcpNagleProp)
		m_tcpNagleProp.m_value = storage.readBool("isNagleEnabled", Defaults.TcpNagle);

	if (m_tcpResetProp)
		m_tcpResetProp.m_value = storage.readBool("closeKind", Defaults.TcpReset);

	if (m_udpBroadcastProp)
		m_udpBroadcastProp.m_value = storage.readBool("udpBroadcast", Defaults.UdpBroadcast);
}

void SocketPropertySet.save(doc.Storage* storage) {
	if (m_remoteAddressProp)
		storage.writeString("address", m_remoteAddressProp.m_value);

	if (m_remotePortProp)
		storage.writeInt("remotePort", m_remotePortProp.m_value);

	if (m_useReconnectProp)
		storage.writeBool("useReconnect", m_useReconnectProp.m_value);

	if (m_reconnectTimeoutProp)
		storage.writeInt("reconnectTimeout", m_reconnectTimeoutProp.m_value);

	if (m_useLocalAddressProp)
		storage.writeBool("useLocalAddress", m_useLocalAddressProp.m_value);

	if (m_adapterProp)
		storage.writeInt("adapterIdx", (int) m_adapterProp.m_currentIndex);

	if (m_localPortProp)
		storage.writeInt("localPort", m_localPortProp.m_value);

	if (m_reuseLocalAddressProp)
		storage.writeBool("reuseLocalAddress", m_reuseLocalAddressProp.m_value);

	if (m_tcpKeepAliveProp)
		storage.writeBool("tcpKeepAlive", m_tcpKeepAliveProp.m_value);

	if (m_tcpNagleProp)
		storage.writeBool("isNagleEnabled", m_tcpNagleProp.m_value);

	if (m_tcpResetProp)
		storage.writeBool("closeKind", m_tcpResetProp.m_value);

	if (m_udpBroadcastProp)
		storage.writeBool("udpBroadcast", m_udpBroadcastProp.m_value);
}

void SocketPropertySet.restoreDefaults() {
	if (m_remotePortProp)
		m_remotePortProp.m_value = m_defaultRemotePort;

	if (m_useReconnectProp)
		m_useReconnectProp.m_value = false;

	if (m_reconnectTimeoutProp)
		m_reconnectTimeoutProp.m_value = m_defaultReconnectTimeout;

	if (m_useLocalAddressProp)
		m_useLocalAddressProp.m_value = false;

	if (m_remotePortProp)
		m_remotePortProp.m_value = m_defaultRemotePort;

	if (m_adapterProp)
		m_adapterProp.m_currentIndex = 0;

	if (m_localPortProp)
		m_localPortProp.m_value = m_defaultLocalPort;

	if (m_reuseLocalAddressProp)
		m_reuseLocalAddressProp.m_value = Defaults.ReuseLocalAddress;

	if (m_tcpKeepAliveProp)
		m_tcpKeepAliveProp.m_value = Defaults.TcpKeepAlive;

	if (m_tcpNagleProp)
		m_tcpNagleProp.m_value = Defaults.TcpNagle;

	if (m_tcpResetProp)
		m_tcpResetProp.m_value = Defaults.TcpReset;

	if (m_udpBroadcastProp)
		m_udpBroadcastProp.m_value = Defaults.UdpBroadcast;
}

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

io.SocketOptions getSocketOptions(
	SocketPropertySet const* socketPropSet,
	BufferPropertySet const* bufferPropSet
) {
	io.SocketOptions options;

	if (socketPropSet.m_reuseLocalAddressProp && socketPropSet.m_reuseLocalAddressProp.m_value)
		options |= io.SocketOptions.ReuseAddress;

	if (socketPropSet.m_tcpKeepAliveProp && socketPropSet.m_tcpKeepAliveProp.m_value)
		options |= io.SocketOptions.TcpKeepAlive;

	if (socketPropSet.m_tcpNagleProp && socketPropSet.m_tcpNagleProp.m_value)
		options |= io.SocketOptions.TcpNagle;

	if (socketPropSet.m_tcpResetProp && socketPropSet.m_tcpResetProp.m_value)
		options |= io.SocketOptions.TcpReset;

	if (socketPropSet.m_udpBroadcastProp && socketPropSet.m_udpBroadcastProp.m_value)
		options |= io.SocketOptions.UdpBroadcast;

	if (bufferPropSet.m_keepReadBlockSizeProp && bufferPropSet.m_keepReadBlockSizeProp.m_value)
		options |= io.SocketOptions.KeepReadBlockSize;

	if (bufferPropSet.m_keepWriteBlockSizeProp && bufferPropSet.m_keepWriteBlockSizeProp.m_value)
		options |= io.SocketOptions.KeepWriteBlockSize;

	return options;
}

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

//! @}

} // namespace ui
