-- 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 is reponsible for the creation and management of
-- MapLink objects, which allow the pathfinding algorithms to
-- discover paths between maps, or shortcuts on the same map.

-- These objects are used to represent flight paths, zeplin and boat routes,
-- and portals.

local maplink_constants = {}
local maplink_getters = {}
local maplink_setters = {}
local maplink_meta = {}

function maplink_meta.__index(self, key)
  assert(type(key) == "string")
  local const = maplink_constants[key]
  if const then return const end
  local getter = maplink_getters[key]
  if getter then return getter(self, key) end
  if maplink_setters[key] then
    error("'"..key.."' is write-only.")
  else
    error("'"..key.."' isn't a valid maplink key.")
  end
end

function maplink_meta.__newindex(self, key, value)
  assert(type(key) == "string")
  local setter = maplink_setters[key]
  if setter then return setter(self, value, key) end
  if maplink_constants[key] or maplink_getters[key] then
    error("'"..key.."' is read-only.")
  else
    error("'"..key.."' isn't a valid maplink key.")
  end
end

local link_data = {}

function shared.createMapLink()
  local link_object = {}
  
  link_data[link_object] = {
    link=link_object,
    enabled=true,
    enabled_implicit=false,
    
    -- These get set by the various functions and are disabled by default.
    forward_time = nil,
    reverse_time = nil,
    
    src_area_id = nil,
    src_level = nil,
    src_x = nil,
    src_y = nil,
    src_ex = nil,
    src_ey = nil,
    
    dest_area_id = nil,
    dest_level = nil,
    dest_x = nil,
    dest_y = nil,
    dest_ex = nil,
    dest_ey = nil
  }
  
  return setmetatable(link_object, maplink_meta)
end

local enabled_maplinks = {}

local on_maplink_enabled, on_maplink_disabled = {}, {}

function shared.onMapLinkEnabled(cb, ...)
  cb = shared.callback(cb, ...)
  table.insert(on_maplink_enabled, cb)
  for maplink in pairs(enabled_maplinks) do cb(maplink) end
end

function shared.onMapLinkDisabled(cb, ...)
  table.insert(on_maplink_disabled, shared.callback(cb, ...))
end

local function updateLinkState(link, modified)
  local data = link_data[link]
  
  local enabled = data.enabled
  
  if not data.src_area_id or not data.dest_area_id then
    -- Can't be enabled, it's not fully connected yet.
    enabled = false
  end
  
  if not data.forward_time and not data.reverse_time then
    -- Can't be enabled, doesn't allow passage in any direction.
    enabled = false
  end
  
  if enabled ~= data.enabled_implicit then
    data.enabled_implicit = enabled
    
    if enabled then
      enabled_maplinks[link] = true
      shared.invokeCallbackList(on_maplink_enabled, link)
    else
      enabled_maplinks[link] = nil
      shared.invokeCallbackList(on_maplink_disabled, link)
    end
  elseif enabled and modified then
    -- We handle the modification of a modified link by
    -- disabling it and then re-enabling it.
    data.enabled, data.enabled_implicit = false, false
    shared.invokeCallbackList(on_maplink_disabled, link)
    data.enabled, data.enabled_implicit = true, true
    shared.invokeCallbackList(on_maplink_enabled, link)
  end
end

function maplink_getters.forward_time(self)
  return link_data[self].forward_time
end

function maplink_getters.reverse_time(self)
  return link_data[self].reverse_time
end

function maplink_setters.forward_time(self, time)
  local data = link_data[self]
  
  local requested_time = tonumber(time)
  assert(requested_time or (time == nil or time == false))
  
  if requested_time ~= data.forward_time then
    data.forward_time = requested_time
    updateLinkState(self, true)
  end
end

function maplink_setters.reverse_time(self, time)
  local data = link_data[self]
  
  local requested_time = tonumber(time)
  assert(requested_time or (time == nil or time == false))
  
  if requested_time ~= data.reverse_time then
    data.reverse_time = requested_time
    updateLinkState(self, true)
  end
end

function maplink_getters.enabled(self)
  return link_data[self].enabled_implicit
end

function maplink_setters.enabled(self, state)
  assert(state == true or state == false)
  
  local data = link_data[self]
  if state ~= data.enabled then
    data.enabled = state
    updateLinkState(self, true)
  end
end

function maplink_constants:getOrigin()
  local data = link_data[self]
  return data.src_area_id, data.src_level, data.src_x, data.src_y, data.src_ex, data.src_ey
end

function maplink_constants:getDestination()
  local data = link_data[self]
  return data.dest_area_id, data.dest_level, data.dest_x, data.dest_y, data.dest_ex, data.dest_ey
end

function maplink_constants:setOrigin(area_id, level, x, y, ex, ey)
  local data = link_data[self]
  
  if not area_id then
    if data.src_area_id then
      data.src_area_id, data.src_level, data.src_x, data.src_y, data.src_ex, data.src_ey = nil, nil, nil, nil, nil, nil
      updateLinkState(self, true)
    end
  else
    area_id, level, x, y, ex, ey = shared.normalizeLocation(area_id, level, x, y, ex, ey)
    
    if data.src_area_id ~= area_id or data.src_level ~= level or data.src_x ~= x or data.src_y ~= y or data.src_ex ~= ex or data.src_ey ~= ey  then
      data.src_area_id, data.src_level, data.src_x, data.src_y, data.src_ex, data.src_ey = area_id, level, x, y, ex, ey
      updateLinkState(self, true)
    end
  end
end

function maplink_constants:setDestination(area_id, level, x, y, ex, ey)
  local data = link_data[self]
  
  if not area_id then
    if data.dest_area_id then
      data.dest_area_id, data.dest_level, data.dest_x, data.dest_y, data.dest_ex, data.dest_ey = nil, nil, nil, nil, nil, nil
      updateLinkState(self, true)
    end
  else
    area_id, level, x, y, ex, ey = shared.normalizeLocation(area_id, level, x, y, ex, ey)
    
    if data.dest_area_id ~= area_id or data.dest_level ~= level or data.dest_x ~= x or data.dest_y ~= y or data.dest_ex ~= ex or data.dest_ey ~= ey  then
      data.dest_area_id, data.dest_level, data.dest_x, data.dest_y, data.dest_ex, data.dest_ey = area_id, level, x, y, ex, ey
      updateLinkState(self, true)
    end
  end
end
