"use strict";

const url = require("url");
const net = require("net");
const asExpression = require("as-expression");
const { validateValue, arrayOf, isString, isBoolean, oneOf, ValidationError, either } = require("validatem");
const splitLimit = require("split-limit");

// FIXME: a 'wrap error' utility, for providing the cause in nested validation chains (eg. server name failed because port number failed because not numeric integer)? or just rely on chained errors for this? need some sort of 'context' feature/wrapper for describing 'virtual' properties of eg. parsed strings, so that we can say `someObject -> roomID -> [serverName] -> [portNumber]: Must be a valid port number`
// FIXME: When validating parsed strings, we're duplicating a lot of code between parsing and validation... need to find a model to remove this duplication, while still integrating into validatem well. Maybe using PEG.js?

// MARKER: virtual properties in validatem, parser combinator design for validatem?

function isNumericInteger(value) {
	isString(value);

	if (value.match(/[^0-9]/)) {
		throw new ValidationError(`Must be a numeric integer`);
	}
}

function isPortNumber(value) {
	isNumericInteger(value);

	if (parseInt(value) > 65535) {
		throw new ValidationError(`Must be a valid port number`);
	}
}

function isIPv4Address(value) {
	isString(value);

	if(!net.isIPv4(value)) {
		throw new ValidationError(`Must be a valid IPv4 address`);
	}
}

function isIPv6Address(value) {
	isString(value);

	if(!net.isIPv6(value)) {
		throw new ValidationError(`Must be a valid IPv6 address`);
	}
}

function isIPAddress(value) {
	isString(value);

	if(net.isIP(value) === 0) {
		throw new ValidationError(`Must be a valid IPv4 or IPv6 address`);
	}
}

function isDNSName(value) {
	isString(value);

	// TODO: Fix to use a more accurate regex (or library) for DNS names
	let dnsNameRegex = /^[0-9a-z.-]{1,255}$/i;

	if (!value.match(dnsNameRegex)) {
		throw new ValidationError(`Must be a valid DNS name`);
	}
}

function isHost(value) {
	either(isIPv4Address, isIPv6Address, isDNSName);
}

function isServerName(value) {
	isString(value);

	let split = value.split(":");

	if (split.length > 2) {
		throw new ValidationError(`Must be a valid server name`);
	} else {
		let [ host, port ] = split;

		if (port != null) {
			// NOTE: We are actually a bit stricter than the spec here, and don't just require a 5-digit number; it also needs to be a valid port number (ie. <= 65535)
			isPortNumber(port);
		}

		isHost(host);		
	}
}

function isNotEmptyString(value) {
	isString(value);

	if (value.length === 0) {
		throw new ValidationError(`Must not be empty`);
	}
}

let isSigil = oneOf([ "@", "!", "$", "+", "#" ]);

function isIdentifier(value) {
	isString(value);
	
	if (value.length > 255) {
		// https://github.com/matrix-org/matrix-doc/issues/1190
		throw new ValidationError(`May not be longer than 255 characters`);
	} else {
		let sigil = value[0];
		// FIXME: parsing context
		isSigil(sigil);

		let [ localPart, domain ] = splitLimit(value.slice(1), ":", 2);

		// FIXME: Parsing context
		isNotEmptyString(localPart);
		isServerName(domain);
	}
}

function parseIdentifier(identifier) {
	// https://matrix.org/docs/spec/appendices#common-identifier-format
	// FIXME: Does needing to use validateValue mean that exported validation functions are *not* composable into custom validators, because validateValue is sitting inbetween, and presumably the validation function would not have behaved as expected *without* validateValue?
	validateValue(identifier, isIdentifier);

	let sigil = identifier[0];
	let string = identifier.slice(1);

	let [ localPart, domain ] = splitLimit(string, ":", 2);

	return {
		localPart: localPart,
		domain: domain,
		type: matchValue(sigil, {
			"@": "user",
			"!": "room",
			"$": "event",
			"+": "group",
			"#": "roomAlias"
		})
	};
}

function isRoomID(value) {
	isString(value);
	isIdentifier(value);


}

// MARKER: Rewrite this all with the new version of validatem, and separate it out into packages (validatem and modular-matrix)

let eventFilter = {
	limit: isNumber, // FIXME: is positive number
	types: arrayOf(isString), // FIXME: Do validation rules exist for types?
	not_types: arrayOf(isString),
	senders: arrayOf(isUserID),
	not_senders: arrayOf(isUserID)
};

let stateFilter = {
	... eventFilter,
	rooms: arrayOf(isRoomID),
	not_rooms: arrayOf(isRoomID),
	lazy_load_members: isBoolean,
	include_redundant_members: isBoolean,
	contains_url: oneOf([ true, false, null ])
};

module.exports = function validateFilter(filter) {
	
};
