HarvestRoute = {}
HarvestRoute.name = "HarvestRoute"
HarvestRoute.displayName = 'HarvestRoute for Harvestmap'
HarvestRoute.version = "1.0.5"
HarvestRoute.author = "generic"
HarvestRoute.settingsVersion = 2
HarvestRoute.savedVars = {}

HarvestRoute.defaultSettings = {
  trackerRange = 5,
  enableTrackerWindow = true,
  showTrackerWindow = false
}

local LibAddonMenu2 = LibAddonMenu2

local Farm = Harvest.farm
local Editor = Farm.editor
local Path = Harvest.path
local MapCache = nil
local Helper = Farm.helper

local atan2 = math.atan2
local zo_round = _G["zo_round"]
local GetPlayerCameraHeading = _G["GetPlayerCameraHeading"]
local GetMapPlayerPosition = _G["GetMapPlayerPosition"]

local HarvestRouteButton = nil
local isActive = false
local isTrackerVisible = false
local currentZoneId = nil
local lastNodeId = nil
local lastNodeAddedToPath = nil
local lastPathIndex = nil
local closesPathNodeId = nil
local closestOutOfPathId = nil
local pathNodeId = nil
local pathNodes = {}
local playerX = 0
local playerY = 0
local playerZ = 0




local function initialize( eventCode, addOnName )

    if ( addOnName ~= HarvestRoute.name ) then
        return
    end
    HarvestRoute.savedVars = ZO_SavedVars:NewAccountWide("HarvestRouteVars", HarvestRoute.settingsVersion, nil, HarvestRoute.defaultSettings)

    local padding = 30
    local control = WINDOW_MANAGER:CreateControlFromVirtual('HarvestRouteButton', HarvestFarmGenerator, "ZO_DefaultButton")
    control:SetText(HarvestRoute.GetLocalization( "buttonstarttracker" ))
    control:SetAnchor(TOPLEFT, Harvest.farm.generator.generatorBar , BOTTOMLEFT, 0, 14)
    control:SetWidth(HarvestFarmGenerator:GetWidth() - padding)
    control:SetClickSound("Click")
    control:SetHandler("OnClicked", HarvestRoute.OnButton )
    HarvestRouteButton = control
    local lastControl = control

    local control = WINDOW_MANAGER:CreateControl(nil, HarvestFarmGenerator, CT_LABEL)
    control:SetFont("ZoFontGame")
    control:SetText(HarvestRoute.GetLocalization( "tourtrackerdescription" ))
    control:SetAnchor(TOPLEFT, lastControl, BOTTOMLEFT, 0, 10)
    control:SetWidth(HarvestFarmGenerator:GetWidth() - padding)

    HarvestRouteTrackerPathInfoTitle:SetText( HarvestRoute.GetLocalization( "pathinfotitle" ) )

    HarvestRouteTrackerNearestNodeTitle:SetText( HarvestRoute.GetLocalization( "nearestnodetitle" ))
    HarvestRouteTrackerNearestNodeTitle:SetHandler("OnMouseEnter", function(self)
        ZO_Tooltips_ShowTextTooltip(self, TOP, HarvestRoute.GetLocalization( "nearestnodetooltip" ))
    end)
    HarvestRouteTrackerNearestNodeTitle:SetHandler("OnMouseExit", function(self)
        ZO_Tooltips_HideTextTooltip()
    end)

    HarvestRouteTrackerLastPathNodeTitle:SetText( HarvestRoute.GetLocalization( "lastpathnodetitle" ))
    HarvestRouteTrackerLastPathNodeTitle:SetHandler("OnMouseEnter", function(self)
        ZO_Tooltips_ShowTextTooltip(self, TOP, HarvestRoute.GetLocalization( "lastpathnodetooltip" ))
    end)
    HarvestRouteTrackerLastPathNodeTitle:SetHandler("OnMouseExit", function(self)
        ZO_Tooltips_HideTextTooltip()
    end)

    HarvestRouteTrackerButton:SetText( HarvestRoute.GetLocalization( "buttonstarttracker" ) )
    HarvestRouteTrackerNearestArrow:SetColor( 0.5, 0.75, 0.5, 0.75)
    HarvestRouteTrackerPathArrow:SetColor( 0.25, 0.25, 0.25, 0.75)

    HarvestRoute.InitializeSettingsMenu()


    EVENT_MANAGER:RegisterForEvent("HarvestRoutePlayerActivated", EVENT_PLAYER_ACTIVATED, HarvestRoute.OnPlayerLoaded )

    Harvest.callbackManager:RegisterForEvent(Harvest.events.TOUR_CHANGED, HarvestRoute.checkPath )

    if HarvestRoute.savedVars.enableTrackerWindow and HarvestRoute.savedVars.showTrackerWindow then
      HarvestRoute:SetTrackerHidden( false )
      HarvestRoute:InitTracker()
      HarvestRoute:UpdateTracker()
    end

end

local function TrackerWindowDisabled() return not HarvestRoute.savedVars.enableTrackerWindow end

function HarvestRoute:InitializeSettingsMenu()
  local panel = {
    type = "panel",
    name = HarvestRoute.name,
    displayName = HarvestRoute.displayName,
    author = HarvestRoute.author,
    version = HarvestRoute.version,
    registerForRefresh = true,
    registerForDefaults = true,
  }
  local options = {
      {
          type = "description",
          text = HarvestRoute.GetLocalization("addonsettingstext"),
      },
      {
        type = "checkbox",
        name = HarvestRoute.GetLocalization("enabletrackerwindow"),
        tooltip = HarvestRoute.GetLocalization("enabletrackerwindowtooltip"),
        getFunc = function () return HarvestRoute.savedVars.enableTrackerWindow end,
        setFunc = function (value)
            HarvestRoute.savedVars.enableTrackerWindow = value
            if not value then HarvestRouteTracker:SetHidden( true )
            elseif HarvestRoute.savedVars.alwaysShowTracker then HarvestRouteTracker:SetHidden( false )
            end
          end,
        default = HarvestRoute.defaultSettings.enableTrackerWindow,
      },
      {
        type = "checkbox",
        name = HarvestRoute.GetLocalization("showtrackerwindow"),
        tooltip = HarvestRoute.GetLocalization("showtrackerwindowtooltip"),
        getFunc = function () return HarvestRoute.savedVars.showTrackerWindow end,
        setFunc = function (value)
            HarvestRoute.savedVars.showTrackerWindow = value
            if value then
              HarvestRoute:SetTrackerHidden( false )
              HarvestRoute:InitTracker()
              HarvestRoute:UpdateTracker()
            end
          end,
        default = HarvestRoute.defaultSettings.showTrackerWindow,
        disabled = TrackerWindowDisabled,
      },

      {
        type = "slider",
        name = HarvestRoute.GetLocalization("routerangemultiplier"),
        tooltip = HarvestRoute.GetLocalization("routerangemultipliertooltip"),
        min = 3,
        max = 20,
        getFunc = function () return HarvestRoute.savedVars.trackerRange end,
        setFunc = function (value) HarvestRoute.savedVars.trackerRange = value end,
        default = HarvestRoute.defaultSettings.trackerRange,
      }
    }
    LibAddonMenu2:RegisterAddonPanel(HarvestRoute.name.."OptionsMenu", panel)
    LibAddonMenu2:RegisterOptionControls(HarvestRoute.name.."OptionsMenu", options)
end

function HarvestRoute:OnButton()
    if isActive then
      isActive = false
      HarvestRoute:StopTracker()
    else
      isActive = true
      HarvestRoute:StartTracker()
    end
    HarvestRoute:UpdateTracker()
end

function HarvestRoute:StartTracker()
      EVENT_MANAGER:RegisterForUpdate("HarvestRouteUpdatePosition", 50, HarvestRoute.OnUpdate)


      local mapMetaData = Harvest.mapTools:GetViewedMapMetaData()
      MapCache = Harvest.Data:GetMapCache( mapMetaData )
      currentZoneId = GetUnitZoneIndex("player")

      HarvestRoute:SetTrackerHidden(false)
      HarvestRoute:InitTracker()
end

function HarvestRoute:StopTracker()
      EVENT_MANAGER:UnregisterForUpdate("HarvestRouteUpdatePosition")
      HarvestRoute:InitTracker()
end

function HarvestRoute:OnPlayerLoaded()
      local newZoneId = GetUnitZoneIndex("player")
      if currentZoneId and currentZoneId == newZoneId then
        return
      end
      HarvestRoute:debug('zone switched')
      lastNodeId = nil
      pathNodeId = nil
      lastPathIndex = nill
      pathNodes = {}
      currentZoneId = newZoneId
      if isActive then
        HarvestRoute:OnButton()
      end
      if HarvestRoute.savedVars.enableTrackerWindow then
        if HarvestRoute.savedVars.showTrackerWindow or isTrackerVisible then
          HarvestRoute:SetTrackerHidden( false )
          HarvestRoute:UpdateTracker()
        end
      end
end

-- reset pathnodes when path has changed from outside, and either last node or last path node are invalid
function HarvestRoute:checkPath(event, path)
  local validpath = false
  local validnode = false
  if lastNodeId and MapCache then
    -- does the nodeId still exist?
    if MapCache.pinTypeId[lastNodeId] then
      validnode = true
      -- check if our path information still works
      if pathNodeId and Farm.path then
        local index = Farm.path:GetIndex(pathNodeId)
        if index and (index == lastPathIndex) then
          validpath = true
        end
      end
    end
  end
  if not validnode then
    HarvestRoute:debug('last node invalid')
    lastNodeId = nil
  end
  if not validpath then
    HarvestRoute:debug('last path index invalid')
    lastNodeId = nil
    pathNodeId = nil
    lastPathIndex = nil
    pathNodes = {}
  end
  HarvestRoute:UpdateTracker()
end

function HarvestRoute:OnUpdate (time)
    if isActive then
      playerX, playerY, playerZ = Harvest.GetPlayer3DPosition()
      local nodeId, nodeDistance, nodePinType = HarvestRoute:GetClosestNode()
      if nodeId then
        if nodePinType ~= Harvest.UNKNOWN and nodeId ~= lastNodeId and (nodeDistance < HarvestRoute.savedVars.trackerRange) then
          HarvestRoute:debug('node in range: '..nodeId)
          local x, y = MapCache:GetLocal(nodeId)
          if Farm.path then
            local index = Farm.path:GetIndex(nodeId)
            if index then

              -- check if we are far enough away from the last inserted node before switching
              local switchToPathNode = true
              if lastNodeAddedToPath then
                dx = MapCache.worldX[nodeId] - MapCache.worldX[lastNodeAddedToPath]
                dy = MapCache.worldY[nodeId] - MapCache.worldY[lastNodeAddedToPath]
                distance = dx * dx + dy * dy
                checkDistance = HarvestRoute.savedVars.trackerRange * HarvestRoute.savedVars.trackerRange
                if (checkDistance > distance) then
                  switchToPathNode = false
                end
              end

              if switchToPathNode then
                HarvestRoute:debug('path node '..nodeId..' ')
                pathNodeId = nodeId
                lastPathIndex = index
              end
            else
              if pathNodeId then
                HarvestRoute:debug('insert '..nodeId..' after path node '..pathNodeId..' ')
                Farm.path:InsertAfter(pathNodeId, nodeId)
                pathNodeId = nodeId
                lastNodeAddedToPath = nodeId
                lastPathIndex = Farm.path:GetIndex(nodeId)
                Harvest.farm.display:Refresh(Farm.path, Harvest.farm.display.selectedPins, Harvest.farm.display.selectedMapCache)
                Harvest.farm.editor.statusLabel:SetText(Harvest.farm.editor.textConstructor())

                -- if we are farming, update the next pin so we can actually finish the loop if we want to
                if Farm.helper:IsRunning() then
                  Farm.helper.nextPathIndex = lastPathIndex
                  Farm.helper:UpdateToNextTarget()
                end
              end
            end
          else --create a new path
            pathNodes[#pathNodes + 1] = nodeId
            HarvestRoute:debug('added '..nodeId..' as path node '..#pathNodes)
            if #pathNodes > 2 then
              path = Harvest.path:New(MapCache)
              path:GenerateFromNodeIds(pathNodes, 1, #pathNodes)
              Farm:SetPath(path)
              lastPathIndex = Farm.path:GetIndex(nodeId)
              pathNodeId = nodeId
              pathNodes = {}
            end
          end
          lastNodeId = nodeId
        end --nodeDistance
        HarvestRoute:UpdateTracker()
      end --nodeId
    end --isActive
end

-- get the closest node to the player, using HarvestMap MapCache Divisions (and only check 4 divisions, from -50 to +50 from current position)
function HarvestRoute:GetClosestNode()
    local bestNodeId = nil
    if not MapCache then --yeah, no cache no cookies.
      return nil, nil
    end

    local halfDivisionInMeters = zo_floor(MapCache.DivisionWidthInMeters / 2) --should be 50 most of the time, but lets keep it dynamic
    local numDivisions = MapCache.numDivisions
    local totalNumDivisions = MapCache.TotalNumDivisions

    local half_before_x = playerX - halfDivisionInMeters
    local half_before_y = playerY - halfDivisionInMeters

    local index = (zo_floor(half_before_x / MapCache.DivisionWidthInMeters) + zo_floor(half_before_y / MapCache.DivisionWidthInMeters) * numDivisions) % MapCache.TotalNumDivisions

    local bestDistance = halfDivisionInMeters * halfDivisionInMeters
    local bestPathDistance = bestDistance
    local bestOutOfPathDistance = bestDistance
    local divisions = MapCache.divisions

    -- check all pintypes, and the 4 divisions that contain the 100 x 100 square around the player
    if divisions then
      for _, pinTypeId in pairs(Harvest.PINTYPES) do
        local pinDivision = MapCache.divisions[pinTypeId]
        if pinDivision then
          for i = index, index + 1 do
            for j = 0, 1 do
              division = pinDivision[(i + j * numDivisions) % totalNumDivisions] or {}

              if division then
                -- check all node in current division, if there are closer nodes to the player
                for _, nodeId in pairs(division) do
                  if MapCache.worldX then
                    dx = MapCache.worldX[nodeId] - playerX
                    dy = MapCache.worldY[nodeId] - playerY
                    distance = dx * dx + dy * dy
                    if distance < bestDistance then
                      bestNodeId = nodeId
                      bestNodePinType = pinTypeId
                      bestDistance = distance
                    end
                    inPath = nil
                    if Farm.path then
                      inPath = Farm.path:GetIndex(nodeId)
                    end
                    if inPath then
                      if distance < bestPathDistance then
                        bestPathDistance = distance
                        closesPathNodeId = nodeId
                      end
                    else
                      if distance < bestOutOfPathDistance then
                        bestOutOfPathDistance = distance
                        closestOutOfPathId = nodeId
                      end
                    end
                  end
                end
              end
            end -- for j
          end -- for i
        end -- if pinDivision
      end --for pinType
      if bestPathDistance == (halfDivisionInMeters * halfDivisionInMeters) then
        closesPathNodeId = nil
      end
      if bestOutOfPathDistance == (halfDivisionInMeters * halfDivisionInMeters) then
        closestOutOfPathId = nodeId
      end
      if bestNodeId then
        realDistance = bestDistance ^ 0.5
        return bestNodeId, realDistance, bestNodePinType
      end
    end
    return nil, nil
end

-- UI element management
function HarvestRoute:SetTrackerHidden(value)
  isTrackerVisible = false
  if not HarvestRoute.savedVars.enableTrackerWindow then value = true end
  HarvestRouteTracker:SetHidden(value)
  isTrackerVisible = not value
end

function HarvestRoute:GetNodeDescription(nodeId)
    local description = HarvestRoute.GetLocalization( "nodeinfounknown" )
    local angle = nil
    if MapCache and MapCache.worldX and MapCache.worldX[nodeId] then
      local x, y, z = Harvest.GetPlayer3DPosition()

      local targetX = MapCache.worldX[nodeId]
      local targetY = MapCache.worldY[nodeId]
      local dx = playerX - targetX
      local dy = playerY - targetY
      local distanceInMeters = (dx * dx + dy * dy)^0.5
      nodeX = zo_round( MapCache.worldX[nodeId] * 100 ) / 100
      nodeY = zo_round( MapCache.worldY[nodeId] * 100 ) / 100
      pinType = MapCache.pinTypeId[nodeId]
      node = Harvest.GetLocalization( "pintype" ..pinType )
      description = zo_strformat( HarvestRoute.GetLocalization( "nodeinfo" ), node, distanceInMeters)
      angle = -atan2(dx, dy)
    end
    return description, angle
end

function HarvestRoute:InitTracker()
  if isActive then
      HarvestRouteButton:SetText(HarvestRoute.GetLocalization( "buttonstoptracker" ))
      HarvestRouteTrackerButton:SetText(HarvestRoute.GetLocalization( "buttonstoptracker" ))

      HarvestRouteTrackerActive:SetText( HarvestRoute.GetLocalization( "trackeractive" ) )

      HarvestRouteTrackerNearestNodeTitle:SetHidden( false )
      HarvestRouteTrackerNearestNode:SetHidden( false )
  else
      HarvestRouteButton:SetText(HarvestRoute.GetLocalization( "buttonstarttracker" ))
      HarvestRouteTrackerButton:SetText(HarvestRoute.GetLocalization( "buttonstarttracker" ))

      HarvestRouteTrackerActive:SetText( HarvestRoute.GetLocalization( "trackerinactive" ) )

      HarvestRouteTrackerNearestNodeTitle:SetHidden( true )
      HarvestRouteTrackerNearestNode:SetHidden( true )
      HarvestRouteTrackerNearestArrow:SetHidden( true )

      HarvestRouteTrackerLastPathNodeTitle:SetHidden( true )
      HarvestRouteTrackerLastPathNode:SetHidden( true )
      HarvestRouteTrackerPathArrow:SetHidden( true )
  end
end

function HarvestRoute:UpdateTracker()
    HarvestRoute:debug('UpdateTracker')
    if not HarvestRoute.savedVars.enableTrackerWindow then
      if isTrackerVisible then
        HarvestRoute:SetTrackerHidden( true )
      end
      return
    end
    if not isTrackerVisible then
      HarvestRoute:debug('tracker not visible')
      return
    end
    if isActive then
      nearestText, nearestAngle = HarvestRoute:GetNodeDescription(closestOutOfPathId)
      HarvestRouteTrackerNearestNode:SetText( nearestText )
      if nearestAngle then
        HarvestRouteTrackerNearestArrow:SetHidden(false)
        HarvestRouteTrackerNearestArrow:SetTextureRotation(-(nearestAngle + GetPlayerCameraHeading()), 0.5, 0.5)
      else
        HarvestRouteTrackerNearestArrow:SetHidden(true)
      end
    end
    if Farm.path then
      num = Farm.path.numNodes
      length = zo_round(Farm.path.length * 10) / 10
      infotext = zo_strformat( HarvestRoute.GetLocalization( "pathinfo" ), num, length)
      HarvestRouteTrackerPathInfo:SetText( infotext )
      if isActive then
        HarvestRouteTrackerLastPathNodeTitle:SetHidden( false )
        HarvestRouteTrackerLastPathNode:SetHidden( false )
        pathText, pathAngle = HarvestRoute:GetNodeDescription(pathNodeId)
        HarvestRouteTrackerLastPathNode:SetText( pathText )
        if pathAngle then
          HarvestRouteTrackerPathArrow:SetHidden(false)
          HarvestRouteTrackerPathArrow:SetTextureRotation(-(pathAngle + GetPlayerCameraHeading()), 0.5, 0.5)
        else
          HarvestRouteTrackerPathArrow:SetHidden(true)
        end
       end
    else
      HarvestRouteTrackerPathInfo:SetText( HarvestRoute.GetLocalization( "pathinfomissing" ) )
      HarvestRouteTrackerLastPathNodeTitle:SetHidden( true )
      HarvestRouteTrackerLastPathNode:SetHidden( true )
      HarvestRouteTrackerPathArrow:SetHidden( true )
    end

end

function HarvestRoute:debug(text)
  --CHAT_SYSTEM:AddMessage(text)
end

EVENT_MANAGER:RegisterForEvent("HarvestRoute", EVENT_ADD_ON_LOADED, initialize)