'use strict';

/*@
Ox.Range <f> Range Object
    ([options[, self]]) -> <o:Ox.Element> Range Object
        change <!> triggered on change of the range
    options <o> Options object
        arrows        <b>          if true, show arrows
        arrowStep     <n>          step when clicking arrows
        arrowSymbols  <[s]>        arrow symbols, like ['minus', 'plus']
        max           <n>          maximum value
        min           <n>          minimum value
        orientation   <s>          'horizontal' or 'vertical'
        step          <n>          step between values
        size          <n>          width or height, in px
        thumbSize     <n>          minimum width or height of thumb, in px
        thumbStyle    <s|'opaque'> Thumb style ('opaque' or 'transparent')
        thumbValue    <b>          if true, display value on thumb
        trackColors   <[s]>        CSS colors
        trackGradient <b|false>    if true, display track colors as gradient
        trackImages   <s|[s]>      one or multiple track background image URLs
        trackStep     <n>          0 (scroll here) or step when clicking track
        value         <n>          initial value
        values        <[s]>        values to display on thumb
    self    <o> shared private variable
@*/

Ox.Range = function(options, self) {

    self = self || {};
    var that = Ox.Element({}, self)
        .defaults({
            arrows: false,
            arrowStep: 1,
            arrowSymbols: ['left', 'right'],
            changeOnDrag: false,
            max: 100,
            min: 0,
            orientation: 'horizontal',
            step: 1,
            size: 128, // fixme: shouldn't this be width?
            thumbSize: 16,
            thumbStyle: 'default',
            thumbValue: false,
            trackColors: [],
            trackGradient: false,
            trackImages: [],
            trackStep: 0,
            value: 0,
            values: []
        })
        .options(options || {})
        .update({
            size: setSizes,
            trackColors: setTrackColors,
            value: setThumb
        })
        .addClass('OxRange')
        .css({
            width: self.options.size + 'px'
        });

    self.hasValues = !Ox.isEmpty(self.options.values);
    if (self.hasValues) {
        self.options.max = self.options.values.length - 1;
        self.options.min = 0;
        self.options.step = 1;
        self.options.thumbValue = true;
        self.options.value = Ox.isNumber(self.options.value)
            ? self.options.values[self.options.value] : self.options.value;
    }
    self.options.arrowStep = options.arrowStep || self.options.step;
    self.options.trackImages = Ox.makeArray(self.options.trackImages);

    self.trackColors = self.options.trackColors.length;
    self.trackImages = self.options.trackImages.length;
    self.values = (
        self.options.max - self.options.min + self.options.step
    ) / self.options.step;

    setSizes();

    if (self.options.arrows) {
        self.$arrows = [];
        Ox.range(0, 2).forEach(function(i) {
            self.$arrows[i] = Ox.Button({
                overlap: i == 0 ? 'right' : 'left',
                title: self.options.arrowSymbols[i],
                type: 'image'
            })
            .addClass('OxArrow')
            .bindEvent({
                mousedown: function(data) {
                    clickArrow(data, i, true);
                },
                mouserepeat: function(data) {
                    clickArrow(data, i, false);
                }
            })
            .appendTo(that);
        });
    }

    self.$track = Ox.Element()
        .addClass('OxTrack')
        .css(Ox.extend({
            width: (self.trackSize - 2) + 'px'
        }, self.trackImages == 1 ? {
            background: 'rgb(0, 0, 0)'
        } : {}))
        .bindEvent(Ox.extend({
            mousedown: clickTrack,
            drag: dragTrack
        }, self.options.changeOnDrag ? {} : {
            dragend: dragendTrack
        }))
        .appendTo(that);

    self.trackColors && setTrackColors();

    if (self.trackImages) {
        self.$trackImages = $('<div>')
            .css({
                width: self.trackSize + 'px',
                marginRight: (-self.trackSize - 1) + 'px'
            })
            .appendTo(self.$track.$element);
        self.options.trackImages.forEach(function(v, i) {
            $('<img>')
                .attr({
                    src: v
                })
                .addClass(i == 0 ? 'OxFirstChild' : '')
                .addClass(i == self.trackImages - 1 ? 'OxLastChild' : '')
                .css({
                    width: self.trackImageWidths[i] + 'px'
                })
                .mousedown(function(e) {
                    e.preventDefault(); // prevent drag
                })
                .appendTo(self.$trackImages);
            //left += self.trackImageWidths[i];
        });
    }

    self.$thumb = Ox.Button({
            id: self.options.id + 'Thumb',
            width: self.thumbSize
        })
        .addClass('OxThumb' + (
            self.options.thumbStyle == 'transparent' ? ' OxTransparent' : ''
        ))
        .appendTo(self.$track);

    setThumb();

    function clickArrow(data, i, animate) {
        // fixme: shift doesn't work, see menu scrolling
        setValue(
            self.options.value
            + self.options.arrowStep * (i == 0 ? -1 : 1) * (data.shiftKey ? 2 : 1),
            animate,
            true
        );
    }

    function clickTrack(data) {
        // fixme: thumb ends up a bit too far on the right
        var isThumb = $(data.target).hasClass('OxThumb');
        self.drag = {
            left: self.$track.offset().left,
            offset: isThumb ? data.clientX - self.$thumb.offset().left - 8 /*self.thumbSize / 2*/ : 0
        };
        setValue(getValue(data.clientX - self.drag.left - self.drag.offset), !isThumb, true);
    }

    function dragTrack(data) {
        setValue(
            getValue(data.clientX - self.drag.left - self.drag.offset),
            false,
            self.options.changeOnDrag
        );
    }

    function dragendTrack(data) {
        self.options.value = void 0;
        setValue(getValue(data.clientX - self.drag.left - self.drag.offset), false, true);
    }

    function getPx(value) {
        var pxPerValue = (self.trackSize - self.thumbSize)
            / (self.options.max - self.options.min);
        value = self.hasValues ? self.options.values.indexOf(value) : value;
        return Math.ceil((value - self.options.min) * pxPerValue);
    }

    /*
    function getTime(oldValue, newValue) {
        return self.animationTime * Math.abs(oldValue - newValue) / (self.options.max - self.options.min);
    }
    */

    function getValue(px) {
        var px = self.trackSize / self.values >= 16 ? px : px - 8,
            valuePerPx = (self.options.max - self.options.min)
                / (self.trackSize - self.thumbSize),
            value = Ox.limit(
                self.options.min
                    + Math.floor(px * valuePerPx / self.options.step) * self.options.step,
                self.options.min,
                self.options.max
            );
        return self.hasValues ? self.options.values[value] : value;
    }

    function setSizes() {
        self.trackSize = self.options.size - self.options.arrows * 32;
        self.thumbSize = Math.max(self.trackSize / self.values, self.options.thumbSize);
        self.trackImageWidths = self.trackImages == 1
            ? [self.trackSize - 16]
            : Ox.splitInt(self.trackSize - 2, self.trackImages);
        self.trackColorStart = self.options.trackGradient
            ? self.thumbSize / 2 / self.options.size : 0;
        self.trackColorStep = self.options.trackGradient
            ? (self.options.size - self.thumbSize) / (self.trackColors - 1) / self.options.size
            : 1 / self.trackColors;
        that.css({
            width: self.options.size + 'px'
        });
        self.$track && self.$track.css({
            width: (self.trackSize - 2) + 'px'
        });
        if (self.$thumb) {
            self.$thumb.options({width: self.thumbSize});
            setThumb();
        }
    }

    function setThumb(animate) {
        self.$thumb.stop().animate({
            marginLeft: getPx(self.options.value) - 1 + 'px'
            //, width: self.thumbSize + 'px'
        }, animate ? 250 : 0, function() {
            self.options.thumbValue && self.$thumb.options({
                title: self.options.value
            });
        });
    }

    function setTrackColors() {
        ['moz', 'o', 'webkit'].forEach(function(browser) {
            self.$track.css({
                background: '-' + browser + '-linear-gradient(left, '
                    + self.options.trackColors[0] + ' 0%, '
                    + self.options.trackColors.map(function(v, i) {
                        var ret = v + ' ' + (
                            self.trackColorStart + self.trackColorStep * i
                        ) * 100 + '%';
                        if (!self.options.trackGradient) {
                            ret += ', ' + v + ' ' + (
                                self.trackColorStart + self.trackColorStep * (i + 1)
                            ) * 100 + '%';
                        }
                        return ret;
                    }).join(', ') + ', '
                    + self.options.trackColors[self.trackColors - 1] + ' 100%)'
            });
        });
    }

    function setValue(value, animate, trigger) {
        // fixme: toPrecision helps with imprecise results of divisions,
        // but won't work for very large values. And is 10 a good value?
        value = Ox.limit(
            self.hasValues ? self.options.values.indexOf(value) : value.toPrecision(10),
            self.options.min,
            self.options.max
        );
        value = self.hasValues ? self.options.values[value] : value;
        if (value != self.options.value) {
            //time = getTime(self.options.value, value);
            self.options.value = value;
            setThumb(animate);
            trigger && that.triggerEvent('change', {value: self.options.value});
        }
    }

    return that;

};
