var async = require('async'),
    config = require('./config'),
    child_process = require('child_process'),
    etc = require('../etc'),
    Muggle = etc.Muggle,
    imagerDb = require('./db'),
    index = require('./'),
    formidable = require('formidable'),
    fs = require('fs'),
    jobs = require('./jobs'),
    path = require('path'),
    urlParse = require('url').parse,
    util = require('util'),
    winston = require('winston');

var IMAGE_EXTS = ['.png', '.jpg', '.gif'];
if (config.VIDEO && !config.DAEMON) {
	console.warn("Please enable imager.config.DAEMON security.");
}

function new_upload(req, resp) {
	var upload = new ImageUpload;
	upload.handle_request(req, resp);
}
exports.new_upload = new_upload;

function get_thumb_specs(image, pinky, scale) {
	var w = image.dims[0], h = image.dims[1];
	var bound = config[pinky ? 'PINKY_DIMENSIONS' : 'THUMB_DIMENSIONS'];
	var r = Math.max(w / bound[0], h / bound[1], 1);
	var dims = [Math.round(w/r) * scale, Math.round(h/r) * scale];
	var specs = {bound: bound, dims: dims, format: 'jpg'};
	// Note: WebMs pretend to be PNGs at this step,
	//       but those don't need transparent backgrounds.
	//       (well... WebMs *can* have alpha channels...)
	if (config.PNG_THUMBS && image.ext == '.png' && !image.video) {
		specs.format = 'png';
		specs.quality = config.PNG_THUMB_QUALITY;
	}
	else if (pinky) {
		specs.bg = '#ffffff';
		specs.quality = config.PINKY_QUALITY;
	}
	else {
		specs.bg = '#ffffff';
		specs.quality = config.THUMB_QUALITY;
	}
	return specs;
}

var ImageUpload = function (client_id) {
	this.db = new imagerDb.Onegai;
	this.client_id = client_id;
};

var IU = ImageUpload.prototype;

var validFields = ['spoiler', 'op'];

IU.status = function (msg) {
	this.client_call('status', msg);
};

IU.client_call = function (t, msg) {
	this.db.client_message(this.client_id, {t: t, arg: msg});
};

IU.respond = function (code, msg) {
	if (!this.resp)
		return;
	const origin = config.MAIN_SERVER_ORIGIN;
	this.resp.writeHead(code, {
		'Content-Type': 'text/html; charset=UTF-8',
		'Access-Control-Allow-Origin': origin,
	});
	this.resp.end('<!doctype html><title>Upload result</title>\n'
		+ 'This is a legitimate imager response.\n'
		+ '<script>\nparent.postMessage(' + etc.json_paranoid(msg)
		+ ', ' + etc.json_paranoid(origin) + ');\n'
		+ '</script>\n');
	this.resp = null;
};

IU.handle_request = function (req, resp) {
	if (req.method.toLowerCase() != 'post') {
		resp.writeHead(405, {Allow: 'POST'});
		resp.end();
		return;
	}
	this.resp = resp;
	var query = req.query || urlParse(req.url, true).query;
	this.client_id = parseInt(query.id, 10);
	if (!this.client_id || this.client_id < 1) {
		this.respond(400, "Bad client ID.");
		return;
	}

	var len = parseInt(req.headers['content-length'], 10);
	if (len > 0 && len > config.IMAGE_FILESIZE_MAX + (20*1024))
		return this.failure(Muggle('File is too large.'));

	var form = new formidable.IncomingForm({
		uploadDir: config.MEDIA_DIRS.tmp,
		maxFieldsSize: 50 * 1024,
		hash: 'md5',
	});
	form.onPart = function (part) {
		if (part.filename && part.name == 'image')
			form.handlePart(part);
		else if (!part.filename && validFields.indexOf(part.name) >= 0)
			form.handlePart(part);
	};
	var self = this;
	form.once('error', function (err) {
		self.failure(Muggle('Upload request problem.', err));
	});
	form.once('aborted', function (err) {
		self.failure(Muggle('Upload was aborted.', err));
	});
	this.lastProgress = 0;
	form.on('progress', this.upload_progress_status.bind(this));

	try {
		form.parse(req, this.parse_form.bind(this));
	}
	catch (err) {
		self.failure(err);
	}
};

IU.upload_progress_status = function (received, total) {
	var percent = Math.floor(100 * received / total);
	var increment = (total > (512 * 1024)) ? 10 : 25;
	var quantized = Math.floor(percent / increment) * increment;
	if (quantized > this.lastProgress) {
		this.status(percent + '% received...');
		this.lastProgress = quantized;
	}
};

IU.parse_form = function (err, fields, files) {
	if (err)
		return this.failure(Muggle('Invalid upload.', err));
	if (!files.image)
		return this.failure(Muggle('No image.'));
	this.image = files.image;
	this.pinky = !!parseInt(fields.op, 10);

	var spoiler = parseInt(fields.spoiler, 10);
	if (spoiler) {
		var sps = config.SPOILER_IMAGES;
		if (sps.normal.indexOf(spoiler) < 0
				&& sps.trans.indexOf(spoiler) < 0)
			return this.failure(Muggle('Bad spoiler.'));
		this.image.spoiler = spoiler;
	}

	this.image.MD5 = index.squish_MD5(this.image.hash);
	this.image.hash = null;

	var self = this;
	this.db.track_temporary(this.image.path, function (err) {
		if (err)
			winston.warn("Temp tracking error: " + err);
		self.process();
	});
};

IU.process = function () {
	if (this.failed)
		return;
	var image = this.image;
	var filename = image.filename || image.name;
	image.ext = path.extname(filename).toLowerCase();
	if (image.ext == '.jpeg')
		image.ext = '.jpg';
	if (image.ext == '.mov')
		image.ext = '.mp4';
	if (IMAGE_EXTS.indexOf(image.ext) < 0
			&& (!config.VIDEO || config.VIDEO_EXTS.indexOf(image.ext) < 0))
		return this.failure(Muggle('Invalid image format.'));
	image.imgnm = filename.substr(0, 256);

	this.status('Verifying...');
	if (config.VIDEO_EXTS.indexOf(image.ext) >= 0)
		video_still(image.path, image.ext, this.verify_video.bind(this));
	else if (image.ext == '.jpg' && jpegtranBin && jheadBin)
		jobs.schedule(new AutoRotateJob(image.path), this.verify_image.bind(this));
	else
		this.verify_image();
};

function AutoRotateJob(src) {
	jobs.Job.call(this);
	this.src = src;
}
util.inherits(AutoRotateJob, jobs.Job);

AutoRotateJob.prototype.describe_job = function () {
	return "jhead+jpegtran auto rotation of " + this.src;
};

AutoRotateJob.prototype.perform_job = function () {
	var self = this;
	child_process.execFile(jheadBin, ['-autorot', this.src], function (err, stdout, stderr) {
		// if it failed, keep calm and thumbnail on
		if (err)
			winston.warn('jhead: ' + (stderr || err));
		self.finish_job(null);
	});
};

function StillJob(src, ext) {
	jobs.Job.call(this);
	this.src = src;
	this.ext = ext;
}
util.inherits(StillJob, jobs.Job);

StillJob.prototype.describe_job = function () {
	return "FFmpeg video still of " + this.src;
};

StillJob.prototype.perform_job = function () {
	var dest = index.media_path('tmp', 'still_'+etc.random_id());
	var args = ['-hide_banner', '-loglevel', 'info',
			'-i', this.src,
			'-f', 'image2', '-vf', 'thumbnail', '-vframes', '1', '-vcodec', 'png',
			'-y', dest];
	var opts = {env: {AV_LOG_FORCE_NOCOLOR: '1'}};
	var self = this;
	child_process.execFile(ffmpegBin, args, opts, function (err, stdout, stderr) {
		var lines = stderr ? stderr.split('\n') : [];
		var first = lines[0];
		if (err) {
			var msg;
			if (/no such file or directory/i.test(first))
				msg = "Video went missing.";
			else if (/invalid data found when/i.test(first))
				msg = "Invalid video file.";
			else if (/^ffmpeg version/i.test(first))
				msg = "Server's ffmpeg is too old.";
			else {
				msg = "Unknown video reading error.";
				winston.warn("Unknown ffmpeg output: "+first);
			}
			fs.unlink(dest, function (err) {
				self.finish_job(Muggle(msg, stderr));
			});
			return;
		}

		self.test_format(first, stderr, function (format_err, has_audio, dur) {
			if (err) {
				fs.unlink(dest, function (_unlink_err) {
					self.finish_job(Muggle(format_err));
				});
				return;
			}
			self.finish_job(null, {
				still_path: dest,
				has_audio: has_audio,
				duration: dur,
			});
		});
	});
};

StillJob.prototype.test_format = function (first, full, cb) {
	/* Could have false positives due to chapter titles. Bah. */
	var has_audio = /stream\s*#0.*audio:/i.test(full);
	/* Spoofable? */
	var dur = /duration: (\d\d):(\d\d):(\d\d)/i.exec(full);
	if (dur) {
		var m = parseInt(dur[2], 10), s = parseInt(dur[3], 10);
		if (dur[1] != '00' || m > 2)
			return cb('Video exceeds 3 minutes.');
		dur = (m ? m + 'm' : '') + s + 's';
		if (dur == '0s')
			dur = '1s';
	}
	else {
		winston.warn("Could not parse duration:\n" + full);
	}

	if (/stream #1/i.test(full))
		return cb('Video contains more than one stream.');

	if (this.ext == '.webm') {
		if (!/matroska,webm/i.test(first))
			return cb('Video stream is not WebM.');
		cb(null, has_audio, dur);
	}
	else if (this.ext == '.mp4') {
		if (!/mp4,/i.test(first))
			return cb('Video stream is not mp4.');
		cb(null, has_audio, dur);
	}
	else {
		cb('Unsupported video format.');
	}
}

function video_still(src, ext, cb) {
	jobs.schedule(new StillJob(src, ext), cb);
}

IU.verify_video = function (err, info) {
	if (err)
		return this.failure(err);
	var self = this;
	this.db.track_temporary(info.still_path, function (err) {
		if (err)
			winston.warn("Tracking error: " + err);

		if (info.has_audio && !config.AUDIO)
			return self.failure(Muggle('Audio is not allowed.'));

		// pretend it's a PNG for the next steps
		var image = self.image;
		image.video = image.ext.replace('.', '');
		image.video_path = image.path;
		image.path = info.still_path;
		image.ext = '.png';
		if (info.has_audio) {
			image.audio = true;
			if (config.AUDIO_SPOILER)
				image.spoiler = config.AUDIO_SPOILER;
		}
		if (info.duration)
			image.duration = info.duration;

		self.verify_image();
	});
};

IU.verify_image = function (err) {
	if (err)
		winston.error(err);
	var image = this.image;
	this.tagged_path = image.ext.replace('.', '') + ':' + image.path;
	var checks = {
		stat: fs.stat.bind(fs, image.video_path || image.path),
		dims: identify.bind(null, this.tagged_path),
	};
	if (image.ext == '.png')
		checks.apng = detect_APNG.bind(null, image.path);

	var self = this;
	async.parallel(checks, function (err, rs) {
		if (err)
			return self.failure(Muggle('Wrong image type.', err));
		image.size = rs.stat.size;
		image.dims = [rs.dims.width, rs.dims.height];
		if (rs.apng)
			image.apng = 1;
		self.verified();
	});
};

IU.verified = function () {
	if (this.failed)
		return;
	var desc = this.image.video ? 'Video' : 'Image';
	var w = this.image.dims[0], h = this.image.dims[1];
	if (!w || !h)
		return this.failure(Muggle('Bad image dimensions.'));
	if (config.IMAGE_PIXELS_MAX && w * h > config.IMAGE_PIXELS_MAX)
		return this.failure(Muggle('Way too many pixels.'));
	if (w > config.IMAGE_WIDTH_MAX && h > config.IMAGE_HEIGHT_MAX)
		return this.failure(Muggle(desc+' is too wide and too tall.'));
	if (w > config.IMAGE_WIDTH_MAX)
		return this.failure(Muggle(desc+' is too wide.'));
	if (h > config.IMAGE_HEIGHT_MAX)
		return this.failure(Muggle(desc+' is too tall.'));

	var self = this;
	perceptual_hash(this.tagged_path, this.image, function (err, hash) {
		if (err)
			return self.failure(err);
		self.image.hash = hash;
		self.db.check_duplicate(hash, function (err) {
			if (err)
				return self.failure(err);
			self.deduped();
		});
	});
};

IU.fill_in_specs = function (specs, kind) {
	specs.src = this.tagged_path;
	specs.ext = this.image.ext;
	specs.dest = this.image.path + '_' + kind;
	this.image[kind + '_path'] = specs.dest;
};

IU.deduped = function (err) {
	if (this.failed)
		return;
	var image = this.image;
	var specs = get_thumb_specs(image, this.pinky, 1);
	var w = image.dims[0], h = image.dims[1];

	/* Determine whether we really need a thumbnail */
	var sp = image.spoiler;
	if (!sp && image.size < 30*1024
			&& ['.jpg', '.png'].indexOf(image.ext) >= 0
			&& !image.apng && !image.video
			&& w <= specs.dims[0] && h <= specs.dims[1]) {
		return this.got_nails();
	}
	this.fill_in_specs(specs, 'thumb');

	// was a composited spoiler selected or forced?
	if (image.audio && config.AUDIO_SPOILER)
		specs.comp = specs.overlay = true;
	if (sp && config.SPOILER_IMAGES.trans.indexOf(sp) >= 0)
		specs.comp = true;

	var self = this;
	if (specs.comp) {
		this.status(specs.overlay ? 'Overlaying...' : 'Spoilering...');
		var comp = composite_src(sp, this.pinky);
		image.comp_path = image.path + '_comp';
		specs.compDims = specs.overlay ? specs.dims : specs.bound;
		image.dims = [w, h].concat(specs.compDims);
		specs.composite = comp;
		specs.compDest = image.comp_path;
		async.parallel([
			self.resize_and_track.bind(self, specs, false),
			self.resize_and_track.bind(self, specs, true),
		], function (err) {
			if (err)
				return self.failure(err);
			self.got_nails();
		});
	}
	else {
		image.dims = [w, h].concat(specs.dims);
		if (!sp)
			this.status('Thumbnailing...');

		self.resize_and_track(specs, false, function (err) {
			if (err)
				return self.failure(err);

			if (config.EXTRA_MID_THUMBNAILS)
				self.middle_nail();
			else
				self.got_nails();
		});
	}
};

IU.middle_nail = function () {
	if (this.failed)
		return;

	var specs = get_thumb_specs(this.image, this.pinky, 2);
	this.fill_in_specs(specs, 'mid');

	var self = this;
	this.resize_and_track(specs, false, function (err) {
		if (err)
			self.failure(err);
		self.got_nails();
	});
};

IU.got_nails = function () {
	if (this.failed)
		return;

	var image = this.image;
	if (image.video_path) {
		// stop pretending this is just a still image
		image.path = image.video_path;
		image.ext = '.' + image.video;
		delete image.video_path;
	}

	var time = Date.now();
	image.src = time + image.ext;
	var base = path.basename;
	var tmps = {src: base(image.path)};

	if (image.thumb_path) {
		image.thumb = time + '.jpg';
		tmps.thumb = base(image.thumb_path);
	}
	if (image.mid_path) {
		image.mid = time + '.jpg';
		tmps.mid = base(image.mid_path);
	}
	if (image.comp_path) {
		image.composite = time + 's' + image.spoiler + '.jpg';
		tmps.comp = base(image.comp_path);
		delete image.spoiler;
	}

	this.record_image(tmps);
};

function composite_src(spoiler, pinky) {
	var file = 'spoiler' + (pinky ? 's' : '') + spoiler + '.png';
	return path.join(config.SPOILER_DIR, file);
}

IU.read_image_filesize = function (callback) {
	var self = this;
	fs.stat(this.image.path, function (err, stat) {
		if (err)
			callback(Muggle('Internal filesize error.', err));
		else if (stat.size > config.IMAGE_FILESIZE_MAX)
			callback(Muggle('File is too large.'));
		else
			callback(null, stat.size);
	});
};

function which(name, callback) {
	child_process.exec('which ' + name, function (err, stdout, stderr) {
		if (err)
			callback(err);
		else
			callback(null, stdout.trim());
	});
}

/* Look up imagemagick paths */
var identifyBin, convertBin;
which('identify', function (err, bin) { if (err) throw err; identifyBin = bin; });
which('convert', function (err, bin) { if (err) throw err; convertBin = bin; });

var ffmpegBin;
if (config.VIDEO) {
	which('ffmpeg', function (err, bin) { if (err) throw err; ffmpegBin = bin; });
}

/* optional JPEG auto-rotation */
var jpegtranBin, jheadBin;
which('jpegtran', function (err, bin) { if (!err && bin) jpegtranBin = bin; });
which('jhead', function (err, bin) { if (!err && bin) jheadBin = bin; });

function identify(taggedName, callback) {
	var m = taggedName.match(/^(\w{3,4}):/);
	var args = ['-format', '%Wx%H', taggedName + '[0]'];
	child_process.execFile(identifyBin, args, function (err,stdout,stderr){
		if (err) {
			var msg = "Bad image.";
			if (stderr.match(/no such file/i))
				msg = "Image went missing.";
			else if (stderr.match(/improper image header/i)) {
				var kind = m && m[1];
				kind = kind ? 'a ' + kind.toUpperCase()
						: 'an image';
				msg = 'File is not ' + kind + '.';
			}
			else if (stderr.match(/no decode delegate/i))
				msg = "Unsupported file type.";
			return callback(Muggle(msg, stderr));
		}

		var line = stdout.trim();
		var m = line.match(/(\d+)x(\d+)/);
		if (!m)
			callback(Muggle("Couldn't read image dimensions."));
		else
			callback(null, {width: parseInt(m[1], 10),
					height: parseInt(m[2], 10)});
	});
}

function ConvertJob(args, src) {
	jobs.Job.call(this);
	this.args = args;
	this.src = src;
}
util.inherits(ConvertJob, jobs.Job);

ConvertJob.prototype.perform_job = function () {
	var self = this;
	child_process.execFile(convertBin, this.args,
				function (err, stdout, stderr) {
		self.finish_job(err ? (stderr || err) : null);
	});
};

ConvertJob.prototype.describe_job = function () {
	return "ImageMagick conversion of " + this.src;
};

function convert(args, src, callback) {
	jobs.schedule(new ConvertJob(args, src), callback);
}

function perceptual_hash(src, image, callback) {
	var tmp = index.media_path('tmp',
			'hash' + etc.random_id() + '.gray');
	var args = [src + '[0]'];
	if (image.dims.width > 1000 || image.dims.height > 1000)
		args.push('-sample', '800x800');
	// do you believe in magic?
	args.push('-background', 'white', '-mosaic', '+matte',
			'-scale', '16x16!',
			'-type', 'grayscale', '-depth', '8',
			tmp);
	convert(args, src, function (err) {
		if (err)
			return callback(Muggle('Hashing error.', err));
		var bin = path.join(__dirname, 'perceptual');
		child_process.execFile(bin, [tmp],
					function (err, stdout, stderr) {
			fs.unlink(tmp, function (err) {
				if (err)
					winston.warn("Deleting " +
						tmp + ": " + err);
			});
			if (err)
				return callback(Muggle('Hashing error.',
						stderr || err));
			var hash = stdout.trim();
			if (hash.length != 64)
				return callback(Muggle('Hashing problem.'));
			callback(null, hash);
		});
	});
}

function detect_APNG(fnm, callback) {
	var bin = path.join(__dirname, 'findapng');
	child_process.execFile(bin, [fnm], function (err, stdout, stderr) {
		if (err)
			return callback(Muggle('APNG detector problem.',
					stderr || err));
		else if (stdout.match(/^APNG/))
			return callback(null, true);
		else if (stdout.match(/^PNG/))
			return callback(null, false);
		else
			return callback(Muggle('APNG detector acting up.',
					stderr || err));
	});
}

function setup_image_params(o) {
	// only the first time!
	if (o.setup) return;
	o.setup = true;

	o.src += '[0]'; // just the first frame of the animation

	o.dest = o.format + ':' + o.dest;
	if (o.compDest)
		o.compDest = o.format + ':' + o.compDest;
	o.flatDims = o.dims[0] + 'x' + o.dims[1];
	if (o.compDims)
		o.compDims = o.compDims[0] + 'x' + o.compDims[1];

	o.quality += ''; // coerce to string
}

function build_im_args(o, args) {
	// avoid OOM killer
	var args = ['-limit', 'memory', '32', '-limit', 'map', '64'];
	var dims = o.dims;
	// resample from twice the thumbnail size
	// (avoid sampling from the entirety of enormous 6000x6000 images etc)
	var samp = dims[0]*2 + 'x' + dims[1]*2;
	if (o.ext == '.jpg')
		args.push('-define', 'jpeg:size=' + samp);
	setup_image_params(o);
	args.push(o.src);
	if (o.ext != '.jpg')
		args.push('-sample', samp);
	// gamma-correct yet shitty downsampling
	args.push('-gamma', '0.454545', '-filter', 'box');
	return args;
}

function resize_image(o, comp, callback) {
	var args = build_im_args(o);
	var dims = comp ? o.compDims : o.flatDims;
	var dest = comp ? o.compDest : o.dest;
	// in the composite case, zoom to fit. otherwise, force new size
	args.push('-resize', dims + (comp ? '^' : '!'));
	// add background
	args.push('-gamma', '2.2');
	if (o.bg)
		args.push('-background', o.bg);
	if (comp)
		args.push(o.composite, '-layers', 'flatten', '-extent', dims);
	else if (o.bg)
		args.push('-layers', 'mosaic', '+matte');
	// disregard metadata, acquire artifacts
	args.push('-strip', '-quality', o.quality);
	args.push(dest);
	convert(args, o.src, function (err) {
		if (err) {
			winston.warn(err);
			callback(Muggle("Resizing error.", err));
		}
		else
			callback(null, dest);
	});
}

IU.resize_and_track = function (o, comp, cb) {
	var self = this;
	resize_image(o, comp, function (err, fnm) {
		if (err)
			return cb(err);

		// HACK: strip IM type tag
		var m = /^\w{3,4}:(.+)$/.exec(fnm);
		if (m)
			fnm = m[1];

		self.db.track_temporary(fnm, cb);
	});
};

function image_files(image) {
	var files = [];
	if (image.path)
		files.push(image.path);
	if (image.thumb_path)
		files.push(image.thumb_path);
	if (image.mid_path)
		files.push(image.mid_path);
	if (image.comp_path)
		files.push(image.comp_path);
	return files;
}

IU.failure = function (err) {
	var err_desc = 'Unknown image processing error.'
	if (err instanceof Muggle) {
		err_desc = err.most_precise_error_message();
		err = err.deepest_reason();
	}
	/* Don't bother logging PEBKAC errors */
	if (!(err instanceof Muggle))
		winston.error(err);

	this.respond(500, err_desc);
	if (!this.failed) {
		this.client_call('error', err_desc);
		this.failed = true;
	}
	if (this.image) {
		var files = image_files(this.image);
		files.forEach(function (file) {
			fs.unlink(file, function (err) {
				if (err)
					winston.warn("Deleting " +
						file + ": " + err);
			});
		});
		this.db.lose_temporaries(files, function (err) {
			if (err)
				winston.warn("Tracking failure: " + err);
		});
	}
	this.db.disconnect();
};

IU.record_image = function (tmps) {
	if (this.failed)
		return;
	var view = {};
	var self = this;
	index.image_attrs.forEach(function (key) {
		if (key in self.image)
			view[key] = self.image[key];
	});
	if (this.image.composite) {
		view.realthumb = view.thumb;
		view.thumb = this.image.composite;
	}
	view.pinky = this.pinky;
	var image_id = etc.random_id().toFixed();
	var alloc = {image: view, tmps: tmps};
	this.db.record_image_alloc(image_id, alloc, function (err) {
		if (err)
			return this.failure("Image storage failure.");
		self.client_call('alloc', image_id);
		self.db.disconnect();
		self.respond(202, 'OK');

		if (index.is_standalone()) {
			var where = view.src;
			var size = Math.ceil(view.size / 1000) + 'kb';
			winston.info('upload: ' + where + ' ' + size);
		}
	});
};

function run_daemon() {
	var cd = config.DAEMON;
	var is_unix_socket = (typeof cd.LISTEN_PORT == 'string');
	if (is_unix_socket) {
		try { fs.unlinkSync(cd.LISTEN_PORT); } catch (e) {}
	}

	var server = require('http').createServer(new_upload);
	server.listen(cd.LISTEN_PORT);
	if (is_unix_socket) {
		fs.chmodSync(cd.LISTEN_PORT, '777'); // TEMP
	}

	index._make_media_dir(null, 'tmp', function (err) {});

	winston.info('Imager daemon listening on '
			+ (cd.LISTEN_HOST || '')
			+ (is_unix_socket ? '' : ':')
			+ (cd.LISTEN_PORT + '.'));
}

if (require.main == module) (function () {
	if (!index.is_standalone())
		throw new Error("Please enable DAEMON in imager/config.js");

	var onegai = new imagerDb.Onegai;
	onegai.delete_temporaries(function (err) {
		onegai.disconnect();
		if (err)
			throw err;
		process.nextTick(run_daemon);
	});
})();
