exports.id = 'httproute';
exports.title = 'HTTP Route';
exports.group = 'HTTP';
exports.color = '#5D9CEC';
exports.icon = 'globe';
exports.input = false;
exports.output = ['#6CAC5A', '#37BC9B'];
exports.version = '1.2.3';
exports.author = 'Martin Smola';
exports.cloning = false;
exports.options = { method: 'GET', url: '', size: 5, cacheexpire: '5 minutes', cachepolicy: 0, timeout: 5 };
exports.dateupdated = '2021-01-21 18:30d';
exports.readme = `# HTTP route

__Outputs__:
- first output: raw data (cache is empty or is disabled)
- second output: cached data

If one of the outputs is disabled then automatic responce with code "503 service unavailable" is sent.

When a request comes in bellow object is available at \`flowdata.data\`:

\`\`\`javascript
{
	params: { id: '1' },     // params for dynamic routes, e.g. /test/{id}
	query: { msg: 'Hello' }, // parsed query string, e.g. /test/1?msg=Hello
	body: { test: 'OK' },    // object if json requests otherwise string
	headers: {},             // headers data
	session: {},             // session data
	user: {},                // user data
	files: [],               // uploaded files
	url: '/users/',          // a relative URL address
	referrer: '/',           // referrer
	mobile: false,           // determines mobile device
	robot: false,            // determines search robots/crawlsers
	language: 'en'           // determines language
}
\`\`\`

See [documentation for flags](https://docs.totaljs.com/latest/en.html#api~HttpRouteOptionsFlags~unauthorize). These method flags are set automatically e.g. \`get, post, put, patch or delete\`

---

\`id:ROUTE_ID\` flag cannot be used since it's already used by this component internally`;

exports.html = `<div class="padding">
	<section>
		<label>@(Main settings)</label>
		<div class="padding npb">

			<div class="row">
				<div class="col-md-3 m">
					<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE,PATCH,OPTIONS">@(HTTP method)</div>
				</div>
				<div class="col-md-9 m">
					<div data-jc="textbox" data-jc-path="url" data-jc-config="required:true;maxlength:500;placeholder:/api/test;error:URL already in use">@(URL address)</div>
				</div>
			</div>
			<div class="row">
				<div class="col-md-6 m">
					<div data-jc="textbox" data-jc-path="flags" data-jc-config="placeholder:json">@(Additional flags)</div>
					<div class="help m">@(Separate flags by comma e.g. <code>json, authorize</code>)</div>
				</div>
				<div class="col-md-3 m">
					<div data-jc="textbox" data-jc-path="size" data-jc-config="placeholder:@(in kB);increment:true;type:number;maxlength:10;align:center">@(Max. request size)</div>
					<div class="help m">@(In <code>kB</code> kilobytes)</div>
				</div>
				<div class="col-md-3 m">
					<div data-jc="textbox" data-jc-path="timeout" data-jc-config="placeholder:@(in seconds);increment:true;type:number;maxlength:5;align:center">@(Timeout)</div>
					<div class="help m">@(In seconds.)</div>
				</div>
			</div>
		</div>
	</section>
	<br />
	<div data-jc="checkbox" data-jc-path="emptyresponse" class="b black">@(Automatically respond with 200 OK?)</div>
	<div class="help m">@(If not checked you need to use HTTP response component to respond to the request.)</div>
	<hr />
	<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
	<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
	</div>
	<hr class="nmt" />
	<div class="padding npt">
		<div class="row">
			<div class="col-md-9 m">
				<div data-jc="dropdown" data-jc-path="cachepolicy" data-jc-config="type:number;items:@(no cache)|0,@(URL)|1,@(URL + query string)|2,@(URL + query string + user instance)|3">@(Cache policy)</div>
				<div class="help">@(User instance must contain <code>id</code> property.)</div>
			</div>
			<div class="col-md-3 m">
				<div data-jc="textbox" data-jc-path="cacheexpire" data-jc-config="maxlength:20;align:center">@(Expiration)</div>
				<div class="help">@(E.g. <code>5 minutes</code>)</div>
			</div>
		</div>
	</div>
<script>

	var httproute_currenturl = '';
	var httproute_currentmethod = 'GET';

	ON('open.httproute', function(component, options) {
		if (options.flags instanceof Array) {
			var method = options.method.toLowerCase();
			options.flags = options.flags.remove(function(item) {
				switch (typeof(item)) {
					case 'string':
						return item.substring(0, 3) === 'id:' || item === method;
					case 'number': // timeout
						return true;
				}
			}).join(', ');
		}
		if (component.isnew) {
			options.url = '';
			options.name = '';
		} else {
			httproute_currenturl = options.url;
			httproute_currentmethod = options.method;
		}
	});

	WATCH('settings.httproute.url', httproutecheckurl);
	WATCH('settings.httproute.method', httproutecheckurl);

	function httproutecheckurl() {
		if (httproute_currenturl !== settings.httproute.url || httproute_currentmethod !== settings.httproute.method) {
			TRIGGER('httproutecheckurl', { url: settings.httproute.url, method: settings.httproute.method }, function(e) {
				var p = 'settings.httproute.url';
				if (e) {
					// invalid
					INVALID(p);
				} else {
					if (!CAN(p))
						RESET(p);
				}
			});
		}
	};

	ON('save.httproute', function(component, options) {
		!component.name && (component.name = options.method + ' ' + options.url);

		var builder = [];
		builder.push('### @(Configuration)');
		builder.push('');
		builder.push('- __' + options.method + ' ' + options.url + '__');
		builder.push('- @(flags): ' + options.flags);
		builder.push('- @(maximum request data length): __' + options.size + ' kB__');
		builder.push('- @(empty response): __' + options.emptyresponse + '__');

		if (options.headers) {
			var headers = [];
			Object.keys(options.headers).forEach(function(key){
				headers.push(key + ': ' + options.headers[key]);
			});
			headers.length && builder.push('- @(headers):\\n\`\`\`' + headers.join('\\n') + '\`\`\`');
		};

		var cp =  '@(no cache)';
		if (options.cachepolicy === 1)
			cp = '@(URL)';
		if (options.cachepolicy === 2)
			cp = '@(URL + query string)';

		if (options.cachepolicy === 3)
			cp = '@(URL + query string + user instance)';

		builder.push('- @(cache policy): __' + cp + '__');

		options.cacheexpire && builder.push('- @(cache expire): __' + options.cacheexpire + '__');

		component.notes = builder.join('\\n');
	});
</script>`;

exports.install = function(instance) {

	var route;

	var uninstall = function(id) {
		if (F.is4) {
			route && route.remove();
			route = null;
		} else
			UNINSTALL('route', id);
	};

	instance.custom.emptyresponse = function(self) {
		self.plain();
	};

	instance.reconfigure = function() {

		var options = instance.options;

		if (!options.url) {
			instance.status('Not configured', 'red');
			return;
		}

		if (typeof(options.flags) === 'string')
			options.flags = options.flags.split(',').trim();

		uninstall('id:' + instance.id);

		var flags = options.flags || [];

		flags.push('id:' + instance.id);

		if (!F.is4)
			flags.push(options.method.toLowerCase());

		options.timeout && flags.push(options.timeout * 1000);

		// Make unique values
		flags = flags.filter(function(v, i, a) {
			if(F.is4 && v.toString().toLowerCase() === options.method.toLowerCase())
				return false; // remove method
			return a.indexOf(v) === i;
		});

		options.flags = flags;
		var handler = function() {

			if (instance.paused || (instance.isDisabled && (instance.isDisabled('output', 0) || instance.isDisabled('output', 1)))) {
				instance.status('503 Service Unavailable');
				this.status = 503;
				this.json();
				return;
			}

			var key;
			var self = this;

			if (instance.options.emptyresponse) {
				instance.status('200 OK');
				setTimeout(instance.custom.emptyresponse, 100, self);

				if (instance.hasConnection(0)) {
					var data = instance.make({
						query: self.query,
						body: self.body,
						session: self.session,
						user: self.user,
						files: self.files,
						headers: self.req.headers,
						url: self.url,
						params: self.params
					});
					instance.send2(0, data);
				}

				return;
			}

			switch (instance.options.cachepolicy) {
				case 1: // URL
					key = 'rro' + instance.id + self.url.hash();
					break;
				case 2: // URL + query
				case 3: // URL + query + user
					key = self.url;
					var keys = Object.keys(self.query);
					keys.sort();
					for (var i = 0, length = keys.length; i < length; i++)
						key += keys[i] + self.query[keys[i]] + '&';
					if (instance.options.cachepolicy === 3 && self.user)
						key += 'iduser' + self.user.id;
					key = 'rro' + instance.id + key.hash();
					break;
			}

			if (key && F.cache.get2(key)) {
				var data = instance.make(F.cache.get2(key));
				data.repository.controller = self;
				instance.send2(1, data);
			} else {

				var data = instance.make({
					query: self.query,
					body: self.body,
					session: self.session,
					user: self.user,
					files: self.files,
					headers: self.req.headers,
					url: self.url,
					params: self.params,
					mobile: self.mobile,
					robot: self.robot,
					referrer: self.referrer,
					language: self.language
				});

				data.repository.controller = self;
				instance.send2(0, data);
				key && FINISHED(self.res, () => F.cache.set(key, self.$flowdata.data, instance.options.cacheexpire));
			}
		};

		if (F.is4)
			route = ROUTE(options.method.toUpperCase() + ' ' + options.url, handler, flags, options.size || 5);
		else
			F.route(options.url, handler, flags, options.size || 5);

		instance.status('Listening', 'green');
	};

	instance.reconfigure();
	instance.on('options', instance.reconfigure);
	instance.on('close', function(){
		uninstall('id:' + instance.id);
	});
};

// check url exists
FLOW.trigger('httproutecheckurl', function(next, data) {
	var url = data.url;
	var method = data.method;
	if (url[url.length - 1] !== '/')
		url += '/';
	var exists = F.routes.web.findItem(r => r.urlraw === url && r.method === method);
	next(exists != null);
});

exports.uninstall = function() {
	FLOW.trigger('httproutecheckurl', null);
};
