local dockVisitor = require('dockvisitor')

local state
local SIZE = 50000
local STEP = SIZE / 10
local WING_OFFSETS = {
  ba.createVector(0, 0, 0),
  ba.createVector(-0.5, 0.5, -0.6),
  ba.createVector(0.5, 0.5, -0.6),
  ba.createVector(-1, 1, -1.2),
  ba.createVector(0, 1, -1.2),
  ba.createVector(1, 1, -1.2)
}

local CULL_DISTANCE = 40000
local DRIVE_WARMUP_SOUND = 50

local function reset()
  state = {inSubspace = {}, overrides = {}, escorts = {}, fadeOut = true, fadeIn = true, x = -SIZE, z = -SIZE}
end

local function isInSubspace(ship)
  return ship and ship:isValid() and state.inSubspace[ship:getSignature()] or false
end

local function placeholder()
  local x = state.x
  local y = -SIZE
  local z = state.z
  if x < SIZE then
    state.x = x + STEP
  elseif z < SIZE then
    state.x = -SIZE
    state.z = z + SIZE
  else
    state.x = -SIZE
    state.z = -SIZE
  end
  return {ba.createVector(x, y, z), ba.createOrientation()}
end

local function formation(separation, waypointList, dx, dy, dz, ships)
  if separation <= 0 then
    for _, ship in ipairs(ships) do
	  if ship:isValid() then
	    local suggestion = ship.Class.Model.Radius * 3
		if suggestion > separation then separation = suggestion end
	  end
	end
  end
  local index = 0
  local waypoint1 = waypointList[1].Position
  local waypoint2 = waypointList[2].Position
  local position = waypoint1 + ba.createVector(dx, dy, dz)
  local orientation = (waypoint2 - waypoint1):getOrientation()
  return function()
    index = index + 1
    local wingOffset = orientation:unrotateVector(WING_OFFSETS[index] * separation)
    return {position + wingOffset, orientation}
  end
end

local function fadeIn()
  if state.fadeIn then mn.runSEXP('( fade-in 500 255 255 255 )') end
end

local function fadeOut()
  if state.fadeOut then mn.runSEXP('( fade-out 500 255 255 255 )') end
end

local function startLockdown(ship, order)
  ship:clearOrders()
  ship:giveOrder(ORDER_PLAY_DEAD_PERSISTENT, nil, nil, 200)
  ship.Physics.RotationalVelocity = ba.createVector(0, 0, 0)
  ship.Physics.RotationalVelocityDesired = ba.createVector(0, 0, 0)
  if ship == hv.Player then
    mn.runSEXP('( player-use-ai )')
    mn.runSEXP('( ignore-key -1 !C! )')
    mn.runSEXP('( set-player-throttle-speed !' .. ship.Name .. '! 53 )')
  end
end

local function clearLockdown(ship)
  ship:clearOrders()
  if ship == hv.Player then
    mn.runSEXP('( player-not-use-ai )')
    mn.runSEXP('( ignore-key 0 !C! )')
  mn.runSEXP('( set-player-throttle-speed !' .. ship.Name .. '! 53 )')
  end
end

local function startSubspace(ship)
  state.inSubspace[ship:getSignature()] = true
  mn.runSEXP('( shields-off !' .. ship.Name .. '!)' )
  if ship == hv.Player then
    mn.runSEXP('( mission-set-subspace 1 )')
    mn.runSEXP('( hud-set-max-targeting-range 1 )')
    mn.runSEXP('( clear-weapons )')
    mn.runSEXP('( clear-debris )')
  end
end

local function clearSubspace(ship)
  state.inSubspace[ship:getSignature()] = false
  mn.runSEXP('( shields-on !' .. ship.Name .. '!)' )
  if ship == hv.Player then
    mn.runSEXP('( mission-set-subspace 0 )')
    mn.runSEXP('( hud-set-max-targeting-range 0 )')
  end
end

local function allowTargeting(ship)
  if ship == hv.Player then
    mn.runSEXP('( hud-set-max-targeting-range 0 )')
  end
end

local function isEscorted(signature)
  local count = #mn.EscortShips
    for i = 1, count do
	if signature == mn.EscortShips[i]:getSignature() then return true end
  end
  return false
end

local startStealth = dockVisitor(function(ship)
  local signature = ship:getSignature()
  state.escorts[signature] = isEscorted(signature)
  ship.Stealthed = true
  ship:setFlag(true, 'friendly-stealth-invisible')
  if not ship:isPlayer() then
    mn.runSEXP('( set-alpha-multiplier 0 !' .. ship.Name .. '!)')
  end
end)

local clearStealth = dockVisitor(function(ship, depth)
  ship.Stealthed = false
  ship:setFlag(false, 'friendly-stealth-invisible')
  if not ship:isPlayer() then
    mn.runSEXP('( set-alpha-multiplier 100 !' .. ship.Name .. '!)')
  end
  if state.escorts[ship:getSignature()] then
    -- there's no API to retrieve a ship's escort priority, so fudge it
	local priority = 50 - depth
	mn.runSEXP('( add-remove-escort !' .. ship.Name .. '! ' .. tostring(priority) .. ')')
  end
end)

local function isInCullDistance(ship, origin)
  return origin and origin:getDistance(ship.Position) < CULL_DISTANCE
end

local function shouldShowEffect(ship)
  return isInCullDistance(ship, hv.Player.Position) or isInCullDistance(ship, state.playerDestination)
end

local function jump(warmup, ship, destination, ...)
  if ship and ship:isValid() then
	if ship == hv.Player then
	  state.playerDestination = destination[1]
      if warmup then
        mn.runSEXP('( hud-display-gauge 4000 !warpout! )')
        ad.playGameSound(DRIVE_WARMUP_SOUND)
      end
    end
    local callbacks = {...}
    local function arrival()
      ship.Position = destination[1]
      ship.Orientation = destination[2]
      for _, callback in ipairs(callbacks) do callback(ship) end
      if ship == hv.Player then
        fadeIn()
		state.playerDestination = nil
      elseif shouldShowEffect(ship) then
        mn.waitAsync(0.001):continueWith(function() ship:warpIn() end)
      end
    end
    startLockdown(ship)
    if shouldShowEffect(ship) then
      state.overrides[ship:getSignature()] = arrival
      ship:warpOut()
    else
      mn.waitAsync(6):continueWith(arrival)
    end
  end
end

local function unwrap(list)
  for k, v in ipairs(list) do list[k] = v[1] end
  return list
end

mn.LuaSEXPs['in-mission-jump'].Action = function(separation, waypointList, dx, dy, dz, ms, ...)
  local ships = unwrap({...})
  local placement = formation(separation, waypointList, dx, dy, dz, ships)
  for _, ship in ipairs(ships) do
    local destination = placement()
    jump(true, ship, placeholder(), startLockdown, startSubspace, startStealth, function(ship)
      ship:doManeuver(ms, 0, 0, 0, true, 0, 0, 0.53, true, 0)
      mn.waitAsync(ms/1000):continueWith(function()
        jump(false, ship, destination, clearLockdown, clearSubspace, clearStealth)
      end)
    end)
  end
end

mn.LuaSEXPs['in-mission-jump-leave'].Action = function(...)
  for _, v in ipairs({...}) do
    jump(true, v[1], placeholder(), startLockdown, startSubspace, startStealth)
  end
end

mn.LuaSEXPs['in-mission-jump-leave-together'].Action = function(separation, waypointList, dx, dy, dz, ...)
  local ships = unwrap({...})
  local placement = formation(separation, waypointList, dx, dy, dz, ships)
  for _, ship in ipairs(ships) do
    jump(true, ship, placement(), clearLockdown, startSubspace, allowTargeting)
  end
end

mn.LuaSEXPs['in-mission-jump-return'].Action = function(separation, waypointList, dx, dy, dz, ...)
  local ships = unwrap({...})
  local placement = formation(separation, waypointList, dx, dy, dz, ships)
  for _, ship in ipairs(ships) do
    jump(false, ship, placement(), clearLockdown, clearSubspace, clearStealth)
  end
end

mn.LuaSEXPs['in-mission-jump-return-now'].Action = function(separation, waypointList, dx, dy, dz, ...)
  local ships = unwrap({...})
  local placement = formation(separation, waypointList, dx, dy, dz, ships)
  for _, ship in ipairs(ships) do
    local destination = placement()
    ship.Position = destination[1]
    ship.Orientation = destination[2]
	clearLockdown(ship)
	clearSubspace(ship)
	clearStealth(ship)
  end
end

mn.LuaSEXPs['in-mission-jump-set-fades'].Action = function(fadeOut, fadeIn)
  state.fadeOut = fadeOut
  state.fadeIn = fadeIn
end

mn.LuaSEXPs['is-in-subspace'].Action = isInSubspace

mn.LuaSEXPs['register-cargo'].Action = function(...) end -- vestigial

engine.addHook('On Gameplay Start', reset)

engine.addHook('On Warp Out', function()
  if hv.Self == hv.Player and state.overrides[hv.Self:getSignature()] then
    mn.waitAsync(1.6):continueWith(fadeOut)
  end
end)

engine.addHook('On Warp Out Complete', function() end, {}, function()
  local signature = hv.Self:getSignature()
  local override = state.overrides[signature]
  if override then
    override()
    state.overrides[signature] = nil
    return true
  else
    return false
  end
end)

return {isInSubspace = isInSubspace}

