--------------------------------------------------------------------------
-- BlindAssist.lua 
--------------------------------------------------------------------------
--[[
BlindAssist
Author: Zensunim of Dragonblight

]]--
BlindAssist = {
	Version = "0.3.5"; -- Version number (text format)
	VersionNumber = 00305; -- Numeric version number for checking out-of-date clients
	Sounds = { };
	StatusScanDelayTime = .15; -- How many seconds to scan for status changes
	StuckTime = .75; -- How many seconds until the game decides you're stuck
	Data = {
		BetaMode = nil; -- WoW Beta/PTR client detection
		ClassicMode = nil; -- WoW Classic client detection
		CurrentStatus = nil;
		LastScan = 0;
		IsMoving = nil;
		LastX = nil;
		LastY = nil;
		LastZ = nil;
		SpeedX = 0;
		SpeedY = 0;
		SpeedZ = 0;
		LastStuckTime = nil;
		LastFollowingAction = nil;
		LastFollowingTarget = nil;
		ScheduleQueue = { };
	};
};

BlindAssistData = {};

if (select(4, GetBuildInfo()) > 80100) then
	BlindAssist.Data.BetaMode = true;
end

if (select(4, GetBuildInfo()) <= 20000) then
	BlindAssist.Data.ClassicMode = true;
end

function BlindAssist.OnLoad()
	BlindAssistFrame:RegisterEvent("VARIABLES_LOADED");
	SlashCmdList["BA"] = BlindAssist.Command;
	SLASH_BA1 = "/BA";
end

function BlindAssist.Command(arg1)
	local Command = string.upper(arg1);
	local DescriptionOffset = string.find(arg1,"%s",1);
	local Description = nil;
	
	if (DescriptionOffset) then
		Command = string.upper(string.sub(arg1, 1, DescriptionOffset - 1));
		Description = tostring(string.sub(arg1, DescriptionOffset + 1));
	end
	
	if (Command == "ZONE") then
		BlindAssist.AnnounceZone();
	end
	if (Command == "STATUS") then
		BlindAssist.Data.CurrentStatus = "NONE";
		BlindAssist.PlayerStatus();
	end
end

function BlindAssist.ChatPrint(str)
	DEFAULT_CHAT_FRAME:AddMessage("[BA] "..tostring(str), 0.25, 1.0, 0.25);
end

function BlindAssist.ErrorPrint(str)
	DEFAULT_CHAT_FRAME:AddMessage("[BA] "..tostring(str), 1.0, 0.5, 0.5);
end

function BlindAssist.DebugPrint(str)
	--DEFAULT_CHAT_FRAME:AddMessage("[BA] "..tostring(str), 0.75, 1.0, 0.25);
end

function BlindAssist.OnEvent(self, event, ...)
	if (event == "VARIABLES_LOADED") then
		BlindAssist.ChatPrint(BlindAssist.Version.." loaded.");
		BlindAssistFrame:RegisterEvent("ZONE_CHANGED_INDOORS")
		BlindAssistFrame:RegisterEvent("ZONE_CHANGED")
		BlindAssistFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
		BlindAssistFrame:RegisterEvent("PLAYER_STARTED_MOVING")
		BlindAssistFrame:RegisterEvent("PLAYER_STOPPED_MOVING")		
		BlindAssistFrame:RegisterEvent("AUTOFOLLOW_END");
		BlindAssistFrame:RegisterEvent("AUTOFOLLOW_BEGIN");
		return;
	end
	if (event == "PLAYER_ENTERING_WORLD") then
		BlindAssist.AnnounceZone();
		return;
	end
	if (event == "ZONE_CHANGED" or event == "ZONE_CHANGED_INDOORS") then
		BlindAssist.PlayerStatus();
		return;
	end
	if (event == "PLAYER_STARTED_MOVING") then
		BlindAssist.Data.IsMoving = true;
		return;
	end
	if (event == "PLAYER_STOPPED_MOVING") then
		BlindAssist.Data.IsMoving = false;
		BlindAssist.Data.LastStuckTime = nil;
		BlindAssist.Data.LastX = nil;
		BlindAssist.Data.LastY = nil;
		BlindAssist.Data.LastZ = nil;
		if (BlindAssist.Data.CurrentStatus == "Stuck") then
			BlindAssist.Data.CurrentStatus = nil;
		end
		return;
	end
	if (event == "AUTOFOLLOW_BEGIN") then
		local follower = ...;
		BlindAssist.DebugPrint("Follow begin "..tostring(follower));
		BlindAssist.DebugPrint("Follow begin event");
		BlindAssist.Schedule("FollowSound", 0, nil, BlindAssist.StartFollowing, follower, BlindAssist.IsScheduled("FollowSound"));
		BlindAssist.LastFollowingAction = "BEGIN";
		return;
	end
	if (event == "AUTOFOLLOW_END") then
		BlindAssist.DebugPrint("Follow end");
		local scheduled = BlindAssist.IsScheduled("FollowSound");
		if (scheduled and BlindAssist.LastFollowingAction == "END") then
			BlindAssist.DebugPrint("Ignore duplicate end follow command");
			return;
		end
		BlindAssist.DebugPrint("Follow end event");
		BlindAssist.Schedule("FollowSound", .2, nil, BlindAssist.StopFollowing, scheduled);
		BlindAssist.LastFollowingAction = "END";
		return;
	end		
end

function BlindAssist.AnnounceZone()
	local zoneText, _ = GetInstanceInfo()
	BlindAssist.ChatPrint("Current Zone: "..tostring(zoneText));
	
	if (BlindAssist.PlaySound(BlindAssist.Sounds.Battlegrounds, zoneText)) then
		return;
	end
	if (BlindAssist.PlaySound(BlindAssist.Sounds.Dungeons, zoneText)) then
		return;
	end
	if (BlindAssist.PlaySound(BlindAssist.Sounds.Islands, zoneText)) then
		return;
	end
	if (BlindAssist.PlaySound(BlindAssist.Sounds.Zones, zoneText)) then
		return;
	end
end

function BlindAssist.OnUpdate()
	local currentTime = GetTime();
	if (BlindAssist.Data.LastScan + BlindAssist.StatusScanDelayTime > currentTime) then
		-- Not ready to scan, stop in order to save on CPU
		return;
	else
		BlindAssist.Data.LastScan = currentTime;
	end
	
	if (BlindAssist.Data.IsMoving and BlindAssist.LastFollowingAction ~= "BEGIN" and (BlindAssist.Data.ClassicMode or (not IsFalling() and not IsFlying()))) then
		-- Calculate movement speed to check if the player is stuck
		-- Ignore when falling or flying due to Blizzard not providing any Z coordinate information publicly
		local y, x, z, _ = UnitPosition("player");
		if (BlindAssist.Data.LastX and (x or 0) ~= 0 and (y or 0) ~= 0) then
			BlindAssist.Data.SpeedX = abs(BlindAssist.Data.LastX - x);
			BlindAssist.Data.SpeedY = abs(BlindAssist.Data.LastY - y);
			BlindAssist.Data.SpeedZ = abs(BlindAssist.Data.LastZ - z);
			local speed = BlindAssist.Round(BlindAssist.Data.SpeedX + BlindAssist.Data.SpeedY + BlindAssist.Data.SpeedZ, 2);
			if (speed < .5) then
				if (BlindAssist.Data.LastStuckTime) then
					if ((BlindAssist.Data.LastStuckTime + BlindAssist.StuckTime) < currentTime) then
						BlindAssist.AnnouncePlayerStatus("Stuck");
					end
				else
					BlindAssist.Data.LastStuckTime = currentTime;
				end
			else
				if (BlindAssist.Data.CurrentStatus == "Stuck") then
					BlindAssist.AnnouncePlayerStatus("Running");
					BlindAssist.Data.CurrentStatus = nil;
				end
				BlindAssist.Data.LastStuckTime = nil;
			end
		end
		BlindAssist.Data.LastX = x;
		BlindAssist.Data.LastY = y;
		BlindAssist.Data.LastZ = z;
	end
	
	BlindAssist.PlayerStatus();
end

function BlindAssist.PlayerStatus()
	if (BlindAssist.Data.CurrentStatus == "Stuck") then
		return;
	end
	
	if (UnitIsDead("player")) then
		return BlindAssist.AnnouncePlayerStatus("Dead");
	end
	if (UnitIsDeadOrGhost("player")) then
		return BlindAssist.AnnouncePlayerStatus("Ghost");
	end
	if (BlindAssist.IsDiving()) then
		return BlindAssist.AnnouncePlayerStatus("Diving");
	end
	if (IsSwimming()) then
		return BlindAssist.AnnouncePlayerStatus("Swimming");
	end
	if (not BlindAssist.Data.ClassicMode and UnitInVehicle("player")) then
		return BlindAssist.AnnouncePlayerStatus("Vehicle");
	end
	if (IsMounted()) then
		return BlindAssist.AnnouncePlayerStatus("Mounted");
	end
	if (IsIndoors()) then
		return BlindAssist.AnnouncePlayerStatus("Inside");
	end
	if (IsOutdoors()) then
		return BlindAssist.AnnouncePlayerStatus("Outside");
	end
end

function BlindAssist.AnnouncePlayerStatus(status)
	if (BlindAssist.Data.CurrentStatus == status) then
		return;
	end
	BlindAssist.ChatPrint("Current Status: "..status);
	if (BlindAssist.Data.CurrentStatus) then
		BlindAssist.PlaySound(BlindAssist.Sounds.Status, status);
	end
	BlindAssist.Data.CurrentStatus = status;
	return;
end

function BlindAssist.PlaySound(library, name)
	if (library) then
		local sound = library[name];
		if (sound) then
			PlaySoundFile(library.Directory..sound.Name..".ogg", "Master");
			return true;
		end
	end
	return;
end

function BlindAssist.IsDiving()
    for i = 1,MIRRORTIMER_NUMTIMERS do
        local name, _, _, delta = GetMirrorTimerInfo(i)
        if name == "BREATH" and delta < 0 then
            return IsSwimming()
        end
    end
    return false
end

function BlindAssist.Round(num, numDecimalPlaces)
	local mult = 10^(numDecimalPlaces or 0)
	return math.floor(num * mult + 0.5) / mult
end

function BlindAssist.StopFollowing(justStarted)
	if (justStarted) then
		return;
	end
	
	if (BlindAssist.Data.IsMoving) then
		BlindAssist.ChatPrint("Current Status: Running");
		BlindAssist.PlaySound(BlindAssist.Sounds.Status, "Running");
	else
		BlindAssist.ChatPrint("Current Status: Stopped");
		BlindAssist.PlaySound(BlindAssist.Sounds.Status, "Stopped");
	end
end

function BlindAssist.StartFollowing(follower, justStopped)
	if (follower) then
		if (BlindAssist.Data.LastFollowingTarget and BlindAssist.Data.LastFollowingTarget == follower and justStopped) then
			return;
		end
		if (BlindAssist.Data.LastFollowingTarget ~= follower) then
			BlindAssist.Data.LastFollowingTarget = follower;
		end
	end
	BlindAssist.ChatPrint("Current Status: Following "..tostring(follower));
	BlindAssist.PlaySound(BlindAssist.Sounds.Status, "Following");
end
