//add element methods
Element.addMethods({

	scrollbar: function(element,para,onset){
		element = $(element);
		Df.Namespace.create('df',element).scrollbar = new scrollbar(element).set(para);
		return element;
	}
});

/*
 ref:		Df.Scrollbar
 type:		Class
 returns:	Df.Scrollbar
 event:		this.element scrollX {instance: this}
 event:		this.element scrollY {instance: this}
*/
scrollbar = Class.create(Df.Ui, {
	initialize: function($super, element){
		$super(element)

		this.setPars({
			scroll: {},
			classNames: {
				y: {
					holder: 'yHolder',
					prev: 'ArrowUp',
					next: 'ArrowDown',
					area: 'SlideArea',
					marker: 'marker'
				},
				x: {
					holder: 'xHolder',
					prev: 'ArrowLeft',
					next: 'ArrowRight',
					area: 'SlideArea',
					marker: 'marker'
				}
			},
			clickPercent:10,
			clickPixel:false,
			clickPause: 500,
			clickRepeat: 25,
			holder: document.body,
			positionY: true,
			positionX: true
		});

		/** @private */
		this.yDrag;
		/** @private */
		this.xDrag;
		/** @private */
		this.yClickMove;
		/** @private */
		this.xClickMove;
		/** @private */
		this.maxY;
		/** @private */
		this.maxX;
		/** @private */
		this.yClickStatusNext = false;
		/** @private */
		this.yClickStatusPrev = false;
		/** @private */
		this.xClickStatusNext = false;
		/** @private */
		this.xClickStatusPrev = false;
		/** @private */
		this.eles = {
			y: {},
			x: {}
		}
		/** @private */
		this.dems = {
			holder:{},
			content:{},
			offset:{},
			x: {
				holder: {},
				prev: {},
				next: {},
				area: {},
				marker: {}
			},
			y: {
				holder: {},
				prev: {},
				next: {},
				area: {},
				marker: {}
			}
		}
		/** @private */
		this.yDragging = false;
		/** @private */
		this.xDragging = false;

		this._wheelControl = this.wheelControl.bind(this)

		return this
	},

	set: function($super, pars){
		$super(pars)

		this.element.scrollTop = 0;
		this.element.scrollLeft = 0

		this.adjustToContent()

		this.getElement().observe(':resize', this.adjustToContent.bind(this))

		this.getElement().observe(':reposition', function(e){
			this.setPositions()
			if(this.eles.y.holder)
				this.positionBarY()

			this.eles.x.holder
				this.positionBarX()

		}.bind(this));

		Event.observe(document.body, 'mouseup', function(e){
				Event.stop(e)
				this.yClickStatusNext = false;
				this.xClickStatusNext = false;
				this.yClickStatusPrev = false;
				this.xClickStatusPrev = false;
		}.bind(this));

		return this
	},

	/*
	 ref:		Df.Scrollbar.getElements
	 type:		Method
	 returns:	{
				y: {
					holder,
					prev,
					area,
					marker,
					next
				},
				x: {
					holder,
					prev,
					area,
					marker,
					next
				}
			}
	*/
	getElements: function(){
		return this.eles;
	},

	adjustToContent: function(e){
		this.setDems();
		this.setPositions();

		this.element.stopObserving(':mousewheel', this._wheelControl);

		if (this.dems.content.height > this.dems.holder.height) {
			this.buildYBar();
			this.element.observe(':mousewheel', this._wheelControl);
			this.sizeDemsY();
			this.setIterationY();
			try{
				this.scrollY(parseInt(this.eles.y.marker.getStyle('top')))
			}catch(e){

			}

		}
		else if(this.eles.y.holder) {
			this.eles.y.holder.remove()
			this.eles.y = {}
		}

		if (this.dems.content.width > this.dems.holder.width) {
			this.buildXBar();
			this.sizeDemsX();
			this.setIterationX();
			try{
				this.scrollX(parseInt(this.eles.x.marker.getStyle('left')))
			}catch(e){

			}
		}
		else if(this.eles.x.holder) {
			this.eles.x.holder.remove()
			this.eles.x = {}
		}
	},

	setIterationY: function(){

		if (this.pars.clickPixel) {
			this.yClickMove = Math.ceil((this.pars.clickPixel/this.dems.content.height) * this.dems.y.area.height);
		}

		else if(this.pars.clickPercent){
			this.yClickMove = Math.ceil((this.pars.clickPercent/100) * this.dems.y.area.height);
		}
	},

	setIterationX: function(){

		if (this.pars.clickPixel) {
			this.xClickMove = Math.ceil((this.pars.clickPixel/this.dems.content.width) * this.dems.x.area.width);
		}

		else if(this.pars.clickPercent){
			this.xClickMove = Math.ceil((this.pars.clickPercent/100) * this.dems.x.area.width);
		}
	},

	scrollY: function(y){
		if (y > this.maxY) {
			y = this.maxY;
		}
		else if (y < 0) {
			y = 0;
		}

		this.eles.y.marker.style.top = y + 'px';
		this.moveContentY();

		return this
	},

	scrollX: function (x) {
		if (x < 0) {
			x = 0;
		}
		if (x > this.maxX) {
			x = this.maxX;
		}
		this.eles.x.marker.style.left = x + 'px'
		this.moveContentX();
	},

	scrollYNext: function(){
		var y = parseInt(this.eles.y.marker.getStyle('top')) + this.yClickMove;
		this.scrollY(y);
		return this
	},

	scrollYPrev: function(){
		var y = parseInt(this.eles.y.marker.getStyle('top')) - this.yClickMove;
		this.scrollY(y);
		return this
	},

	scrollXNext: function(){
		var x = parseInt(this.eles.x.marker.getStyle('left')) + this.xClickMove;
		this.scrollX(x);
		return this
	},

	scrollXPrev: function(){
		var x = parseInt(this.eles.x.marker.getStyle('left')) - this.xClickMove;
		this.scrollX(x);
		return this
	},

	scrollYBy: function(y){
		y = parseInt(this.eles.y.marker.getStyle('top')) - y;
		this.scrollY(y);
		return this
	},

	scrollXBy: function(x){
		x = parseInt(this.eles.x.marker.getStyle('left')) + x;
		this.scrollX(x);
		return this
	},

	setDems: function(){
		this.dems.holder = this.element.getDimensions();
		this.dems.content = {height: this.element.scrollHeight, width: this.element.scrollWidth};
		return this
	},

	setPositions: function(){
		this.dems.offset = {x:parseInt(Position.cumulativeOffset(this.element)[0]), y:parseInt(Position.cumulativeOffset(this.element)[1])};
		return this
	},

	sizeDemsY: function(){
		this.dems.y.holder = {width: parseInt(this.eles.y.holder.getStyle('width')), height: this.dems.holder.height};
		this.dems.y.prev = {width: parseInt(this.eles.y.prev.getStyle('width')), height: parseInt(this.eles.y.prev.getStyle('height'))};
		this.dems.y.next = {width: parseInt(this.eles.y.next.getStyle('width')), height: parseInt(this.eles.y.next.getStyle('height'))};
		this.dems.y.area.width = parseInt(this.eles.y.area.getStyle('width'));
		this.dems.y.area.height = this.dems.holder.height - this.dems.y.prev.height - this.dems.y.next.height;
		this.dems.y.marker.width = parseInt(this.eles.y.marker.getStyle('width'));
		this.dems.y.marker.height = Math.ceil((this.dems.holder.height/this.dems.content.height) * this.dems.y.area.height);

		//size elements
		this.eles.y.holder.style.height = this.dems.y.holder.height + 'px';
		this.eles.y.area.style.height = this.dems.y.area.height + 'px';
		this.eles.y.marker.style.height = this.dems.y.marker.height + 'px';

		//position elements
		if(this.pars.positionY){
			this.positionBarY()
		}

		this.maxY = this.dems.y.area.height - this.dems.y.marker.height;

		this.yDrag.pars.dirY.max = this.maxY
	},

	positionBarY: function(){
		this.eles.y.holder.style.left = this.barYLeft() + 'px';
		this.eles.y.holder.style.top = this.barYTop() + 'px';
	},

	barYLeft: function(){
		return this.dems.offset.x + this.dems.holder.width
	},

	barYTop: function(){
		return this.dems.offset.y
	},

	buildYBar: function(){
		if(!this.eles.y.holder){
			//create elements
			this.buildBarHTML('y');

			//apply actions to marker
			var para = {
				dirX: false,
				onStart: function(){
					this.yDragging = true;
				}.bind(this),
				onDrag: this.moveContentY.bind(this),
				onStop: function(){
					this.yDragging = false;
				}.bind(this),
				dirY: {
					min:0
				}
			}
			this.yDrag = new Df.Drag(this.eles.y.marker).set(para).enable();

			this.eles.y.holder.observe('click', function(e){
				Event.stop(e)
			});

			//apply actions to Buttons
			Event.observe(this.eles.y.next, 'mousedown', this.yNext.bind(this));
			Event.observe(this.eles.y.prev, 'mousedown', this.yPrev.bind(this));

			//apply actions to scroll area
			Event.observe(this.eles.y.area, 'mousedown', this.yMakeScrollDecision.bind(this));
		}
		return this
	},

	sizeDemsX: function(){
		//set dem vars
		this.dems.x.holder = {height: parseInt(this.eles.x.holder.getStyle('height')), width: this.dems.holder.width};
		this.dems.x.prev = {width: parseInt(this.eles.x.prev.getStyle('width')), height: parseInt(this.eles.x.prev.getStyle('height'))};
		this.dems.x.next = {width: parseInt(this.eles.x.next.getStyle('width')), height: parseInt(this.eles.x.next.getStyle('height'))};
		this.dems.x.area.height = parseInt(this.eles.x.area.getStyle('height'));
		this.dems.x.area.width = this.dems.holder.width - this.dems.x.prev.width - this.dems.x.next.width;
		this.dems.x.marker.height = parseInt(this.eles.x.marker.getStyle('height'));
		this.dems.x.marker.width = Math.ceil((this.dems.holder.width/this.dems.content.width) * this.dems.x.area.width);

		//size elements
		this.eles.x.holder.style.width = this.dems.x.holder.width + 'px';
		this.eles.x.area.style.width = this.dems.x.area.width + 'px';
		this.eles.x.marker.style.width = this.dems.x.marker.width + 'px';

		//position elements
		if(this.pars.positionX){
			this.positionBarX()
		}

		this.maxX = this.dems.x.area.width - this.dems.x.marker.width;

		this.xDrag.pars.dirX.max = this.maxX

	},

	positionBarX: function(){
		this.eles.x.holder.style.top = this.barXLeft() + 'px';
		this.eles.x.holder.style.left = this.barXTop() + 'px';
	},

	barXLeft: function(){
		return this.dems.offset.y + this.dems.holder.height
	},

	barXTop: function(){
		return this.dems.offset.x
	},

	buildXBar: function(){
		if(!this.eles.x.holder){

			//create elements
			this.buildBarHTML('x');

			//apply actions to marker
			var para = {
				dirY: false,
				onStart: function(){
					this.xDragging = true;
				}.bind(this),
				onDrag: this.moveContentX.bind(this),
				onStop: function(){
					this.xDragging = false;
				}.bind(this),
				dirX: {
					min:0
				}
			}

			this.xDrag = new Df.Drag(this.eles.x.marker).set(para).enable();

			this.eles.x.holder.observe('click', function(e){
				Event.stop(e)
			});

			//apply actions to Buttons
			Event.observe(this.eles.x.next, 'mousedown', this.xNext.bind(this));
			Event.observe(this.eles.x.prev, 'mousedown', this.xPrev.bind(this));

			//apply actions to scroll area
			Event.observe(this.eles.x.area, 'mousedown', this.xMakeScrollDecision.bind(this));
		}
		return this
	},

	wheelControl: function(e){
		if(e.memo.delta > 0){
			this.scrollYPrev();
		}
		else if(e.memo.delta < 0){
			this.scrollYNext();
		}

		return this
	},

	xMakeScrollDecision: function(event){
		if(!this.xDragging){
			this.xTo(event);
		}
	},

	yMakeScrollDecision: function(event){
		if(!this.yDragging){
			this.yTo(event);
		}
	},

	yNext: function(event){
		this.yClickStatusNext = true
		this.yNextClick(this.pars.clickPause);
	},

	/** @private */
	yPrev: function(event){
		this.yClickStatusPrev = true
		this.yPrevClick(this.pars.clickPause);
	},

	/** @private */
	xNext: function(event){
		this.xClickStatusNext = true
		this.xNextClick(this.pars.clickPause);
	},

	/** @private */
	xPrev: function(event){
		this.xClickStatusPrev = true
		this.xPrevClick(this.pars.clickPause);
	},

	/** @private */
	yPrevClick: function(i){
		if(this.yClickStatusPrev){
			this.scrollYPrev();
			setTimeout(function(){
				this.yPrevClick(this.pars.clickRepeat);
			}.bind(this), i);
		}
	},

	/** @private */
	yNextClick: function(i){
		if(this.yClickStatusNext){
			this.scrollYNext();
			setTimeout(function(){
				this.yNextClick(this.pars.clickRepeat)
			}.bind(this), i);
		}
	},

	/** @private */
	xPrevClick: function(i){
		if(this.xClickStatusPrev){
			this.scrollXPrev();
			setTimeout(function(){
				this.xPrevClick(this.pars.clickRepeat);
			}.bind(this), i);
		}
	},

	/** @private */
	xNextClick: function(i){
		if(this.xClickStatusNext){
			this.scrollXNext();
			setTimeout(function(){
				this.xNextClick(this.pars.clickRepeat);
			}.bind(this), i);
		}
	},

	/** @private */
	yTo: function(event){
		Event.stop(event);
		var top = (Event.pointerY(event) - this.dems.offset.y - this.dems.y.prev.height) - parseInt((this.dems.y.marker.height/2));
		if(top<0){
			top = 0;
		}else if(top > this.maxY){
			top = this.maxY;
		}
		this.eles.y.marker.style.top = top + 'px';
		this.moveContentY();
	},

	/** @private */
	xTo: function(event){
		Event.stop(event);
		var left = (Event.pointerX(event) - this.dems.offset.x - this.dems.x.prev.width) - parseInt((this.dems.x.marker.width/2));
		if(left<0){
			left = 0;
		}else if(left > this.maxX){
			left = this.maxX;
		}
		this.eles.x.marker.style.left = left + 'px';
		this.moveContentX();
	},

	/** @private */
	moveContentY: function(){
		var percent = parseInt(this.eles.y.marker.getStyle('top')) / (this.dems.y.area.height - this.dems.y.marker.height);
		this.element.scrollTop =  parseInt((this.dems.content.height - this.dems.holder.height) * percent);

		this.element.fire(':scrollY', {instance: this})

		return this;
	},

	/** @private */
	moveContentX: function(){
		var percent = parseInt(this.eles.x.marker.getStyle('left')) / (this.dems.x.area.width - this.dems.x.marker.width);
		this.element.scrollLeft =  parseInt((this.dems.content.width - this.dems.holder.width) * percent);

		this.element.fire(':scrollX', {instance: this})

		return this
	},

	/** @private */
	buildBarHTML: function(d){
		this.eles[d].holder = Df.e('div', this.pars.holder,{ className:this.pars.classNames[d].holder } );
		this.eles[d].prev = Df.e('div', this.eles[d].holder,{ className:this.pars.classNames[d].prev } );
		this.eles[d].area = Df.e('div', this.eles[d].holder,{ className:this.pars.classNames[d].area } );
		this.eles[d].marker = Df.e('div', this.eles[d].area,{ className:this.pars.classNames[d].marker } );
		this.eles[d].next = Df.e('div', this.eles[d].holder,{ className:this.pars.classNames[d].next } );
	}
});

/*
 ref:		Df.PagingScrollbar
 type:		Class
 extends:       Df.Scrollbar
 returns:	Df.PagingScrollbar
 event:		this.element page {rowsInView: [], colsInView: []}
*/
Df.PagingScrollbar = Class.create(Df.Scrollbar, {
    initialize: function($super, element){
        $super(element)

        this.setPars({
		rowsOverlap: 2,
		colsOverlap: 2,
		delay: 100,
		rowHeight: 100,
		colWidth: false,
		rows: 50,
		cols: 1,
		useCache: true,
		resetScrollTop: false,
		onCreateCell: function(node, row, col){

			if((row%2 && col%2) || (row%2 == 0 && col%2 == 0)){
				node.setStyle({backgroundColor:'#cccccc'})
			}

			node.update(row + ' _ ' + col)
		}
        })

	this.recHolder

        this.cellsToShow = []
	this.cellCache = {}

        this.rowsInView
	this.colsInView

	this.cellHeight
	this.cellWidth

        return this
    },

    set: function($super, pars){

        this.setPars(pars)

        this.recHolder = Df.e('div', this.element).setStyle({position:'relative', top:0}).update('&nbsp;')

	this.element.observe(':scrollY', this.scrollAction.bind(this))
	this.element.observe(':scrollX', this.scrollAction.bind(this))

	$super()

        return this
    },

    clearCells: function(){
	loopie:
	for(p in this.cellCache){
		this.recHolder.childElements().each(function(n){
			n.remove()
		})
		this.cellCache = {}
		this.cellsToShow = []
		break loopie
	}
    },

    adjustToContent: function($super, e){
	this.clearCells()

	if(this.pars.rowHeight){
		this.cellHeight = this.pars.rowHeight
		this.recHolder.setStyle({height: (this.pars.rows * this.cellHeight ) + 'px'})
        } else {
		this.cellHeight = parseInt(this.element.getStyle('height'))
		this.recHolder.setStyle({height: this.cellHeight + 'px'})
        }
	this.rowsInView = parseInt(this.element.getStyle('height')) / this.pars.rowHeight

        if(this.pars.colWidth){
		this.cellWidth = this.pars.colWidth
		this.recHolder.setStyle({width: (this.pars.cols * this.cellWidth ) + 'px'})
        } else {
		this.cellWidth = parseInt(this.element.getStyle('width'))
		this.recHolder.setStyle({width: this.cellWidth + 'px'})
        }
	this.colsInView = parseInt(this.element.getStyle('width')) / this.pars.colWidth

	$super(e)

	if(this.pars.resetScrollTop){
		this.element.scrollTop = 0
		if(this.eles.y.marker){
			this.eles.y.marker.style.top = "0px"
		}
		this.changePageLogic.bind(this, this.element.scrollTop, this.element.scrollLeft)
	}

	this.element.fire(':scrollY', {instance: this})

	return this
    },

    scrollAction: function(e){
        var t = setTimeout(
            this.changePageLogic.bind(this, this.element.scrollTop, this.element.scrollLeft),
            this.pars.delay
        )
    },

    changePageLogic: function(oldScrollTop, oldScrollLeft){
        if(this.element.scrollTop == oldScrollTop && this.element.scrollLeft == oldScrollLeft){

		var rowTop = parseInt(oldScrollTop / this.pars.rowHeight) + 1
		var rowLeft = parseInt(oldScrollLeft / this.pars.colWidth) + 1

		var rows = $A($R(
			(rowTop - this.pars.rowsOverlap).toRange(1, this.pars.rows),
			(rowTop - 1 + this.rowsInView + this.pars.rowsOverlap).toRange(1, this.pars.rows)
		))

		var cols = $A($R(
			(rowLeft - this.pars.colsOverlap).toRange(1, this.pars.cols),
			(rowLeft - 1 + this.colsInView + this.pars.colsOverlap).toRange(1, this.pars.cols)
		))

		var newCellsToShow = []

		for(var i=0; i<rows.length; i++){
			for(var j=0; j<cols.length; j++){
				newCellsToShow.push([rows[i],cols[j]])
			}
		}

		if(Object.toJSON(this.cellsToShow) != Object.toJSON(newCellsToShow)){

			if(this.pars.useCache){
				var dropCells = []
				for(var i=0; i<this.cellsToShow.length; i++){
					if(!newCellsToShow.any(function(a){return Object.toJSON(a) == Object.toJSON(this)}.bind(this.cellsToShow[i])))
						dropCells.push(this.cellsToShow[i])
				}
				if(dropCells.length > 0){
					this.dropCells(dropCells)
				}
			}

			var addCells = []
			for(var i=0; i<newCellsToShow.length; i++){
				if(!this.cellsToShow.include(function(a){return Object.toJSON(a) == Object.toJSON(this)}.bind(newCellsToShow[i])))
					if(this.pars.useCache || (!this.pars.useCache && !(this.cellCache[(newCellsToShow[i][0] + '_' + newCellsToShow[i][1])]))){
						addCells.push(newCellsToShow[i])
					}
			}
			if(addCells.length > 0){
				this.addCells(addCells)
			}

			this.cellsToShow = newCellsToShow
			this.element.fire(':page', {cellsInView: this.newCellsToShow})
		}
	}
    },

    dropCells: function(dropCells){
	dropCells.each(function(v){
		this.cellCache[(v[0]+'_'+v[1])].remove()
	}.bind(this))

	this.element.fire(':dropCells', {cells: dropCells})
    },

    addCells: function(addCells){
	var newCells = [];

	addCells.each(function(v){

		if(this.cellCache[(v[0]+'_'+v[1])]){
			this.recHolder.insert(this.cellCache[(v[0]+'_'+v[1])])
		} else {

			this.cellCache[(v[0]+'_'+v[1])] = Df.e('div', this.recHolder).setStyle({
				position: 'absolute',
				left: ((v[1]-1) * this.cellWidth) + 'px',
				top: ((v[0]-1) * this.cellHeight) + 'px',
				height: this.cellHeight + 'px',
				width: this.cellWidth + 'px'
			})

			if(this.pars.onCreateCell){
				this.pars.onCreateCell(this.cellCache[(v[0]+'_'+v[1])], v[0], v[1])
			}

			newCells.push({cell: this.cellCache[(v[0]+'_'+v[1])], row: v[0], col: v[1]})

			this.element.fire(':createCell', {cell: this.cellCache[(v[0]+'_'+v[1])], row: v[0], col: v[1]})
		}

	}.bind(this))

	this.element.fire(':addCells', {cells: addCells})
	if (newCells.length >0){
		this.element.fire(':createCells', {cells: newCells})
	}

    }
});