const electron = require('electron');
const UIConfig = require('../system/config.js');

/**
 * Orders pool.
 */
class UIOrdersPool {
	/**
	 * Class constructor.
	 */
	constructor() {
		// get config directory
		let configDir = (electron.app || electron.remote.app).getPath('userData');
		// remove trailing separator, if any
		configDir = configDir.replace(/[\/\\\\]$/, '');

		this.path    = require('path');
		this.handler = require('fs');
		this.folder  = [configDir, 'orders'].join(this.path.sep);
		this.pool    = [];

		// make sure the orders folder exists
		if (!this.handler.existsSync(this.folder)) {
			// nope, create it for the first time
			this.handler.mkdirSync(this.folder);
		}

		// indicates the maximum number of orders that can stay
		// within the same storage file
		this.maxSize = 50;
	}

	/**
	 * Loads the orders.
	 *
	 * @param 	UIConfig  config    The system configuration.
	 * @param 	function  callback  The callback to invoke on completion.
	 *
	 * @return 	void
	 */
	load(config, callback) {
		// reset orders
		this.pool = [];

		// scan all files contained within the orders list
		this.handler.readdir(this.folder, (err, files) => {
			// sort the files by descending number
			files.sort((a, b) => {
				// strip file extension and cast to integer
				a = parseInt(a.replace(/\.json$/));
				b = parseInt(b.replace(/\.json$/));

				return b - a;
			});

			// iterate files from the newest one
			for (let i = 0, go = true; i < files.length && go; i++) {
				// load data from file
				let data = this.handler.readFileSync(this.folder + this.path.sep + files[i]);

				try {
					// decode JSON
					data = JSON.parse(data);
				} catch (e) {
					// malformed syntax
					data = [];
				}

				// create file holder
				let group = {
					id:      files[i],
					changed: false,
					orders:  [],
				};

				// iterate all orders in file
				data.forEach((ord) => {
					ord.compliant = true;

					// make sure the order is compliant
					if (!go || !this.isDateCompliant(ord, config)) {
						// this order is not matching, which means that any
						// other order won't be ok, as the date is prior
						ord.compliant = false;

						// register flag to avoid loading other files
						go = false;
					}

					// push order within the group
					group.orders.push(ord);
				});

				// push order within the pool
				this.pool.push(group);
			}

			if (typeof callback !== 'undefined') {
				// trigger callback and pass orders list
				callback(this.getOrders());
			}
		});
	}

	/**
	 * Checks whether the order is compliant or not.
	 *
	 * @param   object    order   The order details.
	 * @param 	UIConfig  config  The system configuration.
	 */
	isDateCompliant(order, config) {
		let dr = parseInt(config.get('daterange', 2));

		if (dr == 4) {
			// load all
			return true;
		}

		// convert timestamp from seconds to milliseconds
		var ordDate = order.created_on * 1000;
		
		var today = new Date();

		// extend to the end of the day
		today.setHours(23);
		today.setMinutes(59);
		today.setSeconds(59);

		today = today.getTime();
		
		if (dr == 1) {
			// daily
			if ((today - 86400000) < ordDate && ordDate <= today) {
				return true;
			}

		} else if (dr == 2) {
			// weekly
			if ((today - 86400000 * 8) < ordDate && ordDate <= today) {
				return true;
			}

		} else if (dr == 3) {
			// monthly
			if ((today - 86400000 * 31) < ordDate && ordDate <= today) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns the orders list.
	 *
	 * @return 	array
	 */
	getOrders() {
		let orders = [];

		this.pool.forEach((group) => {
			group.orders.forEach((ord) => {
				// take order only if compliant
				if (ord.compliant) {
					orders.push(ord);
				}
			});
		});

		return orders;
	}

	/**
	 * Adds a list of orders within the pool.
	 *
	 * @param 	mixed  orders  Either an array of objects or a single order.
	 *
	 * @return 	self  This object to support chaining.
	 */
	add(orders) {
		if (!orders) {
			return this;
		}

		if (!Array.isArray(orders)) {
			orders = [orders];
		}

		// always sort orders by ascending date
		orders.sort((a, b) => {
			return a.created_on - b.created_on;
		});

		// iterate orders
		orders.forEach((ord) => {
			// make sure we can push the order within the first group
			if (this.pool.length == 0 || this.pool[0].orders.length >= this.maxSize) {
				// nope, we need to create a new group
				let count = 1;

				if (this.pool.length) {
					// calculate next file name
					count = parseInt(this.pool[0].id.replace(/\.json$/)) + 1;
				}
				
				// insert new group at the beginning of the list
				this.pool.unshift({
					id:      count + '.json',
					changed: true,
					orders:  [],
				});
			}

			this.pool[0].orders.unshift(ord);
			// flag the group as changed
			this.pool[0].changed = true;
		});

		// iterate all changed groups and save them
		this.pool.forEach((group) => {
			if (group.changed) {
				// stringify orders
				let data = JSON.stringify(group.orders);

				// save modified group
				this.handler.writeFile(this.folder + this.path.sep + group.id, data, (err) => {
					if (!err) {
						// commit changes
						group.changed = false;
					} else {
						// an error occurred, log it
						console.error('An error ocurred while saving the file', err);
					}
				});
			}
		});

		return this;
	}

	/**
	 * Removes the specified orders from the list.
	 *
	 * @param 	mixed    orders  Either an array of IDs or a single ID.
	 *
	 * @return 	self  This object to support chaining.
	 */
	delete(orders) {
		if (!orders) {
			return false;
		}

		if (!Array.isArray(orders)) {
			orders = [orders];
		}

		// iterate orders and delete one by one
		orders.forEach((id) => {
			// iterate groups to search for the order
			this.pool.forEach((group) => {
				group.orders.forEach((ord, index) => {
					if (ord.id == id) {
						// order found, splice the array
						group.orders.splice(index, 1);
						// flag group as changed
						group.changed = true;
					}
				});
			});
		});

		// iterate all changed groups and save/delete them
		this.pool.forEach((group) => {
			if (group.changed) {
				if (group.orders.length) {
					// stringify orders
					let data = JSON.stringify(group.orders);

					// save modified group
					this.handler.writeFile(this.folder + this.path.sep + group.id, data, (err) => {
						if (!err) {
							// commit changes
							group.changed = false;
						} else {
							// an error occurred, log it
							console.error('An error ocurred while saving the file', err);
						}
					});
				} else {
					// empty group, delete it
					this.handler.unlink(this.folder + this.path.sep + group.id, (err) => {
						if (err) {
							// commit changes
							group.changed = false;
						} else {
							// an error occurred, log it
							console.error('An error ocurred while deleting the file', err);
						}
					});
				}
			}
		});

		return this;
	}

	/**
	 * Reset all the orders.
	 *
	 * @param 	function  callback  The callback to invoke on completion.
	 *
	 * @return 	self  This object to support chaining.
	 */
	reset(callback) {
		// scan all files inside the folder
		this.handler.readdir(this.folder, (err, files) => {
			// iterate files and delete them one by one
			files.forEach((file) => {
				this.handler.unlinkSync(this.folder + this.path.sep + file);
			});

			// clean orders pool
			this.pool = [];

			if (typeof callback !== 'undefined') {
				// invoke callback on completion
				callback();
			}
		});
	}

	/**
	 * The garbage collector get rid of files
	 * that are no more touched by a considerable of time.
	 *
	 * @return 	self  This object to support chaining.
	 */
	gc() {
		// garbage only once
		if (this.didGC) {
			return this;
		}

		this.didGC = true;

		// create threshold of 3 months in the past
		let threshold = new Date();
		threshold.setMonth(threshold.getMonth() - 3);

		// scan all files inside the folder
		this.handler.readdir(this.folder, (err, files) => {
			// iterate files and obtain stats
			files.forEach((file) => {
				let filePath = this.folder + this.path.sep + file;

				this.handler.stat(filePath, (err, stats) => {
					// check whether the file should be deleted
					if (threshold > stats.mtime) {
						this.handler.unlinkSync(filePath);
					}
				});
			});
		});

		return this;
	}
}

// export for external usage
module.exports = UIOrdersPool;
