/*
 Copyright (c) 2006  Eric J. Feminella  <eric@ericfeminella.com>
 All rights reserved.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.

 @internal
 */

package com.ericfeminella.collections
{
    import flash.utils.Dictionary;
    import mx.resources.IResourceBundle;

    /**
     *
     * <code>IMap</code> implementation which dynamically creates
     * a Map of key / value pairs and provides a standard API for
     * working with an instance of an <code>ResourceBundle</code>.
     *
     * @example The following example demonstrates a typical use
     * case in which a <code>ResourceMap</code> instance has keys
     * and values: created, retrieved, updated and deleted (CRUD)
     * via an <code>IMap</code> implementation.
     *
     * <listing version="3.0">
     *
     * import com.ericfeminella.collections.ResourceMap;
     * import com.ericfeminella.collections.IMap;
     *
     * import mx.resources.ResourceBundle;
     *
     * [ResourceBundle("resources")]
     * private static const rb:ResourceBundle;
     * // resources.properties content:
     * // PROPERTY_A = value A
     * // PROPERTY_B = value B
     *
     * var map:IMap = new ResourceMap();
     * map.put("PROPERTY_A", "value A");
     * map.put("PROPERTY_B", "new value...");
     *
     * trace( map.getKeys() );
     * trace( map.getValues() );
     * trace( map.size() );
     *
     * // outputs the following:
     * // PROERTY_A, PROERTY_B
     * // value A, new value...
     * // 2
     *
     * </listing>
     *
     * @see http://livedocs.adobe.com/flex/3/mx/resources/IResourceBundle.html
     * @see http://livedocs.adobe.com/flex/3/mx/resources/ResourceBundle.html
     * @see com.ericfeminella.collections.IMap
     *
     *
     */
    public class ResourceMap implements IMap
    {
        /**
         *
         * Defines the reference to the underlying content object
         * generated by the compiler for the specified instance of
         * a <code>ResourceBundle</code>
         *
         * <p>
         * This reference is used to perform CRUD operations on
         * a <code>ResourceBundle</code> instance via a concrete
         * <code>IMap</code> implementation.
         * </p>
         *
         * @see http://livedocs.adobe.com/flex/3/mx/resources/IResourceBundle.html#content
         *
         */
        private var resource:Object = null;

        /**
         *
         * Creates a new <code>ResourceMap</code> instance which
         * contains a map of key / value pairs initially defined
         * in the specified <code>ResourceBundle</code>
         *
         * <p>
         * By default, weak key references are used in order to
         * ensure that objects are eligible for Garbage Collection
         * immediatly after they are no longer being referenced.
         * </p>
         *
         * @example The following example demonstrates how to
         * create a new instance of <code>ResourceMap</code>
         *
         * <listing version="3.0">
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         *
         * </listing>
         *
         * @param <code>IResourceBundle</code> instance to convert to map
         *
         */
        public function ResourceMap(bundle:IResourceBundle)
        {
            resource = bundle.content;
        }

        /**
         *
         * Adds a key and value to the <code>ResourceMap</code>
         * instance.
         *
         * @example
         * <listing version="3.0">
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "amount", 200.32 );
         *
         * </listing>
         *
         * @param the key to add to the underlying map
         * @param the value of the specified key
         *
         */
        public function put(key:*, value:*) : void
        {
            resource[key] = value;
        }

        /**
         *
         * Places all name / value pairs into the current
         * <code>IMap</code> instance.
         *
         * @example
         * <listing version="3.0">

         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         *
         * var table:Object = {a: "foo", b: "bar"};
         * map.putAll( table );
         *
         * trace( map.getKeys() ); // a, b
         * trace( map.getValues() ); // foo, bar
         *
         * </listing>
         *
         * @param an <code>Object</code> of name / value pairs
         *
         */
        public function putAll(table:Dictionary) : void
        {
            for (var prop:String in table)
            {
                put( prop, table[prop] );
            }
        }

        /**
         *
         * <code>putEntry</code> is intended as a pseudo-overloaded
         * <code>put</code> implementation whereby clients may call
         * <code>putEntry</code> to pass an <code>IHashMapEntry</code>
         * rather than a key and value.
         *
         * @param concrete <code>IHashMapEntry</code> implementation
         *
         */
        public function putEntry(entry:IHashMapEntry) : void
        {
            put( entry.key, entry.value );
        }

        /**
         *
         * Removes a key and value from the <code>ResourceMap</code>
         * instance
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.remove( "admin" );
         *
         * </listing>
         *
         * @param the key to remove from the map
         *
         */
        public function remove(key:*) : void
        {
            delete resource[key];
        }

        /**
         *
         * Determines if a key exists in the <code>ResourceMap</code>
         * instance
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         *
         * trace( map.containsKey( "admin" ) );
         * //true
         *
         * </listing>
         *
         * @param  key from which to determine existance in the map
         * @return true if the key exists, otherwise false
         *
         */
        public function containsKey(key:*) : Boolean
        {
            return resource[key] != null;
        }

        /**
         *
         * Determines if a value exists in the <code>ResourceMap</code>
         * instance
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         *
         * trace( map.containsValue( adminVO ) );
         * //true
         *
         * </listing>
         *
         * @param  value in which to determine existance in the map
         * @return true if the value exisits, otherwise false
         *
         */
        public function containsValue(value:*) : Boolean
        {
            var result:Boolean;

            for ( var key:* in resource )
            {
                if ( resource[key] == value )
                {
                    result = true;
                    break;
                }
            }
            return result;
        }

        /**
         *
         * Returns a key based on the value of the key in the
         * underlying <code>ResourceMap</code> instance
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         *
         * trace( map.getKey( adminVO ) );
         * //admin
         *
         * </listing>
         *
         * @param  the key in which to retrieve the value of
         * @return the value of the specified key
         *
         */
        public function getKey(value:*) : *
        {
            var id:String = null;

            for ( var key:* in resource )
            {
                if ( resource[key] == value )
                {
                    id = key;
                    break;
                }
            }
            return id;
        }

        /**
         *
         * Returns each key added to the <code>ResourceMap</code>
         * instance
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.put( "editor", editorVO );
         *
         * trace( map.getKeys() );
         * //admin, editor
         *
         * </listing>
         *
         * @return Array of key identifiers
         *
         */
        public function getKeys() : Array
        {
            var keys:Array = [];

            for ( var key:* in resource )
            {
                keys.push( key );
            }
            return keys;
        }

        /**
         *
         * Retrieves the value of the specified key from the
         * <code>ResourceMap</code> instance
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.put( "editor", editorVO );
         *
         * trace( map.getValue( "editor" ) );
         * // [object, editorVO]
         *
         * </listing>
         *
         * @param  the key in which to retrieve the value of
         * @return value of specified key, otherwise undefined
         *
         */
        public function getValue(key:*) : *
        {
            return resource[key];
        }

        /**
         *
         * Retrieves each value assigned to each key in the
         * <code>ResourceMap</code> instance
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.put( "editor", editorVO );
         *
         * trace( map.getValues() ); //[object, adminVO],[object, editorVO]
         *
         * </listing>
         *
         * @return Array of values assigned for all keys in the map
         *
         */
        public function getValues() : Array
        {
            var values:Array = [];

            for ( var key:* in resource )
            {
                values.push( resource[key] );
            }
            return values;
        }

        /**
         *
         * Determines the size of the <code>ResourceMap</code>
         * instance
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.put( "editor", editorVO );
         *
         * trace( map.size() ); //2
         *
         * </listing>
         *
         * @return the current size of the map instance
         *
         */
        public function size() : int
        {
            var length:int = 0;

            for ( var key:* in resource )
            {
                length++;
            }
            return length;
        }

        /**
         *
         * Determines if the current <code>ResourceMap</code>
         * instance is empty
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * trace( map.isEmpty() ); //true
         *
         * map.put( "admin", adminVO );
         * trace( map.isEmpty() );
         * // false
         *
         * </listing>
         *
         * @return true if the current map is empty, false if not
         *
         */
        public function isEmpty() : Boolean
        {
            return size() <= 0;
        }

        /**
         *
         * Resets all key value assignments in the <code>ResourceMap</code>
         * instance to null
         *
         * @example
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.put( "editor", editorVO );
         * map.reset();
         *
         * trace( map.getValues() );
         * // null, null
         *
         * </listing>
         *
         */
        public function reset() : void
        {
            for ( var key:* in resource )
            {
                resource[key] = null;
            }
        }

        /**
         *
         * Resets all key / values defined in the <code>ResourceMap</code>
         * instance to null with the exception of the specified key
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.put( "editor", editorVO );
         *
         * trace( map.getValues() );
         * // [object, adminVO],[object, editorVO]
         *
         * map.resetAllExcept( "editor", editorVO );
         * trace( map.getValues() );
         * // null,[object, editorVO]
         *
         * </listing>
         *
         * @param the key which is not to be cleared from the map
         *
         */
        public function resetAllExcept(keyId:*) : void
        {
            for ( var key:* in resource )
            {
                if ( key != keyId )
                {
                    resource[key] = null;
                }
            }
        }

        /**
         *
         * Resets all key / values in the <code>ResourceMap</code>
         * instance to null
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.put( "editor", editorVO );
         * trace( map.size() );
         * //2
         * map.clear();
         * trace( map.size() );
         * //0
         *
         * </listing>
         *
         */
        public function clear() : void
        {
            for ( var key:* in resource )
            {
                remove( key );
            }
        }

        /**
         *
         * Clears all key / values defined in the <code>ResourceMap</code>
         * instance with the exception of the specified key
         *
         * @example
         * <listing version="3.0">
         *
         * import com.ericfeminella.collections.ResourceMap;
         * import com.ericfeminella.collections.IMap;
         * import mx.resources.ResourceBundle;
         *
         * [ResourceBundle("resources")]
         * private static const rb:ResourceBundle;
         *
         * var map:IMap = new ResourceMap( rb, false );
         * map.put( "admin", adminVO );
         * map.put( "editor", editorVO );
         * trace( map.size() );
         * //2
         *
         * map.clearAllExcept( "editor", editorVO );
         * trace( map.getValues() );
         * //[object, editorVO]
         * trace( map.size() );
         * //1
         *
         * </listing>
         *
         * @param the key which is not to be cleared from the map
         *
         */
        public function clearAllExcept(keyId:*) : void
        {
            for ( var key:* in resource )
            {
                if ( key != keyId )
                {
                    remove( key );
                }
            }
        }

        /**
         *
         * Returns an <code>Array</code> of <code>IHashMapEntry</code>
         * objects based on the underlying internal map.
         *
         * @param <code>Array</code> of <code>IHashMapEntry</code> objects
         *
         */
        public function getEntries() : Array
        {
            var list:Array = new Array();

            for ( var key:* in resource )
            {
                list.push( new HashMapEntry( key, resource[key] ) );
            }
            return list
        }
    }
}