    package flare.scale
    {
        import flash.utils.Dictionary;
        import flare.util.Arrays;

        /**
         * Scale for ordered sequential data. This supports both numeric and
         * non-numeric data, and simply places each element in sequence using
         * the ordering found in the input data array.
         */
        public class OrdinalScale extends Scale
        {
            private var _ordinals:Array;
            private var _lookup:Dictionary;

            /**
             * Creates a new OrdinalScale.
             * @param ordinals an ordered array of data values to include in the
             *  scale
             * @param flush the flush flag for scale padding
             * @param copy flag indicating if a copy of the input data array should
             *  be made. True by default.
             * @param labelFormat the formatting pattern for value labels
             */
            public function OrdinalScale(ordinals:Array=null, flush:Boolean=false,
                copy:Boolean=true, labelFormat:String=null)
            {
                _ordinals = (ordinals==null ? new Array() :
                             copy ? Arrays.copy(ordinals) : ordinals);
                buildLookup();
                _flush = flush;
                _format = labelFormat;
            }

            /** @inheritDoc */
            public override function get scaleType():String {
                return ScaleType.ORDINAL;
            }

            /** @inheritDoc */
            public override function clone() : Scale
            {
                return new OrdinalScale(_ordinals, _flush, true, _format);
            }

            // -- Properties ------------------------------------------------------

            /** The number of distinct values in this scale. */
            public function get length():int
            {
                return _ordinals.length;
            }

            /** The ordered data array defining this scale. */
            public function get ordinals():Array
            {
                return _ordinals;
            }
            public function set ordinals(val:Array):void
            {
                _ordinals = val; buildLookup();
            }

            /**
             * Builds a lookup table for mapping values to their indices.
             */
            protected function buildLookup():void
            {
                _lookup = new Dictionary();
                for (var i:uint = 0; i < _ordinals.length; ++i)
                    _lookup[ordinals[i]] = i;
            }

            /** @inheritDoc */
            public override function get min():Object { return _ordinals[0]; }

            /** @inheritDoc */
            public override function get max():Object { return _ordinals[_ordinals.length-1]; }

            // -- Scale Methods ---------------------------------------------------

            /**
             * Returns the index of the input value in the ordinal array
             * @param value the value to lookup
             * @return the index of the input value. If the value is not contained
             *  in the ordinal array, this method returns -1.
             */
            public function index(value:Object):int
            {
                var idx:* = _lookup[value];
                return (idx==undefined ? -1 : int(idx));
            }

            /** @inheritDoc */
            public override function interpolate(value:Object):Number
            {
                if (_ordinals==null || _ordinals.length==0) return 0.5;

                if (_flush) {
                    return Number(_lookup[value]) / (_ordinals.length-1);
                } else {
                    return (0.5 + _lookup[value]) / _ordinals.length;
                }
            }

            /** @inheritDoc */
            public override function lookup(f:Number):Object
            {
                if (_flush) {
                    return _ordinals[int(Math.round(f*(_ordinals.length-1)))];
                } else {
                    f = Math.max(0, Math.min(1, f*_ordinals.length - 0.5));
                    return _ordinals[int(Math.round(f))];
                }
            }

            /** @inheritDoc */
            public override function values(num:int=-1):Array
            {
                var a:Array = new Array();
                var stride:Number = num<0 ? 1
                    : Math.max(1, Math.floor(_ordinals.length / num));
                for (var i:uint = 0; i < _ordinals.length; i += stride) {
                    a.push(_ordinals[i]);
                }
                return a;
            }

        } // end of class OrdinalScale
    }