var async = require('async'),
    authcommon = require('../admin/common'),
    check = require('./msgcheck').check,
    common = require('../common'),
    config = require('../config'),
    db = require('../db'),
    hooks = require('../hooks');

var RANGES = require('./state').dbCache.ranges;

function can_access_board(ident, board) {
	if (board == 'graveyard' && can_administrate(ident))
		return true;
	if (board == config.STAFF_BOARD && !can_moderate(ident))
		return false;
	if (ident.ban || ident.suspension)
		return false;
	if (!temporal_access_check(ident, board))
		return false;
	return db.is_board(board);
}
exports.can_access_board = can_access_board;

exports.can_access_thread = function (ident, op) {
	var tags = db.tags_of(op);
	if (!tags)
		return false;
	for (var i = 0; i < tags.length; i++)
		if (can_access_board(ident, tags[i]))
			return tags[i];
	return false;
};

function temporal_access_check(ident, board) {
	var info = {ident: ident, board: board, access: true};
	hooks.trigger_sync('temporalAccessCheck', info);
	return info.access;
}
exports.temporal_access_check = temporal_access_check;

exports.can_ever_access_board = function (ident, board) {
	if (can_access_board(ident, board))
		return true;
	if (!temporal_access_check(ident, board))
		return true;
	return false;
};

function can_moderate(ident) {
	return (ident.auth === 'Admin' || ident.auth === 'Moderator');
}
exports.can_moderate = can_moderate;

function can_administrate(ident) {
	return ident.auth === 'Admin';
}
exports.can_administrate = can_administrate;

function denote_priv(info) {
	if (info.data.priv)
		info.header.push(' (priv)');
}

function dead_media_paths(paths) {
	paths.src = '../dead/src/';
	paths.thumb = '../dead/thumb/';
	paths.mid = '../dead/mid/';
}

exports.augment_oneesama = function (oneeSama, opts) {
	var ident = opts.ident;
	oneeSama.ident = ident;
	if (can_moderate(ident))
		oneeSama.hook('headerName', authcommon.append_mnemonic);
	if (can_administrate(ident)) {
		oneeSama.hook('headerName', denote_priv);
		oneeSama.hook('headerName', authcommon.denote_hidden);
	}
	if (can_administrate(ident) && opts.board == 'graveyard')
		oneeSama.hook('mediaPaths', dead_media_paths);
};

exports.mod_handler = function (func) {
	return function (nums, client) {
		if (!can_moderate(client.ident))
			return false;
		var opts = nums.shift();
		if (!check({when: 'string'}, opts) || !check('id...', nums))
			return false;
		if (!(opts.when in authcommon.delayDurations))
			return false;
		var delay = authcommon.delayDurations[opts.when];
		if (!delay)
			func(nums, client);
		else
			setTimeout(func.bind(null, nums, client), delay*1000);
		return true;
	};
};

function parse_ip(ip) {
	var m = ip.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)(?:\/(\d+))?$/);
	if (!m)
		return false;
	// damn you signed int32s!
	var num = 0;
	for (var i = 4, shift = 1; i > 0; i--) {
		num += parseInt(m[i], 10) * shift;
		shift *= 256;
	}

	var info = {full: ip, num: num};
	if (m[5]) {
		var bits = parseInt(m[5], 10);
		if (bits > 0 && bits <= 32) {
			info.mask = 0x100000000 - Math.pow(2, 32 - bits);
			info.num &= info.mask;
		}
	}
	return info;
}

function parse_ranges(ranges) {
	if (!ranges)
		return [];
	ranges = ranges.map(function (o) {
		if (typeof o == 'object') {
			o.ip = parse_ip(o.ip);
			return o;
		}
		else
			return {ip: parse_ip(o)};
	});
	ranges.sort(function (a, b) { return a.ip.num - b.ip.num; });
	return ranges;
}

function range_lookup(ranges, num) {
	if (!ranges)
		return null;
	/* Ideally would have a tree lookup here or something */
	var result = null;
	for (var i = 0; i < ranges.length; i++) {
		var box = ranges[i].ip;
		/* sint32 issue here doesn't matter for realistic ranges */
		if ((box.mask ? (num & box.mask) : num) === box.num)
			result = ranges[i];
		/* don't break out of loop */
	}
	return result;
}

hooks.hook('reloadHot', function (hot, cb) {
	var r = global.redis;
	async.forEach(authcommon.suspensionKeys, function (key, cb) {
		global.redis.smembers('hot:' + key, function (err, ranges) {
			if (err)
				return cb(err);
			if (key == 'suspensions')
				ranges = parse_suspensions(ranges);
			var up = key.toUpperCase();
			hot[up] = (hot[up] || []).concat(ranges || []);
			RANGES[key] = parse_ranges(hot[up]);
			cb(null);
		});
	}, cb);
});

function parse_suspensions(suspensions) {
	if (!suspensions)
		return [];
	var parsed = [];
	suspensions.forEach(function (s) {
		try {
			parsed.push(JSON.parse(s));
		}
		catch (e) {
			winston.error("Bad suspension JSON: " + s);
		}
	});
	return parsed;
}

exports.lookup_ident = function (ip, country) {
	var ident = {ip, country, readOnly: config.READ_ONLY};
	if (country
		&& config.RESTRICTED_COUNTRIES
		&& config.RESTRICTED_COUNTRIES.indexOf(country) >= 0) {
		ident.readOnly = true;
	}
	var num = parse_ip(ip).num;
	var ban = range_lookup(RANGES.bans, num);
	if (ban) {
		ident.ban = ban.ip.full;
		return ident;
	}
	ban = range_lookup(RANGES.timeouts, num);
	if (ban) {
		ident.ban = ban.ip.full;
		ident.timeout = true;
		return ident;
	}
	var suspension = range_lookup(RANGES.suspensions, num);
	if (suspension) {
		ident.suspension = suspension;
		return ident;
	}

	var priv = range_lookup(RANGES.boxes, num);
	if (priv)
		ident.priv = priv.ip.full;

	var slow = range_lookup(RANGES.slows, num);
	if (slow)
		ident.slow = slow;

	return ident;
};


