text: 'use strict';
  'require view';
  'require dom';
  'require fs';
  'require ui';
  'require uci';
  'require form';
  'require tools.widgets as widgets';
  
  var aclList = {};
  
  function globListToRegExp(section_id, option) {
  	var list = L.toArray(uci.get('rpcd', section_id, option)),
  	    positivePatterns = [],
  	    negativePatterns = [];
  
  	if (option == 'read')
  		list.push.apply(list, L.toArray(uci.get('rpcd', section_id, 'write')));
  
  	for (var i = 0; i < list.length; i++) {
  		var array, glob;
  
  		if (list[i].match(/^\s*!/)) {
  			glob = list[i].replace(/^\s*!/, '').trim();
  			array = negativePatterns;
  		}
  		else {
  			glob = list[i].trim(),
  			array = positivePatterns;
  		}
  
  		array.push(glob.replace(/[.*+?^${}()|[\]\\]/g, function(m) {
  			switch (m[0]) {
  			case '?':
  				return '.';
  
  			case '*':
  				return '.*';
  
  			default:
  				return '\\' + m[0];
  			}
  		}));
  	}
  
  	return [
  		new RegExp('^' + (positivePatterns.length ? '(' + positivePatterns.join('|') + ')' : '') + '$'),
  		new RegExp('^' + (negativePatterns.length ? '(' + negativePatterns.join('|') + ')' : '') + '$')
  	];
  }
  
  var cbiACLLevel = form.DummyValue.extend({
  	textvalue: function(section_id) {
  		var allowedAclMatches = globListToRegExp(section_id, this.option.match(/read/) ? 'read' : 'write'),
  		    aclGroupNames = Object.keys(aclList),
  		    matchingGroupNames = [];
  
  		for (var j = 0; j < aclGroupNames.length; j++)
  			if (allowedAclMatches[0].test(aclGroupNames[j]) && !allowedAclMatches[1].test(aclGroupNames[j]))
  				matchingGroupNames.push(aclGroupNames[j]);
  
  		if (matchingGroupNames.length == aclGroupNames.length)
  			return E('span', { 'class': 'label' }, [ _('full', 'All permissions granted') ]);
  		else if (matchingGroupNames.length > 0)
  			return E('span', { 'class': 'label' }, [ _('partial (%d/%d)', 'Some permissions granted').format(matchingGroupNames.length, aclGroupNames.length) ]);
  		else
  			return E('span', { 'class': 'label warning' }, [ _('denied', 'No permissions granted') ]);
  	}
  });
  
  var cbiACLSelect = form.Value.extend({
  	renderWidget: function(section_id) {
  		var readMatches = globListToRegExp(section_id, 'read'),
  		    writeMatches = globListToRegExp(section_id, 'write');
  
  		var table = E('div', { 'class': 'table' }, [
  			E('div', { 'class': 'tr' }, [
  				E('div', { 'class': 'th' }, [ _('ACL group') ]),
  				E('div', { 'class': 'th' }, [ _('Description') ]),
  				E('div', { 'class': 'th' }, [ _('Access level') ])
  			]),
  			E('div', { 'class': 'tr' }, [
  				E('div', { 'class': 'td' }, [ '' ]),
  				E('div', { 'class': 'td' }, [ '' ]),
  				E('div', { 'class': 'td' }, [
  					_('Set all: ', 'Set all permissions in the table below to one of the given values'),
  					E('a', { 'href': '#', 'click': function() {
  						table.querySelectorAll('select').forEach(function(select) { select.value = select.options[0].value });
  					} }, [ _('denied', 'No permissions granted') ]), ' | ',
  					E('a', { 'href': '#', 'click': function() {
  						table.querySelectorAll('select').forEach(function(select) { select.value = 'read' });
  					} }, [ _('readonly', 'Only read permissions granted') ]), ' | ',
  					E('a', { 'href': '#', 'click': function() {
  						table.querySelectorAll('select').forEach(function(select) { select.value = 'write' });
  					} }, [ _('full', 'All permissions granted') ]),
  				])
  			])
  		]);
  
  		Object.keys(aclList).sort().forEach(function(aclGroupName) {
  			var isRequired = (aclGroupName == 'unauthenticated' || aclGroupName == 'luci-base'),
  			    isReadable = (readMatches[0].test(aclGroupName) && !readMatches[1].test(aclGroupName)) || null,
  			    isWritable = (writeMatches[0].test(aclGroupName) && !writeMatches[1].test(aclGroupName)) || null;
  
  			table.appendChild(E('div', { 'class': 'tr' }, [
  				E('div', { 'class': 'td' }, [ aclGroupName ]),
  				E('div', { 'class': 'td' }, [ aclList[aclGroupName].description || '-' ]),
  				E('div', { 'class': 'td' }, [
  					E('select', { 'data-acl-group': aclGroupName }, [
  						isRequired ? E([]) : E('option', { 'value': '' }, [ _('denied', 'No permissions granted') ]),
  						E('option', { 'value': 'read', 'selected': isReadable }, [ _('readonly', 'Only read permissions granted') ]),
  						E('option', { 'value': 'write', 'selected': isWritable }, [ _('full', 'All permissions granted') ])
  					])
  				])
  			]));
  		});
  
  		return table;
  	},
  
  	formvalue: function(section_id) {
  		var node = this.map.findElement('data-field', this.cbid(section_id)),
  		    data = {};
  
  		node.querySelectorAll('[data-acl-group]').forEach(function(select) {
  			var aclGroupName = select.getAttribute('data-acl-group'),
  			    value = select.value;
  
  			if (!value)
  				return;
  
  			switch (value) {
  			case 'write':
  				data.write = data.write || [];
  				data.write.push(aclGroupName);
  				/* fall through */
  
  			case 'read':
  				data.read = data.read || [];
  				data.read.push(aclGroupName);
  				break;
  			}
  		});
  
  		return data;
  	},
  
  	write: function(section_id, value) {
  		if (L.isObject(value) && Array.isArray(value.read))
  			uci.set('rpcd', section_id, 'read', value.read);
  
  		if (L.isObject(value) && Array.isArray(value.write))
  			uci.set('rpcd', section_id, 'write', value.write);
  	}
  });
  
  return view.extend({
  	load: function() {
  		return L.resolveDefault(fs.list('/usr/share/rpcd/acl.d'), []).then(function(entries) {
  			var tasks = [
  				L.resolveDefault(fs.stat('/usr/sbin/uhttpd'), null),
  				fs.lines('/etc/passwd')
  			];
  
  			for (var i = 0; i < entries.length; i++)
  				if (entries[i].type == 'file' && entries[i].name.match(/\.json$/))
  					tasks.push(L.resolveDefault(fs.read('/usr/share/rpcd/acl.d/' + entries[i].name).then(JSON.parse)));
  
  			return Promise.all(tasks);
  		});
  	},
  
  	render: function(data) {
  		ui.addNotification(null, E('p', [
  			_('The LuCI ACL management is in an experimental stage! It does not yet work reliably with all applications')
  		]), 'warning');
  
  		var has_uhttpd = data[0],
  		    known_unix_users = {};
  
  		for (var i = 0; i < data[1].length; i++) {
  			var parts = data[1][i].split(/:/);
  
  			if (parts.length >= 7)
  				known_unix_users[parts[0]] = true;
  		}
  
  		for (var i = 2; i < data.length; i++) {
  			if (!L.isObject(data[i]))
  				continue;
  
  			for (var aclName in data[i]) {
  				if (!data[i].hasOwnProperty(aclName))
  					continue;
  
  				aclList[aclName] = data[i][aclName];
  			}
  		}
  
  		var m, s, o;
  
  		m = new form.Map('rpcd', _('LuCI Logins'));
  
  		s = m.section(form.GridSection, 'login');
  		s.anonymous = true;
  		s.addremove = true;
  
  		s.modaltitle = function(section_id) {
  			return _('LuCI Logins') + ' » ' + (uci.get('rpcd', section_id, 'username') || _('New account'));
  		};
  
  		o = s.option(form.Value, 'username', _('Login name'));
  		o.rmempty = false;
  
  		o = s.option(form.ListValue, '_variant', _('Password variant'));
  		o.modalonly = true;
  		o.value('shadow', _('Use UNIX password in /etc/shadow'));
  		o.value('crypted', _('Use encrypted password hash'));
  		o.value('plain', _('Use plain password'));
  		o.cfgvalue = function(section_id) {
  			var value = uci.get('rpcd', section_id, 'password') || '';
  
  			if (value.substring(0, 3) == '$p$')
  				return 'shadow';
  			else if (value.substring(0, 3) == '$1$' || value == null)
  				return 'crypted';
  			else
  				return 'plain';
  		};
  		o.write = function() {};
  
  		o = s.option(widgets.UserSelect, '_account', _('UNIX account'), _('The system account to use the password from'));
  		o.modalonly = true;
  		o.depends('_variant', 'shadow');
  		o.cfgvalue = function(section_id) {
  			var value = uci.get('rpcd', section_id, 'password') || '';
  			return value.substring(3);
  		};
  		o.write = function(section_id, value) {
  			uci.set('rpcd', section_id, 'password', '$p$' + value);
  		};
  		o.remove = function() {};
  
  		o = s.option(form.Value, 'password', _('Password value'));
  		o.modalonly = true;
  		o.password = true;
  		o.rmempty = false;
  		o.depends('_variant', 'crypted');
  		o.depends('_variant', 'plain');
  		o.cfgvalue = function(section_id) {
  			var value = uci.get('rpcd', section_id, 'password') || '';
  			return (value.substring(0, 3) == '$p$') ? '' : value;
  		};
  		o.validate = function(section_id, value) {
  			var variant = this.map.lookupOption('_variant', section_id)[0];
  
  			switch (value.substring(0, 3)) {
  			case '$p$':
  				return _('The password may not start with "$p$".');
  
  			case '$1$':
  				variant.getUIElement(section_id).setValue('crypted');
  				break;
  
  			default:
  				if (variant.formvalue(section_id) == 'crypted' && value.length && !has_uhttpd)
  					return _('Cannot encrypt plaintext password since uhttpd is not installed.');
  			}
  
  			return true;
  		};
  		o.write = function(section_id, value) {
  			var variant = this.map.lookupOption('_variant', section_id)[0];
  
  			if (variant.formvalue(section_id) == 'crypted' && value.substring(0, 3) != '$1$')
  				return fs.exec('/usr/sbin/uhttpd', [ '-m', value ]).then(function(res) {
  					if (res.code == 0 && res.stdout)
  						uci.set('rpcd', section_id, 'password', res.stdout.trim());
  					else
  						throw new Error(res.stderr);
  				}).catch(function(err) {
  					throw new Error(_('Unable to encrypt plaintext password: %s').format(err.message));
  				});
  
  			uci.set('rpcd', section_id, 'password', value);
  		};
  		o.remove = function() {};
  
  		o = s.option(form.Value, 'timeout', _('Session timeout'));
  		o.default = '300';
  		o.datatype = 'uinteger';
  		o.textvalue = function(section_id) {
  			var value = uci.get('rpcd', section_id, 'timeout') || this.default;
  			return +value ? '%ds'.format(value) : E('em', [ _('does not expire') ]);
  		};
  
  		o = s.option(cbiACLLevel, '_read', _('Read access'));
  		o.modalonly = false;
  
  		o = s.option(cbiACLLevel, '_write', _('Write access'));
  		o.modalonly = false;
  
  		o = s.option(form.ListValue, '_level', _('Acess level'));
  		o.modalonly = true;
  		o.value('write', _('full', 'All permissions granted'));
  		o.value('read', _('readonly', 'Only read permissions granted'));
  		o.value('individual', _('individual', 'Select individual permissions manually'));
  		o.cfgvalue = function(section_id) {
  			var readList = L.toArray(uci.get('rpcd', section_id, 'read')),
  			    writeList = L.toArray(uci.get('rpcd', section_id, 'write'));
  
  			if (writeList.length == 1 && writeList[0] == '*')
  				return 'write';
  			else if (readList.length == 1 && readList[0] == '*')
  				return 'read';
  			else
  				return 'individual';
  		};
  		o.write = function(section_id) {
  			switch (this.formvalue(section_id)) {
  			case 'write':
  				uci.set('rpcd', section_id, 'read', '*');
  				uci.set('rpcd', section_id, 'write', '*');
  				break;
  
  			case 'read':
  				uci.set('rpcd', section_id, 'read', '*');
  				uci.unset('rpcd', section_id, 'write');
  				break;
  			}
  		};
  		o.remove = function() {};
  
  		o = s.option(cbiACLSelect, '_acl');
  		o.modalonly = true;
  		o.depends('_level', 'individual');
  
  		return m.render();
  	}
  });
  