ConversationState = {
	Disabled = 0,
	Talking = 1,
	Waiting = 2,
}

function Conversation()
	local self = {
		state = ConversationState.Disabled,
		percent = 0,
		character = nil,
		font = SpriteFont(),
		advance_font = SpriteFont(""),
		choice_fonts = {},
		font_blink_timer = 0,
		dialog = {},
		speed = 30,
		choice = 1,
		override_input = false,

		Update = function(self, path, scene, player)

			local crop_top = GetScreenHeight() * 0.17
			local crop_bottom = GetScreenHeight() * 0.21
			local text_offset = GetScreenWidth() * 0.2
			self.font.SetPos(Vector(text_offset, GetScreenHeight() - crop_bottom + 10))
			self.font.SetHorizontalWrapping(GetScreenWidth() - self.font.GetPos().GetX() * 2)
			self.advance_font.SetPos(Vector(GetScreenWidth() - self.font.GetPos().GetX(), GetScreenHeight() - 50))

			-- Update conversation percentage (fade in/out of conversation)
			if self.state == ConversationState.Disabled then
				self.percent = math.lerp(self.percent, 0, getDeltaTime() * 4)
			else
				self.percent = math.lerp(self.percent, 1, getDeltaTime() * 4)
			end
			path.SetCropTop(self.percent * crop_top)
			path.SetCropBottom(self.percent * crop_bottom)
			local cam = GetCamera()
			cam.SetApertureSize(self.percent)

			if self.percent < 0.9 then
				self.font.SetHidden(true)
				self.font.ResetTypewriter()
			else
				self.font.SetHidden(false)
			end
			self.advance_font.SetColor(Vector())
			self.override_input = false
			for i,choice_font in ipairs(self.choice_fonts) do
				choice_font.SetColor(Vector())
			end
			self.font_blink_timer = self.font_blink_timer + getDeltaTime()

			-- Focus on character:
			if self.character ~= nil then
				cam.SetFocalLength(vector.Subtract(scene.Component_GetTransform(self.character.head).GetPosition(), cam.GetPosition()).Length())
			end

			-- State flow:
			if self.state == ConversationState.Disabled then
				self.font.SetHidden(true)
			elseif self.state == ConversationState.Talking then

				self.choice = 1
				self.override_input = true
				self:CinematicCamera(self.character, player, scene, cam)

				-- End of talking:
				if self.font.IsTypewriterFinished() then
					self.state = ConversationState.Waiting
					self.font_blink_timer = 0
				end

				-- Skip talking:
				if input.Press(KEYBOARD_BUTTON_ENTER) or input.Press(KEYBOARD_BUTTON_SPACE) or input.Press(GAMEPAD_BUTTON_2) then
					self.font.TypewriterFinish()
				end

				-- Turn on talking animation:
				if self.character.expression ~= INVALID_ENTITY and self.percent >= 0.9 then
					scene.Component_GetExpression(self.character.expression).SetForceTalkingEnabled(true)
				end

			elseif self.state == ConversationState.Waiting then

				-- End of talking, waiting for input:
				
				if self.dialog.choices ~= nil then
					-- Dialog choices:
					self.override_input = true
					self:CinematicCamera(player, self.character, scene, cam)

					local pos = vector.Add(self.font.GetPos(), Vector(20, 10 + self.font.TextSize().GetY()))
					for i,choice in ipairs(self.dialog.choices) do
						if self.choice_fonts[i] == nil then
							self.choice_fonts[i] = SpriteFont()
							path.AddFont(self.choice_fonts[i])
						end
						self.choice_fonts[i].SetPos(pos)
						self.choice_fonts[i].SetSize(self.font.GetSize())
						self.choice_fonts[i].SetHorizontalWrapping(GetScreenWidth() - self.choice_fonts[i].GetPos().GetX() * 2)
						if i == self.choice then
							self.choice_fonts[i].SetText(" " .. choice[1])
							self.choice_fonts[i].SetColor(vector.Lerp(Vector(1,1,1,1), self.font.GetColor(), math.abs(math.sin(self.font_blink_timer * math.pi))))
						else
							self.choice_fonts[i].SetText("     " .. choice[1])
							self.choice_fonts[i].SetColor(self.font.GetColor())
						end
						pos = vector.Add(pos, Vector(0, self.choice_fonts[i].TextSize().GetY() + 5))
					end

					-- Dialog input:
					if input.Press(KEYBOARD_BUTTON_UP) or input.Press(string.byte('W')) or input.Press(GAMEPAD_BUTTON_UP) or input.Press(GAMEPAD_ANALOG_THUMBSTICK_L_AS_BUTTON_UP) then
						self.choice = self.choice - 1
						self.font_blink_timer = 0
					end
					if input.Press(KEYBOARD_BUTTON_DOWN) or input.Press(string.byte('S')) or input.Press(GAMEPAD_BUTTON_DOWN) or input.Press(GAMEPAD_ANALOG_THUMBSTICK_L_AS_BUTTON_DOWN) then
						self.choice = self.choice + 1
						self.font_blink_timer = 0
					end
					if self.choice < 1 then
						self.choice = #self.dialog.choices
					end
					if self.choice > #self.dialog.choices then
						self.choice = 1
					end
					if input.Press(KEYBOARD_BUTTON_ENTER) or input.Press(KEYBOARD_BUTTON_SPACE) or input.Press(GAMEPAD_BUTTON_2) then
						if self.dialog.choices[self.choice].action ~= nil then
							self.dialog.choices[self.choice].action() -- execute dialog choice action
						end
						self:Next()
					end
				else
					-- No dialog choices:
					self.override_input = true
					self:CinematicCamera(self.character, player, scene, cam)

					if input.Press(KEYBOARD_BUTTON_ENTER) or input.Press(KEYBOARD_BUTTON_SPACE) or input.Press(GAMEPAD_BUTTON_2) then
						if self.dialog.action_after ~= nil then
							self.dialog.action_after() -- execute dialog action after ended
						end
						self:Next()
						self.font.ResetTypewriter()
					end
					-- Blinking advance conversation icon:
					self.advance_font.SetColor(vector.Lerp(Vector(0,0,0,0), self.font.GetColor(), math.abs(math.sin(self.font_blink_timer * math.pi))))	
				end

				-- Turn off talking animation:
				if self.character.expression ~= INVALID_ENTITY then
					scene.Component_GetExpression(self.character.expression).SetForceTalkingEnabled(false)
				end
			end

		end,
		
		Enter = function(self, character)
			backlog_post("Entering conversation")
			self.state = ConversationState.Talking
			self.character = character
			if #self.character.dialogs < self.character.next_dialog then
				self.character.next_dialog = 1
			end
			self:Next()
		end,
	
		Exit = function(self)
			backlog_post("Exiting conversation")
			self.state = ConversationState.Disabled
			self.dialog = {}
			self.override_input = false
			self.font.ResetTypewriter()
		end,

		Next = function(self)
			if #self.character.dialogs < self.character.next_dialog then
				self:Exit()
			end
			if self.state == ConversationState.Disabled then
				return
			end
			self.dialog = self.character.dialogs[self.character.next_dialog]
			self.character.next_dialog = self.character.next_dialog + 1
			self.state = ConversationState.Talking
			self.font.SetText(self.dialog[1])
			self.font.SetTypewriterTime(string.len(self.dialog[1] or "") / self.speed)
			self.font.ResetTypewriter()
			if self.dialog.action ~= nil then
				self.dialog.action() -- execute dialog action
			end
		end,

		CinematicCamera = function(self, character_foreground, character_background, scene, camera)
			local head_pos = scene.Component_GetTransform(character_foreground.head).GetPosition()
			local forward_dir = vector.Subtract(character_background.position, character_foreground.position).Normalize()
			forward_dir = vector.TransformNormal(forward_dir, matrix.RotationY(math.pi * 0.1))
			local up_dir = Vector(0,1,0)
			local right_dir = vector.Cross(up_dir, forward_dir).Normalize()
			local cam_pos = vector.Add(head_pos, forward_dir)
			local cam_target = vector.Add(vector.Add(head_pos, vector.Multiply(right_dir, -0.4)), Vector(0,-0.1))
			local lookat = matrix.Inverse(matrix.LookAt(cam_pos, cam_target))
			camera.TransformCamera(lookat)
			camera.UpdateCamera()
			camera.SetFocalLength(vector.Subtract(head_pos, camera.GetPosition()).Length())
		end
	}
	self.font.SetSize(20)
	self.font.SetColor(Vector(0.6,0.8,1,1))
	self.advance_font.SetSize(24)
	local sound = Sound()
	local soundinstance = SoundInstance()
	audio.CreateSound(script_dir() .. "typewriter.wav", sound)
	audio.CreateSoundInstance(sound, soundinstance)
	audio.SetVolume(0.2, soundinstance)
	self.font.SetTypewriterSound(sound, soundinstance)
	return self
end
