util =
{
  table = {}
}

function table.deepcopy(object)
  local lookup_table = {}
  local function _copy(object)
    if type(object) ~= "table" then
      return object
    elseif lookup_table[object] then
      return lookup_table[object]
    end
    local new_table = {}
    lookup_table[object] = new_table
    for index, value in pairs(object) do
      new_table[_copy(index)] = _copy(value)
    end
    return setmetatable(new_table, getmetatable(object))
  end
  return _copy(object)
end

function table.compare( tbl1, tbl2 )
  if tbl1 == tbl2 then return true end
  for k, v in pairs( tbl1 ) do
    if  type(v) == "table" and type(tbl2[k]) == "table" then
      if not table.compare( v, tbl2[k] )  then return false end
    else
      if ( v ~= tbl2[k] ) then return false end
    end
  end
  for k, v in pairs( tbl2 ) do
    if tbl1[k] == nil then return false end
  end
  return true
end

util.table.deepcopy = table.deepcopy
util.table.compare = table.compare
util.copy = util.table.deepcopy

function util.distance(position1, position2)
  local x1 = position1[1] or position1.x
  local y1 = position1[2] or position1.y
  local x2 = position2[1] or position2.x
  local y2 = position2[2] or position2.y
  return ((x1 - x2) ^ 2 + (y1 - y2) ^ 2) ^ 0.5
end

function util.positiontostr(pos)
  return string.format("[%g, %g]", pos[1] or pos.x, pos[2] or pos.y)
end

function util.formattime(ticks)
  local seconds = ticks / 60
  local minutes = math.floor((seconds)/60)
  local seconds = math.floor(seconds - 60*minutes)
  return string.format("%d:%02d", minutes, seconds)
end

function util.color(hex)  -- supports 'rrggbb', 'rgb', 'rrggbbaa', 'rgba', 'ww', 'w'
  local function h(i,j)
    return j and tonumber("0x"..hex:sub(i,j)) / 255 or tonumber("0x"..hex:sub(i,i)) / 15
  end

  hex = hex:gsub("#","")
  return #hex == 6 and {r = h(1,2), g = h(3,4), b = h(5,6)}
    or #hex == 3 and {r = h(1), g = h(2), b = h(3)}
    or #hex == 8 and {r = h(1,2), g = h(3,4), b = h(5,6), a = h(7,8)}
    or #hex == 4 and {r = h(1), g = h(2), b = h(3), a = h(4)}
    or #hex == 2 and {r = h(1,2), g = h(1,2), b = h(1,2)}
    or #hex == 1 and {r = h(1), g = h(1), b = h(1)}
    or {r=1, g=1, b=1}
end

function util.premul_color(color)
  local r = color.r or color[1]
  local g = color.g or color[2]
  local b = color.b or color[3]
  local a = color.a or color[4] or 1
  return
  {
    r = r and (r * a),
    g = g and (g * a),
    b = b and (b * a),
    a = a
  }
end

function util.mix_color(c1, c2)
  return
  {
    (c1.r or c1[1] or 0) * (c2.r or c2[1] or 0),
    (c1.g or c1[2] or 0) * (c2.g or c2[2] or 0),
    (c1.b or c1[3] or 0) * (c2.b or c2[3] or 0),
    (c1.a or c1[4] or 1) * (c2.a or c2[4] or 1)
  }
end

function util.multiply_color(c1, n)
  return
  {
    (c1.r or c1[1] or 0) * (n or 0),
    (c1.g or c1[2] or 0) * (n or 0),
    (c1.b or c1[3] or 0) * (n or 0),
    (c1.a or c1[4] or 1) * (n or 1)
  }
end

function util.get_color_with_alpha(color, alpha, normalized_alpha)
  local new_color =
  {
    r = color.r or color[1] or 0,
    g = color.g or color[2] or 0,
    b = color.b or color[3] or 0
  }
  new_color.a = normalized_alpha and (new_color.r > 1 or new_color.g > 1 or new_color.b > 1) and alpha * 255 or alpha
  return new_color
end

util.direction_vectors = {
  [defines.direction.north]          = { 0, -1 },
  [defines.direction.northnortheast] = { 1, -2 },
  [defines.direction.northeast]      = { 1, -1 },
  [defines.direction.eastnortheast]  = { 2, -1 },
  [defines.direction.east]           = { 1,  0 },
  [defines.direction.eastsoutheast]  = { 2,  1 },
  [defines.direction.southeast]      = { 1,  1 },
  [defines.direction.southsoutheast] = { 1,  2 },
  [defines.direction.south]          = { 0,  1 },
  [defines.direction.southsouthwest] = {-1,  2 },
  [defines.direction.southwest]      = {-1,  1 },
  [defines.direction.westsouthwest]  = {-2,  1 },
  [defines.direction.west]           = {-1,  0 },
  [defines.direction.westnorthwest]  = {-2, -1 },
  [defines.direction.northwest]      = {-1, -1 },
  [defines.direction.northnorthwest] = {-1, -2 },
}

function util.moveposition(position, direction, distance)
  local direction_vector = util.direction_vectors[direction]
  if not direction_vector then error(direction .. " is not a valid or supported direction") end

  local x = position[1] or position.x
  local y = position[2] or position.y
  return {x + direction_vector[1] * distance, y + direction_vector[2] * distance}
end

-- orientation of 1 = 360 degrees
function util.rotate_position(position, orientation)
  local x = position[1] or position.x
  local y = position[2] or position.y
  local radians = orientation * 2 * math.pi
  return {x = x * math.cos(radians) - y * math.sin(radians),
          y = x * math.sin(radians) + y * math.cos(radians)}
end

function util.oppositedirection(direction)
  if not tonumber(direction) then error(direction .. " is not a valid direction") end
  return (direction + 8) % 16
end

function util.multiplystripes(count, stripes)
  local ret = {}
  for _, stripe in ipairs(stripes) do
    for _ = 1, count do
      ret[#ret + 1] = stripe
    end
  end
  return ret
end

function util.by_pixel(x,y)
  return {x / 32, y / 32}
end

function util.by_pixel_hr(x,y)
  return {x / 64, y / 64}
end

function util.foreach_sprite_definition(table_, fun_)
  --for k, tab in pairs(table_) do
    fun_(table_)
  --end
  return table_
end

function util.add_shift(a, b)
  if not (a and b) then
    return a or b
  end
  return { a[1] + b[1], a[2] + b[2] }
end

function util.add_shift_offset(offset_, table_)
  return
    util.foreach_sprite_definition(table_, function(tab)
      tab.shift = util.add_shift(tab.shift, offset_)
    end)
end

function util.mul_shift(shift, scale)
  if not (shift and scale) then
    return shift
  end
  local x = shift[1] or shift.x
  local y = shift[2] or shift.y
  return {x * scale, y * scale}
end

function util.format_number(amount, append_suffix)
  local suffix = ""
  if append_suffix then
    local suffix_list =
      {
        ["T"] = 1000000000000,
        ["B"] = 1000000000,
        ["M"] = 1000000,
        ["k"] = 1000
      }
    for letter, limit in pairs (suffix_list) do
      if math.abs(amount) >= limit then
        amount = math.floor(amount/(limit/10))/10
        suffix = letter
        break
      end
    end
  end
  local formatted, k = amount, nil
  while true do
    formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2')
    if (k==0) then
      break
    end
  end
  return formatted..suffix
end

function util.increment(t, k, v)
  t[k] = t[k] + (v or 1)
end

function util.conditional_return(value, data)
  return value and data
end

-- Recursively merges and/or deep-copies tables.
-- Entries in later tables override entries in earlier ones, unless
-- both entries are themselves tables, in which case they are recursively merged.
-- Non-merged tables are deep-copied, so that the result is brand new.
function util.merge(tables)
  local ret = {}
  for i, tab in ipairs(tables) do
    for k, v in pairs(tab) do
      if (type(v) == "table") then
        if (type(ret[k] or false) == "table") then
          ret[k] = util.merge{ret[k], v}
        else
          ret[k] = table.deepcopy(v)
        end
      else
        ret[k] = v
      end
    end
  end
  return ret
end

util.insert_safe = function(entity, item_dict)
  if not (entity and entity.valid and item_dict) then return end
  local items = prototypes.item
  local insert = entity.insert
  for name, count in pairs (item_dict) do
    if items[name] then
      insert{name = name, count = count}
    else
      log("Item to insert not valid: "..name)
    end
  end
end

util.remove_safe = function(entity, item_dict)
  if not (entity and entity.valid and item_dict) then return end
  local items = prototypes.item
  local remove = entity.remove_item
  for name, count in pairs (item_dict) do
    if items[name] then
      remove{name = name, count = count}
    else
      log("Item to remove not valid: "..name)
    end
  end
end

util.split_whitespace = function(string)
  if not string then return {} end

  local result = {}
  for w in string:gmatch("%S+") do
    table.insert(result, w)
  end
  return result
end

util.split = function(inputstr, sep)
  local result = {}

  for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
    table.insert(result, str)
  end

  return result
end

util.string_starts_with = function(str, start)
  return str.sub(str, 1, string.len(start)) == start
end

util.string_replace = function(str, what, with)
  -- Only use the first returned value from gsub
  local what_escaped = string.gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1")
  local with_escaped = string.gsub(with, "[%%]", "%%%%")
  str = string.gsub(str, what_escaped, with_escaped)
  return str --only return the first returned value from
end

util.clamp = function(x, lower, upper)
  return math.max(lower, math.min(upper, x))
end

local walkable_if_not_collides_with = {"item", "object", "player", "water_tile"}

local is_walkable = function(mask)
  for k, layer in pairs (walkable_if_not_collides_with) do
    if mask.layers[layer] then return false end
  end
  return true
end

util.get_walkable_tile = function()
  for name, tile in pairs (prototypes.tile) do
    if is_walkable(tile.collision_mask) and not tile.items_to_place_this then
      return name
    end
  end
  error("No walkable tile in prototype list")
end

-- This function takes 2 icons tables, and adds the second to the first, but applies scale, shift and tint to the entire second set.
-- This allows you to manipulate the entire second icons table in the same way as you would manipulate a single icon when adding to the icons table.
function util.combine_icons(icons1, icons2, inputs, default_icon_size)
  scale = inputs.scale or 1
  shift = inputs.shift or {0, 0}
  tint = inputs.tint or {r = 1, g = 1, b = 1, a = 1}

  local icons = table.deepcopy(icons1)
  for _,icon_to_add in pairs(icons2) do
    local icon = {}
    icon.icon = icon_to_add.icon
    icon.icon_size = icon_to_add.icon_size or default_icon_size or error("No icon size defined for icon \n"..serpent.block(icon))
    icon.scale = scale * (icon_to_add.scale or (0.5 * defines.default_icon_size / icon.icon_size))
    if icon_to_add.shift then
      icon.shift = {icon_to_add.shift[1] * scale + shift[1], icon_to_add.shift[2] * scale + shift[2]}
    else
      icon.shift = shift
    end
    if icon_to_add.tint then
      icon.tint = util.mix_color(tint, icon_to_add.tint)
    else
      icon.tint = tint
    end
    table.insert(icons,icon)
  end
  return icons
end

local energy_chars =
{
  k = 10^3,
  M = 10^6,
  G = 10^9,
  T = 10^12,
  P = 10^15,
  E = 10^18,
  Z = 10^21,
  Y = 10^24,
  R = 10^27,
  Q = 10^30,
}

function util.technology_icon_constant_damage(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256,
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-damage.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_speed(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256,
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-speed.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_movement_speed(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256,
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-movement-speed.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_range(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256,
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-range.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_planet(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256,
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-planet.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_equipment(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256,
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-equipment.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_followers(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256,
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-count.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_capacity(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-capacity.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_stack_size(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-capacity.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_productivity(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-mining-productivity.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_recipe_productivity(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-recipe-productivity.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_braking_force(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-braking-force.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end
function util.technology_icon_constant_mining(technology_icon)
  local icons =
  {
    {
      icon = technology_icon,
      icon_size = 256
    },
    {
      icon = "__core__/graphics/icons/technology/constants/constant-mining.png",
      icon_size = 128,
      scale = 0.5,
      shift = {50, 50},
      floating = true
    }
  }
  return icons
end

function util.parse_energy(energy)

  local ending = energy:sub(energy:len())
  if not (ending == "J" or ending == "W") then
    error(ending.. " is not a valid unit of energy")
  end

  local multiplier = (ending == "W" and  1 / 60) or 1
  local magnitude = energy:sub(energy:len() - 1, energy:len() - 1)

  if tonumber(magnitude) then
    return tonumber(energy:sub(1, energy:len()-1)) * multiplier
  end

  multiplier = multiplier * (energy_chars[magnitude] or error(magnitude.. " is not valid magnitude"))
  return tonumber(energy:sub(1, energy:len()-2)) * multiplier

end

function util.product_amount(product)
  return product.probability * (product.amount or ((product.amount_min + product.amount_max) / 2))
end

function util.empty_sprite()
  return
  {
    filename = "__core__/graphics/empty.png",
    priority = "extra-high",
    width = 1,
    height = 1
  }
end

function util.empty_animation(animation_length)
  return
  {
    filename = "__core__/graphics/empty.png",
    priority = "extra-high",
    width = 1,
    height = 1,
    repeat_count = animation_length,
    direction_count = 1
  }
end

function util.empty_icon()
  return
  {
    icon = "__core__/graphics/empty.png",
    icon_size = 64,
    scale = 0.5
  }
end

function util.draw_as_glow(layer)
  layer.draw_as_glow = true
  return layer
end

function util.sprite_load(path, table)
  local original_shift = table.shift or {0, 0}
  local multiply_shift = table.multiply_shift or 1
  local sprite_data = require(path)
  table.width       = sprite_data.width
  table.height      = sprite_data.height
  table.shift       = {sprite_data.shift[1] * multiply_shift + original_shift[1], sprite_data.shift[2] * multiply_shift + original_shift[2]}
  table.line_length = sprite_data.line_length
  table.frames      = sprite_data.frames

  if table.frame_index then
    local column_index = table.frame_index % sprite_data.line_length
    local row_index = (table.frame_index - column_index) / sprite_data.line_length
    table.x = column_index * sprite_data.width
    table.y = row_index * sprite_data.height
    table.frame_index = nil
  end

  if sprite_data.filenames then
    local t = {}
    for k, v in pairs(sprite_data.filenames) do
      t[k] = path .. v
    end
    table.filenames = t
    table.lines_per_file = sprite_data.lines_per_file
  else
    table.filename = path .. '.png'
  end

  table.multiply_shift = nil

  return table
end

function util.spritesheets_to_pictures(spritesheets)
  local pictures = {}
  for _, spritesheet in pairs(spritesheets) do
    for i = 1, spritesheet.frame_count or 1, 1 do
      table.insert(pictures, util.sprite_load(spritesheet.path,
        {
          frame_index = i - 1,
          scale = spritesheet.scale or 0.5,
          dice_y = spritesheet.dice_y
        })
      )
    end
  end
  return pictures
end

function util.remove_tile_references(data, array_of_tiles_to_remove)
  -- Does not handle:
  --   explicit tile filters in "selection-tool" items
  --   ItemPrototype::place_as_tile
  --   TilePrototype::next_direction
  --   TilePrototype::transition_merges_with_tile
  --   general tile transitions, only removes tile names from water_tile_type_names

  if (type(array_of_tiles_to_remove) ~= "table") then
    error("The second parameter of util.remove_tile_reference() is expected to be array of strings.")
  end

  local tiles_to_remove = {}
  for i, n in pairs(array_of_tiles_to_remove) do
    tiles_to_remove[n] = true
  end

  local remove_from_mapping = function(mapping)
    if not mapping then
      return
    end

    for _, item in pairs(mapping) do
      if item.tiles then
        for i, t in pairs(item.tiles) do
          if t and tiles_to_remove[t] then
            item.tiles[i] = nil
          end
        end
      end
    end
  end

  for k,e in pairs(data.raw["character"]) do
    remove_from_mapping(e.footstep_particle_triggers)
    remove_from_mapping(e.synced_footstep_particle_triggers)
    remove_from_mapping(e.footprint_particles)
  end

  for k,e in pairs(data.raw["car"]) do
    remove_from_mapping(e.track_particle_triggers)
  end

  if data.raw["fire"] then
    for k, fire in pairs(data.raw["fire"]) do
      if fire.burnt_patch_alpha_variations then
        for i, v in pairs(fire.burnt_patch_alpha_variations) do
          if v and v.tile and tiles_to_remove[v.tile] then
            fire.burnt_patch_alpha_variations[i] = nil
          end
        end
      end
    end
  end

  if water_tile_type_names then
    for i = #water_tile_type_names, 1, -1 do
      if tiles_to_remove[water_tile_type_names[i]] then
          table.remove(water_tile_type_names, i)
      end
    end
  end
end

local remove = table.remove
util.remove_from_list = function(list, value)
  for k, v in pairs (list) do
    if v == value then
      remove(list, k)
      return true
    end
  end
  return false
end

util.list_to_map = function(list)
  local map = {}
  --Here I am trusting you not to give a list of junk.
  for k, value in pairs(list) do
    map[value] = true
  end
  return map
end

util.normalize_recipe_product = function(raw_product)
  local product = util.copy(raw_product)

  if product.amount then
    product.amount_min = product.amount
    product.amount_max = product.amount
    product.amount = nil
  end

  return product
end

util.normalize_recipe_products = function(recipe)
  if not recipe.results then
    error("Recipe has no results: ".. recipe.name)
  end
  local products = {}

  for _,raw_product in pairs(recipe.results) do
    if type(raw_product) ~= "table" then
      error("Recipe "..recipe.name.." has malformed results: it should only contain tables (one per product) but "..type(raw_product).." was found")
    end

    table.insert(products, util.normalize_recipe_product(raw_product))
  end

  return products
end

-- Returns the normalized main product or nil if the recipe defintion is invalid or there is no main product
util.get_recipe_main_product = function(recipe, normalized_products)
  if not normalized_products then
    normalized_products = util.normalize_recipe_products(recipe)
  end

  local main_product_index = 0
  local main_product = recipe.main_product
  if main_product and main_product ~= "" then
    for k,product in pairs(normalized_products) do
      if product.name == main_product then
        main_product_index = k
        break
      end
    end
  elseif main_product == nil and table_size(normalized_products) == 1 then
    main_product_index = 1
  end

  return normalized_products[main_product_index]
end

local function is_sprite_def(array)
  return array.icon or array.width and array.height and (array.filename or array.stripes or array.filenames)
end
--- Recursively tint all sprite definitions in the given table.
--- If `tint` is `false`, all tinting will be removed.
function util.recursive_tint(array, tint)
  for _, v in pairs(array) do
    if type(v) == "table" then
      if is_sprite_def(v) then
        if tint == false then
          v.tint = nil
        else
          v.tint = tint
        end
      end
      v = util.recursive_tint(v, tint)
    end
  end
  return array
end

gram = 1
grams = gram
kg = 1000*grams
tons = 1000*kg
second = 60
minute = 60 * second
hour = 60 * minute
meter = 1
kilometer = 1000

return util
