/**
 * Dragdealer JS v0.9.5
 * http://code.ovidiu.ch/dragdealer-js
 *
 * Copyright (c) 2010, Ovidiu Chereches
 * MIT License
 * http://legal.ovidiu.ch/licenses/MIT
 */

/* Cursor */

var Cursor =
{
    x: 0, y: 0,
    init: function()
    {
        this.setEvent('mouse');
        this.setEvent('touch');
    },
    setEvent: function(type)
    {
        var moveHandler = document['on' + type + 'move'] || function(){};
        document['on' + type + 'move'] = function(e)
        {
            moveHandler(e);
            Cursor.refresh(e);
        }
    },
    refresh: function(e)
    {
        if(!e)
        {
            e = window.event;
        }
        if(e.type == 'mousemove')
        {
            this.set(e);
        }
        else if(e.touches)
        {
            this.set(e.touches[0]);
        }
    },
    set: function(e)
    {
        if(e.pageX || e.pageY)
        {
            this.x = e.pageX;
            this.y = e.pageY;
        }
        else if(e.clientX || e.clientY)
        {
            this.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
            this.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
        }
    }
};
Cursor.init();

/* Position */

var Position =
{
    get: function(obj)
    {
        var curleft = curtop = 0;
        if(obj.offsetParent)
        {
            do
            {
                curleft += obj.offsetLeft;
                curtop += obj.offsetTop;
            }
            while((obj = obj.offsetParent));
        }
        return [curleft, curtop];
    }
};

/* Dragdealer */

var Dragdealer = function(wrapper, options)
{
    if(typeof(wrapper) == 'string')
    {
        wrapper = document.getElementById(wrapper);
    }
    if(!wrapper)
    {
        return;
    }
    var handle = wrapper.getElementsByTagName('div')[0];
    if(!handle || handle.className.search(/(^|\s)handle(\s|$)/) == -1)
    {
        return;
    }
    this.init(wrapper, handle, options || {});
    this.setup();
};
Dragdealer.prototype =
{
    init: function(wrapper, handle, options)
    {
        this.wrapper = wrapper;
        this.handle = handle;
        this.options = options;
        
        this.disabled = this.getOption('disabled', false);
        this.horizontal = this.getOption('horizontal', true);
        this.vertical = this.getOption('vertical', false);
        this.slide = this.getOption('slide', true);
        this.steps = this.getOption('steps', 0);
        this.snap = this.getOption('snap', false);
        this.loose = this.getOption('loose', false);
        this.speed = this.getOption('speed', 10) / 100;
        this.xPrecision = this.getOption('xPrecision', 0);
        this.yPrecision = this.getOption('yPrecision', 0);
        
        this.callback = options.callback || null;
        this.animationCallback = options.animationCallback || null;
        
        this.bounds = {
            left: options.left || 0, right: -(options.right || 0),
            top: options.top || 0, bottom: -(options.bottom || 0),
            x0: 0, x1: 0, xRange: 0,
            y0: 0, y1: 0, yRange: 0
        };
        this.value = {
            prev: [-1, -1],
            current: [options.x || 0, options.y || 0],
            target: [options.x || 0, options.y || 0]
        };
        this.offset = {
            wrapper: [0, 0],
            mouse: [0, 0],
            prev: [-999999, -999999],
            current: [0, 0],
            target: [0, 0]
        };
        this.change = [0, 0];
        
        this.activity = false;
        this.dragging = false;
        this.tapping = false;
    },
    getOption: function(name, defaultValue)
    {
        return this.options[name] !== undefined ? this.options[name] : defaultValue;
    },
    setup: function()
    {
        this.setWrapperOffset();
        this.setBoundsPadding();
        this.setBounds();
        this.setSteps();
        
        this.addListeners();
    },
    setWrapperOffset: function()
    {
        this.offset.wrapper = Position.get(this.wrapper);
    },
    setBoundsPadding: function()
    {
        if(!this.bounds.left && !this.bounds.right)
        {
            this.bounds.left = Position.get(this.handle)[0] - this.offset.wrapper[0];
            this.bounds.right = -this.bounds.left;
        }
        if(!this.bounds.top && !this.bounds.bottom)
        {
            this.bounds.top = Position.get(this.handle)[1] - this.offset.wrapper[1];
            this.bounds.bottom = -this.bounds.top;
        }
    },
    setBounds: function()
    {
        this.bounds.x0 = this.bounds.left;
        this.bounds.x1 = this.wrapper.offsetWidth + this.bounds.right;
        this.bounds.xRange = (this.bounds.x1 - this.bounds.x0) - this.handle.offsetWidth;
        
        this.bounds.y0 = this.bounds.top;
        this.bounds.y1 = this.wrapper.offsetHeight + this.bounds.bottom;
        this.bounds.yRange = (this.bounds.y1 - this.bounds.y0) - this.handle.offsetHeight;
        
        this.bounds.xStep = 1 / (this.xPrecision || Math.max(this.wrapper.offsetWidth, this.handle.offsetWidth));
        this.bounds.yStep = 1 / (this.yPrecision || Math.max(this.wrapper.offsetHeight, this.handle.offsetHeight));
    },
    setSteps: function()
    {
        if(this.steps > 1)
        {
            this.stepRatios = [];
            for(var i = 0; i <= this.steps - 1; i++)
            {
                this.stepRatios[i] = i / (this.steps - 1);
            }
        }
    },
    addListeners: function()
    {
        var self = this;
        
        this.wrapper.onselectstart = function()
        {
            return false;
        }
        this.handle.onmousedown = this.handle.ontouchstart = function(e)
        {
            self.handleDownHandler(e);
        };
        this.wrapper.onmousedown = this.wrapper.ontouchstart = function(e)
        {
            self.wrapperDownHandler(e);
        };
        var mouseUpHandler = document.onmouseup || function(){};
        document.onmouseup = function(e)
        {
            mouseUpHandler(e);
            self.documentUpHandler(e);
        };
        var touchEndHandler = document.ontouchend || function(){};
        document.ontouchend = function(e)
        {
            touchEndHandler(e);
            self.documentUpHandler(e);
        };
        var resizeHandler = window.onresize || function(){};
        window.onresize = function(e)
        {
            resizeHandler(e);
            self.documentResizeHandler(e);
        };
        this.wrapper.onmousemove = function(e)
        {
            self.activity = true;
        }
        this.wrapper.onclick = function(e)
        {
            return !self.activity;
        }
        
        this.interval = setInterval(function(){ self.animate() }, 25);
        self.animate(false, true);
    },
    handleDownHandler: function(e)
    {
        this.activity = false;
        Cursor.refresh(e);
        
        this.preventDefaults(e, true);
        this.startDrag();
        this.cancelEvent(e);
    },
    wrapperDownHandler: function(e)
    {
        Cursor.refresh(e);
        
        this.preventDefaults(e, true);
        this.startTap();
    },
    documentUpHandler: function(e)
    {
        this.stopDrag();
        this.stopTap();
        //this.cancelEvent(e);
    },
    documentResizeHandler: function(e)
    {
        this.setWrapperOffset();
        this.setBounds();
        
        this.update();
    },
    enable: function()
    {
        this.disabled = false;
        this.handle.className = this.handle.className.replace(/\s?disabled/g, '');
    },
    disable: function()
    {
        this.disabled = true;
        this.handle.className += ' disabled';
    },
    setStep: function(x, y, snap)
    {
        this.setValue(
            this.steps && x > 1 ? (x - 1) / (this.steps - 1) : 0,
            this.steps && y > 1 ? (y - 1) / (this.steps - 1) : 0,
            snap
        );
    },
    setValue: function(x, y, snap)
    {
        this.setTargetValue([x, y || 0]);
        if(snap)
        {
            this.groupCopy(this.value.current, this.value.target);
        }
    },
    startTap: function(target)
    {
        if(this.disabled)
        {
            return;
        }
        this.tapping = true;
        
        if(target === undefined)
        {
            target = [
                Cursor.x - this.offset.wrapper[0] - (this.handle.offsetWidth / 2),
                Cursor.y - this.offset.wrapper[1] - (this.handle.offsetHeight / 2)
            ];
        }
        this.setTargetOffset(target);
    },
    stopTap: function()
    {
        if(this.disabled || !this.tapping)
        {
            return;
        }
        this.tapping = false;
        
        this.setTargetValue(this.value.current);
        this.result();
    },
    startDrag: function()
    {
        if(this.disabled)
        {
            return;
        }
        this.offset.mouse = [
            Cursor.x - Position.get(this.handle)[0],
            Cursor.y - Position.get(this.handle)[1]
        ];
        
        this.dragging = true;
    },
    stopDrag: function()
    {
        if(this.disabled || !this.dragging)
        {
            return;
        }
        this.dragging = false;
        
        var target = this.groupClone(this.value.current);
        if(this.slide)
        {
            var ratioChange = this.change;
            target[0] += ratioChange[0] * 4;
            target[1] += ratioChange[1] * 4;
        }
        this.setTargetValue(target);
        this.result();
    },
    feedback: function()
    {
        var value = this.value.current;
        if(this.snap && this.steps > 1)
        {
            value = this.getClosestSteps(value);
        }
        if(!this.groupCompare(value, this.value.prev))
        {
            if(typeof(this.animationCallback) == 'function')
            {
                this.animationCallback(value[0], value[1]);
            }
            this.groupCopy(this.value.prev, value);
        }
    },
    result: function()
    {
        if(typeof(this.callback) == 'function')
        {
            this.callback(this.value.target[0], this.value.target[1]);
        }
    },
    animate: function(direct, first)
    {
        if(direct && !this.dragging)
        {
            return;
        }
        if(this.dragging)
        {
            var prevTarget = this.groupClone(this.value.target);
            
            var offset = [
                Cursor.x - this.offset.wrapper[0] - this.offset.mouse[0],
                Cursor.y - this.offset.wrapper[1] - this.offset.mouse[1]
            ];
            this.setTargetOffset(offset, this.loose);
            
            this.change = [
                this.value.target[0] - prevTarget[0],
                this.value.target[1] - prevTarget[1]
            ];
        }
        if(this.dragging || first)
        {
            this.groupCopy(this.value.current, this.value.target);
        }
        if(this.dragging || this.glide() || first)
        {
            this.update();
            this.feedback();
        }
    },
    glide: function()
    {
        var diff = [
            this.value.target[0] - this.value.current[0],
            this.value.target[1] - this.value.current[1]
        ];
        if(!diff[0] && !diff[1])
        {
            return false;
        }
        if(Math.abs(diff[0]) > this.bounds.xStep || Math.abs(diff[1]) > this.bounds.yStep)
        {
            this.value.current[0] += diff[0] * this.speed;
            this.value.current[1] += diff[1] * this.speed;
        }
        else
        {
            this.groupCopy(this.value.current, this.value.target);
        }
        return true;
    },
    update: function()
    {
        if(!this.snap)
        {
            this.offset.current = this.getOffsetsByRatios(this.value.current);
        }
        else
        {
            this.offset.current = this.getOffsetsByRatios(
                this.getClosestSteps(this.value.current)
            );
        }
        this.show();
    },
    show: function()
    {
        if(!this.groupCompare(this.offset.current, this.offset.prev))
        {
            if(this.horizontal)
            {
                this.handle.style.left = String(this.offset.current[0]) + 'px';
            }
            if(this.vertical)
            {
                this.handle.style.top = String(this.offset.current[1]) + 'px';
            }
            this.groupCopy(this.offset.prev, this.offset.current);
        }
    },
    setTargetValue: function(value, loose)
    {
        var target = loose ? this.getLooseValue(value) : this.getProperValue(value);
        
        this.groupCopy(this.value.target, target);
        this.offset.target = this.getOffsetsByRatios(target);
    },
    setTargetOffset: function(offset, loose)
    {
        var value = this.getRatiosByOffsets(offset);
        var target = loose ? this.getLooseValue(value) : this.getProperValue(value);
        
        this.groupCopy(this.value.target, target);
        this.offset.target = this.getOffsetsByRatios(target);
    },
    getLooseValue: function(value)
    {
        var proper = this.getProperValue(value);
        return [
            proper[0] + ((value[0] - proper[0]) / 4),
            proper[1] + ((value[1] - proper[1]) / 4)
        ];
    },
    getProperValue: function(value)
    {
        var proper = this.groupClone(value);

        proper[0] = Math.max(proper[0], 0);
        proper[1] = Math.max(proper[1], 0);
        proper[0] = Math.min(proper[0], 1);
        proper[1] = Math.min(proper[1], 1);
        
        if((!this.dragging && !this.tapping) || this.snap)
        {
            if(this.steps > 1)
            {
                proper = this.getClosestSteps(proper);
            }
        }
        return proper;
    },
    getRatiosByOffsets: function(group)
    {
        return [
            this.getRatioByOffset(group[0], this.bounds.xRange, this.bounds.x0),
            this.getRatioByOffset(group[1], this.bounds.yRange, this.bounds.y0)
        ];
    },
    getRatioByOffset: function(offset, range, padding)
    {
        return range ? (offset - padding) / range : 0;
    },
    getOffsetsByRatios: function(group)
    {
        return [
            this.getOffsetByRatio(group[0], this.bounds.xRange, this.bounds.x0),
            this.getOffsetByRatio(group[1], this.bounds.yRange, this.bounds.y0)
        ];
    },
    getOffsetByRatio: function(ratio, range, padding)
    {
        return Math.round(ratio * range) + padding;
    },
    getClosestSteps: function(group)
    {
        return [
            this.getClosestStep(group[0]),
            this.getClosestStep(group[1])
        ];
    },
    getClosestStep: function(value)
    {
        var k = 0;
        var min = 1;
        for(var i = 0; i <= this.steps - 1; i++)
        {
            if(Math.abs(this.stepRatios[i] - value) < min)
            {
                min = Math.abs(this.stepRatios[i] - value);
                k = i;
            }
        }
        return this.stepRatios[k];
    },
    groupCompare: function(a, b)
    {
        return a[0] == b[0] && a[1] == b[1];
    },
    groupCopy: function(a, b)
    {
        a[0] = b[0];
        a[1] = b[1];
    },
    groupClone: function(a)
    {
        return [a[0], a[1]];
    },
    preventDefaults: function(e, selection)
    {
        if(!e)
        {
            e = window.event;
        }
        if(e.preventDefault)
        {
            e.preventDefault();
        }
        e.returnValue = false;
        
        if(selection && document.selection)
        {
            document.selection.empty();
        }
    },
    cancelEvent: function(e)
    {
        if(!e)
        {
            e = window.event;
        }
        if(e.stopPropagation)
        {
            e.stopPropagation();
        }
        e.cancelBubble = true;
    }
};

