-- 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, ...)

-- This file supplies pather objects for generating straight line paths
-- between two paths.

local meta = {}
meta.__index = meta

function shared.createStraightLinePather(map_id)
  return setmetatable({map_id = map_id, starts={}}, meta)
end

-- Marks a pather dirty; returns true if it was previously clean.
local function markDirty(self)
  local was_dirty = self.dirty
  self.dirty = true
  return not was_dirty
end

function meta:addLink(map_link)
  -- TODO
  
  return markDirty(self)
end

function meta:removeLink(map_link)
  -- TODO
  
  return markDirty(self)
end

function meta:clearLinks(map_link)
  -- TODO
  
  return markDirty(self)
end

function meta:updateTravelSpeedAssumptions(run, swim, mount, air)
  local changed = false
  
  if self.inside_speed ~= run then
    self.inside_speed = run
    changed = true
  end
  
  local outside_speed = math.max(run, mount or 0, air or 0)
  
  if self.outside_speed ~= outside_speed then
    self.outside_speed = outside_speed
    changed = true
  end
  
  return changed and markDirty(self)
end

function meta:clearStart()
  self.dirty = nil
  table.wipe(self.starts)
end

function meta:addStart(src_area_id, src_level, src_x, src_y, src_ex, src_ey, time_overhead, distance_overhead)
  local x, y, map = shared.mapVirtualPosition(src_area_id, src_level, src_x,  src_y)
  
  assert(map == self.map_id)
  
  local ex, ey
  
  if src_ex then
    ex, ey = shared.mapVirtualPosition(src_area_id, src_level, src_ex, src_ey)
  end
  
  if time_overhead then
    table.insert(self.starts, {
      mx=x, my=y, mex=ex, mey=ey,
      area_id = src_area_id, level = src_level, x = src_x, y = src_y, ex = src_ex, ey = src_ey,
      time_overhead=time_overhead,
      distance_overhead=distance_overhead
    })
  else
    table.insert(self.starts, {
      mx=x, my=y, mex=ex, mey=ey,
      area_id = src_area_id, level = src_level, x = src_x, y = src_y, ex = src_ex, ey = src_ey,
      time_overhead=0,
      distance_overhead=0
    })
  end
end

function meta:setStart(area_id, level, x, y, ex, ey, time_overhead, distance_overhead)
  self:clearStart()
  self:addStart(area_id, level, x, y, ex, ey, time_overhead, distance_overhead)
end

local function mag(x, y)
  return sqrt(x*x + y*y)
end

local function sameEntrance(x1, y1, x2, y2)
  if x1 and x2 then
    return mag(x2-x1, y2-y1) < 20
  end
  
  return false
end

function meta:getEndTravelTime(area_id, level, x, y, ex, ey, path)
  local x, y, map = shared.mapVirtualPosition(area_id, level, x, y)
  
  assert(map == self.map_id, "Pather object is for map #"..self.map_id.." but coordinates are for map #"..map)
  
  local best_start, best_time, best_distance
  
  if ex then
    ex, ey = shared.mapVirtualPosition(area_id, level, ex, ey)
  end
  
  for i, start in ipairs(self.starts) do
    local inside_distance, outside_distance = 0, 0
    
    if not start.mex and not ex then
      -- Both outside
      outside_distance = mag(x-start.mx, y-start.my)
    elseif sameEntrance(start.mex, start.mey, ex, ey) then
      -- Both inside the same building.
      inside_distance = mag(x-start.mx, y-start.my)
    else
      if start.ex then
        if ex then
          -- Inside to Outside to Inside
          inside_distance = mag(start.mex-start.mx, start.mey-start.my) + mag(x-ex, y-ey)
          outside_distance = mag(ex-start.mex, ey-start.mey)
        else
          -- Inside to Outside
          inside_distance = mag(start.mex-start.mx, start.mey-start.my)
          outside_distance = mag(x-start.mex, y-start.mey)
        end
      else
        -- Outside to Inside
        assert(ex)
        inside_distance = mag(x-ex, y-ey)
        outside_distance = mag(ex-start.mx, ey-start.my)
      end
    end
    
    local time = start.time_overhead + inside_distance / self.inside_speed + outside_distance / self.outside_speed
    
    if not best_start or time < best_time then
      best_start, best_time = start, time
      
      if start.distance_overhead then
        best_distance = start.distance_overhead + inside_distance + outside_distance
      else
        best_distance = nil
      end
    end
  end
  
  if best_start then
    if path then
      local len = #path
      path[len+1] = best_start.mx
      path[len+2] = best_start.my
      path[len+3] = map
      len = len + 3
      
      if (not ex and not best_start.mex) or not sameEntrance(best_start.mex, best_start.mey, ex, ey) then
        if best_start.ex then
          path[len+1] = best_start.mex
          path[len+2] = best_start.mey
          path[len+3] = map
          len = len + 3
        end
        
        if ex then
          path[len+1] = ex
          path[len+2] = ey
          path[len+3] = map
          len = len + 3
        end
      end
      
      path[len+1] = x
      path[len+2] = y
      path[len+3] = map
    end
    
    return best_time, best_distance, best_start.area_id, best_start.level, best_start.x, best_start.y, best_start.ex, best_start.ey
  end
end

function meta:calculateTravelTime(
    src_area_id,  src_level,  src_x,  src_y,  src_ex,  src_ey,
    dest_area_id, dest_level, dest_x, dest_y, dest_ex, dest_ey, path)
  
  self:setStart(src_area_id,  src_level,  src_x,  src_y,  src_ex,  src_ey)
  return self:getEndTravelTime(dest_area_id, dest_level, dest_x, dest_y, dest_ex, dest_ey, path)
end
