local Model, GetTime, API, _, L, ani, m2 = {}, GetTime, ImmersionAPI, ...
L.ModelMixin = Model

----------------------------------
-- Animation wrappers
----------------------------------
function Model:Read()  self:SetAnimation(self.ani.reading) end
function Model:Ask()   self:SetAnimation(self.ani.asking) end
function Model:Yell()  self:SetAnimation(self.ani.yelling) end
function Model:Talk()  self:SetAnimation(self.ani.talking) end
function Model:Reset() self:SetAnimation(0) end

function Model:RunNextAnimation() if
	self.reading then self:Read() elseif
	self.asking  then self:Ask() elseif
	self.yelling then self:Yell() elseif
	self.talking then self:Talk() else
	self:Reset() end 
end

function Model:SetAnimation(...)
	self.animation = ...
	self.animstart = GetTime()
	getmetatable(self).__index.SetAnimation(self, ...)
end

----------------------------------
-- Unit stuff
----------------------------------
function Model:IsPlayer() return self.unit == 'player' end
function Model:IsNPC()    return self.unit == 'npc' or self.unit == 'questnpc' end
function Model:IsEther()  return self.unit == 'ether' end
function Model:GetUnit()  return self.unit end
function Model:GetVoice()
	local gender = UnitSex(self.unit)
	local ttsvoice = L('ttsvoice')
	if gender == 2 then
		ttsvoice = L('ttsmalevoice')
	elseif gender == 3 then
		ttsvoice = L('ttsfemalevoice')
	end
	return ttsvoice	
end

function Model:GetCreature()
	return self.creatureID or API:GetCreatureID(self.unit)
end

function Model:SetUnit(unit)
	self.unitDirty = unit
	if self:IsVisible() then
		self:ClearModel()
		self:MarkDefectModel(false)
		self:ApplyModelFromUnit(unit)
	end
end

function Model:MarkDefectModel(enabled)
	self.defectmodel = enabled
end

function Model:IsDefectModel()
	return self.defectmodel
end

function Model:ApplyModelFromUnit(unit)
	if self.file[unit] then
		self:SetModel(self.file[unit])
		self:SetCamDistanceScale(.4)
		self:SetPortraitZoom(0)
		self:SetPosition(0, 0, .25)
		self.unit = 'ether'
	else
		local mt = getmetatable(self).__index
		local creatureID = tonumber(unit) -- or API:GetCreatureID(unit)
		local apply = creatureID and mt.SetCreature or unit and mt.SetUnit
		self:SetCamDistanceScale(1)
		self:SetPortraitZoom(.85)
		self:SetPosition(0, 0, 0)
		if apply then
			apply(self, creatureID or unit)
			self.creatureID = creatureID
			self.unit = creatureID and 'npc' or unit
		end
	end
end

----------------------------------
-- Calculate state and remaining time
----------------------------------
function Model:SetRemainingTime(start, remaining)
	self.timestamp = start or GetTime()
	self.delay = remaining or 0
end

function Model:GetRemainingTime(start, remaining)
	if start and remaining then
		local time = GetTime()
		local diff = time - start
		-- shave off a second to avoid awkwardly long animation sequences
		if diff < ( remaining  - 1 ) then
			return time, diff
		end
	end
end

function Model:IsPrematureFinish(start)
	if start then
		local difference = GetTime() - start
		return difference < self.premature, difference
	end
end

function Model:PrepareAnimation(text, isEmote, isSequence)
	-- if no unit/text or if the text is a description rather than spoken words
	if ( not self.unit or not text ) or ( isEmote ) then
		for state in pairs(self.ani) do
			self[state] = nil
		end
	else
		self.reading = self.unit:match('player') and true
		if not self.reading then
			self.asking  = text:match('?')
			self.yelling = text:match('!')
			self.talking = true
		end
	end
end

function Model:RunSequence(remainingTime, isSequence)
	if self:IsNPC() then
		if not L('disableanisequence') then
			self:SetRemainingTime(GetTime(), remainingTime)
			if self.asking and not isSequence then
				self:Ask()
			elseif self.yelling then
				self:Randomize(self.ani.yelling)
			else
				self:Talk()
			end
		end
	elseif self:IsPlayer() then
		self:Read()
	end
end

function Model:Randomize(animation)
	local rand = random(2) == 2
	self:SetAnimation(rand and animation or self.ani.talking)
end

----------------------------------
-- Handler
----------------------------------
function Model:OnAnimFinished()
	if self:IsDefectModel() then return end
	-----------------------------------
	local isPremature, difference = self:IsPrematureFinish(self.animstart)
	if isPremature then
		self:MarkDefectModel(true)
		self:Reset()
		return
	end
	-----------------------------------
	if self:IsPlayer() then
		self:Read()
	else
		local newTime, difference = self:GetRemainingTime(self.timestamp, self.delay)
		if newTime and difference then
			self:SetRemainingTime(newTime, ( self.delay - 1) - difference)
			self.talking = true
			if self.asking then
				self:Ask()
			elseif self.yelling then
				self:Randomize(self.ani.yelling)
			else
				self:Talk()
			end
		else
			self:SetRemainingTime(nil, nil)
			self:PrepareAnimation(nil, nil)
			self:Reset()
		end
	end
end

function Model:OnShow()
	self:ClearModel()
	if self.unitDirty then
		self:MarkDefectModel(false)
		self:ApplyModelFromUnit(self.unitDirty)
	end
end


----------------------------------
-- Consts
----------------------------------
Model.ani = {
	reading = 520,
	asking  = 65,
	yelling = 64,
	talking = 60,
}

Model.file = {
	AvailableQuest	= 'interface\\buttons\\talktome.m2',
	ActiveQuest		= 'interface\\buttons\\talktomequestionmark.m2',
	IncompleteQuest = 'interface\\buttons\\talktomequestion_grey.m2',
	GossipGossip	= 'interface\\buttons\\talktome_chat.m2',
}

Model.LightValues = {
	omnidirectional  = false;
	point            = CreateVector3D(-250, 0, 0);
	ambientIntensity = 75;
	ambientColor     = CreateColor(1, 1, 1);
	diffuseIntensity = 0.25;
	diffuseColor     = CreateColor(1, 1, 1);
}

Model.premature = 0.5