/**
 * @module Dictionary
 * FrankenScript Dictionaries are simple key-value stores.
 *
 * The keys in a Dictionary remained fixed at instantiation. No keys can be
 * added, removed, or renamed. The values in a Dictionary must be of a primitive
 * type or be Tuples (same rules for Tuple values). Dictionary values, however,
 * are not immutable as Tuple values are. And Dictionaries are similar to Types,
 * but they do not support inheritance and are stricter about the values they
 * can hold.
 *
 * @author Derek Dunagan (http://github.com/derekdunagan)
 */
(function (FK) {
    'use strict'; if (FK.Dictionary) { return; }

    FK.Dictionary = FK.Structure.create({
        constructor: constructDictionary,
    });

    Object.defineProperty(FK.Dictionary.prototype, 'toString', {
        value: convertDictionaryToString
    });


    /**
     * Construct a Dictionary.
     *
     * @param POJSO items
     *
     * @todo Add proper error handling.
     */
    function constructDictionary(items) {

        var key,
            value;

        // Guarantee arguments are of the correct data type.
        switch (true) {
            case !FK.is.pojso(items):
                throw 'The Dictionary\'s items must be a POJSO.';
            case !Object.getOwnPropertyNames(items).length:
                throw 'Dictionaries must have at least one item.';
        }

        for (key in items) {
            value = items[key];
            if (!FK.is.primitive(value) && !(value instanceof FK.Tuple)) {
                throw 'Each item in a Dictionary must be of a primitive type or'
                    + ' be a Tuple.';
            }
            this[key] = value;
        }

        // Only allow changes to the value of existing items.
        Object.seal(this);
    }


    /**
     * Convert the Dictionary to a human-readable string.
     */
    function convertDictionaryToString() {

        var string_ = '',
            key,
            tabStop = Object.keys(this)
                .reduce(determineKeyLength)
                + 2;

        for (key in this) {
            string_ += '\n'
                + key
                + repeatString(' ', tabStop - key.length)
                + prepareValueForToString(this[key]);
        }
        return string_ + '\n';
    }
    function determineKeyLength(keyLength, currentKey, index) {
        switch (true) {
            case index === 1:
                return keyLength.length;
            case keyLength < currentKey.length:
                return currentKey.length;
            default:
                return keyLength;
        }
    }
    function repeatString(string_, repetitions) {
        if (repetitions < 1) { return ''; }
        var result = '';
        while (repetitions > 1) {
            (repetitions & 1) && (result += string_);
            repetitions >>= 1;
            string_ += string_;
        }
        return result + string_;
    }
    function prepareValueForToString(value) {
        switch (true) {
            case FK.is.string(value):
                return "'" + value + "'";
            default:
                return ' ' + value;
        }
    }

}(FrankenScript));
