var platform = {};

if (typeof AppPreferencesLocalStorage === "undefined") {
	try {
		platform = require ('./platform');
	} catch (e) {
	}
} else {
	platform = new AppPreferencesLocalStorage ();
}

/**
 * @constructor
 */
function AppPreferences (defaultArgs) {
	this.defaultArgs = defaultArgs || {};
}

var promiseLib;
if (typeof Promise !== "undefined") {
	promiseLib = Promise;
} else if (typeof WinJS !== "undefined" && WinJS.Promise) {
	promiseLib = WinJS.Promise;
} else if (typeof $ !== "undefined" && $.Deferred) {
	promiseLib = function (init) {
		var d = $.Deferred ();
		init (d.resolve.bind (d), d.reject.bind (d));
		return d.promise ();
	}
}

function promiseCheck (maxArgs, successCallback, errorCallback) {
	if (
		typeof successCallback !== 'function' && typeof errorCallback !== 'function'
		&& arguments.length <= maxArgs + 1 // argCount
		&& promiseLib
	) {
		return true;
	} else {
		return false;
	}
}

if (!platform.nativeExec && typeof cordova !== "undefined")
	platform.nativeExec = cordova.exec.bind (cordova);

AppPreferences.prototype.prepareKey = platform.prepareKey || function (mode, dict, key, value) {

	var args = {};

	for (var k in this.defaultArgs) {
		args[k] = this.defaultArgs[k];
	}

	var argList = [].slice.apply(arguments);
	argList.shift();
	if (
		(mode == 'get' && argList.length == 1) ||
		(mode == 'get' && argList.length == 2 && argList[1] == null) ||
		(mode == 'set' && argList.length == 2) ||
		(mode == 'set' && argList.length == 3 && argList[2] == null)
	) {
		argList.unshift (undefined);
	}

	args.key = argList[1];

	if (argList[0] !== undefined)
		args.dict = argList[0]

	if (mode == 'set')
		args.value = argList[2];

	// console.log (JSON.stringify (argList), JSON.stringify (args));
	return args;
}

/**
 * Get a preference value
 *
 * @param {Function} successCallback The function to call when the value is available
 * @param {Function} errorCallback The function to call when value is unavailable
 * @param {String} dict Dictionary for key (OPTIONAL)
 * @param {String} key Key
 */
AppPreferences.prototype.fetch = platform.fetch || function (
	successCallback, errorCallback, dict, key
) {

	var argCount = 2; // dict, key
	var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));
	// for promises
	if (promise) {
		dict = successCallback;
		key  = errorCallback;
	}

	var args = this.prepareKey ('get', dict, key);

	var _successCallback = function (_value) {
		var value = _value;
		try {
			value = JSON.parse (_value);
		} catch (e) {
		}
		successCallback (value);
	}

	var nativeExec = function (resolve, reject) {
		if (!args.key) {
			return reject ();
		}

		if (resolve !== successCallback) {
			successCallback = resolve;
		}

		if (platform.nativeFetch) {
			return platform.nativeFetch(_successCallback, reject, args);
		}
		return platform.nativeExec(_successCallback, reject, "AppPreferences", "fetch", [args]);
	}

	if (promise) {
		return new promiseLib (nativeExec);
	} else {
		nativeExec (successCallback, errorCallback);
	}

};

/**
 * Set a preference value
 *
 * @param {Function} successCallback The function to call when the value is set successfully
 * @param {Function} errorCallback The function to call when value is not set
 * @param {String} dict Dictionary for key (OPTIONAL)
 * @param {String} key Key
 * @param {String} value Value
 */
AppPreferences.prototype.store = platform.store || function (
	successCallback, errorCallback, dict, key, value
	) {

	var argCount = 3; // dict, key, value
	var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));
	// for promises
	if (promise) {
		value = dict;
		key  = errorCallback;
		dict = successCallback;
	}

	var args = this.prepareKey ('set', dict, key, value);

	args.type  = typeof args.value;

	// VERY IMPORTANT THING
	// WP platform has some limitations, so we need to encode all values to JSON.
	// On plugin side we store value according to it's type.
	// So, every platform plugin must check for type, decode JSON and store
	// value decoded for basic types.
	// TODO: don't think about array of strings, it's android only.
	// Complex structures must be stored as JSON string.
	// On iOS strings stored as strings and JSON stored as NSData
	// Android:
	// Now, interesting thing: how to differentiate between string value
	// and complex value, encoded as json and stored as string?
	// I'm introduce setting named _<preference>_type with value "JSON"
	// Windows Phone ?
	args.value = JSON.stringify (args.value);

	var nativeExec = function (resolve, reject) {
		if (!args.key || args.value === null || args.value === undefined) {
			return reject ();
		}

		if (platform.nativeStore) {
			return platform.nativeStore (resolve, reject, args);
		}
		return platform.nativeExec (resolve, reject, "AppPreferences", "store", [args]);
	}

	if (promise) {
		return new promiseLib (nativeExec);
	} else {
		nativeExec (successCallback, errorCallback);
	}

};

/**
 * Remove value from preferences
 *
 * @param {Function} successCallback The function to call when the value is available
 * @param {Function} errorCallback The function to call when value is unavailable
 * @param {String} dict Dictionary for key (OPTIONAL)
 * @param {String} key Key
 */
AppPreferences.prototype.remove = platform.remove || function (
	successCallback, errorCallback, dict, key
) {

	var argCount = 2; // dict, key
	var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));
	// for promises
	if (promise) {
		key  = errorCallback;
		dict = successCallback;
	}

	var args = this.prepareKey ('get', dict, key);

	var nativeExec = function (resolve, reject) {
		if (!args.key) {
			return reject ();
		}

		if (platform.nativeRemove) {
			return platform.nativeRemove (resolve, reject, args);
		}
		return platform.nativeExec (resolve, reject, "AppPreferences", "remove", [args]);
	}

	if (promise) {
		return new promiseLib (nativeExec);
	} else {
		nativeExec (successCallback, errorCallback);
	}

};

/**
 * Clear preferences
 *
 * @param {Function} successCallback The function to call when the value is available
 * @param {Function} errorCallback The function to call when value is unavailable
 * @param {String} dict Dictionary for key (OPTIONAL)
 * @param {String} key Key
 */
AppPreferences.prototype.clearAll = platform.clearAll || function (
	successCallback, errorCallback
) {

	var argCount = 0;
	var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));

	var args = {};

	for (var k in this.defaultArgs) {
		args[k] = this.defaultArgs[k];
	}

	var nativeExec = function (resolve, reject) {

		if (platform.nativeClearAll) {
			return platform.nativeClearAll (resolve, reject, args);
		}

		return platform.nativeExec (resolve, reject, "AppPreferences", "clearAll", [args]);
	}

	if (promise) {
		return new promiseLib (nativeExec);
	} else {
		nativeExec (successCallback, errorCallback);
	}

};

/**
 * Show native preferences interface
 *
 * @param {Function} successCallback The function to call when the value is available
 * @param {Function} errorCallback The function to call when value is unavailable
 * @param {String} dict Dictionary for key (OPTIONAL)
 * @param {String} key Key
 */
AppPreferences.prototype.show = platform.show || function (
successCallback, errorCallback
) {

	var argCount = 0;
	var promise = promiseCheck.apply (this, [argCount].concat ([].slice.call(arguments)));

	var nativeExec = function (resolve, reject) {
		return platform.nativeExec (resolve, reject, "AppPreferences", "show", []);
	}

	if (promise) {
		return new promiseLib (nativeExec);
	} else {
		nativeExec (successCallback, errorCallback);
	}
};

/**
 * Watch for preferences change
 *
 * @param {Function} successCallback The function to call when the value is available
 * @param {Function} errorCallback   The function to call when value is unavailable
 * @param {Boolean}  subscribe       true value to subscribe, false - unsubscribe
 * @example How to get notified:
 * ```javascript
 * plugins.appPreferences.watch();
 * document.addEventListener ('preferencesChanged', function (evt) {
 *     // with some platforms can give you details what is changed
 *     if (evt.key) {
 *         // handle key change
 *     } else if (evt.all) {
 *         // after clearAll
 *     }
 * });
 * ```
 */
AppPreferences.prototype.watch = platform.watch || function (
	successCallback, errorCallback, subscribe
) {
	if (typeof subscribe === "undefined") {
		subscribe = true;
	}

	var args = {};

	for (var k in this.defaultArgs) {
		args[k] = this.defaultArgs[k];
	}

	args.subscribe = subscribe;

	var nativeExec = function (resolve, reject) {

		if (platform.nativeWatch) {
			return platform.nativeWatch (resolve, reject, args);
		}

		return platform.nativeExec (resolve, reject, "AppPreferences", "watch", [args]);
	}

	nativeExec (successCallback, errorCallback);
};

/**
 * Return named configuration context
 * In iOS you'll get a suite configuration, on Android — named file
 * Supports: Android, iOS
 * @param   {String}         suiteName suite name
 * @returns {AppPreferences} AppPreferences object, bound to that suite
 */

AppPreferences.prototype.iosSuite =
AppPreferences.prototype.suite =
	function (suiteName) {

	var appPrefs = new AppPreferences ({
		iosSuiteName: suiteName, // deprecated, remove when ios code is ready
		suiteName: suiteName,
	});

	return appPrefs;
}

/**
 * Return cloud synchronized configuration context
 * Currently supports Windows and iOS/macOS
 * @returns {AppPreferences} AppPreferences object, bound to that suite
 */

AppPreferences.prototype.cloudSync = function () {
	var appPrefs = new AppPreferences ({cloudSync: true});

	return appPrefs;
}

/**
 * Return default configuration context
 * Currently supports Windows and iOS/macOS
 * @returns {AppPreferences} AppPreferences object, bound to that suite
 */

AppPreferences.prototype.defaults = function () {
	var appPrefs = new AppPreferences ();

	return appPrefs;
}


// WIP: functions to bind selected preferences to the form

function setFormFields (formEl, fieldsData) {
	for (var i = 0; i < formEl.elements.length; i ++) {
		var formField = formEl.elements[i];
		if (!(formField.name in fieldsData)) {
			continue;
		}

		// TODO: multiple checkboxes value for one form field
		if (formField.type === 'radio' || formField.type === 'checkbox') {
			if (
				formField.value === fieldsData[formField.name]
				|| formField.value === fieldsData[formField.name].toString()
			) {
				formField.checked = true;
			}
		} else {
			formField.value = fieldsData[formField.name];
		}
	}
}

function bindFormToData (formEl, formData) {
	[].slice.apply (formEl.elements).forEach (function (el) {
		if (el.type.match (/^(?:radio|checkbox)$/)) {
			el.addEventListener ('change', getFormFields.bind (window, formEl, formData), false);
		} else {
			el.addEventListener ('input', getFormFields.bind (window, formEl, formData), false);
		}
	});
}

function getFormFields (formEl, formData) {
	formData = formData || {};
	for (var k in formData) {
		delete formData[k];
	}
	for (var i = 0; i < formEl.elements.length; i ++) {
		var formField = formEl.elements[i];
		var checkedType = formField.type.match (/^(?:radio|checkbox)$/);
		if ((checkedType && formField.checked) || !checkedType) {
			formData[formField.name] = formField.value;
		}
	}
	// console.log (formData);
	return formData;
}

if (typeof module !== "undefined") {
	module.exports = new AppPreferences();
}

