// Cronicle API Layer - Categories
// Copyright (c) 2015 Joseph Huckaby
// Released under the MIT License

var fs = require('fs');
var assert = require("assert");
var async = require('async');

var Class = require("pixl-class");
var Tools = require("pixl-tools");

module.exports = Class.create({
	
	//
	// Categories:
	//
	
	api_get_categories: function(args, callback) {
		// get list of categories (with pagination)
		var self = this;
		var params = args.params;
		if (!this.requireMaster(args, callback)) return;
		
		this.loadSession(args, function(err, session, user) {
			if (err) return self.doError('session', err.message, callback);
			if (!self.requireValidUser(session, user, callback)) return;
			
			self.storage.listGet( 'global/categories', parseInt(params.offset || 0), parseInt(params.limit || 0), function(err, items, list) {
				if (err) {
					// no cats found, not an error for this API
					return callback({ code: 0, rows: [], list: { length: 0 } });
				}
				
				// success, return cats and list header
				callback({ code: 0, rows: items, list: list });
			} ); // got category list
		} ); // loaded session
	},
	
	api_create_category: function(args, callback) {
		// add new category
		var self = this;
		var cat = args.params;
		if (!this.requireMaster(args, callback)) return;
		
		if (!this.requireParams(cat, {
			title: /\S/,
			max_children: /^\d+$/
		}, callback)) return;
		
		// make sure title doesn't contain HTML metacharacters
		if (cat.title && cat.title.match(/[<>]/)) {
			return this.doError('api', "Malformed title parameter: Cannot contain HTML metacharacters", callback);
		}
		
		this.loadSession(args, function(err, session, user) {
			if (err) return self.doError('session', err.message, callback);
			if (!self.requireAdmin(session, user, callback)) return;
			
			args.user = user;
			args.session = session;
			
			if (cat.id) cat.id = cat.id.toString().toLowerCase().replace(/\W+/g, '');
			if (!cat.id) cat.id = self.getUniqueID('c');
			
			cat.created = cat.modified = Tools.timeNow(true);
			
			if (user.key) {
				// API Key
				cat.api_key = user.key;
			}
			else {
				cat.username = user.username;
			}
			
			self.logDebug(6, "Creating new category: " + cat.title, cat);
			
			self.storage.listUnshift( 'global/categories', cat, function(err) {
				if (err) {
					return self.doError('category', "Failed to create category: " + err, callback);
				}
				
				self.logDebug(6, "Successfully created category: " + cat.title, cat);
				self.logTransaction('cat_create', cat.title, self.getClientInfo(args, { cat: cat }));
				self.logActivity('cat_create', { cat: cat }, args);
				
				callback({ code: 0, id: cat.id });
				
				// broadcast update to all websocket clients
				self.updateClientData( 'categories' );
			} ); // list insert
		} ); // load session
	},
	
	api_update_category: function(args, callback) {
		// update existing category
		var self = this;
		var params = args.params;
		if (!this.requireMaster(args, callback)) return;
		
		if (!this.requireParams(params, {
			id: /^\w+$/
		}, callback)) return;
		
		// make sure title doesn't contain HTML metacharacters
		if (params.title && params.title.match(/[<>]/)) {
			return this.doError('api', "Malformed title parameter: Cannot contain HTML metacharacters", callback);
		}
		
		this.loadSession(args, function(err, session, user) {
			if (err) return self.doError('session', err.message, callback);
			if (!self.requireAdmin(session, user, callback)) return;
			
			args.user = user;
			args.session = session;
			
			self.storage.listFind( 'global/categories', { id: params.id }, function(err, cat) {
				if (err || !cat) {
					return self.doError('event', "Failed to locate category: " + params.id, callback);
				}
				
				params.modified = Tools.timeNow(true);
				
				self.logDebug(6, "Updating category: " + cat.title, params);
				
				// pull abort flag out of event object, for use later
				var abort_jobs = 0;
				if (params.abort_jobs) {
					abort_jobs = params.abort_jobs;
					delete params.abort_jobs;
				}
				
				self.storage.listFindUpdate( 'global/categories', { id: params.id }, params, function(err) {
					if (err) {
						return self.doError('category', "Failed to update category: " + err, callback);
					}
					
					// merge params into cat, just so we have the full updated record
					for (var key in params) cat[key] = params[key];
					
					self.logDebug(6, "Successfully updated category: " + cat.title, params);
					self.logTransaction('cat_update', cat.title, self.getClientInfo(args, { cat: params }));
					self.logActivity('cat_update', { cat: params }, args);
					
					callback({ code: 0 });
					
					// broadcast update to all websocket clients
					self.updateClientData( 'categories' );
					
					// if cat is disabled, abort all applicable jobs
					if (!cat.enabled && abort_jobs) {
						var all_jobs = self.getAllActiveJobs(true);
						for (var key in all_jobs) {
							var job = all_jobs[key];
							if ((job.category == cat.id) && !job.detached) {
								var msg = "Category '" + cat.title + "' has been disabled.";
								self.logDebug(4, "Job " + job.id + " is being aborted: " + msg);
								self.abortJob({ id: job.id, reason: msg });
							} // matches cat
						} // foreach job
					} // cat disabled
					
					// if cat is being enabled, force scheduler to re-tick the minute
					var dargs = Tools.getDateArgs( new Date() );
					if (params.enabled && !self.schedulerGraceTimer && !self.schedulerTicking && (dargs.sec != 59)) {
						self.schedulerMinuteTick( null, true );
					}
					
				} ); // update cat
			} ); // find cat
		} ); // load session
	},
	
	api_delete_category: function(args, callback) {
		// delete existing category
		var self = this;
		var params = args.params;
		if (!this.requireMaster(args, callback)) return;
		
		if (!this.requireParams(params, {
			id: /^\w+$/
		}, callback)) return;
		
		this.loadSession(args, function(err, session, user) {
			if (err) return self.doError('session', err.message, callback);
			if (!self.requireAdmin(session, user, callback)) return;
			
			args.user = user;
			args.session = session;
			
			// Do not allow deleting category if any matching events in schedule
			self.storage.listFind( 'global/schedule', { category: params.id }, function(err, item) {
				if (item) {
					return self.doError('plugin', "Failed to delete category: Still in use by one or more events.", callback);
				}
				
				self.logDebug(6, "Deleting category: " + params.id);
				
				self.storage.listFindDelete( 'global/categories', { id: params.id }, function(err, cat) {
					if (err) {
						return self.doError('category', "Failed to delete category: " + err, callback);
					}
					
					self.logDebug(6, "Successfully deleted category: " + cat.title, cat);
					self.logTransaction('cat_delete', cat.title, self.getClientInfo(args, { cat: cat }));
					self.logActivity('cat_delete', { cat: cat }, args);
					
					callback({ code: 0 });
					
					// broadcast update to all websocket clients
					self.updateClientData( 'categories' );
					
				} ); // listFindDelete (category)
			} ); // listFind (schedule)
		} ); // load session
	}
	
} );
