/* Copyright 2009 Palm, Inc.  All rights reserved. */



IntegerPopup = Class.create({
	
    initialize: function(controller, target, elmentClass, elementOpenClass, navNextCallback, changeCallback, modeChangeCallback, parseCallback, params){
        this.controller = controller;
		this.items = params.items;
        this.currentItem = params.currentItem || 0;
        this.exposeItems = 2;
        this.integerItem = params.integerItem;
        this.integerMode = false;
        this.dragEndIndex = -1;
        this.target = target;
        this.targetClass = elmentClass;
        this.targetOpenClass = elementOpenClass;
        this.topStart = this.exposeItems;
        this.navNextCallback = navNextCallback;
        this.changeCallback = changeCallback;
        this.modeCallack = modeChangeCallback;
        this.parseCallback = parseCallback;
        this.lastDragHighlight = -1;
        this.tempOpen = false;
        this.flicking = false;
        this.pendingMode = null;
        this.minValue = params.min || 0;
        this.maxValue = params.max || 9999;
        
        this.mode = IntegerPopup.MODE_NONE;
        
        if (this.integerItem) {
            this.integerMode = true;
            this.topStart++;
            this.selected = this.topStart;
        }
        else {
            this.selected = this.currentItem;
        }
        
        this.parent = this.controller.get(this.target.parentNode);
        
        this.clickHandler = this.click.bindAsEventListener(this);
        this.mouseDownHandler = this.mouseDown.bindAsEventListener(this);
        this.mouseUpHandler = this.mouseUp.bindAsEventListener(this);
        this.keyPressHandler = this.keyPress.bindAsEventListener(this);
        
        this.dragStartHandler = this.dragStart.bindAsEventListener(this);
        this.target.observe(Mojo.Event.dragStart, this.dragStartHandler);
        this.parent.observe(Mojo.Event.dragStart, this.dragStartHandler);
        
        this.itemHtml = '';
        this.keyStr = '';
    },
    
	getTemplatePath: function(templateDir) {
		// TODO When added to the frame uncomment 
		//return Mojo.Widget.getSystemTemplatePath(templateDir);
		return "../../../com.palm.app.calendar/app/views/" + templateDir;
	},
  
    initItems: function(){
        if (this.integerMode) {
            this.numItems = this.topStart * 2 + 1;
            this.integerModeOffset = 0;
            this.selected = this.topStart;
            this.itemHtml = '';
            var valueStart = this.integerItem - this.topStart;
            if (valueStart < this.minValue) {
                valueStart = this.maxValue + (valueStart - this.minValue) + 1;
            }
            for (var i = 0; i < this.numItems; i++) {
                if ((valueStart + i) > this.maxValue) {
                    valueStart = this.minValue - i;
                }
                this.itemHtml += Mojo.View.render({
                    object: {value: valueStart + i},
                    template: this.getTemplatePath("datetime/picker-value")
                });
            }
        } else {
            if (this.itemHtml == '') {
                this.numItems = this.items.length;
                this.items.each(function(item){
                    this.itemHtml += Mojo.View.render({
                        object: {value: item},
                        template: this.getTemplatePath("datetime/picker-value")
                    });
                }, this);
                this.itemHtml += this.itemHtml;
            }
        }
        this.target.update(this.itemHtml);
        
        this.targetSize = this.target.getDimensions();
        if (this.integerMode == false) 
            this.targetSize.height = this.targetSize.height / 2;
        this.itemHeight = this.targetSize.height / this.numItems;
        this.target.makePositioned();
        this.moveTo(this.selected);
    },
    
    setNoneMode: function(doCallback){
        if (this.flicking) {
            this.pendingMode = IntegerPopup.MODE_NONE;
            return;
        }
        
        if (doCallback && this.modeCallack) 
            this.modeCallack(IntegerPopup.MODE_NONE);
        
        this.parent.stopObserving('click', this.clickHandler);
        this.parent.stopObserving('mousedown', this.mouseDownHandler);
        this.parent.stopObserving('mouseup', this.mouseUpHandler);
		Element.observe(this.controller.element.ownerDocument, 'keypress', this.keyPressHandler);
        if (this.mode == IntegerPopup.MODE_EDIT) {
            this.parent.removeClassName('focus');
        } else if (this.mode == IntegerPopup.MODE_PICK) {
            this.cleanupEvents();
            this.parent.removeClassName(this.targetOpenClass);
            this.parent.addClassName(this.targetClass);
            if (this.integerMode) {
                this.integerItem = this.integerItem + this.integerModeOffset + this.selected - this.topStart;
                this.target.update(this.integerItem);
            }
            else {
                this.target.update(this.items[this.selected]);
            }
            this.target.setStyle({
                top: '0px'
            });
        }
        this.mode = IntegerPopup.MODE_NONE;
    },
    
    setDisplayMode: function(doCallback){
        if (this.flicking) {
            this.pendingMode = IntegerPopup.MODE_DISPLAY;
            return;
        }
        
        if (doCallback && this.modeCallack) 
            this.modeCallack(IntegerPopup.MODE_DISPLAY);
        
        this.parent.stopObserving('click', this.clickHandler);
        this.parent.stopObserving('mousedown', this.mouseDownHandler);
        this.parent.stopObserving('mouseup', this.mouseUpHandler);
		Element.stopObserving(this.controller.element.ownerDocument, 'keypress', this.keyPressHandler);
        if (this.mode == IntegerPopup.MODE_EDIT) {
            this.parent.removeClassName('focus');
        } else if (this.mode == IntegerPopup.MODE_PICK) {
            this.cleanupEvents();
            this.parent.removeClassName(this.targetOpenClass);
            this.parent.addClassName(this.targetClass);
            if (this.integerMode) {
                this.integerItem = this.integerItem + this.integerModeOffset + this.selected - this.topStart;
                this.target.update(this.integerItem);
            }
            else {
                this.target.update(this.items[this.selected]);
            }
            this.target.setStyle({
                top: '0px'
            });
        }
        this.mode = IntegerPopup.MODE_DISPLAY;
    },
    
    setPickMode: function(doCallback){
        if (this.flicking) {
            return;
        }
        
        if (doCallback && this.modeCallack) 
            this.modeCallack(IntegerPopup.MODE_PICK);
        
        if ((this.mode == IntegerPopup.MODE_DISPLAY) || (this.mode == IntegerPopup.MODE_NONE)) {
            this.parent.removeClassName(this.targetClass);
            this.parent.addClassName(this.targetOpenClass);
			Element.observe(this.controller.element.ownerDocument, 'keypress', this.keyPressHandler);
            this.parent.observe('click', this.clickHandler);
            this.parent.observe('mousedown', this.mouseDownHandler);
            this.parent.observe('mouseup', this.mouseUpHandler);
            this.initItems();
            this.mode = IntegerPopup.MODE_PICK;
        } else if (this.mode == IntegerPopup.MODE_EDIT) {
            this.parent.removeClassName('focus');
            this.parent.removeClassName(this.targetClass);
            this.parent.addClassName(this.targetOpenClass);
            this.initItems();
            this.mode = IntegerPopup.MODE_PICK;
        }
    },
    
    setEditMode: function(doCallback, updateValue){
        if (this.flicking) {
            return;
        }
        
        if (doCallback && this.modeCallack) 
            this.modeCallack(IntegerPopup.MODE_EDIT);
        
        if ((this.mode == IntegerPopup.MODE_DISPLAY) || (this.mode == IntegerPopup.MODE_NONE)) {
            this.parent.addClassName('focus');
			Element.observe(this.controller.element.ownerDocument, 'keypress', this.keyPressHandler);
            this.parent.observe('click', this.clickHandler);
            this.parent.observe('mousedown', this.mouseDownHandler);
            this.parent.observe('mouseup', this.mouseUpHandler);
        } else if (this.mode == IntegerPopup.MODE_PICK) {
            this.cleanupEvents();
            this.parent.addClassName('focus');
            this.parent.addClassName(this.targetClass);
            this.parent.removeClassName(this.targetOpenClass);
            if (this.integerMode) {
                this.integerItem = this.integerItem + this.integerModeOffset + this.selected - this.topStart;
                if (this.integerItem < this.minValue) {
                    this.integerItem = this.maxValue - (this.minValue - this.integerItem) + 1;
                }
                if (this.integerItem > this.maxValue) {
                    this.integerItem = this.minValue + (this.integerItem - this.maxValue) - 1;
                }
                if (updateValue) {
                    this.target.update(updateValue);
                    this.integerItem = updateValue;
                } else {
                    this.target.update(this.integerItem);
                }
            } else {
                this.target.update(this.items[this.selected]);
            }
            this.target.setStyle({
                top: '0px'
            });
        }
        this.mode = IntegerPopup.MODE_EDIT;
    },
    
    setMinMax: function(min, max){
        if (this.mode == IntegerPopup.MODE_PICK) {
            this.setEditMode(true);
        }
        this.minValue = min;
        this.maxValue = max;
        if (this.integerItem < this.minValue) {
            this.integerItem = this.minValue;
        }
        if (this.integerItem > this.maxValue) {
            this.integerItem = this.maxValue;
        }
    },
    
    setInteger: function(intValue){
        if (this.mode == IntegerPopup.MODE_PICK) {
            this.setEditMode(true);
            this.integerItem = intValue;
            this.target.update(this.integerItem);
            this.notifyChange(this.selected);
        } else {
            this.integerItem = intValue;
            this.target.update(this.integerItem);
            this.integerModeOffset = 0;
            this.selected = this.topStart;
            this.notifyChange(this.selected);
        }
    },
    
    setIndex: function(index, scroll){
        if (this.mode == IntegerPopup.MODE_EDIT) {
            this.selected = index;
            this.target.update(this.items[this.selected]);
            this.notifyChange(this.selected);
        } else if (this.mode == IntegerPopup.MODE_PICK) {
            this.setEditMode(true);
            this.selected = index;
            this.target.update(this.items[this.selected]);
            this.notifyChange(this.selected);
        } else {
            this.selected = index;
        }
    },
    
    keyPress: function(event){
        if (this.mode == IntegerPopup.MODE_NONE)
			return;
			
        if (event.charCode == 13) {
            this.setDisplayMode(true);
            if (this.navNextCallback) 
                this.navNextCallback();
            return;
        }
        
        var old = this.keyStr;
        this.keyStr += String.fromCharCode(event.charCode).capitalize();
        if (this.timer) 
            clearTimeout(this.timer);
        this.timer = setTimeout(this.clearKeyStr.bind(this), 1000);
        
        if (this.integerMode) {
            var intValue = parseInt(this.keyStr);
            if (isNaN(intValue)) {
                this.keyStr = old;
            } else {
                if (this.parseCallback) {
                    intValue = this.parseCallback(intValue);
                }
                if (intValue > this.maxValue) {
                    this.keyStr = old;
                } else {
                    if (intValue >= this.minValue) {
                        this.setInteger(intValue);
                    }
                }
            }
        } else {
            var index = 0;
            var lookup = this.items.find(function(item){
                if (item.startsWith(this.keyStr)) {
                    return true;
                }
                index++;
                return false;
            }, this);
            
            if (lookup) {
                this.setIndex(index, true);
            } else {
                this.keyStr = old;
            }
        }
    },
    
    clearKeyStr: function(){
        this.keyStr = '';
    },
    
    mouseDown: function(event){
        switch (this.mode) {
            case IntegerPopup.MODE_DISPLAY:
                this.setEditMode(true);
                break;
            case IntegerPopup.MODE_EDIT:
                this.tempOpen = true;
                this.setPickMode(true);
                break;
            case IntegerPopup.MODE_PICK:
                break;
        }
    },
    
    mouseUp: function(event){
        this.tempOpen = false;
    },
    
    click: function(event){
        var targetParent = this.controller.get(event.target.parentNode);
        if (targetParent && (targetParent.hasClassName('dp-value') || targetParent.hasClassName('tp-value') || targetParent.hasClassName('ip-value'))) {
            var index = Math.floor(event.target.previousSiblings().length / 2) % this.numItems;
            if ((this.dragEndIndex == -1) || (this.dragEndIndex != index)) {
                this.moveTo(index, true, this.setEditMode.bind(this, true, this.valueFromIndex(index)));
            }
            this.dragEndIndex = -1;
        }
		Event.stop(event);
    },
    
    dragStart: function(event){
        if (this.mode == IntegerPopup.MODE_NONE) 
            return;
        
        if (this.mode != IntegerPopup.MODE_PICK) {
            this.setPickMode(true);
            this.tempOpen = true;
        }
        
        this.fps = 45.0;
        this.flickRate = 1.0 / this.fps;
        this.flickThreshold = 300;
        this.handleFirstMove = true;
        this.prepareTarget(event.down);
        
        this.cleanupEvents();
        this.parent.stopObserving('click', this.clickHandler);
        
        this.draggedHandler = this.dragged.bindAsEventListener(this);
        this.target.observe(Mojo.Event.dragging, this.draggedHandler);
        this.parent.observe(Mojo.Event.dragging, this.draggedHandler);
        this.dragEndHandler = this.dragEnd.bindAsEventListener(this);
        this.target.observe(Mojo.Event.dragEnd, this.dragEndHandler);
        this.parent.observe(Mojo.Event.dragEnd, this.dragEndHandler);
        this.flickHandler = this.flick.bindAsEventListener(this);
        this.target.observe(Mojo.Event.flick, this.flickHandler);
        this.parent.observe(Mojo.Event.flick, this.flickHandler);
        this.valid = true;
        this.top = 0;
        this.snapping = false;
        this.HighlightOff(this.selected);
        
		Event.stop(event);
    },
    
    moveTo: function(index, scroll, callback){
        this.HighlightOff(this.selected);
        if (scroll) {
			this.snapCoast(-this.itemHeight * (index - this.exposeItems), 0, callback);
		} else {
			this.updateTop(-this.itemHeight * (index - this.exposeItems));
			
			this.selected = index;
			this.HighlightOn(this.selected);
			if (callback) 
				callback();
			this.notifyChange(this.selected);
		}
    },
    
    dragged: function(event){
        this.mouseMove(event.move);
		Event.stop(event);
    },
    
    prepareTarget: function(event){
        var parent = this.controller.get(this.target.parentNode);
        var parentPos = parent.cumulativeOffset();
        var pointerOffset = Event.pointerY(event);
        this.originalPointerOffset = pointerOffset;
        var elementPos = this.target.cumulativeOffset();
        this.yOffset = (pointerOffset - parentPos[1]) - (elementPos[1] - parentPos[1]);
        this.velocity = 0;
    },
    
    updateTop: function(top){
        if (this.integerMode) {
            this.top = top;
            while (this.top > 0) {
                this.top -= this.itemHeight;
                this.yOffset += this.itemHeight;
                this.updateOffset(-1);
            }
            while (this.top < (-this.itemHeight * (this.topStart - 1))) {
                this.top += this.itemHeight;
                this.yOffset -= this.itemHeight;
                this.updateOffset(1);
            }
        } else {
            this.top = top;
            if (this.top > 0) 
                this.top = -this.targetSize.height + this.top;
            else if (-this.top > this.targetSize.height) 
                this.top += this.targetSize.height;
        }
        this.target.setStyle({
            top: this.top + 'px'
        });
        return this.top;
    },
    
    updateOffset: function(delta){
        this.integerModeOffset += delta;
        var valueStart = this.integerItem + this.integerModeOffset - this.topStart;
        if (valueStart < this.minValue) {
            valueStart = this.maxValue + (valueStart - this.minValue) + 1;
        }
        var children = this.target.descendants();
        for (var i = 0; i < this.numItems; i++) {
            if ((valueStart + i) > this.maxValue) {
                valueStart = this.minValue - i;
            }
            children[i * 2 + 1].update(valueStart + i);
        }
        this.updateHighlight(-delta);
        
        if ((this.integerItem + this.integerModeOffset) < this.minValue) {
            this.integerModeOffset = this.maxValue - this.integerItem;
        }
        if ((this.integerItem + this.integerModeOffset) > this.maxValue) {
            this.integerModeOffset = this.minValue - this.integerItem;
        }
    },
    
    mouseMove: function(event){
        var pointerY = event.filteredPointer.y;
        if (this.lastPointerY != pointerY) {
            this.lastPointerY = pointerY;
            var parent = this.controller.get(this.target.parentNode);
            var parentPos = parent.cumulativeOffset();
            var newOffsetY = pointerY - parentPos[1] - this.yOffset;
            if (this.handleFirstMove) {
                this.handleFirstMove = false;
            }
            this.updateTop(newOffsetY);
            
            var nearestItem = this.getNearestItem();
            if ((this.lastDragHighlight >= 0) && (nearestItem != this.lastDragHighlight)) 
                this.HighlightOff(this.lastDragHighlight);
            this.HighlightOn(nearestItem);
            this.lastDragHighlight = nearestItem;
        }
    },
    
    dragEnd: function(event){
        this.dragEndIndex = -1;
        if (event.target) 
            this.dragEndIndex = Math.floor(event.target.previousSiblings().length / 2) % this.numItems;
        this.cleanupEvents();
        if (!this.flickExecutor) {
            this.snapToNearestItem();
        }
		Event.stop(event)
    },
    
    flick: function(event){
        if (this.lastDragHighlight >= 0) 
            this.HighlightOff(this.lastDragHighlight);
        
        this.flicking = true;
        this.cleanupEvents();
        this.velocity = event.velocity.y;
        this.flickExecutor = new MyPeriodicalExecuter(this.controller.window, this.coast.bind(this), this.flickRate);
 		Event.stop(event);
    },
    
    cleanupEvents: function(){
        this.target.stopObserving(Mojo.Event.dragging, this.draggedHandler);
        this.parent.stopObserving(Mojo.Event.dragging, this.draggedHandler);
        this.target.stopObserving(Mojo.Event.dragEnd, this.dragEndHandler);
        this.parent.stopObserving(Mojo.Event.dragEnd, this.dragEndHandler);
        this.target.stopObserving(Mojo.Event.flick, this.flickHandler);
        this.parent.stopObserving(Mojo.Event.flick, this.flickHandler);
    },
    
    stopCoasting: function(){
        if (this.flickExecutor) {
            this.flickExecutor.stop();
            delete this.flickExecutor;
        }
    },
    
    notifyChange: function(index){
        if (this.changeCallback) {
            this.changeCallback(this.valueFromIndex(index));
        }
    },
    
    valueFromIndex: function(index){
        if (this.integerMode) {
            var value = this.integerItem + this.integerModeOffset + index - this.topStart;
            if (value < this.minValue) {
                value = this.maxValue + (value - this.minValue) + 1;
            }
            if (value > this.maxValue) {
                value = this.minValue + (value - this.maxValue) - 1;
            }
            return value;
        } else {
            return index;
        }
    },
    
    updateSelected: function(pos){
        this.selected = -(pos / this.itemHeight % this.numItems);
        this.selected += this.exposeItems;
        this.selected = this.selected % this.numItems;
        this.HighlightOn(this.selected);
        this.notifyChange(this.selected);
    },
    
    HighlightOn: function(itemIndex){
        var items = this.target.descendants();
        if (itemIndex * 2 + 1 > items.length - 1) 
            return;
        if (!items[itemIndex * 2 + 1].hasClassName('ip-item-selected')) 
            items[itemIndex * 2 + 1].toggleClassName('ip-item-selected');
        if (!this.integerItem && !items[(itemIndex + this.numItems) * 2 + 1].hasClassName('ip-item-selected')) 
            items[(itemIndex + this.numItems) * 2 + 1].toggleClassName('ip-item-selected');
    },
    
    updateHighlight: function(delta){
        var items = this.target.descendants();
        var highlightIndex = null;
        for (var i = 0; i < items.length; i++) {
            if (items[i].hasClassName('ip-item-selected')) 
                highlightIndex = i;
        }
        if (highlightIndex) {
            items[highlightIndex].toggleClassName('ip-item-selected');
            if (((highlightIndex + delta * 2) > 0) &&
            ((highlightIndex + delta * 2) < items.length)) {
                items[highlightIndex + delta * 2].toggleClassName('ip-item-selected');
            }
        }
    },
    
    HighlightOff: function(itemIndex){
        var items = this.target.descendants();
        if (itemIndex * 2 + 1 > items.length - 1) 
            return;
        if (items[itemIndex * 2 + 1].hasClassName('ip-item-selected')) 
            items[itemIndex * 2 + 1].toggleClassName('ip-item-selected');
        if (!this.integerItem && items[(itemIndex + this.numItems) * 2 + 1].hasClassName('ip-item-selected')) 
            items[(itemIndex + this.numItems) * 2 + 1].toggleClassName('ip-item-selected');
    },
    
    snapToNextItem: function(velocity){
        if (velocity == 0) {
            this.snapToNearestItem();
        } else if (velocity < 0) {
            var overcast = this.top % this.itemHeight;
            this.snapCoast(this.top - overcast - this.itemHeight, velocity);
        } else {
            var overcast = this.top % this.itemHeight;
            if (this.top >= 0) 
                this.snapCoast(this.top - overcast + this.itemHeight, velocity);
            else 
                this.snapCoast(this.top - overcast, velocity);
        }
    },
    
    getIndexFromPos: function(pos){
        return (-(pos / this.itemHeight % this.numItems) + this.exposeItems) % this.numItems;
    },
    
    getNearestItem: function(){
        var overcast = this.top % this.itemHeight;
        if (overcast == 0) {
            return this.getIndexFromPos(this.top);
        }
        if (Math.abs(overcast) <= this.itemHeight / 2) {
            return this.getIndexFromPos(this.top - overcast);
        } else {
            if (overcast > 0) 
                return this.getIndexFromPos(this.top - overcast + this.itemHeight);
            else 
                return this.getIndexFromPos(this.top - overcast - this.itemHeight);
        }
    },
    
    snapToNearestItem: function(){
        var overcast = this.top % this.itemHeight;
        if (overcast == 0) 
            return;
			
        if (Math.abs(overcast) <= this.itemHeight / 2) {
            this.snapCoast(this.top - overcast);
        } else {
            if (overcast > 0) 
                this.snapCoast(this.top - overcast + this.itemHeight);
            else 
                this.snapCoast(this.top - overcast - this.itemHeight);
        }
    },
    
    closeTempOpen: function(){
        if (this.tempOpen) {
            this.setEditMode(true);
            this.tempOpen = false;
        }
    },
    
    snapCoast: function(newTop, velocity, callback){
        if (this.peSnap) {
            this.snapping = false;
            this.peSnap.stop();
        }
        
        this.updateSelected(newTop);
        if (velocity) {
            var startTop = this.top;
            var dx = newTop - this.top;
            var duration = dx / Math.abs(velocity); // in secs
            var halfVelocity = 0.5 * velocity;
            var dxScale = halfVelocity * duration / dx;
            var dt = 0;
            this.snapping = true;
            this.peSnap = new MyPeriodicalExecuter(this.controller.window, function(pe){
                dt += this.flickRate;
                if (velocity < 0) {
                    var top = startTop - (halfVelocity * dt / dxScale);
                    if (top <= newTop) {
                        this.endSnapCoast(newTop, callback);
                        pe.stop();
                    } else {
                        this.updateTop(top);
                    }
                } else {
                    var top = startTop + (halfVelocity * dt / dxScale);
                    if (top >= newTop) {
                        this.endSnapCoast(newTop, callback);
                        pe.stop();
                    } else {
                        this.updateTop(top);
                    }
                }
            }.bind(this), this.flickRate);
        } else {
            var distance = newTop - this.top;
            if (distance > this.itemHeight * this.exposeItems) 
                distance += -this.itemHeight * this.numItems;
            var delta = (distance > 0) ? 2 : -2;
            var iterations = distance / delta;
            if (iterations <= 1) {
                this.endSnapCoast(newTop, callback);
                return;
            }
            this.snapping = true;
            this.peSnap = new MyPeriodicalExecuter(this.controller.window, function(pe){
                if (iterations <= 1) {
                    this.endSnapCoast(newTop, callback);
                    pe.stop();
                } else {
                    this.updateTop(this.top + ((iterations > 20) ? delta * 2 : delta));
                }
                iterations--;
                if (iterations > 20) 
                    iterations--;
            }.bind(this), this.flickRate);
        }
    },
    
    endSnapCoast: function(newTop, callback){
        this.updateTop(newTop);
        this.updateTop(newTop);
        this.snapping = false;
        this.closeTempOpen();
        if (callback) 
            callback();
        else 
            this.closeTempOpen();
        
        this.flicking = false;
        if (this.pendingMode) {
            switch (this.pendingMode) {
                case IntegerPopup.MODE_NONE:
                    this.setNoneMode();
                    break;
                case IntegerPopup.MODE_DISPLAY:
                    this.setDisplayMode();
                    break;
                case IntegerPopup.MODE_EDIT:
                    this.setEditMode();
                    break;
                case IntegerPopup.MODE_PICK:
                    this.setPickMode();
                    break;
            }
            this.pendingMode = null;
        }
        this.parent.observe('click', this.clickHandler);
    },
    
    updateSpeedAndAcceleration: function(){
        var deltaTime = this.flickRate;
        //    var currentA = -12.0*this.velocity;
        var currentA = -3.0 * this.velocity;
        var distanceTravelled = this.velocity * deltaTime + 0.5 * (currentA * deltaTime * deltaTime);
        if (distanceTravelled < 0) {
            distanceTravelled = Math.ceil(distanceTravelled);
        } else {
            distanceTravelled = Math.floor(distanceTravelled);
        }
        var newTop = this.top + distanceTravelled;
        var done = false;
        if (distanceTravelled == 0) {
            done = true;
        }
        this.velocity = distanceTravelled / deltaTime;
        this.updateTop(newTop);
        if (done || (Math.abs(this.velocity) < 200)) {
            this.stopCoasting();
            this.snapToNextItem(this.velocity);
        }
    },
    
    coast: function(){
        if (this.snapping) {
            return;
        }
        this.updateSpeedAndAcceleration();
    },
    
    flickStop: function(){
        this.stopCoasting();
    }
    
});

IntegerPopup.MODE_NONE = 0;
IntegerPopup.MODE_DISPLAY = 1;
IntegerPopup.MODE_EDIT = 2;
IntegerPopup.MODE_PICK = 3;

var MyPeriodicalExecuter = Class.create({
  initialize: function(win, callback, frequency) {
    this.window = win;
	this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = this.window.setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  execute: function() {
    this.callback(this);
  },

  stop: function() {
    if (!this.timer) return;
    this.window.clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.execute();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
});

