-- dependencies
local Event = require 'utils.event'
local Token = require 'utils.token'
local Task = require 'utils.task'
local Global = require 'utils.global'
local Retailer = require 'features.retailer'
local market_items = require 'map_gen.maps.april_fools.scenario.market_items'
local fish_market_bonus_message = require 'resources.fish_messages'
local ScoreTracker = require 'utils.score_tracker'
local Color = require 'resources.color_presets'
local change_for_player = ScoreTracker.change_for_player
local get_for_player = ScoreTracker.get_for_player
local coins_earned_name = 'coins-earned'

-- localized functions
local pairs = pairs
local round = math.round
local random = math.random
local format = string.format
local market_config = global.config.market
local currency = market_config.currency
local entity_drop_amount = market_config.entity_drop_amount
local max_coins_earned = 5

-- local vars

local nth_tick_token
local running_speed_boost_messages = {
  '%s found the lost Dragon Scroll and got a lv.1 speed boost!',
  'Guided by Master Oogway, %s got a lv.2 speed boost!',
  'Kung Fu Master %s defended the village and was awarded a lv.3 speed boost!',
  'Travelled at the speed of light. %s saw a black hole. Oops.',
}

local mining_speed_boost_messages = {
  '%s is going on a tree harvest!',
  'In search of a sharper axe, %s got a lv.2 mining boost!',
  'Wood fiend, %s, has picked up a massive chain saw and is awarded a lv.3 mining boost!',
  'Better learn to control that saw, %s, chopped off their legs. Oops.',
}

-- Global registered local vars
local primitives = { event_registered = nil }
local markets = {}
local speed_records = {}
local mining_records = {}

Global.register({ markets = markets, speed_records = speed_records, mining_records = mining_records, primitives = primitives }, function(tbl)
  markets = tbl.markets
  speed_records = tbl.speed_records
  mining_records = tbl.mining_records
  primitives = tbl.primitives
end)

-- local functions

local function register_event()
  if not primitives.event_registered then
    Event.add_removable_nth_tick(907, nth_tick_token)
    primitives.event_registered = true
  end
end

local function unregister_event()
  if primitives.event_registered then
    Event.remove_removable_nth_tick(907, nth_tick_token)
    primitives.event_registered = nil
  end
end

local function spawn_market(args, player)
  if args and args.removeall == 'removeall' then
    local count = 0
    for _, market in pairs(markets) do
      if market.valid then
        count = count + 1
        market.destroy()
      end
    end
    player.print(count .. ' markets removed')
    return
  end
  local surface = game.get_surface('mines')
  local force = game.forces.player
  local maket_spawn_pos = market_config.standard_market_location

  if args and args.surface then
    surface = game.get_surface(type(args.surface) == 'table' and args.surface.name or args.surface)
  end

  if player then -- If we have a player, this is coming from a player running the command
    surface = player.surface
    force = player.force
    maket_spawn_pos = player.position
    maket_spawn_pos.y = round(maket_spawn_pos.y - 4)
    maket_spawn_pos.x = round(maket_spawn_pos.x)
    player.print('Market added. To remove it, highlight it with your cursor and use the /destroy command, or use /market removeall to remove all markets placed.')
  end

  local market = surface.create_entity({ name = 'market', position = maket_spawn_pos, force = 'neutral' })
  markets[#markets + 1] = market
  market.destructible = false

  Retailer.add_market('fish_market', market)

  if table.size(Retailer.get_items('fish_market')) == 0 then
    for _, prototype in pairs(market_items) do
      Retailer.set_item('fish_market', prototype)
    end
  end

  force.add_chart_tag(surface, { icon = { type = 'item', name = currency }, position = maket_spawn_pos, text = 'Market' })
end

local function fish_earned(event, amount)
  amount = math.random(math.min(amount, max_coins_earned))
  local player_index = event.player_index
  local player = game.get_player(player_index)

  local stack = { name = currency, count = amount }
  local inserted = player.insert(stack)
  if amount > 0 then
    player.surface.create_entity {
      name = 'flying-text',
      position = { player.position.x - 1, player.position.y },
      text = '+' .. amount .. ' [img=item.coin]',
      color = Color.gold,
      render_player_index = player.index,
    }
  end

  local diff = amount - inserted
  if diff > 0 then
    stack.count = diff
    player.surface.spill_item_stack(player.position, stack, true)
  end

  change_for_player(player_index, coins_earned_name, amount)
  if get_for_player(player_index, coins_earned_name) % 70 == 0 and player and player.valid then
    if math.random() < 0.05 then
      local message = fish_market_bonus_message[random(#fish_market_bonus_message)]
      player.print(message)
    end
  end
end

local function pre_player_mined_item(event)
  local type = event.entity.type
  if type == 'simple-entity' then -- Cheap check for rock, may have other side effects
    fish_earned(event, 10)
    return
  end

  if type == 'tree' and random(1, 4) == 1 then
    fish_earned(event, 4)
  end
end

local spill_items = Token.register(function(data)
  data.surface.spill_item_stack(data.position, { name = currency, count = data.count }, true)
end)

-- Determines how many coins to drop when enemy entity dies based upon the entity_drop_amount table in config.lua
local function fish_drop_entity_died(event)
  local entity = event.entity
  if not entity or not entity.valid then
    return
  end

  local bounds = entity_drop_amount[entity.name]
  if not bounds then
    return
  end

  local chance = bounds.chance

  if chance == 0 then
    return
  end

  if chance == 1 or random() <= chance then
    local count = random(bounds.low, bounds.high)
    if count > 0 then
      Task.set_timeout_in_ticks(1, spill_items, { count = count, surface = entity.surface, position = entity.position })
    end
  end
end

local function reset_player_running_speed(player)
  local index = player.index
  player.character_running_speed_modifier = speed_records[index].pre_boost_modifier
  speed_records[index] = nil
end

local function reset_player_mining_speed(player)
  local index = player.index
  player.character_mining_speed_modifier = mining_records[index].pre_mining_boost_modifier
  mining_records[index] = nil
end

local function boost_player_running_speed(player)
  local index = player.index
  local p_name = player.name
  if not speed_records[index] then
    speed_records[index] = { start_tick = game.tick, pre_boost_modifier = player.character_running_speed_modifier, boost_lvl = 0 }
  end
  speed_records[index].boost_lvl = 1 + speed_records[index].boost_lvl

  player.character_running_speed_modifier = 1 + player.character_running_speed_modifier

  if speed_records[index].boost_lvl >= 4 then
    game.print(format(running_speed_boost_messages[speed_records[index].boost_lvl], p_name))
    reset_player_running_speed(player)
    player.character.die(player.force, player.character)
    return
  end

  player.print(format(running_speed_boost_messages[speed_records[index].boost_lvl], p_name))
  register_event()
end

local function boost_player_mining_speed(player)
  local index = player.index
  local p_name = player.name
  if not mining_records[index] then
    mining_records[index] = { start_tick = game.tick, pre_mining_boost_modifier = player.character_mining_speed_modifier, boost_lvl = 0 }
  end
  mining_records[index].boost_lvl = 1 + mining_records[index].boost_lvl

  player.character_mining_speed_modifier = 1 + player.character_mining_speed_modifier

  if mining_records[index].boost_lvl >= 4 then
    game.print(format(mining_speed_boost_messages[mining_records[index].boost_lvl], p_name))
    reset_player_mining_speed(player)
    player.character.die(player.force, player.character)
    return
  end

  player.print(format(mining_speed_boost_messages[mining_records[index].boost_lvl], p_name))
  register_event()
end

local function market_item_purchased(event)
  local item_name = event.item.name
  if item_name == 'temporary-running-speed-bonus' then
    boost_player_running_speed(event.player)
    return
  end

  if item_name == 'temporary-mining-speed-bonus' then
    boost_player_mining_speed(event.player)
    return
  end
end

nth_tick_token = Token.register(function()
  local tick = game.tick
  for k, v in pairs(speed_records) do
    if tick - v.start_tick > 3000 then
      local player = game.get_player(k)
      if player and player.valid and player.connected and player.character then
        reset_player_running_speed(player)
      end
    end
  end

  for k, v in pairs(mining_records) do
    if tick - v.start_tick > 6000 then
      local player = game.get_player(k)
      if player and player.valid and player.connected and player.character then
        reset_player_mining_speed(player)
      end
    end
  end

  if not next(speed_records) and not next(mining_records) then
    unregister_event()
  end
end)

local function fish_player_crafted_item(event)
  if random(1, 50) == 1 then
    fish_earned(event, 1)
  end
end

local function player_created(event)
  local player = game.get_player(event.player_index)

  if not player or not player.valid then
    return
  end

  local count = global.config.player_rewards.info_player_reward and 1 or 10
  player.insert { name = currency, count = count }
end

Event.add(defines.events.on_pre_player_mined_item, pre_player_mined_item)
Event.add(defines.events.on_entity_died, fish_drop_entity_died)
Event.add(Retailer.events.on_market_purchase, market_item_purchased)
Event.add(defines.events.on_player_crafted_item, fish_player_crafted_item)
Event.add(defines.events.on_player_created, player_created)

if market_config.create_standard_market then
  local delay = market_config.delay or 30
  if delay then
    local spawn_market_token = Token.register(spawn_market)
    Event.on_init(function()
      Task.set_timeout_in_ticks(delay, spawn_market_token)
    end)
  else
    Event.on_init(spawn_market)
  end
end