define [
	"moment"
	"ide/colors/ColorManager"
	"ide/history/util/displayNameForUser"
	"ide/history/util/HistoryViewModes"
	"ide/history/controllers/HistoryV2ListController"
	"ide/history/controllers/HistoryV2DiffController"
	"ide/history/controllers/HistoryV2FileTreeController"
	"ide/history/directives/infiniteScroll"
	"ide/history/components/historyEntriesList"
	"ide/history/components/historyEntry"
	"ide/history/components/historyFileTree"
	"ide/history/components/historyFileEntity"
], (moment, ColorManager, displayNameForUser, HistoryViewModes) ->
	class HistoryManager
		constructor: (@ide, @$scope) ->
			@reset()
			@$scope.HistoryViewModes = HistoryViewModes

			@$scope.toggleHistory = () =>
				if @$scope.ui.view == "history"
					@hide()
				else
					@show()

			@$scope.toggleHistoryViewMode = () =>
				if @$scope.history.viewMode == HistoryViewModes.COMPARE
					@reset()
					@$scope.history.viewMode = HistoryViewModes.POINT_IN_TIME
				else
					@reset()
					@$scope.history.viewMode = HistoryViewModes.COMPARE

			@$scope.$watch "history.selection.updates", (updates) =>
				if @$scope.history.viewMode == HistoryViewModes.COMPARE
					if updates? and updates.length > 0
						@_selectDocFromUpdates()
						@reloadDiff()
			
			@$scope.$watch "history.selection.pathname", (pathname) =>
				if @$scope.history.viewMode == HistoryViewModes.POINT_IN_TIME
					if pathname?
						@loadFileAtPointInTime()
				else 
					@reloadDiff()

		show: () ->
			@$scope.ui.view = "history"
			@reset()
			@$scope.history.viewMode = HistoryViewModes.POINT_IN_TIME

		hide: () ->
			@$scope.ui.view = "editor"

		reset: () ->
			@$scope.history = {
				isV2: true
				updates: []
				viewMode: null
				nextBeforeTimestamp: null
				atEnd: false
				selection: {
					updates: []
					docs: {}
					pathname: null
					range: {
						fromV: null
						toV: null
					}
				}
				files: []
				diff: null # When history.viewMode == HistoryViewModes.COMPARE
				selectedFile: null # When history.viewMode == HistoryViewModes.POINT_IN_TIME
			}

		restoreFile: (version, pathname) ->
			url = "/project/#{@$scope.project_id}/restore_file"

			@ide.$http.post(url, {
				version, pathname,
				_csrf: window.csrfToken
			})

		loadFileTreeForUpdate: (update) ->
			{fromV, toV} = update
			url = "/project/#{@$scope.project_id}/filetree/diff"
			query = [ "from=#{toV}", "to=#{toV}" ]
			url += "?" + query.join("&")
			@$scope.history.loadingFileTree = true
			@$scope.history.selectedFile = null
			@$scope.history.selection.pathname = null
			@ide.$http
				.get(url)
				.then (response) =>
					@$scope.history.files = response.data.diff
					@$scope.history.loadingFileTree = false

		MAX_RECENT_UPDATES_TO_SELECT: 5
		autoSelectRecentUpdates: () ->
			return if @$scope.history.updates.length == 0

			@$scope.history.updates[0].selectedTo = true

			indexOfLastUpdateNotByMe = 0
			for update, i in @$scope.history.updates
				if @_updateContainsUserId(update, @$scope.user.id) or i > @MAX_RECENT_UPDATES_TO_SELECT
					break
				indexOfLastUpdateNotByMe = i

			@$scope.history.updates[indexOfLastUpdateNotByMe].selectedFrom = true

		autoSelectLastUpdate: () ->
			return if @$scope.history.updates.length == 0
			@selectUpdate @$scope.history.updates[0]

		selectUpdate: (update) ->
			selectedUpdateIndex = @$scope.history.updates.indexOf update
			if selectedUpdateIndex == -1
				selectedUpdateIndex = 0
			for update in @$scope.history.updates
				update.selectedTo = false
				update.selectedFrom = false
			@$scope.history.updates[selectedUpdateIndex].selectedTo = true
			@$scope.history.updates[selectedUpdateIndex].selectedFrom = true
			@loadFileTreeForUpdate @$scope.history.updates[selectedUpdateIndex]

		BATCH_SIZE: 10
		fetchNextBatchOfUpdates: () ->
			url = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}"
			if @$scope.history.nextBeforeTimestamp?
				url += "&before=#{@$scope.history.nextBeforeTimestamp}"
			@$scope.history.loading = true
			@$scope.history.loadingFileTree = true
			@ide.$http
				.get(url)
				.then (response) =>
					{ data } = response
					@_loadUpdates(data.updates)
					@$scope.history.nextBeforeTimestamp = data.nextBeforeTimestamp
					if !data.nextBeforeTimestamp?
						@$scope.history.atEnd = true
					@$scope.history.loading = false

		loadFileAtPointInTime: () ->
			pathname = @$scope.history.selection.pathname
			toV =  @$scope.history.selection.updates[0].toV
			url = "/project/#{@$scope.project_id}/diff"
			query = ["pathname=#{encodeURIComponent(pathname)}", "from=#{toV}", "to=#{toV}"]
			url += "?" + query.join("&")
			@$scope.history.selectedFile =
				loading: true
			@ide.$http
				.get(url)
				.then (response) =>
					{text, binary} = @_parseDiff(response.data.diff)
					@$scope.history.selectedFile.binary = binary
					@$scope.history.selectedFile.text = text
					@$scope.history.selectedFile.loading = false
				.catch () ->

		reloadDiff: () ->
			diff = @$scope.history.diff
			{updates} = @$scope.history.selection
			{fromV, toV, pathname} = @_calculateDiffDataFromSelection()

			if !pathname?
				@$scope.history.diff = null
				return

			return if diff? and
				diff.pathname == pathname and
				diff.fromV    == fromV and
				diff.toV      == toV

			@$scope.history.diff = diff = {
				fromV:    fromV
				toV:      toV
				pathname: pathname
				error:    false
			}

			diff.loading = true
			url = "/project/#{@$scope.project_id}/diff"
			query = ["pathname=#{encodeURIComponent(pathname)}"]
			if diff.fromV? and diff.toV?
				query.push "from=#{diff.fromV}", "to=#{diff.toV}"
			url += "?" + query.join("&")

			@ide.$http
				.get(url)
				.then (response) =>
					{ data } = response
					diff.loading = false
					{text, highlights, binary} = @_parseDiff(data.diff)
					diff.binary = binary
					diff.text = text
					diff.highlights = highlights
				.catch () ->
					diff.loading = false
					diff.error = true

		_parseDiff: (diff) ->
			if diff.binary
				return { binary: true }
			row    = 0
			column = 0
			highlights = []
			text   = ""
			for entry, i in diff or []
				content = entry.u or entry.i or entry.d
				content ||= ""
				text += content
				lines   = content.split("\n")
				startRow    = row
				startColumn = column
				if lines.length > 1
					endRow    = startRow + lines.length - 1
					endColumn = lines[lines.length - 1].length
				else
					endRow    = startRow
					endColumn = startColumn + lines[0].length
				row    = endRow
				column = endColumn

				range = {
					start:
						row: startRow
						column: startColumn
					end:
						row: endRow
						column: endColumn
				}

				if entry.i? or entry.d?
					user = entry.meta.users?[0]
					name = displayNameForUser(user)
					date = moment(entry.meta.end_ts).format("Do MMM YYYY, h:mm a")
					if entry.i?
						highlights.push {
							label: "Added by #{name} on #{date}"
							highlight: range
							hue: ColorManager.getHueForUserId(user?.id)
						}
					else if entry.d?
						highlights.push {
							label: "Deleted by #{name} on #{date}"
							strikeThrough: range
							hue: ColorManager.getHueForUserId(user?.id)
						}

			return {text, highlights}

		_loadUpdates: (updates = []) ->
			previousUpdate = @$scope.history.updates[@$scope.history.updates.length - 1]

			for update in updates or []
				for user in update.meta.users or []
					if user?
						user.hue = ColorManager.getHueForUserId(user.id)

				if !previousUpdate? or !moment(previousUpdate.meta.end_ts).isSame(update.meta.end_ts, "day")
					update.meta.first_in_day = true

				update.selectedFrom = false
				update.selectedTo = false
				update.inSelection = false

				previousUpdate = update

			firstLoad = @$scope.history.updates.length == 0

			@$scope.history.updates =
				@$scope.history.updates.concat(updates)

			if firstLoad 
				if @$scope.history.viewMode == HistoryViewModes.COMPARE
					@autoSelectRecentUpdates()
				else 
					@autoSelectLastUpdate()

		_perDocSummaryOfUpdates: (updates) ->
			# Track current_pathname -> original_pathname
			# create bare object for use as Map
			# http://ryanmorr.com/true-hash-maps-in-javascript/
			original_pathnames = Object.create(null)

			# Map of original pathname -> doc summary
			docs_summary = Object.create(null)

			updatePathnameWithUpdateVersions = (pathname, update, deletedAtV) ->
				# docs_summary is indexed by the original pathname the doc
				# had at the start, so we have to look this up from the current
				# pathname via original_pathname first
				if !original_pathnames[pathname]?
						original_pathnames[pathname] = pathname
				original_pathname = original_pathnames[pathname]
				doc_summary = docs_summary[original_pathname] ?= {
					fromV: update.fromV, toV: update.toV,
				}
				doc_summary.fromV = Math.min(
					doc_summary.fromV,
					update.fromV
				)
				doc_summary.toV = Math.max(
					doc_summary.toV,
					update.toV
				)
				if deletedAtV?
					doc_summary.deletedAtV = deletedAtV

			# Put updates in ascending chronological order
			updates = updates.slice().reverse()
			for update in updates
				for pathname in update.pathnames or []
					updatePathnameWithUpdateVersions(pathname, update)
				for project_op in update.project_ops or []
					if project_op.rename?
						rename = project_op.rename
						updatePathnameWithUpdateVersions(rename.pathname, update)
						original_pathnames[rename.newPathname] = original_pathnames[rename.pathname]
						delete original_pathnames[rename.pathname]
					if project_op.add?
						add = project_op.add
						updatePathnameWithUpdateVersions(add.pathname, update)
					if project_op.remove?
						remove = project_op.remove
						updatePathnameWithUpdateVersions(remove.pathname, update, project_op.atV)

			return docs_summary

		_calculateDiffDataFromSelection: () ->
			fromV = toV = pathname = null

			selected_pathname = @$scope.history.selection.pathname

			for pathname, doc of @_perDocSummaryOfUpdates(@$scope.history.selection.updates)
				if pathname == selected_pathname
					{fromV, toV} = doc
					return {fromV, toV, pathname}

			return {}

		# Set the track changes selected doc to one of the docs in the range
		# of currently selected updates. If we already have a selected doc
		# then prefer this one if present.
		_selectDocFromUpdates: () ->
			affected_docs = @_perDocSummaryOfUpdates(@$scope.history.selection.updates)
			@$scope.history.selection.docs = affected_docs

			selected_pathname = @$scope.history.selection.pathname
			if selected_pathname? and affected_docs[selected_pathname]
				# Selected doc is already open
			else
				# Set to first possible candidate
				for pathname, doc of affected_docs
					selected_pathname = pathname
					break

			@$scope.history.selection.pathname = selected_pathname

		_updateContainsUserId: (update, user_id) ->
			for user in update.meta.users
				return true if user?.id == user_id
			return false
