import { Log } from 'src/log.js';
import { Database } from 'src/database.js';
import { uuid } from 'src/uuid.js';

class BaseModel {

	static ITEM_TYPE_NOTE = 1;
	static ITEM_TYPE_FOLDER = 2;
	static tableInfo_ = null;
	static tableKeys_ = null;
	static db_ = null;

	static tableName() {
		throw new Error('Must be overriden');
	}

	static useUuid() {
		return false;
	}

	static itemType() {
		throw new Error('Must be overriden');
	}

	static trackChanges() {
		return false;
	}

	static byId(items, id) {
		for (let i = 0; i < items.length; i++) {
			if (items[i].id == id) return items[i];
		}
		return null;
	}

	static hasField(name) {
		let fields = this.fieldNames();
		return fields.indexOf(name) >= 0;
	}

	static fieldNames() {
		return this.db().tableFieldNames(this.tableName());
	}

	static fields() {
		return this.db().tableFields(this.tableName());
	}

	static new() {
		let fields = this.fields();
		let output = {};
		for (let i = 0; i < fields.length; i++) {
			let f = fields[i];
			output[f.name] = f.default;
		}
		return output;
	}

	static fromApiResult(apiResult) {
		let fieldNames = this.fieldNames();
		let output = {};
		for (let i = 0; i < fieldNames.length; i++) {
			let f = fieldNames[i];
			output[f] = f in apiResult ? apiResult[f] : null;
		}
		return output;
	}

	static modOptions(options) {
		if (!options) {
			options = {};
		} else {
			options = Object.assign({}, options);
		}
		if (!('trackChanges' in options)) options.trackChanges = true;
		if (!('isNew' in options)) options.isNew = 'auto';
		return options;
	}

	static load(id) {
		return this.db().selectOne('SELECT * FROM ' + this.tableName() + ' WHERE id = ?', [id]);
	}

	static applyPatch(model, patch) {
		model = Object.assign({}, model);
		for (let n in patch) {
			if (!patch.hasOwnProperty(n)) continue;
			model[n] = patch[n];
		}
		return model;
	}

	static diffObjects(oldModel, newModel) {
		let output = {};
		for (let n in newModel) {
			if (!newModel.hasOwnProperty(n)) continue;
			if (!(n in oldModel) || newModel[n] !== oldModel[n]) {
				output[n] = newModel[n];
			}
		}
		return output;
	}

	static saveQuery(o, isNew = 'auto') {
		if (isNew == 'auto') isNew = !o.id;

		let temp = {}
		let fieldNames = this.fieldNames();
		for (let i = 0; i < fieldNames.length; i++) {
			let n = fieldNames[i];
			if (n in o) temp[n] = o[n];
		}
		o = temp;

		let query = '';
		let itemId = o.id;

		if (!o.updated_time && this.hasField('updated_time')) {
			o.updated_time = Math.round((new Date()).getTime() / 1000);
		}

		if (isNew) {
			if (this.useUuid() && !o.id) {
				o = Object.assign({}, o);
				itemId = uuid.create();
				o.id = itemId;
			}

			if (!o.created_time && this.hasField('created_time')) {
				o.created_time = Math.round((new Date()).getTime() / 1000);
			}

			query = Database.insertQuery(this.tableName(), o);
		} else {
			let where = { id: o.id };
			let temp = Object.assign({}, o);
			delete temp.id;
			query = Database.updateQuery(this.tableName(), temp, where);
		}

		query.id = itemId;

		Log.info('Saving', o);

		return query;
	}

	static save(o, options = null) {
		options = this.modOptions(options);

		let isNew = options.isNew == 'auto' ? !o.id : options.isNew;
		let query = this.saveQuery(o, isNew);

		return this.db().transaction((tx) => {
			tx.executeSql(query.sql, query.params);

			if (options.trackChanges && this.trackChanges()) {
				// Cannot import this class the normal way due to cyclical dependencies between Change and BaseModel
				// which are not handled by React Native.
				const { Change } = require('src/models/change.js');

				if (isNew) {
					let change = Change.newChange();
					change.type = Change.TYPE_CREATE;
					change.item_id = query.id;
					change.item_type = this.itemType();

					let changeQuery = Change.saveQuery(change);
					tx.executeSql(changeQuery.sql, changeQuery.params);
				} else {
					for (let n in o) {
						if (!o.hasOwnProperty(n)) continue;

						let change = Change.newChange();
						change.type = Change.TYPE_UPDATE;
						change.item_id = query.id;
						change.item_type = this.itemType();
						change.item_field = n;

						let changeQuery = Change.saveQuery(change);
						tx.executeSql(changeQuery.sql, changeQuery.params);
					}
				}
			}
		}).then((r) => {
			o = Object.assign({}, o);
			o.id = query.id;
			return o;
		}).catch((error) => {
			Log.error('Cannot save model', error);
		});
	}

	static delete(id, options = null) {
		options = this.modOptions(options);

		if (!id) {
			Log.warn('Cannot delete object without an ID');
			return;
		}

		return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]).then(() => {
			if (options.trackChanges && this.trackChanges()) {
				const { Change } = require('src/models/change.js');

				let change = Change.newChange();
				change.type = Change.TYPE_DELETE;
				change.item_id = id;
				change.item_type = this.itemType();

				return Change.save(change);
			}
		});
	}

	static db() {
		if (!this.db_) throw new Error('Accessing database before it has been initialised');
		return this.db_;		
	}

}

export { BaseModel };