-- 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 map_stack = {}

-- Stores the current map so that you can change maps temporarly and then call popMap() to
-- restore the map back to the state it was in previously.
function shared.pushMap()
  assert(#map_stack < 32*3, "Too many pushMap calls, not enough popMap calls.")
  
  local len = #map_stack
  map_stack[len+1] = GetCurrentMapAreaID()
  map_stack[len+2] = GetCurrentMapContinent()
  map_stack[len+3] = GetCurrentMapDungeonLevel()
end

-- Restores the map previously stored by pushMap()
function shared.popMap()
  local len = #map_stack
  assert(len > 2, "No map states are currently pushed.")
  
  local area, continent, level = map_stack[len-2], map_stack[len-1], map_stack[len]
  
  if GetCurrentMapAreaID() ~= area or
     GetCurrentMapContinent() ~= continent or
     GetCurrentMapDungeonLevel() ~= level then
    if area < 0 then
      SetMapZoom(continent)
    else
      SetMapByID(area)
      SetDungeonMapLevel(level)
    end
  end
  
  map_stack[len-2], map_stack[len-1], map_stack[len] = nil, nil, nil
end

-- These tables are in Static/areas.lua
local map_offset, area_info = shared.static.map_offset, shared.static.area_info

for area_id, info in pairs(area_info) do
  
  for level, level_info in pairs(info.levels) do
    level_info.map, level_info.virtual_map = level_info.map or info.map, level_info.virtual_map or info.virtual_map
  end
end

-- Every time we create information about a new unknown region, we use
-- this as the map region and decrease this value.
-- This is done so that multiple unknown regions don't appear to exist
-- in the same space.
local next_bad_map_id = -2

local function getArea(area_id, level)
  local area_data = area_info[area_id]
  
  if not area_data then
    print("Unknown area #"..area_id)
    area_data = { levels = {} }
    
    area_info[area_id] = area_data
    
    next_bad_map_id, area_data.map = next_bad_map_id-1, next_bad_map_id
  end
  
  local level_info = area_data.levels[level]
  
  if not level_info then
    shared.pushMap()
    
    SetMapByID(area_id)
    SetDungeonMapLevel(level)
    
    local actual_id, actual_level, x1, x2, y1, y2 = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel()
    
    shared.popMap()
    
    if actual_id == area_id and actual_level == level and x1 then
      level_info = {
        map = area_data.map,
        virtual_map = area_data.virtual_map,
        x1 = -x1,
        y1 = -y1,
        x2 = -x2,
        y2 = -y2
      }
    else
      print("Unknown map for area #"..area_id..", level "..level)
      
      level_info = {
        map = next_bad_map_id,
        x1 = 0,
        y1 = 0,
        x2 = 1500,
        y2 = 1000
      }
      
      next_bad_map_id = next_bad_map_id-1
    end
    
    area_data.levels[level] = level_info
  end
  
  return level_info
end

-- Gets the location of an area on a map.
-- This is only useful for displaying minimap tiles; you probably want mapVirtualPosition instead.
function shared.mapPosition(area_id, level, x, y)
  if not area_id then
    return nil, nil
  end
  
  local info = getArea(area_id, level)
  
  return (info.x2-info.x1)*x+info.x1, (info.y2-info.y1)*y+info.y1, info.map
end

local function worldMapPosition(x, y, map)
  -- These transforms were generated by dev/support_addons/WorldMapPositions/build_transforms
  
  if map == 0 then
    return x*2.0958080260307e-05 + 0.76946105123686, y*3.1437138609193e-05 + 0.46407173402318
  elseif map == 1 then
    return x*2.0958084472108e-05 + 0.1776447109327, y*3.1437126861421e-05 + 0.57934128889193
  elseif map == 571 then
    return x*2.09580662841e-05 + 0.52894210631555, y*3.1437119607471e-05 + 0.34730532757803
  end
end

function shared.areaRecommendedLevel(area_id)
  local info = area_info[area_id]
  
  if info then
    return info.level_min, info.level_max
  else
    return 1, 85
  end
end

-- Converts a map position into a position in a given area.
function shared.areaPosition(x, y, map, area_id, level)
  local info = getArea(area_id, level)
  
  if area_id == -1 then
    return worldMapPosition(x, y, map)
  elseif map == info.virtual_map then
    local offset = map_offset[info.map][info.virtual_map]
    return (x-info.x1-offset.x)/(info.x2-info.x1), (y-info.y1-offset.y)/(info.y2-info.y1)
  elseif map == info.map then
    return (x-info.x1)/(info.x2-info.x1), (y-info.y1)/(info.y2-info.y1)
  end
end

function shared.areaMap(area_id, level)
  local info = getArea(area_id, level)
  return info and info.map
end

function shared.areaVirtualMap(area_id, level)
  local info = getArea(area_id, level)
  return info and (info.virtual_map or info.map)
end

-- Gets the location of a position on the virtual map.
-- This is the function you want to use if you want to calculate directions or distances.
function shared.mapVirtualPosition(area_id, level, x, y)
  if not area_id then
    return nil, nil
  end
  
  local info = getArea(area_id, level)
  
  if info.virtual_map then
    local offset = map_offset[info.map][info.virtual_map]
    return (info.x2-info.x1)*x+info.x1+offset.x, (info.y2-info.y1)*y+info.y1+offset.y, info.virtual_map
  end
  
  return (info.x2-info.x1)*x+info.x1, (info.y2-info.y1)*y+info.y1, info.map
end

-- Convert coordinates that are relative to one area to be relative to another area.
function shared.convertCoords(area_id, level, x, y, dest_area_id, dest_level)
  if area_id == dest_area_id and level == dest_level then
    return x, y
  end
  
  local x, y, map = shared.mapVirtualPosition(area_id, level, x, y)
  return shared.areaPosition(x, y, map, dest_area_id, dest_level)
  --[[
  if not area_id or not dest_area_id then
    return nil, nil
  end
  
  if dest_area_id == -1 then
    -- Special case: World Map.
    return worldMapPosition(shared.mapVirtualPosition(area_id, level, x, y))
  end
  
  local src, dest = getArea(area_id, level), getArea(dest_area_id, dest_level)
  
  if src == dest then
    return x, y
  end
  
  local src_map, dest_map = src.virtual_map or src.map, dest.virtual_map or dest.map
  
  if src_map ~= dest_map or not src.x1 or not dest.x1 then
    -- Don't exist on the same map, can't convert.
    return nil, nil
  end
  
  local src_xoff, src_yoff, dest_xoff, dest_yoff = 0, 0, 0, 0
  
  if src_map ~= src.map then
    local offset = map_offset[src.map][src_map]
    src_xoff, src_yoff = offset.x, offset.y
  end
  
  if dest_map ~= dest.map then
    local offset = map_offset[dest.map][dest_map]
    dest_xoff, dest_yoff = offset.x, offset.y
  end
  
  return ((src.x2-src.x1)*x+src_xoff+src.x1-dest_xoff-dest.x1)/(dest.x2-dest.x1),
         ((src.y2-src.y1)*y+src_yoff+src.y1-dest_yoff-dest.y1)/(dest.y2-dest.y1)
  ]]
end
