const dns = require('dns');
const dgram = require('dgram');

module.exports = {

	clean: string => string.replace(/\^[^^]/g, ''),

	isIP: ip => ip.match(/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/),

	status: (address, port, callback, timeout = 1000) => {

		if (!module.exports.isIP(address)) {

			let resolved = false;

			dns.lookup(address, (e, result) => {

				if (!result && !resolved) {
					resolved = true;
					callback('Failed to resolve ' + address + '.', null);
				} else if (!resolved) {
					resolved = true;
					module.exports.status(result, port, callback, timeout / 2);
				}

			});

			setTimeout(() => {

				if (!resolved) {
					resolved = true;
					callback('DNS resolve of ' + address + ' timed out.', null);
				}

			}, timeout / 2);

			return;

		}

		const socket = dgram.createSocket('udp4');
		socket.unref();

		socket.on('message', (response, info) => {

			if (info.address !== address || info.port !== port) {
				return;
			}

			const header = 'statusResponse\n';

			if (response.length <= 4 + header.length) {
				_callback('Received truncated status response.', null);
				return;
			}

			for (let i = 0; i < 4; i++) {
				if (response[i] !== 0xff) {
					_callback('Received an invalid header prefix in status response.', null);
					return;
				}
			}

			if (response.slice(4, header.length + 4).toString().toLowerCase() !== header.toLowerCase()) {
				_callback('Received an invalid header in status response.', null);
				return;
			}

			const lines = response.slice(4 + header.length, response.length - 1).toString().split('\n');
			const cvars = lines[0].substring(1).split('\\');

			const status = {
				info: {},
				clients: [],
			};

			for (let i = 0; i + 1 < cvars.length; i += 2) {
				status.info[cvars[i].toLowerCase()] = cvars[i + 1];
			}

			for (let i = 1; i < lines.length; i++) {

				let matches = lines[i].match(/^(\d+) (\d+) "(.+)"$/);

				if (matches) {
					status.clients.push({
						name: module.exports.clean(matches[3]),
						score: parseInt(matches[1]),
						ping: parseInt(matches[2]),
					});
				}

			}

			if ('sv_hostname' in status.info) {
				status.info.sv_hostname = module.exports.clean(status.info.sv_hostname);
				_callback(null, status);
			} else {
				_callback('An invalid general status response received.', null);
			}

		});

		const buffer = Buffer.from([0xff, 0xff, 0xff, 0xff, 0x67, 0x65, 0x74, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x0a]);

		socket.send(buffer, 0, buffer.length, port, address, (error, bytes) => {

			if (error) {
				_callback(error, null);
			}

		});

		const _callback = (error, response) => {

			clearTimeout(_timeout);

			// Prevent multiple resolves.
			if (callback) {

				socket.removeAllListeners();
				socket.close();

				callback(error, response);
				callback = null;

			}

		};

		const _timeout = setTimeout(() => _callback('Status response timed out.', null), timeout).unref();

	},

};