package io.openems.common.websocket;

import java.util.Optional;
import java.util.UUID;

import org.java_websocket.WebSocket;
import org.java_websocket.handshake.Handshakedata;

public class WebsocketUtils {

	/**
	 * Gets a String value from a {@link Handshakedata}.
	 * 
	 * <p>
	 * NOTE: Per <a href=
	 * "https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">specification</a>
	 * "Field names are case-insensitive".
	 *
	 * @param handshakedata the {@link Handshakedata}
	 * @param fieldName     the name of the field
	 * @return the field value; or null
	 */
	public static String getAsString(Handshakedata handshakedata, String fieldName) {
		return getAsOptionalString(handshakedata, fieldName).orElse(null);
	}

	/**
	 * Gets a String value from a {@link Handshakedata}.
	 *
	 * <p>
	 * NOTE: Per <a href=
	 * "https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">specification</a>
	 * "Field names are case-insensitive".
	 *
	 * @param handshakedata the {@link Handshakedata}
	 * @param fieldName     the name of the field
	 * @return the field value as optional; empty if not found
	 */
	public static Optional<String> getAsOptionalString(Handshakedata handshakedata, String fieldName) {
		for (var iter = handshakedata.iterateHttpFields(); iter.hasNext();) {
			var field = iter.next();
			if (fieldName.equalsIgnoreCase(field)) {
				return Optional.of(handshakedata.getFieldValue(field).trim());
			}
		}
		return Optional.empty();
	}

	/**
	 * Gets a String value from a {@link Handshakedata}.
	 *
	 * <p>
	 * NOTE: Per <a href=
	 * "https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">specification</a>
	 * "Field names are case-insensitive".
	 *
	 * @param handshakedata the {@link Handshakedata}
	 * @param header        the header to search for
	 * @return the field value as optional; empty if not found
	 */
	public static Optional<String> getAsOptionalString(Handshakedata handshakedata, CommonHttpHeader header) {
		return getAsOptionalString(handshakedata, header.asString());
	}

	/**
	 * Gets a UUID value from a {@link Handshakedata}.
	 *
	 * <p>
	 * NOTE: Per <a href=
	 * "https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">specification</a>
	 * "Field names are case-insensitive".
	 *
	 * @param handshakedata the {@link Handshakedata}
	 * @param header        the header to search for
	 * @return the field value as optional; empty if not found or not a valid UUID
	 */
	public static Optional<UUID> getAsOptionalUuid(Handshakedata handshakedata, CommonHttpHeader header) {
		return getAsOptionalString(handshakedata, header) //
				.map((raw) -> {
					try {
						return UUID.fromString(raw);
					} catch (IllegalArgumentException e) {
						return null;
					}
				});
	}

	private static final String[] REMOTE_IDENTIFICATION_HEADERS = new String[] { //
			"Forwarded", "X-Forwarded-For", "X-Real-IP" };

	/**
	 * Parses a identifier for the Remote from the {@link Handshakedata}.
	 * 
	 * <p>
	 * Tries to use the headers "Forwarded", "X-Forwarded-For" or "X-Real-IP". Falls
	 * back to `ws.getRemoteSocketAddress()`. See https://serverfault.com/a/920060
	 * 
	 * @param ws            the {@link WebSocket}
	 * @param handshakedata the {@link Handshakedata}
	 * @return an identifier String
	 */
	public static String parseRemoteIdentifier(WebSocket ws, Handshakedata handshakedata) {
		for (var key : REMOTE_IDENTIFICATION_HEADERS) {
			var value = getAsString(handshakedata, key);
			if (value != null) {
				return value;
			}
		}
		// fallback
		return ws.getRemoteSocketAddress().toString();
	}

	/**
	 * Gets the toLogString() content of the WsData attachment of the WebSocket; or
	 * empty string if not available.
	 *
	 * @param ws the WebSocket
	 * @return the {@link WsData#toLogString()} content
	 */
	public static String generateWsDataString(WebSocket ws) {
		if (ws == null) {
			return "";
		}
		WsData wsData = ws.getAttachment();
		if (wsData == null) {
			return "";
		}
		var logString = wsData.toLogString();
		if (logString == null) {
			return "";
		}
		return logString;
	}
}
