-- 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

local atan2 = math.atan2

local compass = CreateFrame("Frame", "QuestHelper2_Compass", UIParent)
local background = compass:CreateTexture(nil, "BACKGROUND")
local center_marker = compass:CreateTexture(nil, "BACKGROUND")

background:SetTexture("Interface\\AddOns\\QuestHelper2\\Compass\\Compass.tga", true)
center_marker:SetTexture("Interface\\AddOns\\QuestHelper2\\Compass\\Center.tga")

center_marker:SetPoint("CENTER")
center_marker:SetWidth(32)
center_marker:SetHeight(64)
center_marker:SetAlpha(0.4)

background:SetWidth(256)
background:SetHeight(32)
background:SetAllPoints()
background:SetAlpha(0.4)

compass:Hide()
compass:SetFrameStrata("BACKGROUND")
compass:SetPoint("TOP", UIParent, "TOP", 0, -50)
compass:SetWidth(1024)
compass:SetHeight(32)
compass:SetScale(0.7)
compass:SetMovable(true)
compass:RegisterForDrag("LeftButton")
compass:SetClampedToScreen(true)

compass:SetScript("OnDragStart", compass.StartMoving)
compass:SetScript("OnDragStop", compass.StopMovingOrSizing)

local compass_menu_settings = {
  "compass_shown",
  "compass_hide_combat",
  "compass_locked"
}

compass:SetScript("OnMouseUp", function (self, button)
  if button == "RightButton" then
    shared.showMainMenu(self, 64, 0, compass_menu_settings, "compass")
  end
end)

-- Unused marker textures.
local unused_markers = {}

-- Maps an objective to a set of positions.
local objectives = {}

-- Maps a position to a marker, or to false if we decided not to show a marker.
local positions = {}

-- Maps a marker to the percentage across the compass, or to true to for the offset to be
-- calculated on the fly from marker.{x,y,map}
local markers = {}

local north, east, south, west = 
  compass:CreateFontString(nil, "BACKGROUND", "SystemFont_OutlineThick_Huge4"),
  compass:CreateFontString(nil, "BACKGROUND", "SystemFont_OutlineThick_Huge2"),
  compass:CreateFontString(nil, "BACKGROUND", "SystemFont_OutlineThick_Huge2"),
  compass:CreateFontString(nil, "BACKGROUND", "SystemFont_OutlineThick_Huge2")

shared.onSetting("locale", function ()
  north:SetText(L.MARKER_NORTH)
  east:SetText(L.MARKER_EAST)
  south:SetText(L.MARKER_SOUTH)
  west:SetText(L.MARKER_WEST)
end)

markers[north] = 0
markers[east] = .25
markers[south] = .5
markers[west] = .75

local path = {}
local function positionInfo(position)
  local area, level, x, y, ex, ey = shared.whereAmI()
  
  if area then
    local time, distance = shared.calculateTravelTime(area, level, x, y, ex, ey, position[1], position[2], position[3], position[4], position[5], position[6], path)
    local target_x, target_y, target_map = path[4], path[5], path[6]
    
    table.wipe(path)
    
    if target_x and distance then
      local x, y, map = shared.mapVirtualPosition(area, level, x, y, ex, ey)
      
      if map == target_map then
        local dx, dy = target_x-x, y-target_y
        local extra = (dx*dx + dy*dy)^.5
        return target_x, target_y, target_map, distance-extra, extra, atan2(dx, dy)*0.1591549430918953
      end
    end
  end
end

local function baseOffset()
  return GetPlayerFacing()*0.1591549430918953 + 0.5
end

local function colourMarker(marker, distance)
  if distance < 50 then
    marker:SetDrawLayer("OVERLAY")
  elseif distance < 200 then
    marker:SetDrawLayer("ARTWORK")
  elseif distance < 1000 then
    marker:SetDrawLayer("BORDER")
  elseif distance < 2000 then
    marker:SetDrawLayer("BACKGROUND")
  end
  
  local log_distance = math.log(distance*0.00343656365691809+1)
  
  if log_distance < 0.5 then
    -- Green to Yellow
    marker:SetVertexColor(log_distance*2, 1, 0)
  elseif log_distance < 1.0 then
    -- Yellow to Red
    marker:SetVertexColor(1, 2-log_distance*2, 0, 0.5/log_distance)
  else
    -- Red, fade out based on distance.
    marker:SetVertexColor(1, 0, 0, 0.5/log_distance)
  end
end

local function compassUpdate()
  local base, w = baseOffset(), compass:GetWidth()
  
  local x, y, map = shared.mapVirtualPosition(shared.whereAmI())
  
  if map then
    for marker, offset in pairs(markers) do
      if offset == true then
        if marker.map == map then
          local dx, dy = marker.x-x, y-marker.y
          local offset, distance = atan2(dx, dy)*0.1591549430918953, marker.distance+(dx*dx+dy*dy)^.5
          
          marker:SetPoint("CENTER", compass, "TOPLEFT", (base+offset)%1*w, -16)
          colourMarker(marker, distance)
          marker:Show()
        else
          marker:Hide()
        end
      else
        marker:SetPoint("CENTER", compass, "TOPLEFT", (base+offset)%1*w, -16)
      end
    end
  end
  
  background:SetTexCoord(-2-base*4, 2-base*4, 0, 1)
  
  return 0.05
end

local function clearPositionSet(position_set)
  for position in pairs(position_set) do
    local marker = positions[position]
    
    if marker then
      markers[marker] = nil
      unused_markers[marker] = true
      marker:Hide()
    end
    
    position_set[position] = nil
    positions[position] = nil
  end
end

local function updateMarker(position)
  local marker = positions[position]
  
  local x, y, map, distance, distance_extra, offset = positionInfo(position)
  
  if x then
    if not marker then
      marker = next(unused_markers)
      
      if marker then
        unused_markers[marker] = nil
      else
        marker = compass:CreateTexture(nil, "ARTWORK")
        marker:SetTexture("Interface\\AddOns\\QuestHelper2\\Compass\\Marker.tga")
        marker:SetWidth(32)
        marker:SetHeight(32)
      end
      
      positions[position] = marker
    end
    
    markers[marker] = true
    marker.x, marker.y, marker.map, marker.distance = x, y, map, distance
    marker:SetPoint("CENTER", compass, "TOPLEFT", (baseOffset()+offset)%1*compass:GetWidth(), -16)
    colourMarker(marker, distance+distance_extra)
    marker:Show()
  else
    if marker then
      markers[marker] = nil
      unused_markers[marker] = true
      marker:Hide()
    end
    
    positions[position] = false
  end
end

local function updateObjective(objective)
  local position_set = objectives[objective]
  
  if objective.enabled and objective.obtainable and not objective.complete then
    if position_set then
      clearPositionSet(position_set)
    else
      position_set = {}
      objectives[objective] = position_set
    end
    
    if shared.settings.compass_only_route_locations then
      local area, level, x, y, ex, ey = objective:getRouteLocation()
      
      if area then
        local position = { area, level, x, y, ex, ey }
        position_set[position] = true
        updateMarker(position)
      end
    else
      for area, level, x, y, ex, ey in objective:iterate() do
        local position = { area, level, x, y, ex, ey }
        position_set[position] = true
        updateMarker(position)
      end
    end
  else
    if position_set then
      clearPositionSet(position_set)
      objectives[objective] = nil
    end
  end
end

shared.onSetting("compass_only_route_locations", function ()
  for objective in pairs(objectives) do
    updateObjective(objective)
  end
end)

shared.onCombatEnter(function ()
  compass:EnableMouse(false)
  
  if shared.settings.compass_hide_combat then
    compass:StopMovingOrSizing()
    compass:Hide()
  end
end)

shared.onCombatLeave(function ()
  if not shared.settings.compass_locked then
    compass:EnableMouse(true)
  end
  
  if shared.settings.compass_hide_combat and shared.settings.compass_shown then
    compass:Show()
  end
end)

shared.onSetting("compass_locked", function (locked)
  if locked then
    compass:EnableMouse(false)
  elseif not InCombatLockdown() then
    compass:EnableMouse(true)
  end
end)

local function updateVisibility()
  if shared.settings.compass_shown and not (shared.settings.compass_hide_combat and InCombatLockdown()) then
    compass:Show()
  else
    compass:Hide()
  end
end

shared.onSetting("compass_shown", updateVisibility)
shared.onSetting("compass_hide_combat", updateVisibility)

shared.onSetting("compass_width", function (width)
  compass:SetWidth(width)
  compassUpdate()
end)

shared.onSetting("compass_scale", function (scale)
  compass:SetScale(scale)
end)

shared.onObjectiveEnabled(updateObjective)
shared.onObjectiveModified(updateObjective)
shared.onObjectiveDisabled(updateObjective)

local next_position = nil

local function updateNextPosition()
  if positions[next_position] ~= nil then
    updateMarker(next_position)
    next_position = next(positions, next_position)
  else
    next_position = next(positions, nil)
  end
  
  return 0.1
end

local function updateAllPositions()
  for position in pairs(positions) do
    updateMarker(position)
  end
  
  next_position = nil
end

shared.onEvent("MINIMAP_UPDATE_ZOOM", shared.callOnce, function ()
  if compass:IsVisible() then
    updateAllPositions()
  end
end)

compass:SetScript("OnShow", function ()
  compassUpdate()
  updateAllPositions()
  
  shared.onDelay(0.05, compassUpdate)
  shared.onDelay(0.1, updateNextPosition)
end)

compass:SetScript("OnHide", function ()
  shared.cancelDelayed(compassUpdate)
  shared.cancelDelayed(updateNextPosition)
end)
