-- Copyright (C) 2010 Tyson Brown
-- 
-- This file is part QuestHelper2, an AddOn for World of Warcraft.
-- 
-- QuestHelper2 is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- 
-- QuestHelper2 is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with QuestHelper2. If not, see <http://www.gnu.org/licenses/>.

if LibDebug then LibDebug() end
local shared = select(2, ...)
local L = shared.L

-- This file is in charge of adding a frame to the tracker
-- displaying an estimate of the remaining flight timer
-- remaining, and removing it when the player lands.

local frame = CreateFrame("Frame", nil, UIParent)
local icon = frame:CreateTexture(nil, "ARTWORK")
local progress_left = frame:CreateTexture(nil, "ARTWORK")
local progress_right = frame:CreateTexture(nil, "ARTWORK")

frame:SetHeight(46)

icon:SetWidth(32)
icon:SetHeight(32)
icon:SetPoint("LEFT")
icon:SetTexture("Interface\\AddOns\\QuestHelper2\\FlightTimer\\Icon.tga")

progress_left:SetHeight(10)
progress_left:SetPoint("BOTTOMLEFT", 40, 2)
progress_left:SetTexture("Interface\\AddOns\\QuestHelper2\\FlightTimer\\ProgressBar.tga")
progress_left:SetVertexColor(.2,1,.2)

progress_right:SetHeight(10)
progress_right:SetPoint("BOTTOMRIGHT", -8, 2)
progress_right:SetTexture("Interface\\AddOns\\QuestHelper2\\FlightTimer\\ProgressBar.tga")
progress_right:SetVertexColor(.6,.6,.6,.5)

local dest_text = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
local time_text = frame:CreateFontString(nil, "OVERLAY", "TextStatusBarTextLarge")

time_text:SetJustifyH("CENTER")

local progress_width = 0
local start_time = 0
local estimated_total_time = 0

local function updateProgress()
  local now = GetTime()
  local remaining = start_time+estimated_total_time-now
  
  if remaining < -3 then
    time_text:SetText(L.FLIGHT_ETA_UNKNOWN)
    progress_left:Hide()
    progress_right:Hide()
    return nil
  else
    remaining = math.max(0, math.floor(remaining+0.5))
    
    -- We prevent pct from being exactly 0 or 1, because setting the texture widths to
    -- 0 sets the textures to their default width instead of making them invisible.
    local pct = math.max(0.01, math.min(0.99, (now-start_time)/estimated_total_time))
    
    time_text:SetFormattedText(L.FLIGHT_ETA, floor(remaining/60), remaining%60)
    
    progress_left:SetWidth(pct*progress_width)
    progress_right:SetWidth((1-pct)*progress_width)
    progress_left:SetTexCoord(0,pct,0,1)
    progress_right:SetTexCoord(pct,1,0,1)
  end
  
  return 1
end

local old_setWidth = frame.SetWidth
function frame:SetWidth(w)
  old_setWidth(self, w)
  progress_width = w-48
  updateProgress()
end

local timer_running = false

local function stopTimer()
  timer_running = false
  shared.removeFrameFromTracker(frame)
  shared.cancelDelayed(updateProgress)
  shared.cancelEvent("PLAYER_CONTROL_GAINED", stopTimer)
end

local function startTimer(dest, eta)
  stopTimer()
  shared.onEvent("PLAYER_CONTROL_GAINED", stopTimer)
  shared.onDelay(1, updateProgress)
  dest_text:SetText(dest)
  
  updateProgress()
  
  local dest_height, time_height = dest_text:GetStringHeight(), time_text:GetStringHeight()
  
  dest_text:SetPoint("TOPLEFT", frame, "TOPLEFT", 32, -2)
  dest_text:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 0, -2-dest_height)
  
  time_text:SetPoint("TOPLEFT", frame, "TOPLEFT", 32, -4-dest_height)
  time_text:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 0, -4-dest_height-time_height)
  
  frame:SetHeight(18+dest_height+time_height)
  
  timer_running = true
  start_time = GetTime()
  estimated_total_time = eta
  progress_left:Show()
  progress_right:Show()
  
  if shared.settings.flight_timer_shown then
    shared.addFrameToTracker(frame, 0)
  end
end

shared.onSetting("flight_timer_shown", function (show)
  if timer_running then
    if show then
      shared.addFrameToTracker(frame, 0)
    else
      shared.removeFrameFromTracker(frame)
    end
  end
end)

local node_eta = {}

hooksecurefunc("TakeTaxiNode", function (node)
  local eta = node_eta[node]
  
  if eta then
    local name = TaxiNodeName(node)
    startTimer(name:match("^(.-),") or name, eta)
  end
end)

hooksecurefunc("TaxiNodeOnButtonEnter", function (button)
  if shared.settings.flight_map_estimates then
    local eta = node_eta[button:GetID()]
    if eta then
      eta = floor(eta+0.5)
      
      -- Append the estimated flight time to the node's tooltip.
      GameTooltip:AddLine(L.FLIGHT_ETA:format(floor(eta/60), eta%60))
      GameTooltip:Show()
    end
  end
end)

local function nodeMapPosition(node_id)
  local position = shared.static.flight_nodes[node_id]
  if position then
    return shared.mapVirtualPosition(unpack(position))
  else
    print("Missing node: ", node_id)
  end
end

local function convertFlightMapPosition(x, y, map_id)
  local region = shared.static.flight_maps[map_id]
  
  return (region.x2-region.x1)*x+region.x1, (region.y2-region.y1)*y+region.y1
end

-- Try to calculate some reasonable time to distance ratios
-- from the existing data, for use in estimating times we don't have data for.
local time_fallbacks = {}
for map_id, start_map in pairs(shared.static.flight_time_data) do
  local distance_total, time_total = 0, 0
  
  for origin, end_map in pairs(start_map) do
    for destination, duration in pairs(end_map) do
      local x1, y1, map1 = nodeMapPosition(origin)
      local x2, y2, map2 = nodeMapPosition(destination)
      
      assert(x1)
      assert(x2)
      assert(map1 == map2)
      assert(map1 == map_id)
      
      distance_total = distance_total + ((x1-x2)^2 + (y1-y2)^2)^.5
      time_total = time_total + duration
    end
  end
  
  time_fallbacks[map_id] = time_total/distance_total
end

local function estimateTime(map_id, sx, sy, dx, dy)
  sx, sy = convertFlightMapPosition(sx, sy, map_id)
  dx, dy = convertFlightMapPosition(dx, dy, map_id)
  
  local best_end_map, best_end_map_dist
  
  for origin, end_map in pairs(shared.static.flight_time_data[map_id]) do
    local x, y = nodeMapPosition(origin)
    local d = (sx-x)^2 + (sy-y)^2
    if not best_end_map or d < best_end_map_dist then
      best_end_map, best_end_map_dist = end_map, d
    end
  end
  
  if best_end_map and best_end_map_dist^.5 < 1 then
    local best_duration, best_duration_dist
    
    for destination, duration in pairs(best_end_map) do
      local x, y = nodeMapPosition(destination)
      local d = (dx-x)^2 + (dy-y)^2
      if not best_duration or d < best_duration_dist then
        best_duration, best_duration_dist = duration, d
      end
    end
    
    if best_duration and best_duration_dist^.5 < 1 then
      return best_duration, false
    end
  end
  
  return ((sx-dx)^2 + (sy-dy)^2)^.5 * time_fallbacks[map_id], true
end

--[===[@debug@
local function TaxiNodePosName(x, y)
  local best_name, best_dist
  
  for k=1,NumTaxiNodes() do
    local nx, ny = TaxiNodePosition(k)
    local dist = ((x-nx)^2+(y-ny)^2)^.5
    
    if not best_name or dist < best_dist then
      best_name, best_dist = TaxiNodeName(k), dist
    end
  end
  
  return best_name or "???"
end
--@end-debug@]===]

shared.onEvent("TAXIMAP_OPENED", function ()
  table.wipe(node_eta)
  
  local map_id = shared.areaVirtualMap(shared.whereAmI())
  
  if map_id then
    for i=1,NumTaxiNodes() do
      if "REACHABLE" == TaxiNodeGetType(i) then
        --TaxiNodeSetCurrent(i)
        local eta = 1
        
        for j=1,GetNumRoutes(i) do
          local time, guessed = estimateTime(map_id, TaxiGetSrcX(i, j), TaxiGetSrcY(i, j), TaxiGetDestX(i, j), TaxiGetDestY(i, j))
          eta = eta + time
          
          --[===[@debug@
          if guessed then
            _G.print(("Missing link from %s to %s; guessed travel time to be %.1f minutes."):format(TaxiNodePosName(TaxiGetSrcX(i, j), TaxiGetSrcY(i, j)), TaxiNodePosName(TaxiGetDestX(i, j), TaxiGetDestY(i, j)), time/60))
          end
          --@end-debug@]===]
        end
        
        node_eta[i] = eta
      end
    end
  end
end)
