local _, L = ...
local UI, NPC, Selector, Titles, TalkBox = ConsolePortUI, {}, {}, {}, {}
local frame = L.frame
local KEY = ConsolePort:GetData().KEY
local Control = UI:GetControlHandle()
local TEMPLATE = {}

function NPC:OnShow()
	self:ResetButtons()
	Control:AddHint(KEY.TRIANGLE, GOODBYE)
end

function NPC:ResetButtons() self.TitleButtons:Hide() end
function NPC:ShowButtons() self.TitleButtons:Show() end

function NPC:OnEvent(event, ...)
--	print(event, ...)
	local time = GetTime()
	if self.timeStamp ~= time then
		self.TalkBox:Play()
	end
	self.lastEvent = event
	self.timeStamp = time
	self:ResetElements()
	if self[event] then
		self[event](self, ...)
	end
	self:UpdateItems()
end

-- Events

function NPC:GOSSIP_SHOW(...)
	self:ShowButtons()
	self:UpdateTalkingHead(GetUnitName('npc'), GetGossipText(), 'GossipGossip')
	self.TalkBox:Play()
	NPCFriendshipStatusBar_Update(self.TalkBox)
	NPCFriendshipStatusBar:ClearAllPoints()
	NPCFriendshipStatusBar:SetStatusBarColor(0.5, 0.7, 1)
	NPCFriendshipStatusBar:SetPoint('TOPLEFT', self.TalkBox, 'TOPLEFT', 32, 0)
end

function NPC:GOSSIP_CLOSED(...)
	self:ResetButtons()
end

function NPC:QUEST_GREETING(...)
	self:UpdateTalkingHead(GetUnitName('questnpc') or GetUnitName('npc'), GetGreetingText(), 'AvailableQuest')
	self:ShowButtons()
end

function NPC:QUEST_PROGRESS(...) -- special case, doesn't use QuestInfo
	Control:AddHint(KEY.CROSS, CONTINUE)
	local npcType
	if IsQuestCompletable() then
		Control:SetHintEnabled(KEY.CROSS)
		npcType = 'ActiveQuest'
	else
		Control:SetHintDisabled(KEY.CROSS)
		npcType = 'IncompleteQuest'
	end
	self:UpdateTalkingHead(GetTitleText(), GetProgressText(), npcType)
	local elements = self.TalkBox.Elements
	local textColor, titleTextColor = GetMaterialTextColors('Stone')
	elements.Progress.ReqText:SetTextColor(unpack(titleTextColor))
	elements.Progress.MoneyText:SetTextColor(unpack(textColor))
	elements:Show()
	elements:SetHeight(1)
	elements.Progress:Show()
	local top, offset = elements:GetTop()
	for _, child in pairs({elements.Progress:GetRegions(), elements.Progress:GetChildren()}) do
		if child:IsVisible() then
			local childOffset = child:GetBottom()
			if childOffset and ( not offset or childOffset < offset ) then
				offset = childOffset
			end
		end
	end
	if offset then
		local atop, aoff = abs(top), abs(offset)
		local newHeight = abs(offset) - abs(top) + 32
		elements:SetSize(364, newHeight)
		elements.Progress:SetHeight(newHeight)
		self.TalkBox:SetExtraOffset(newHeight - 16)
	else
		self:ResetElements()
	end
end

function NPC:QUEST_COMPLETE(...)
	self:UpdateTalkingHead(GetTitleText(), GetRewardText(), 'ActiveQuest')
	self:AddQuestInfo(TEMPLATE.QUEST_REWARD)
	self.Inspector.HintText = CHOOSE
	Control:AddHint(KEY.CROSS, COMPLETE_QUEST)
end

function NPC:QUEST_FINISHED(...)
	CloseQuest()
end

function NPC:QUEST_DETAIL(...)
	self:UpdateTalkingHead(GetTitleText(), GetQuestText(), 'AvailableQuest')
	self:AddQuestInfo(TEMPLATE.QUEST_DETAIL, QuestFrameAcceptButton)
	self.Inspector.HintText = nil
	Control:AddHint(KEY.CROSS, ACCEPT)
	if CanIgnoreQuest() then
		Control:AddHint(KEY.CIRCLE, IGNORE)
	elseif IsQuestIgnored() then
		Control:AddHint(KEY.CIRCLE, UNIGNORE_QUEST)
	end
end

function NPC:SetItemTooltip(tooltip, item)
	local objType = item.objectType
	tooltip.Button:SetScript('OnClick', function() item:Click() end)
	if QuestInfoFrame.questLog then
		if objType == 'item' then
			tooltip:SetQuestLogItem(item.type, item:GetID())
		elseif objType == 'currency' then
			tooltip:SetQuestCurrency(item.type, item:GetID()) 
		end
	else
		if objType == 'item' then
			tooltip:SetQuestItem(item.type, item:GetID())
		elseif objType == 'currency' then
			tooltip:SetQuestCurrency(item.type, item:GetID())
		end
	end
	tooltip.Icon.Texture:SetTexture(item.Icon:GetTexture())
end

function NPC:GetItemColumn(owner, id)
	local columns = owner and owner.Columns
	if columns and id then
		local column = columns[id]
		local anchor = columns[id - 1]
		if not column then
			column = CreateFrame('Frame', '$parentColumn'..id, owner)
			column:SetSize(1, 1) -- set size to make sure children are drawn
			UI:ApplyMixin(column, nil, 'AdjustToChildren')
			columns[id] = column
		end
		if anchor then
			column:SetPoint('TOPLEFT', anchor, 'TOPRIGHT', 30, 0)
		else
			column:SetPoint('TOPLEFT', owner, 0, 0)
		end
		column:Show()
		return column
	end
end

function NPC:ShowItems()
	local maxColumns = 3
	local inspector = self.Inspector
	local items = inspector.Items
	local active = inspector.Active
	local extras = inspector.Extras
	local choices = inspector.Choices
	local hasChoice
	extras:SetSize(1, 1)
	choices:SetSize(1, 1)
	inspector:Show()
	for _, item in pairs(items) do
		local tooltip = UI:GetTooltip()
		local owner = item.type == 'choice' and choices or extras
		local tooltips = owner.Tooltips
		local id = #tooltips + 1
		local columnID = ( id % maxColumns == 0 ) and maxColumns or ( id % maxColumns )
		local column = self:GetItemColumn(owner, columnID)

		tooltip:SetParent(column)
		tooltip:SetOwner(column, "ANCHOR_NONE")
		tooltip.owner = owner

		self:SetItemTooltip(tooltip, item)

		tooltips[id] = tooltip

		-- if item is choice, add to active pool
		if QuestInfoFrame.chooseItems then
			if item.type == 'choice' then
				hasChoice = true
				active[id] = tooltip.Button
				tooltip.Button:SetID(id)
				tooltip:SetCheckable(true)
				if QuestInfoFrame.itemChoice == item:GetID() then
					tooltip:SetChecked(true)
				end
			end
		end
		local width, height = tooltip:GetSize()
		tooltip:SetWidth(width + 30)

		if column.lastItem then
			tooltip:SetPoint('TOP', column.lastItem, 'BOTTOM', 0, 0)
		else
			tooltip:SetPoint('TOP', column, 'TOP', 0, 0)
		end

		column.lastItem = tooltip
	end
	if self.TalkBox.Elements.Progress.ReqText:IsVisible() then
		extras.Text:SetText(self.TalkBox.Elements.Progress.ReqText:GetText())
	end
	if QuestInfoRewardsFrame.ItemChooseText:IsVisible() then
		choices.Text:SetText(QuestInfoRewardsFrame.ItemChooseText:GetText())
	elseif QuestInfoRewardsFrame.ItemReceiveText:IsVisible() then
		if hasChoice then
			choices.Text:SetText(QuestInfoRewardsFrame.ItemReceiveText:GetText())
		else
			extras.Text:SetText(QuestInfoRewardsFrame.ItemReceiveText:GetText())
		end
	end
	inspector.Threshold = #active
	inspector:AdjustToChildren()
	inspector:SetFocus(1)
end

function NPC:UpdateItems()
	local items, numItems = self:GetItems()
	if numItems > 0 then
		self.hasItems = true
		Control:AddHint(KEY.CIRCLE, INSPECT)
	else
		self.hasItems = false
		Control:RemoveHint(KEY.CIRCLE)
	end
end

function NPC:GetItems()
	local items = self.Inspector.Items
	wipe(items)
	for _, item in pairs(QuestInfoRewardsFrame.RewardButtons) do
		if item:IsVisible() then
			items[#items + 1] = item
		end
	end
	for _, item in pairs(self.TalkBox.Elements.Progress.Items) do
		if item:IsVisible() then
			items[#items + 1] = item
		end
	end
	return items, #items
end

function NPC:AddQuestInfo(template, acceptButton)
	local elements = self.TalkBox.Elements
	local content = elements.Content
	QuestInfo_Display(template, content, QuestFrameAcceptButton, 'Stone')
	local elementsTable = template.elements
	local height, lastFrame = 0
	for i = 1, #elementsTable, 3 do
		local shownFrame, bottomShownFrame = elementsTable[i]()
		if ( shownFrame ) then
			shownFrame:SetParent(content)
			height = height + shownFrame:GetHeight() + abs(elementsTable[i+2])
			if ( lastFrame ) then
				shownFrame:SetPoint('TOPLEFT', lastFrame, 'BOTTOMLEFT', elementsTable[i+1], elementsTable[i+2])
			else
				shownFrame:SetPoint('TOPLEFT', content, 'TOPLEFT', elementsTable[i+1] + 32, elementsTable[i+2] - 16)	
			end
			shownFrame:Show()
			elements.Active[#elements.Active + 1] = shownFrame
			lastFrame = bottomShownFrame or shownFrame
		end
	end
	-- hacky fix to stop a content frame that only contains a spacer from showing. 
	if height > 20 then
		elements:SetSize(570, height + 32)
		elements:Show()
		content:Show()
	else
		elements:Hide()
		content:Hide()
	end
	self.TalkBox:SetExtraOffset(height)
	self.TalkBox.NameFrame.FadeIn:Play()
end

function NPC:ResetElements()
	self.Inspector:Hide()
	local elements = self.TalkBox.Elements
	for _, frame in pairs(elements.Active) do
		frame:Hide()
	end
	wipe(elements.Active)
	elements:Hide()
	elements.Content:Hide()
	elements.Progress:Hide()
end

function NPC:UpdateTalkingHead(title, text, npcType)
	local unit
	if ( UnitExists('questnpc') and not UnitIsUnit('questnpc', 'player') ) then
		unit = 'questnpc'
	elseif ( UnitExists('npc') and not UnitIsUnit('npc', 'player') ) then
		unit = 'npc'
	else
		unit = 'player'
	end
	local zoom = ( unit and unit == 'player' ) and 0.8 or 0.85
	local talkBox = self.TalkBox
	talkBox:SetExtraOffset(0)
	talkBox.MainFrame.Indicator:SetTexture('Interface\\GossipFrame\\' .. npcType .. 'Icon')
	talkBox.MainFrame.Model.unit = unit
	talkBox.NameFrame.Name:SetText(title)
	talkBox.TextFrame.Text:SetText(text)
	talkBox.MainFrame.Model:SetUnit(unit)
	talkBox.MainFrame.Model:SetPortraitZoom(zoom)
end

local inputs = {
	[KEY.UP] = function(self, down) 
		if down then 
			self.TitleButtons:SetPrevious()
		end
	end,
	[KEY.DOWN] = function(self, down)
		if down then
			self.TitleButtons:SetNext() 
		end
	end,
	[KEY.TRIANGLE] = function(self, down) if down then CloseGossip() CloseQuest() end end,
	[KEY.SQUARE] = function(self, down)
		if down then
			local text = self.TalkBox.TextFrame.Text
			if text:IsSequence() then
				text:ForceNext()
			end
		end
	end,
	[KEY.CIRCLE] = function(self, down)
		if down then
			if self.isInspecting then
				self.Inspector:Hide()
			elseif self.hasItems then
				self:ShowItems()
			elseif CanIgnoreQuest() then
				IgnoreQuest()
			elseif IsQuestIgnored() then
				UnignoreQuest()
			end
		end
	end,
	[KEY.CROSS] = function(self, down)
		if down then
			if self.isInspecting then
				self.Inspector:ClickFocused()
				self.Inspector:Hide()
			-- Gossip/multiple quest choices
			elseif GossipFrame:IsVisible() or QuestFrameGreetingPanel:IsVisible() then
				self.TitleButtons:ClickFocused()
			-- Shortcut to select quest reward
			elseif self.hasItems and GetNumQuestChoices() > 1 and QuestInfoFrame.itemChoice == 0 and
					QuestFrameCompleteQuestButton:IsVisible() then
				self:ShowItems()
			-- Complete quest
			elseif QuestFrameCompleteQuestButton:IsVisible() then
				QuestFrameCompleteQuestButton:Click()
			-- Accept quest
			elseif QuestFrameAcceptButton:IsVisible() then
				QuestFrameAcceptButton:Click()
			-- Progress quest
			elseif QuestFrameCompleteButton:IsVisible() then
				if IsQuestCompletable() then
					CompleteQuest()
				end
			end
		end
	end,
	[KEY.LEFT] = function(self, down)
		if down then
			if self.isInspecting then
				self.Inspector:SetPrevious()
			end
		end
	end,
	[KEY.RIGHT] = function(self, down)
		if down then
			if self.isInspecting then
				self.Inspector:SetNext()
			end
		end
	end,
}


function NPC:OnInput(button, down)
	button = tonumber(button)
	if inputs[button] then
		inputs[button](self, down)
	end
end

function Selector:SetFocus(index)
	if self:IsVisible() and index then
		local focus = self:GetFocus()
		if focus then
			focus:UnlockHighlight()
			focus:OnLeave()
		end
		local max = self:GetMaxIndex() 
		self.index = ( index > max and max ) or ( index < 1 and 1 ) or index
		self:SetFocusHighlight()
		if max > 0 and self.HintText then
			Control:AddHint(KEY.CROSS, self.HintText)
		else
			Control:RemoveHint(KEY.CROSS)
		end
	end
end

function Selector:SetNext()
	local nextButton = self:GetNextButton(self.index, 1)
	self:SetFocus(nextButton and nextButton:GetID())
end

function Selector:SetPrevious()
	local prevButton = self:GetNextButton(self.index, -1)
	self:SetFocus(prevButton and prevButton:GetID())
end

function Selector:ClickFocused()
	local focus = self:GetFocus()
	if focus then
		focus:Click()
	end
end

function Selector:SetFocusHighlight()
	local focus = self:GetFocus()
	if focus then
		focus:LockHighlight()
		focus:OnEnter()
	end
end

function Selector:GetFocus()
	return self.Active[self.index]
end

function Selector:GetActive()
	return pairs(self.Active)
end

function Selector:GetNextButton(index, delta)
	if index then
		local modifier = delta
		while true do
			local key = index + delta
			if key < 1 or key > self.Threshold then
				return self.Active[self.index]
			end
			if self.Active[key] then
				return self.Active[key]
			else
				delta = delta + modifier
			end
		end
	end
end

function Selector:GetMaxIndex()
	local maxIndex = 0
	for i, button in pairs(self.Active) do
		if i > maxIndex then
			maxIndex = i
		end
	end
	return maxIndex
end

function Titles:AdjustHeight(newHeight)
	self:SetFocus(1)
	self:SetScript('OnUpdate', function(self)
		local height = self:GetHeight()
		local diff = newHeight - height
		if abs(newHeight - height) < 0.05 then
			self:SetHeight(newHeight)
			self:SetScript('OnUpdate', nil)
		else
			self:SetHeight(height + ( diff / 10 ) )
		end
	end)
end

function Titles:OnShow()
	self:SetHeight(0)
end

function Titles:OnHide()
	for i, button in pairs(self.Active) do
		button:UnlockHighlight()
	end
	wipe(self.Active)
	self.focus = nil
end

function Titles:UpdateActive(button)
	self.Active[button:GetID()] = button:IsVisible() and button or nil
	local newHeight = 0
	for i, button in pairs(self.Active) do
		newHeight = newHeight + button:GetHeight()
	end
	self:AdjustHeight(newHeight)
end

function TalkBox:SetOffset(newOffset)
	newOffset = newOffset or 150
	self.offset = newOffset
	newOffset = newOffset + ( self.extraOffset or 0 )
	self:SetScript('OnUpdate', function(self)
		local offset = self:GetBottom()
		local diff = newOffset - offset
		if abs(newOffset - offset) < 0.05 then
			self:SetPoint('BOTTOM', UIParent, 'BOTTOM', 0, newOffset)
			self:SetScript('OnUpdate', nil)
		else
			self:SetPoint('BOTTOM', UIParent, 'BOTTOM', 0, offset + ( diff / 10) )
		end
	end)
end

function TalkBox:SetExtraOffset(newOffset)
	self.extraOffset = newOffset
	self:SetOffset(self.offset or 150)
end

function TalkBox:Play()
	self.NameFrame.FadeIn:Play()
	self.MainFrame.SheenAnim:Play()
	self.MainFrame.InAnim:Play()
	if not self.BackgroundFrame.FadeIn:IsPlaying() then
		self.BackgroundFrame.FadeIn:Play()
	end
	self:SetPoint('BOTTOM', UIParent, 'BOTTOM', 0, -150)
	self:SetOffset(self.offset or 150)
end

UI.Utils.Mixin(frame, NPC)
UI.Utils.Mixin(frame.TalkBox, TalkBox)
UI.Utils.Mixin(frame.Inspector, Selector)
UI.Utils.Mixin(frame.TitleButtons, Selector, Titles)


TEMPLATE.QUEST_DETAIL = { questLog = nil, chooseItems = nil, contentWidth = 450,
	canHaveSealMaterial = nil, sealXOffset = 160, sealYOffset = -6,
	elements = {
		QuestInfo_ShowObjectivesHeader, 0, -15,	
		QuestInfo_ShowObjectivesText, 0, -5,
		QuestInfo_ShowSpecialObjectives, 0, -10,
		QuestInfo_ShowGroupSize, 0, -10,
		QuestInfo_ShowRewards, 0, -15,
		QuestInfo_ShowSpacer, 0, -15,
	}
}

TEMPLATE.QUEST_REWARD = { questLog = nil, chooseItems = true, contentWidth = 450,
	canHaveSealMaterial = nil, sealXOffset = 160, sealYOffset = -6,
	elements = {
	--	QuestInfo_ShowTitle, 5, -10,
	--	QuestInfo_ShowRewardText, 0, -5,
		QuestInfo_ShowRewards, 0, -10,
		QuestInfo_ShowSpacer, 0, -10
	}
}