var Undo = {
	index: 0,
	history: [],
	initEdit: function(aspects) {
		//Before
		if (aspects && Undo.current_save) {
			//This "before" is the same as the "after" of the previous step
			if (Objector.equalKeys(aspects, Undo.current_save.aspects) && aspects.cubes !== selected) {
				//return;
			}
		}
		Undo.current_save = new Undo.save(aspects)
	},
	finishEdit: function(action, aspects) {
		if (!Undo.current_save) return;
		aspects = aspects || Undo.current_save.aspects
		//After
		Blockbench.dispatchEvent('finish_edit', {aspects})
		var entry = {
			before: Undo.current_save,
			post: new Undo.save(aspects),
			action: action
		}
		Undo.current_save = entry.post

		if (Undo.history.length-1 > Undo.index) {
			Undo.history.length = Undo.index+1
		}
	 
		Undo.history.push(entry)

		if (Undo.history.length > settings.undo_limit.value) {
			Undo.history.shift()
		}
		Undo.index = Undo.history.length
		if (!aspects || !aspects.keep_saved) {
			Prop.project_saved = false;
		}
		Blockbench.dispatchEvent('finished_edit', {aspects})
	},
	cancelEdit: function() {
		if (!Undo.current_save) return;
		outlines.children.length = 0
		Undo.loadSave(Undo.current_save, new Undo.save(Undo.current_save.aspects))
		delete Undo.current_save;
	},
	undo: function() {
		if (Undo.history.length <= 0 || Undo.index < 1) return;

		Prop.project_saved = false;
		Undo.index--;

		var entry = Undo.history[Undo.index]
		Undo.loadSave(entry.before, entry.post)
		console.log('Undo: '+entry.action)
		Blockbench.dispatchEvent('undo', {entry})
	},
	redo: function() {
		if (Undo.history.length <= 0) return;
		if (Undo.index >= Undo.history.length) {
			return;
		}
		Prop.project_saved = false;
		Undo.index++;

		var entry = Undo.history[Undo.index-1]
		Undo.loadSave(entry.post, entry.before)
		console.log('Redo: '+entry.action)
		Blockbench.dispatchEvent('redo', {entry})
	},
	getItemByUUID: function(list, uuid) {
		if (!list || typeof list !== 'object' || !list.length) {return false;}
		var i = 0;
		while (i < list.length) {
			if (list[i].uuid === uuid) {
				return list[i]
			}
			i++;
		}
		return false;
	},
	save: function(aspects) {
		var scope = this;
		this.aspects = aspects

		if (aspects.selection) {
			this.selection = []
			selected.forEach(function(obj) {
				scope.selection.push(obj.uuid)
			})
			if (selected_group) {
				this.selection_group = selected_group.uuid
			}
		}

		if (aspects.cubes) {
			this.cubes = {}
			aspects.cubes.forEach(function(obj) {
				if (aspects.uv_only) {
					var copy = new Cube(obj)
					copy = {
						uv_offset: copy.uv_offset,
						faces: copy.faces,
					}
				} else {
					var copy = new Cube(obj)
				}
				copy.uuid = obj.uuid
				scope.cubes[obj.uuid] = copy
			})
		}

		if (aspects.outliner) {
			this.outliner = compileGroups(true)
		}

		if (aspects.group) {
			this.group = aspects.group.getChildlessCopy()
			this.group.uuid = aspects.group.uuid
		}

		if (aspects.textures) {
			this.textures = {}
			aspects.textures.forEach(function(t) {
				var tex = t.getUndoCopy(aspects.bitmap)
				scope.textures[t.uuid] = tex
			})
		}

		if (aspects.settings) {
			this.settings = aspects.settings
		}

		if (aspects.resolution) {
			this.resolution = {
				width:  Project.texture_width,
				height: Project.texture_height
			}
		}

		if (aspects.animation) {
			this.animation = aspects.animation ? aspects.animation.undoCopy() : null; 
		}
		if (aspects.keyframes && Animator.selected && Animator.selected.getBoneAnimator()) {
			this.keyframes = {
				animation: Animator.selected.uuid,
				bone: Animator.selected.getBoneAnimator().uuid
			}
			aspects.keyframes.forEach(kf => {
				scope.keyframes[kf.uuid] = kf.undoCopy()
			})
		}

		if (aspects.display_slots) {
			scope.display_slots = {}
			aspects.display_slots.forEach(slot => {
				if (display[slot]) {
					scope.display_slots[slot] = display[slot].copy()
				} else {
					scope.display_slots[slot] = null
				}
			})
		}
	},
	loadSave: function(save, reference) {
		if (save.cubes) {
			for (var uuid in save.cubes) {
				if (save.cubes.hasOwnProperty(uuid)) {
					var data = save.cubes[uuid]
					var obj = elements.findInArray('uuid', uuid)
					if (obj) {
						for (var face in obj.faces) {
							obj.faces[face].reset()
						}
						obj.extend(data)
						Canvas.adaptObjectPosition(obj)
						Canvas.adaptObjectFaces(obj)
						Canvas.updateUV(obj)
					} else {
						obj = new Cube(data, uuid).init()
					}
				}
			}
			for (var uuid in reference.cubes) {
				if (reference.cubes.hasOwnProperty(uuid) && !save.cubes.hasOwnProperty(uuid)) {
					var obj = elements.findInArray('uuid', uuid)
					if (obj) {
						obj.remove(false)
					}
				}
			}
			loadOutlinerDraggable()
			Canvas.updateVisibility()
		}

		if (save.outliner) {
			selected_group = undefined
			parseGroups(save.outliner)
			if (Blockbench.entity_mode) {
				Canvas.updateAllPositions()
			}
		}

		if (save.selection_group) {
			selected_group = undefined
			var sel_group = TreeElements.findRecursive('uuid', save.selection_group)
			if (sel_group) {
				sel_group.select()
			}
		}

		if (save.selection) {
			selected.length = 0;
			elements.forEach(function(obj) {
				if (save.selection.includes(obj.uuid)) {
					selected.push(obj)
				}
			})
		}

		if (save.group) {
			var group = TreeElements.findRecursive('uuid', save.group.uuid)
			if (group) {
				group.extend(save.group)
				if (Blockbench.entity_mode) {
					group.forEachChild(function(obj) {
						if (obj.type === 'cube') {
							Canvas.adaptObjectPosition(obj)
						}
					})
				}
			}
		}

		if (save.textures) {
			Painter.current = {}
			for (var uuid in save.textures) {
				if (reference.textures[uuid]) {
					var tex = Undo.getItemByUUID(textures, uuid)
					if (tex) {
						tex.extend(save.textures[uuid]).updateMaterial()
					}
				} else {
					new Texture(save.textures[uuid]).load().add(false)
				}
			}
			for (var uuid in reference.textures) {
				if (!save.textures[uuid]) {
					var tex = Undo.getItemByUUID(textures, uuid)
					if (tex) {
						textures.splice(textures.indexOf(tex), 1)
					}
				}
			}
			Canvas.updateAllFaces()
		}
		if (save.settings) {
			for (var key in save.settings) {
				settings[key].value = save.settings[key]
			}
		}

		if (save.resolution) {
			Project.texture_width = save.resolution.width
			Project.texture_height = save.resolution.height
		}

		if (save.animation) {

			var animation = Animator.animations.findInArray('uuid', save.animation)
			if (!animation) {
				animation = new Animation()
			}
			Animation.extend(save.animation)

		} else if (reference.animation) {
			//remove
			var animation = Animator.animations.findInArray('uuid', reference.animation.uuid)
			if (animation.remove) {
				animation.remove()
			}
		}

		if (save.keyframes && Animator.selected) {
			var animation = false;
			if (Animator.selected.uuid !== save.keyframes.animation) {
				animation = Animator.animations.findInArray('uuid', save.keyframes.animation)
				if (animation.select) {
					animation.select()
				}
			}

			var bone = Animator.selected.getBoneAnimator();
			if (!bone || bone.uuid !== save.keyframes.bone) {
				for (var uuid in Animator.selected.bones) {
					if (uuid === save.keyframes.bone) {
						bone = Animator.selected.bones[uuid]
						bone.select()
					}
				}
			}


			function getKeyframe(uuid) {
				var i = 0;
				while (i < Timeline.keyframes.length) {
					if (Timeline.keyframes[i].uuid === uuid) {
						return Timeline.keyframes[i];
					}
					i++;
				}
			}
			var added = 0;
			for (var uuid in save.keyframes) {
				if (uuid.length === 36 && save.keyframes.hasOwnProperty(uuid)) {
					var data = save.keyframes[uuid]
					var kf = getKeyframe(uuid)
					if (kf) {
						kf.extend(data)
					} else {
						kf = new Keyframe(data)
						kf.parent = bone;
						kf.uuid = uuid;
						Timeline.keyframes.push(kf)
						added++;
					}
				}
			}

			for (var uuid in reference.keyframes) {
				if (uuid.length === 36 && reference.keyframes.hasOwnProperty(uuid) && !save.keyframes.hasOwnProperty(uuid)) {
					var kf = getKeyframe(uuid)
					if (kf) {
						kf.remove()
					}
				}
			}
			if (added) {
				Vue.nextTick(Timeline.update)
			}
			updateKeyframeSelection()
			Animator.preview()
		}

		if (save.display_slots) {
			for (var slot in save.display_slots) {
				var data = save.display_slots[slot]

				if (!display[slot] && data) {
					display[slot] = new DisplaySlot()
				} else if (data === null && display[slot]) {
					display[slot].default()
				}
				display[slot].extend(data).update()
			}
		}
		if (open_dialog == 'uv_dialog') {
			for (var key in uv_dialog.editors) {
				if (uv_dialog.editors[key]) {
					uv_dialog.editors[key].loadData()
				}
			}
		}
		updateSelection()
	}
}
Undo.save.prototype.addTexture = function(texture) {
	if (!this.textures) return;
	if (this.aspects.textures.safePush(texture)) {
		this.textures[texture.uuid] = texture.getUndoCopy(this.aspects.bitmap)
	}
}
BARS.defineActions(function() {
	
	new Action({
		id: 'undo',
		icon: 'undo',
		category: 'edit',
		condition: () => (!open_dialog || open_dialog === 'uv_dialog'),
		work_in_dialog: true,
		keybind: new Keybind({key: 90, ctrl: true}),
		click: Undo.undo
	})
	new Action({
		id: 'redo',
		icon: 'redo',
		category: 'edit',
		condition: () => (!open_dialog || open_dialog === 'uv_dialog'),
		work_in_dialog: true,
		keybind: new Keybind({key: 89, ctrl: true}),
		click: Undo.redo
	})
})