package flare.scale
{
    import flare.util.Dates;
    import flare.util.Maths;
    
    /**
     * Scale for timelines represented using <code>Date</code> values. This
     * scale represents a linear, quantitative time line. The class attempts
     * to automatically configure date value labels based on the time span
     * between the earliest and latest date in the scale. The label formatting
     * pattern can also be manually set using the <code>labelFormat</code>
     * property.
     */
    public class TimeScale extends Scale
    {
        private var _dmin:Date = new Date(0);
        private var _dmax:Date = new Date(0);
        private var _smin:Date;
        private var _smax:Date;
        private var _autofmt:Boolean = true;
        
        /**
         * Creates a new TimeScale.
         * @param min the minimum (earliest) date value
         * @param max the maximum (latest) date value
         * @param flush the flush flag for scale padding
         * @param labelFormat the formatting pattern for value labels
         */
        public function TimeScale(min:Date=null, max:Date=null,
            flush:Boolean=false, labelFormat:String=null)
        {
            if (min) this.dataMin = min;
            if (max) this.dataMax = max;
            this.flush = flush;
            this.labelFormat = labelFormat;
        }
        
        /** @inheritDoc */
        public override function get scaleType():String {
            return ScaleType.TIME;
        }
        
        /** @inheritDoc */
        public override function clone():Scale {
            return new TimeScale(_dmin, _dmax, _flush, _format);
        }
        
        // -- Properties ------------------------------------------------------
        
        /** @inheritDoc */
        public override function set flush(val:Boolean):void
        {
            _flush = val; updateScale();
        }
        
        /** @inheritDoc */
        public override function get labelFormat():String
        {
            return (_autofmt ? null : super.labelFormat);
        }
        
        public override function set labelFormat(fmt:String):void
        {
            if (fmt != null) {
                super.labelFormat = fmt;
                _autofmt = false;
            } else {
                _autofmt = true;
                updateScale();
            }
        }
        
        /** @inheritDoc */
        public override function get min():Object { return dataMin; }
        public override function set min(o:Object):void { dataMin = o as Date; }
        
        /** @inheritDoc */
        public override function get max():Object { return dataMax; }
        public override function set max(o:Object):void { dataMax = o as Date; }
        
        /** The minimum (earliest) Date value in the underlying data.
         *  This property is the same as the <code>minimum</code>
         *  property, but properly typed. */
        public function get dataMin():Date
        {
            return _dmin;
        }
        public function set dataMin(val:Date):void
        {
            _dmin = val; updateScale();
        }

        /** The maximum (latest) Date value in the underlying data.
         *  This property is the same as the <code>maximum</code>
         *  property, but properly typed. */
        public function get dataMax():Date
        {
            return _dmax;
        }
        public function set dataMax(val:Date):void
        {
            _dmax = val; updateScale();
        }
        
        /** The minimum (earliest) Date value in the scale. */
        public function get scaleMin():Date
        {
            return _smin;
        }
        
        /** The maximum (latest) Date value in the underlying data. */
        public function get scaleMax():Date
        {
            return _smax;
        }
        
        // -- Scale Methods ---------------------------------------------------
        
        /** @inheritDoc */
        public override function interpolate(value:Object):Number
        {
            var t:Number = value is Date ? (value as Date).time : Number(value);
            return Maths.invLinearInterp(t, _smin.time, _smax.time);
        }
        
        /** @inheritDoc */
        public override function lookup(f:Number):Object
        {
            var t:Number = Math.round(Maths.linearInterp(f, _smin.time, _smax.time));
            return new Date(t);
        }
        
        /**
         * Updates the scale range when the data range is changed.
         */
        protected function updateScale():void
        {
            var span:int = Dates.timeSpan(_dmin, _dmax);
            if (_flush) {
                _smin = _dmin;
                _smax = _dmax;
            } else {
                _smin = Dates.roundTime(_dmin, span, false);
                _smax = Dates.roundTime(_dmax, span, true);
            }
            if (_autofmt) {
                super.labelFormat = formatString(span);
            }
        }
        
        /**
         * Determines the format string to be used based on a measure of
         * the time span covered by this scale.
         * @param span the time span covered by this scale. Should use the
         *  format of the <code>flare.util.Dates</code> class.
         * @return the label formatting pattern
         */
        protected function formatString(span:int):String
        {
            if (span >= Dates.YEARS) {
                return "yyyy";
            } else if (span == Dates.MONTHS) {
                return "MMM";
            } else if (span == Dates.DAYS) {
                return "d";
            } else if (span == Dates.HOURS) {
                return "h:mmt";
            } else if (span == Dates.MINUTES) {
                return "h:mmt";
            } else if (span == Dates.SECONDS) {
                return "h:mm:ss";
            } else {
                return "s.fff";
            }
        }
        
        /** @inheritDoc */
        public override function values(num:int=-1):Array
        {   
            var a:Array = new Array();
            var span:int = Dates.timeSpan(_dmin, _dmax);
            var step:Number = Dates.timeStep(span);
            var max:Number = _smax.time;
            var d:Date = _flush ? Dates.roundTime(scaleMin, span, true) : scaleMin;

            if (span < Dates.MONTHS) {
                for (var x:Number = _smin.time; x <= max; x += step) {
                    a.push(new Date(x));
                }
            } else if (span == Dates.MONTHS) {
                for (; d.time <= max; d = Dates.addMonths(d,1)) {
                    a.push(d);
                }
            } else {
                var y:int = int(step);
                for (; d.time <= max; d = Dates.addYears(d,y)) {
                    a.push(d);
                }
            }
            return a;
        }
        
    } // end of class TimeScale
}