local class = require('class')

local DEFAULT_ALPHA = 192
local MARK_CULL_DISTANCE_SQUARED = 100000000

local NON_TARGETED_FACTOR = 0.6
local NON_TARGETED_BONUS = 75

local NON_TARGETED_ESCORT_FACTOR = 0.0
local NON_TARGETED_ESCORT_BONUS = 200

local FONT = gr.Fonts[3]
local LINE_HEIGHT = FONT.Height

local function visible(str)
    local index = str:find('#', 0, true)
    if index then
        return str:sub(1, index - 1)
    else
      return str
    end
end

local function className(ship)
  local name = ship:getAltClassName()
  if name == '' then
    return visible(ship.Class.Name)
  else
    return visible(name)
  end
end

local function targeted(ship)
  return hv.Player.Target == ship
end

local function targetedSubsystem(subsystem)
  return hv.Player.TargetSubsystem == subsystem
end

local function cull(ship)
  return (not ship:isValid())
      or (ship.HitpointsLeft <= 0)
	  or (ship:isWarpingStage1())
	  or (ship:isWarpingStage2())
	  or (hv.Player.Position:getDistanceSquared(ship.Position) > MARK_CULL_DISTANCE_SQUARED)
end

local function omitHp(ship)
  return ship.Class.CustomData["MarkOmitsHP"] == 'true'
end

local Marker = class(function(self)
  self._active = true
  self._subsystems = {}
end)

function Marker:setActive(active)
  self._active = active
end

function Marker:setText(text)
  self._text = text
end

function Marker:setTerse(terse)
  self._terse = terse
end

function Marker:setColor(red, green, blue, alpha)
  if green then
    self._red = red
    self._green = green
    self._blue = blue
    self._alpha = alpha
  elseif red then
    local color = red
    self._red = color.Red
    self._green = color.Green
    self._blue = color.Blue
    self._alpha = color.Alpha
  end
end

function Marker:forSubsystem(subsystem)
  local result = self._subsystems[subsystem]
  if not result then
    result = Marker()
    self._subsystems[subsystem] = result
  end
  return result
end

function Marker:setOffscreen(offscreen)
  self._offscreen = offscreen
end

function Marker:draw(ship, escort, override)
  if self._active and not cull(ship) then
    self:_applyColor(ship, escort, override)
    local left, top, right, bottom = self:_drawBrackets(ship)
    if left then
      if self._text then
        gr.drawString(self._text, left, top - LINE_HEIGHT)
      end
      if not self._terse then
        -- If the ship isn't targeted, draw its name and class name. If it is
        -- targeted, these are already drawn and we don't have to.
        if not targeted(ship) then
          gr.drawString(visible(ship.Name), right + 3, top)
          gr.drawString(className(ship))
          local distance = hu.getTargetDistance(ship)
          local meters = ('%.0f'):format(distance)
          gr.drawString(meters, right - gr.getStringWidth(meters), bottom + 3)
        end
        -- If the box is large enough, also include the ship's hit points as a percentage.
        if (bottom - top) > (LINE_HEIGHT * 3) and not omitHp(ship) then
          local hp = ('%.1f%%'):format(ship.HitpointsLeft * 100 / ship.HitpointsMax)
          gr.drawString(hp, right + 3, bottom - LINE_HEIGHT)
        end
      end
    elseif self._offscreen then
      gr.drawOffscreenIndicator(ship, true, true)
    end
    for subsystem, marker in pairs(self._subsystems) do
      marker:_drawSubsystem(ship, subsystem, escort, override)
    end
  end
end

function Marker:_drawSubsystem(ship, subsystem, escort, override)
  if self._active then
    local hpLeft = subsystem.HitpointsLeft
    if hpLeft > 0 then
      self:_applyColor(ship, escort, override)
      local draw = not targetedSubsystem(subsystem)
      local left, top, right, bottom = gr.drawSubsystemTargetingBrackets(subsystem, draw)
      if left then
        if self._text then
          gr.drawString(self._text, left, top - LINE_HEIGHT)
        end
        local hp = ('%.1f%%'):format(hpLeft * 100 / subsystem.HitpointsMax)
        gr.drawString(hp, right + 3, bottom - LINE_HEIGHT)
      end
    end
  end
end

function Marker:_applyColor(ship, escort, override)
  local r, g, b, a
  local a = self._alpha
  if a then
    r = self._red
    g = self._green
    b = self._blue
  end
  if (escort or not a) and override then
    r = override.Red
    g = override.Green
    b = override.Blue
    a = override.Alpha
  end
  if not a then
    r, g, b = ship.Team:getColor()
    a = DEFAULT_ALPHA
  end
  if a then
    if not targeted(ship) then
      local factor = escort and NON_TARGETED_ESCORT_FACTOR or NON_TARGETED_FACTOR
      local bonus = escort and NON_TARGETED_ESCORT_BONUS or NON_TARGETED_BONUS
      r = r * factor + bonus
      g = g * factor + bonus
      b = b * factor + bonus
    end
  end
  gr.setColor(r, g, b, a)
end

function Marker:_drawBrackets(ship)
  if targeted(ship) then
    return gr.drawTargetingBrackets(ship, false)
  else
    gr.setLineWidth(2)
    local left, top, right, bottom = gr.drawTargetingBrackets(ship, true)
    gr.setLineWidth(1)
    return left, top, right, bottom
  end
end

local MarkerManager = class(function() end)

function MarkerManager:reset()
  self._ships = {}
  self._wings = {}
  self._colorOverrides = {}
  self._dummy = Marker()
  self._drawEscorts = true
end

function MarkerManager:draw()
  local oldColor = gr.getColor(true)
  local oldFont = gr.CurrentFont
  gr.CurrentFont = FONT
  self:_drawShips()
  self:_drawWings()
  if self._drawEscorts then
    for i=1, #mn.EscortShips do
      self:_drawEscort(mn.EscortShips[i])
    end
  end
  -- Restore the original graphic settings
  gr.CurrentFont = oldFont
  gr.setColor(oldColor)
end

function MarkerManager:forShip(ship)
  local signature = ship:getSignature()
  local result = self._ships[signature]
  if result then
    return result[2]
  else
    local marker = Marker()
    self._ships[signature] = {ship, marker}
    return marker
  end
end

function MarkerManager:forWing(wing)
  local marker = self._wings[wing]
  if not marker then
    marker = Marker()
    self._wings[wing] = marker
  end
  return marker
end

function MarkerManager:setColorOverride(name, r, g, b)
  self._colorOverrides[name] = gr.createColor(r, g, b, DEFAULT_ALPHA)
end

function MarkerManager:drawEscorts(drawEscorts)
  self._drawEscorts = drawEscorts
end

function MarkerManager:_drawShips()
  for signature, data in pairs(self._ships) do
    local ship = data[1]
    local marker = data[2]
    marker:draw(ship, false, self._colorOverrides[ship.Name])
  end
end

function MarkerManager:_drawWings()
  for wing, marker in pairs(self._wings) do
    for i=1, #wing do
      local ship = wing[i]
      if not self._ships[ship] then
        marker:draw(ship, false, self._colorOverrides[wing.Name])
      end
    end
  end
end

function MarkerManager:_drawEscort(ship)
  if not self._ships[ship:getSignature()] then
    self._dummy:draw(ship, true, self._colorOverrides[ship.Name])
  end
end

return MarkerManager()

