$.currentVertScrollerID = 0

$.fn.vertScroller = function (settings) {
	var internal_vert_scroller_var = this

	this.resize = function () {
		var new_height = 0

		if (this.extras.length > 0) {
			var max_height = 0
			this.extras.each(function () {
				var height = $(this).outerHeight(true)
				if (height > max_height) max_height = height
			})

			var i = 0
			while (new_height < max_height && i < this.elements.length) {
				new_height += this.elements.eq(i++).outerHeight(true)
			}
			this.numPerPage = i

			this.view.height(new_height)
			this.extras.parent().height(this.view.parent().height())
		} else {
			var available_height = this.view.parent().height()
			var view = this.view[0]
			this.view.parent().children().each(function () {
				if (this != view) available_height -= $(this).outerHeight(true)
			})

			var new_height = 0
			var i = this.index
			do {
				new_height += this.elements.eq(i++).outerHeight(true)
			} while (i < this.elements.length && available_height >= new_height + this.elements.eq(i).outerHeight(true))

			this.numPerPage = i - this.index

			// if the scroller has been enlarged and we are at the end of the list, there is a chance that we could fit extra entries into the view
			if (i == this.elements.length) {
				i = this.index - 1
				while (i >= 0 && available_width >= new_height + this.elements.eq(i).outerHeight(true))
					new_height += this.elements.eq(i++).outerHeight(true)
			}

			this.view.height(new_height)

			if (++i < this.index) this.scrollTo(i, 0)
		}

		this.update()
	}

	this.lastIndex = function () {
		var last_index = 0
		var total_height = this.container.height() - this.view.height()
		if (total_height > 0) {
			this.elements.each(function () {
				last_index++
				if ((total_height -= $(this).outerHeight(true)) <= 0) return false
			})
		}
		return last_index
	}

	this.update = function () {
		if (this.index == 0) {
			this.prevButton
				.attr('src', this.settings.prevDisabledImage)
				.css('cursor', 'default')
				.addClass('disabled')
				.unbind()
		} else {
			this.prevButton
				.attr('src', this.settings.prevImage)
				.css('cursor', 'pointer')
				.removeClass('disabled')
				.unbind().click(function () { internal_vert_scroller_var.scrollPrev() })
		}
		if (this.index == this.lastIndex()) {
			this.nextButton
				.attr('src', this.settings.nextDisabledImage)
				.css('cursor', 'default')
				.addClass('disabled')
				.unbind()
		} else {
			this.nextButton
				.attr('src', this.settings.nextImage)
				.css('cursor', 'pointer')
				.removeClass('disabled')
				.unbind().click(function () { internal_vert_scroller_var.scrollNext() })
		}

		if (typeof(this.active) == 'undefined' || this.active < this.index)
			this.setActive(this.index, true)
		else if (this.active >= this.index + (this.elements.length - this.lastIndex()))
			this.setActive(this.index + this.elements.length - this.lastIndex() - 1, true)
	}

	this.setActive = function (index, keepTimer) {
		this.elements.removeClass('active')
		this.extras.removeClass('active').detach().css('display', 'none') // we both detach() and hide() the extras to work around an ie7 bug
		this.bullets.removeClass('active').css('background-position', this.settings.bulletsPos.normal)

		this.active = index
		this.elements.eq(index).addClass('active')
		this.extras_parent.append(this.extras.eq(index).addClass('active').css('display', 'block')) // ditto. we append() and show() the extras to work around an ie7 bug
		this.bullets.eq(index).addClass('active').css('background-position', this.settings.bulletsPos.active)

		if (index < this.index || index >= this.index + this.numPerPage) this.scrollTo(index)
		if (!keepTimer) clearInterval(this.timer)
	}

	this.scrollTo = function (index, speed) {
		this.index = this.lastIndex()
		if (index < this.index) this.index = index
		this.view.animate({ scrollTop: this.elements.eq(index).position().top }, typeof(speed) == 'undefined' ? this.settings.scrollDuration : speed)
		this.update()
	}

	this.scrollNext = function () {
		if (this.index < this.lastIndex()) {
			this.view.animate({ scrollTop: this.elements.eq(++this.index).position().top }, this.settings.scrollDuration)
			this.update()
		}
		return this.index
	}

	this.scrollPrev = function () {
		if (this.index > 0) {
			this.view.animate({ scrollTop: this.elements.eq(--this.index).position().top }, this.settings.scrollDuration)
			this.update()
		}
		return this.index
	}

	this.autoScroll = function () {
		var index = this.active + 1
		if (index >= this.elements.length) index = 0
		this.setActive(index, true)
	}

	function floor(value) {
		return typeof(value) == 'undefined' ? '0' : value.replace(/(\d+)\.\d+/, '$1')
	}

	// if any of the elements consist entirely of block elements, the first block's top margin and the last block's
	// bottom margin aren't included in the parent's height. this is retarded and we need to work around it
	function adjustBlockMargins(element) {
		element.each(function () {
			var children = $(this).children()
			children.each(function () { adjustBlockMargins($(this)) })
			if (children.map(function () { return $(this).css('display') == 'block' || $(this).css('display') == 'inline-block' ? this : null }).length == children.length) {
				$(this).animate({ paddingTop: '+=' + floor(children.first().css('margin-top')) }, 0)
				children.first().css('margin-top', '0')
				$(this).animate({ paddingBottom: '+=' + floor(children.last().css('margin-bottom')) }, 0)
				children.last().css('margin-bottom', '0')
			}
		})
	}		

	if (typeof(root) == 'undefined') root = ''

	var defaults = {
		elements: '.elements',
		extras: '.extras',
		prevImage: root + 'assets/up.jpg',
		prevDisabledImage: root + 'assets/disable-up.jpg',
		nextImage: root + 'assets/down.jpg',
		nextDisabledImage: root + 'assets/disable-down.jpg',
		bulletImage: root + 'assets/vert-scroller-bullets.png',
		bulletsPos: { normal: '4px 0', active: '-8px 0' },
		scrollDuration: 0,
		scrollInterval: 5000,
		autoScroll: false
	}
	this.settings = $.extend(defaults, settings)

	var domID = 'vertScroller' + $.currentVertScrollerID++
	window[domID] = this
	this.domID = domID = 'window.' + domID

	this.view = this.children(this.settings.elements)
	this.view.children().wrapAll('<div><div>')
	this.view = this.view.children()
	this.view.css('overflow', 'hidden')
	this.view.css('position', 'relative')
	this.container = this.view.children()
	this.container.css('position', 'relative')

	this.elements = this.container.children()
	adjustBlockMargins(this.container)
	this.view.css('padding-top', this.container.css('padding-top'))
	this.view.css('padding-bottom', this.container.css('padding-bottom'))
	this.container.css('padding-top', '0')
	this.container.css('padding-bottom', '0')
	this.elements.each(function (index) { $(this).wrap('<a href="javascript: ' + domID + '.setActive(' + index + ')"></a>') })

	this.extras_parent = this.children(this.settings.extras)
	this.extras = this.extras_parent.children()

	this.view.before('<img class="button" />')
	this.prevButton = this.view.prev()
	this.prevButton.addClass(this.settings.prevClass).attr('src', this.settings.prevImage).attr('alt', 'Scroll to prev')
	this.view.after('<img class="button" />')
	this.nextButton = this.view.next()
	this.nextButton.addClass(this.settings.nextClass).attr('src', this.settings.nextImage).attr('alt', 'Scroll to next')

	if (this.settings.bulletImage) {
		this.bullets = this.children('ul.bullets')
		if (this.bullets.length == 0) this.bullets = $('<ul class="bullets"></ul>').prependTo(this)
		this.bullets.css('list-style', 'none').css('padding', '0')
		this.elements.each(function (index) {
			$('<li>&nbsp;</li>').appendTo(internal_vert_scroller_var.bullets)
				.click(function () { internal_vert_scroller_var.setActive(index) })
				.css('background', 'url(' + internal_vert_scroller_var.settings.bulletImage + ') no-repeat ' + internal_vert_scroller_var.settings.bulletsPos.normal)
		})
		this.bullets.css('margin-top', function() {
			var height = $(this).height()
			return height / 2 * -1
		})
		this.bullets = this.bullets.children()
	}

	var max_width = 0
	this.elements.each(function () { max_width = Math.max(max_width, $(this).width()) })
	this.container.height('auto').width(max_width)

	this.scrollTo(0)
	this.setActive(0)

	if (this.settings.autoScroll) this.timer = setInterval(this.domID + '.autoScroll()', this.settings.scrollInterval)

	$(this.prevButton).one('load', function () { internal_vert_scroller_var.resize() })
	$(this.nextButton).one('load', function () { internal_vert_scroller_var.resize() })
	$(window).bind('resize', function () { internal_vert_scroller_var.resize() })
	
	return this
}
