mod_gui = require("mod-gui")
require("util")
require("story")

tightspot_prices =
{
  ["coal"]=5,
  ["transport-belt"]=5,
  ["underground-belt"]=20,
  ["fast-transport-belt"]=50,
  ["fast-underground-belt"]=200,
  ["splitter"]=25,
  ["fast-splitter"]=50,
  ["burner-inserter"]=10,
  ["inserter"]=10,
  ["long-handed-inserter"]=15,
  ["fast-inserter"]=20,
  ["wooden-chest"]=5,
  ["iron-chest"]=10,
  ["stone-furnace"]=10,
  ["steel-furnace"]=50,
  ["electric-furnace"]=70,
  ["offshore-pump"]=10,
  ["pipe"]=5,
  ["pipe-to-ground"]=20,
  ["boiler"]=15,
  ["steam-engine"]=50,
  ["small-electric-pole"]=10,
  ["medium-electric-pole"]=50,
  ["big-electric-pole"]=100,
  ["substation"]=150,
  ["assembling-machine-1"]=30,
  ["assembling-machine-2"]=50,
  ["assembling-machine-3"]=100,
  ["electric-mining-drill"]=50,
  ["burner-mining-drill"]=10,
  ["pumpjack"]=50,
  ["oil-refinery"]=100,
  ["chemical-plant"]=50,
  ["storage-tank"]=40,
  ["pump"]=20,
  ["logistic-robot"]=150,
  ["passive-provider-chest"]=50,
  ["requester-chest"]=50
}

function tightspot_make_offer(item)
  if not tightspot_prices[item] then game.print(item.." price is not defined. Please report this on the factorio bug forum") return end
  return { price={{name = "coin", count = tightspot_prices[item]}}, offer={type="give-item", item=item}}
end

function tightspot_init(level)
  game.players[1].disable_recipe_groups()
  game.players[1].minimap_enabled = false
  game.map_settings.pollution.enabled = false
  storage.result_chest = {game.get_entity_by_tag("result-chest").name, game.get_entity_by_tag("result-chest").position}
  storage.market = {game.get_entity_by_tag("market").name, game.get_entity_by_tag("market").position}
  storage.speed = 8
  local result = {}
  result.level = level
  result.demand = {}
  for index, item in pairs(level.demand) do
    result.demand[item.item] = item.price
  end
  return result
end

function tightspot_get_required_balance()
  if game.difficulty == defines.difficulty.easy then
    return level.required_balance.easy
  elseif game.difficulty == defines.difficulty.normal then
    return level.required_balance.normal
  else
    return level.required_balance.hard
  end
end

function tightspot_init_level(tightspot)
  local player = game.players[1]
  local character = player.character
  player.set_controller{type=defines.controllers.god}
  if character then character.destroy() end
  local playerforce = player.force
  playerforce.manual_mining_speed_modifier = 1000

  local level = tightspot.level
  tightspot.debt = level.money
  tightspot.income = 0
  local surface = player.surface
  local wallCollisionBox = prototypes.entity["stone-wall"].collision_box

  for x=level.area[1][1],level.area[2][1],1 do
    for y=level.area[1][2],level.area[2][2],1 do
      local entities = game.players[1].surface.find_entities{{x + 0.1, y + 0.1}, {x + 0.9, y + 0.9}}
      local shouldcover = true
      for index, item in pairs(entities) do
        if item.type == "resource" or
           item.type == "tree" then
          item.minable = false
          if item.type ~= "tree" then
            shouldcover = false
          end
        end
      end
      if shouldcover and surface.can_place_entity{name = "stone-wall", position = {x + 0.5, y + 0.5}} then
        surface.create_entity{name = "stone-wall", position = {x, y}, force=playerforce}
      end
    end
  end
  local market = game.surfaces[1].find_entity(storage.market[1], storage.market[2])
  for index, item in pairs(level.offers) do
    market.add_market_item(item)
  end
  game.players[1].insert{name = "coin", count = level.money}
  game.players[1].force.disable_all_prototypes()
  local recipe_list = game.players[1].force.recipes
  for index, item in pairs(level.recipes) do
    recipe_list[item].enabled = true
  end
  tightspot_init_spending_frame(tightspot)
  tightspot_update_spending(tightspot)
  game.players[1].clear_recipe_notifications()
end

function tightspot_init_spending_frame(tightspot)
  local player = game.players[1]

  local flow = mod_gui.get_frame_flow(player)
  local frame = flow.add{type="frame", name="tightspot_frame", style=mod_gui.frame_style, caption = {"level", tightspot.level.level_number}, direction="vertical"}
  local inner = frame.add{type = "frame", style = "inside_shallow_frame", direction = "vertical"}
  inner.style.padding = 4
  local info_table = inner.add{type = "table", column_count = 1, style = "bordered_table"}

  local money_flow = info_table.add{type = "flow", direction = "vertical"}

  money_flow.add{type="label", caption={"money"}, style="caption_label"}
  local table = money_flow.add{type="table", column_count=2}

  local available = game.players[1].get_item_count("coin")
  local debt = tightspot.debt

  table.add{type="label", caption={"", {"balance"}, {"colon"}}}
  storage.balance_label = table.add{type="label", caption=util.format_number(debt - available), name="balance"}
  table.add{type="label", caption={"", {"required"}, {"colon"}}}
  table.add{type="label", caption=util.format_number(tightspot_get_required_balance())}

  local level = tightspot.level
  table.add{type="label", caption={"", {"time"}, {"colon"}}}
  table.add{type="label", caption=util.formattime(level.time)}

  local demand_flow = info_table.add{type = "flow", direction = "vertical"}

  demand_flow.add{type="label", caption = {"demand"}, style="caption_label"}

  local demand_table = demand_flow.add{type = "table", column_count = 3}
  demand_table.style.column_alignments[3] = "right"
  local item_prototypes = prototypes.item
  for index, item in pairs(level.demand) do
    local sprite = demand_table.add{type ="sprite", sprite = "item/"..item.item, style = "small_text_image"}
    demand_table.add{type ="label", caption = {"", item_prototypes[item.item].localised_name, {"colon"}}}
    demand_table.add{type ="label", caption = util.format_number(item.price)}
  end

  mod_gui.get_button_flow(player).add{type = "button", name="tightspot_start", caption={"start"}, style = mod_gui.button_style}
end

function tightspot_init_progress_frame(tightspot)
  local player = game.players[1]
  local flow = mod_gui.get_frame_flow(player)
  local frame = flow.add{type = "frame", name="tightspot_progress_frame", style=mod_gui.frame_style, caption={"progress"}, direction="vertical"}
  local inner = frame.add{type = "frame", direction = "vertical", style = "inside_shallow_frame"}
  inner.style.padding = 4
  local info_table = inner.add{type = "table", column_count = 1, style = "bordered_table"}
  storage.progress_bar = info_table.add{type="progressbar", name="tightspot_progressbar", style = "electric_satisfaction_statistics_progressbar"}
  storage.progress_bar.style.horizontally_stretchable = true
  storage.speed_label = info_table.add{type="label"}
  local controlflow = info_table.add{type="flow"}
  controlflow.add{type="button", caption="+", name="faster"}
  controlflow.add{type="button", caption="-", name="slower"}
  tightspot_update_speed_label()
end

function tightspot_update_progress(tightspot)
  if (game.tick - tightspot.simulation_started_at) % 60 ~= 0 then
    return
  end
  local time = game.tick - tightspot.simulation_started_at
  storage.progress_bar.value = time / level.time
  storage.progress_bar.caption = {"", {"time"}, {"colon"}, " ", util.formattime(time)}
end

function tightspot_update_spending(tightspot)
  local available
  if game.players[1].controller_type ~= defines.controllers.ghost then
    available = game.players[1].get_item_count("coin")
  else
    available = tightspot.available
  end
  local debt = tightspot.debt
  storage.balance_label.caption = util.format_number(available - debt + tightspot.income)
end

function tightspot_get_missing_to_win(tightspot)
  local available = tightspot.available
  local debt = tightspot.debt
  local required = tightspot_get_required_balance()
  return required + debt - tightspot.income - available
end

function tightspot_sell_back(tightspot)
  local level = tightspot.level
  local player = game.players[1]
  local addition = 0
  for index, item in pairs(level.offers) do
    local count = player.get_item_count(item.offer.item)
    addition = addition + count * item.price[1].count
  end
  player.create_local_flying_text{text = "+" .. addition, position = player.position, color = {g = 1}, time_to_live = 150}
  tightspot.debt = tightspot.debt - addition
end

function leave_remote_controller(player)
  if player.controller_type == defines.controllers.remote then
    player.teleport(player.position, player.surface) -- trick to quit remote controller in a way that preserves god controller inventory. set_controller was getting rid of coins
    assert(player.controller_type ~= defines.controllers.remote)
  end
end

function tightspot_start_level(tightspot)
  leave_remote_controller(game.players[1])
  map_save()
  tightspot_sell_back(tightspot)
  mod_gui.get_button_flow(game.players[1]).tightspot_start.destroy()
  game.speed = storage.speed
  tightspot_init_progress_frame(tightspot)
  mod_gui.get_button_flow(game.players[1]).add{type = "button", name="tightspot_reload", caption={"reload"}, style = mod_gui.button_style}
  tightspot.available = game.players[1].get_item_count("coin")
  game.players[1].set_controller{type=defines.controllers.ghost}
  tightspot.simulation_started_at = game.tick
  local level = tightspot.level
  local entities = game.players[1].surface.find_entities(level.area)
  for index, item in pairs(entities) do
    item.active = true
  end
  tightspot_update_spending(tightspot)
end

function tightspot_show_level_description(tightspot)
  local level = tightspot.level
  if level.description ~= nil then
    game.show_message_dialog(level.description)
  end
end

function tightspot_update_speed_label()
  storage.speed_label.caption = {"", {"speed"}, {"colon"}, " ", game.speed}
end

local safe_name = "safe-surface-really-nice-place-and-peaceful"
local get_save_surface = function()
  return game.surfaces[safe_name] or game.create_surface(safe_name, {width = 200, height = 200})
end

local save_area = {{-200, -200}, {200, 200}}
local save_surface = function()
  game.surfaces[1].clone_area
  {
    source_area = save_area,
    destination_area = save_area,
    destination_surface = get_save_surface(),
    clone_tiles = true,
    clone_entities = true,
    clone_decoratives = true,
    clear_destination_entities = true,
    clear_destination_decoratives = true,
    expand_map= true
  }
end

local load_surface = function()
  get_save_surface().clone_area
  {
    source_area = save_area,
    destination_area = save_area,
    destination_surface = game.surfaces[1],
    clone_tiles = true,
    clone_entities = true,
    clone_decoratives = true,
    clear_destination_entities = true,
    clear_destination_decoratives = true,
    expand_map= true
  }
end

function map_load()
  game.speed = 1
  storage.tightspot = storage.save.tightspot
  load_surface()
  game.players[1].set_controller{type=defines.controllers.god}
  for k = 1, 10 do
    local inv = game.players[1].get_inventory(k)
    if inv then
      inv.clear()
      if storage.save.player_inventory[k] then
        for k, item in pairs (storage.save.player_inventory[k]) do
          game.players[1].insert(item)
        end
      end
    end
  end
  if mod_gui.get_button_flow(game.players[1]).tightspot_reload then
    mod_gui.get_button_flow(game.players[1]).tightspot_reload.destroy()
  end
  if mod_gui.get_frame_flow(game.players[1]).tightspot_progress_frame then
    mod_gui.get_frame_flow(game.players[1]).tightspot_progress_frame.destroy()
  end
  story_jump_to(storage.story, "building")
  mod_gui.get_button_flow(game.players[1]).add{type = "button", name="tightspot_start", caption={"start"}, style = mod_gui.button_style}

end

function map_save()
  storage.save = {}
  local player = game.players[1]
  local chest = game.surfaces[1].find_entity(storage.result_chest[1], storage.result_chest[2])
  if chest then
    for k, item in pairs (chest.get_output_inventory().get_contents()) do
      player.insert(item)
    end
    chest.clear_items_inside()
  end
  storage.save.tightspot = util.table.deepcopy(storage.tightspot)

  save_surface()
  player.clear_cursor()
  local player_inventory = {}
  for k = 1, 10 do
    local inv = player.get_inventory(k)
    if inv then
      player_inventory[k] = inv.get_contents()
    end
  end
  storage.save.player_inventory = player_inventory
end

function tightspot_check_level(tightspot, event)
  if event.name == defines.events.on_gui_click then
    if event.element.name == "slower" and storage.speed > 1 then
      storage.speed = storage.speed / 2
      game.speed = storage.speed
      tightspot_update_speed_label()
    elseif event.element.name == "faster" and storage.speed < 64 then
      storage.speed = storage.speed * 2
      game.speed = storage.speed
      tightspot_update_speed_label()
    elseif event.element.name == "tightspot_reload" then
      map_load()
      return false
    end
    return false
  end
  local result_chest = game.surfaces[1].find_entity(storage.result_chest[1], storage.result_chest[2])
  local contents = result_chest.get_inventory(defines.inventory.chest).get_contents()
  local addition = 0
  for k, item in pairs(contents) do
    local price = tightspot.demand[item.name]
    if price ~= nil then
      addition = addition + price * item.count
    end
  end
  if addition ~= 0 then
    game.players[1].create_local_flying_text{text = "+" .. addition, position = result_chest.position, color = {g = 1}, time_to_live = 150}
    tightspot.income = tightspot.income + addition
    tightspot_update_spending(tightspot)
    result_chest.clear_items_inside()
  end
  tightspot_update_progress(tightspot)
  return (game.tick - tightspot.simulation_started_at) >= level.time
end

function land_price(level, position)
  return math.abs(position.x - level.center.x) + math.abs(position.y - level.center.y)
end

story_table =
{
  {
    {
      action = function()
        tightspot_init_level(storage.tightspot)
      end
    },
    {
      action = function()
        if level.showrules ~= nil then
          game.show_message_dialog{text={"welcome"}}
          game.show_message_dialog{text={"rules1"}}
          game.show_message_dialog{text={"rules2"}}
          game.show_message_dialog{text={"rules3"}}
        end
      end
    },
    {
      action = function()
        tightspot_show_level_description(storage.tightspot)
      end
    },
    {
      name = "building",
      update =
      function(event)
        if event.name == defines.events.on_tick then return end
        tightspot_update_spending(storage.tightspot)
        if event.name == defines.events.on_built_entity then
          if event.entity.name == "stone-wall" then
            local entity = event.entity
            local position = entity.position
            local area = {{position.x - 0.4, position.y - 0.4}, {position.x + 0.4, position.y + 0.4}}
            if #game.players[1].surface.find_entities_filtered{area = area, type = "resource"} ~= 0 then
              event.entity.destroy()
              game.players[1].insert{name = "stone-wall", count = 1}
              game.players[1].print("This area is not resellable")
              return
            end
            local price = land_price(level, event.entity.position)
            game.players[1].create_local_flying_text{text = "+" .. price, position = event.entity.position, color = {g = 1}, time_to_live = 150}
            game.players[1].insert{name = "coin", count = price}
          else
            event.entity.active = false
          end
        end
        if event.name == defines.events.on_pre_player_mined_item and
           event.entity.name == "stone-wall" then
          local position = event.entity.position
          local price = land_price(level, event.entity.position)
          game.players[1].create_local_flying_text{text = "-" .. price, position = event.entity.position, color = {r = 1}, time_to_live = 150}
          local available = game.players[1].get_item_count("coin")
          game.players[1].remove_item{name = "coin", count = price}
          if available < price then
            storage.tightspot.debt = storage.tightspot.debt + 5000
            game.players[1].insert{name = "coin", count = 5000 - price + available}
            game.players[1].print("Borrowed 5000 coins to pay for the land")
          end
        end
      end,
      condition =
      function(event)
        return event.name == defines.events.on_gui_click and event.element.name == "tightspot_start"
      end,
      action =
      function()
        tightspot_start_level(storage.tightspot)
      end
    },
    {
      condition =
      function(event)
        return tightspot_check_level(storage.tightspot, event)
      end,
      action =
      function()
        local missing = tightspot_get_missing_to_win(storage.tightspot)
        if missing > 0 then
          game.show_message_dialog{text={"lost-message", missing}}
        else
          game.show_message_dialog{text={"won-message", -missing}}
        end
      end
    },
    {
      condition =
      function()
        if tightspot_get_missing_to_win(storage.tightspot) > 0 then
          map_load()
          return false
        end
        return true
      end,
      action = function() end
    }
  }
}

story_init_helpers(story_table)

tightspot_events =
{
  defines.events.on_gui_click,
  defines.events.on_tick,
  defines.events.on_built_entity,
  defines.events.on_player_cursor_stack_changed,
  defines.events.on_pre_player_mined_item
}
