#include cookies.js
#include watcher.js

// really need to replace null with undefined where possible, in mami too
// wondering if there's room for the different backends here

var AmiSettings = function() {
    var pub = {};
    var prefix = 'ami_',
        types = ['boolean', 'number', 'string'];
    var settings = new Map,
        locked = [];

    var createSetting = function(name, type, defaultValue, mutable, virtual) {
        var pub = {},
            cookieName = prefix + name,
            watchers = new AmiWatcher;

        var virtualValue = undefined;

        var getValue = function() {
            if(!mutable)
                return defaultValue;

            var value = virtual ? virtualValue : AmiCookies.getCookie(cookieName);
            if(value === undefined)
                return defaultValue;

            value = JSON.parse(value);
            if(value === null)
                return defaultValue;

            return value;
        };

        var callWatchers = function() {
            watchers.call(pub, getValue(), name);
        };

        var removeValue = function() {
            if(locked.indexOf(name) >= 0)
                return;
            locked.push(name);

            if(virtual) virtualValue = undefined;
            else AmiCookies.removeCookie(cookieName);
            callWatchers();

            $arrayRemoveValue(locked, name);
        };

        var setValue = function(value) {
            if(value === undefined || value === null || value === defaultValue) {
                removeValue();
                return;
            }

            if(typeof value !== type)
                throw 'value is not correct type';

            if(locked.indexOf(name) >= 0)
                return;
            locked.push(name);

            value = JSON.stringify(value);
            if(virtual) virtualValue = value;
            else AmiCookies.setCookie(cookieName, value);
            callWatchers();

            $arrayRemoveValue(locked, name);
        };

        pub.getName = function() { return name; };
        pub.getType = function() { return type; };
        pub.getDefault = function() { return defaultValue; };
        pub.isMutable = function() { return mutable; };
        pub.isVirtual = function() { return virtual; };

        pub.getValue = getValue;
        pub.setValue = setValue;
        pub.removeValue = removeValue;
        pub.touch = callWatchers;

        pub.hasValue = function() {
            if(!mutable) return false;

            var value = virtual ? virtualValue : AmiCookies.getCookie(cookieName);
            if(value === undefined) return false;

            value = JSON.parse(value);
            if(value === null || value === defaultValue) return false;

            return true;
        };

        pub.toggleValue = function() {
            if(type !== 'boolean')
                throw 'cannot toggle non-boolean setting';

            if(locked.indexOf(name) >= 0)
                return;
            locked.push(name);

            var value = virtual ? virtualValue : AmiCookies.getCookie(cookieName);
            if(value === undefined)
                value = defaultValue;
            else
                value = JSON.parse(virtual ? virtualValue : AmiCookies.getCookie(cookieName));

            value = JSON.stringify(!value);
            if(virtual) virtualValue = value;
            else AmiCookies.setCookie(cookieName, value);
            callWatchers();

            $arrayRemoveValue(locked, name);
        };

        pub.watch = function(watcher) {
            if(typeof watcher !== 'function')
                throw 'watcher must be a function';
            watchers.watch(watcher, pub, getValue(), name);
        };

        pub.unwatch = function(watcher) {
            watchers.unwatch(watcher);
        };

        pub.virtualise = function() {
            if(!mutable || virtual)
                return;
            virtual = true;
            virtualValue = AmiCookies.getCookie(cookieName);
        };

        return pub;
    };

    return {
        has: function(name) {
            var setting = settings.get(name);
            if(setting === undefined) return false;
            return setting.hasValue();
        },
        get: function(name) {
            var setting = settings.get(name);
            if(setting === undefined)
                throw 'attempting to get value of undefined setting';
            return setting.getValue();
        },
        getSetting: function() {
            return settings.get(name);
        },
        set: function(name, value) {
            var setting = settings.get(name);
            if(setting === undefined)
                throw 'attempting to set value of undefined setting';
            setting.setValue(value);
        },
        remove: function(name) {
            var setting = settings.get(name);
            if(setting === undefined)
                throw 'attempting to remove value of undefined setting';
            setting.removeValue();
        },
        toggle: function(name) {
            var setting = settings.get(name);
            if(setting === undefined)
                throw 'attempting to toggle undefined setting';
            setting.toggleValue();
        },
        touch: function(name) {
            var setting = settings.get(name);
            if(setting === undefined)
                throw 'attempting to touch undefined setting';
            setting.touch();
        },
        watch: function(name, watcher) {
            var setting = settings.get(name);
            if(setting === undefined)
                throw 'attempting to watch undefined setting';
            setting.watch(watcher);
        },
        unwatch: function(name, watcher) {
            var setting = settings.get(name);
            if(setting === undefined)
                throw 'attempting to unwatch undefined setting';
            setting.unwatch(watcher);
        },
        virtualise: function(name) {
            var setting = settings.get(name);
            if(setting === undefined)
                throw 'attempting to virtualise undefined setting';
            setting.virtualise();
        },
        define: function(name, type, defaultValue, mutable, virtual) {
            if(typeof name !== 'string')
                throw 'name must be string';
            name = name.trim();

            if(types.indexOf(type) < 0)
                throw 'invalid type specified';

            if(mutable === undefined) mutable = true;
            else mutable = !!mutable;

            virtual = !!virtual;

            if(defaultValue !== null) {
                if(defaultValue === undefined)
                    defaultValue = null;
                else if(type !== typeof defaultValue)
                    throw 'defaultValue type must match setting type or be null';
            }

            var setting = createSetting(name, type, defaultValue, mutable, virtual);
            settings.set(name, setting);
            return setting;
        },
        undefine: function(name) {
            var setting = settings.get(name);
            if(setting === undefined)
                return;
            settings.delete(name);
            setting.removeValue();
        },
        forEach: function(body) {
            settings.forEach(body);
        },
    };
};