-- 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 arrow = CreateFrame("Frame", "QuestHelper2_QuestArrow", UIParent)
arrow:SetWidth(64)
arrow:SetHeight(64)
arrow:SetScale(0.7)
arrow:SetFrameStrata("BACKGROUND")
arrow:SetPoint("TOP", UIParent, "TOP", 0, -30)
arrow:Hide()
arrow:SetMovable(true)
arrow:RegisterForDrag("LeftButton")
arrow:SetClampedToScreen(true)

local arrow_menu_settings = {
  "arrow_shown",
  "arrow_hide_combat",
  "arrow_locked",
  "arrow_text",
  "arrow_distance"
}

arrow:SetScript("OnDragStart", arrow.StartMoving)
arrow:SetScript("OnDragStop", arrow.StopMovingOrSizing)
arrow:SetScript("OnMouseUp", function (self, button)
  if button == "RightButton" then
    shared.showMainMenu(self, 64, 0, arrow_menu_settings, "arrow")
  end
end)

local texture = arrow:CreateTexture()
texture:SetAllPoints()

local arrow_text = arrow:CreateFontString(nil, "OVERLAY", "GameFontNormal")
arrow_text:SetPoint("TOP", arrow, "BOTTOM", 0, 8)
local current_arrow_text = ""

local distance_text = arrow:CreateFontString(nil, "OVERLAY", "GameFontNormal")
distance_text:SetPoint("TOP", arrow_text, "BOTTOM", 0, -2)

local arrow_text_prev = arrow:CreateFontString(nil, "OVERLAY", "GameFontNormal")
arrow_text_prev:Hide()
local text_transition = 1

local arrow_text_size_preview = arrow:CreateTexture(nil, "BACKGROUND")
arrow_text_size_preview:SetTexture(1,0,1,.4)
arrow_text_size_preview:SetAllPoints(arrow_text)

local shown = true
local distances = false
local text_shown = true
local objective = nil

local animation = 0

-- The target location, not updated every frame.
local target_x, target_y, target_map, target_distance

local update_target_delay = 0

local path = {}

local function updateTargetLocation()
  update_target_delay = 0
  
  target_x, target_y, target_map, target_distance = nil, nil, nil, nil
  
  local a,l,x,y,ex,ey = shared.whereAmI()
  
  if a then
    local ta,tl,tx,ty,tex,tey
    local best_time = nil
    
    for oa,ol,ox,oy,oex,oey in objective:iterate() do
      local time = shared.calculateTravelTime(a,l,x,y,ex,ey,oa,ol,ox,oy,oex,oey)
      
      if time and (not best_time or time < best_time) then
        ta,tl,tx,ty,tex,tey,best_time = oa,ol,ox,oy,oex,oey,time
      end
    end
    
    if ta then
      table.wipe(path)
      
      target_distance = select(2, shared.calculateTravelTime(a,l,x,y,ex,ey,ta,tl,tx,ty,tex,tey,path))
      target_x, target_y, target_map = path[4], path[5], path[6]
      
      if target_x and target_distance then
        target_distance = target_distance - ((path[1]-target_x)^2 + (path[2]-target_y)^2)^.5
      end
    end
  end
end

shared.onEvent("MINIMAP_UPDATE_ZOOM", shared.callOnce, function()
  if objective and shown then
    -- If the player moves in or out of a building, update the
    -- target location immediately, because there's a good chance the arrow was
    -- telling the player to walk through its entrance, which they just did.
    updateTargetLocation()
  end
end)

local function updateArrowDirection()
  assert(objective and shown)
  
  if update_target_delay > 30 then
    updateTargetLocation()
  else
    update_target_delay = update_target_delay + 1
  end
  
  local best_angle, best_dist
  
  if target_x then
    local x, y, map = shared.mapVirtualPosition(shared.whereAmI())
    
    if x and map == target_map then
      local dx, dy = target_x-x, y-target_y
      best_angle = math.atan2(dx, dy)
      
      if target_distance then
        best_dist = target_distance + sqrt(dx*dx + dy*dy)
      end
    end
  end
  
  local index = 0
  
  if best_angle then
    if best_dist and best_dist < 3 then
      texture:SetTexture("Interface\\AddOns\\QuestHelper2\\Arrow\\ArrowDown.tga")
      texture:SetVertexColor(0, 1, 0)
      animation = (animation + 1)%64
      index = animation
    else
      texture:SetTexture("Interface\\AddOns\\QuestHelper2\\Arrow\\Arrow.tga")
      index = math.floor((best_angle+GetPlayerFacing())*10.1859163578813+0.5)%64
      
      -- Will be red at 500 yards,
      -- Will be yellow at 189 yards.
      -- Will be green at 0 yards.
      local colour = best_dist and math.min(math.log(best_dist*0.00343656365691809+1), 1) or 1
      
      if colour < 0.5 then
        texture:SetVertexColor(colour*2, 1, 0)
        -- Green to Yellow
      else
        -- Yellow to Red
        texture:SetVertexColor(1, 2-colour*2, 0)
      end
    end
  else
    texture:SetTexture("Interface\\AddOns\\QuestHelper2\\Arrow\\ArrowUnknown.tga")
    texture:SetVertexColor(1, .5, 0)
    animation = (animation + 1)%64
    index = animation 
  end
  
  local scale = 1/8
  local x = (index%8)*scale
  local y = math.floor(index/8)*scale
  
  texture:SetTexCoord(x,x+scale,y,y+scale)
  
  if distances and best_dist then
    distance_text:SetFormattedText(shared.L.ARROW_DISTANCE, best_dist)
    distance_text:Show()
  else
    distance_text:Hide()
  end
  
  return 0.08
end

local function hideTextPreview()
  arrow_text_size_preview:Hide()
end

shared.onSetting("arrow_text", function (is_shown)
  text_shown = is_shown
  
  if is_shown then
    arrow_text:Show()
    
    if objective then
      arrow_text:SetText(objective.formatted_text)
    end
    
    distance_text:SetPoint("TOP", arrow_text, "BOTTOM", 0, -2)
  else
    arrow_text:Hide()
    distance_text:SetPoint("TOP", arrow, "BOTTOM", 0, 8)
  end
end)

shared.onSetting("arrow_text_width", function (width)
  arrow_text:SetWidth(width)
  arrow_text_prev:SetWidth(width)
  shared.cancelDelayed(hideTextPreview)
  shared.onDelay(1, hideTextPreview)
  arrow_text_size_preview:Show()
end)

shared.onSetting("arrow_scale", function (scale)
  arrow:SetScale(scale)
end)

shared.onSetting("arrow_locked", function (locked)
  arrow:EnableMouse(not locked and not InCombatLockdown())
end)

local function updateHiddenState(entering_combat)
  local old_shown = shown
  
  shown = shared.settings.arrow_shown and not (shared.settings.arrow_hide_combat and (InCombatLockdown() or entering_combat))
  
  if shown ~= old_shown then
    shared.cancelDelayed(updateArrowDirection)

    if shown and objective then
      updateTargetLocation()
      updateArrowDirection()
      arrow:Show()
      shared.onDelay(0.1, updateArrowDirection)
    else
      arrow:Hide()
    end
  end
end

shared.onCombatEnter(function ()
  arrow:StopMovingOrSizing()
  arrow:EnableMouse(false)
  updateHiddenState(true)
end)

shared.onCombatLeave(function ()
  updateHiddenState(false)
  arrow:EnableMouse(not shared.settings.arrow_locked)
end)

shared.onSetting("arrow_shown", updateHiddenState, false)
shared.onSetting("arrow_hide_combat", updateHiddenState, false)

shared.onSetting("arrow_distance", function (show_distances)
  distances = show_distances
  
  if objective and shown then
    updateArrowDirection()
  end
end)

local function arrowOnUpdate(arrow, delta)
  text_transition = text_transition + delta*10.0
  if text_transition > 1 then
    text_transition = 1
    arrow_text:SetAlpha(1)
    arrow_text:SetPoint("TOP", arrow, "BOTTOM", 0, 8)
    arrow_text_prev:Hide()
    arrow:SetScript("OnUpdate", nil)
  else
    arrow_text:SetPoint("TOP", arrow, "BOTTOM", 0, 8*text_transition)
    arrow_text_prev:SetPoint("TOP", arrow, "BOTTOM", 0, 8-8*text_transition)
    arrow_text:SetAlpha(text_transition)
    arrow_text_prev:SetAlpha(1-text_transition)
  end
end

shared.onPrimaryObjectiveChanged(function (new_objective)
  objective = new_objective
  
  shared.cancelDelayed(updateArrowDirection)
  
  if objective then
    if text_shown then
      local text = objective.formatted_text
      
      if text ~= current_arrow_text then
        arrow_text_prev:SetText(current_arrow_text)
        arrow_text:SetText(text)
        current_arrow_text = text
        text_transition = 1-text_transition
        arrowOnUpdate(arrow, 0)
        arrow_text:Show()
        arrow_text_prev:Show()
        arrow:SetScript("OnUpdate", arrowOnUpdate)
      end
    end
    
    if shown then
      updateTargetLocation()
      updateArrowDirection()
      arrow:Show()
      shared.onDelay(0.1, updateArrowDirection)
    end
  else
    arrow:Hide()
  end
end)
