-- #########################################
-- #######   EZWand version v2.0.0   #######
-- #########################################

dofile_once("data/scripts/gun/procedural/gun_action_utils.lua")
dofile_once("data/scripts/gun/gun_enums.lua")
dofile_once("data/scripts/lib/utilities.lua")
dofile_once("data/scripts/gun/procedural/wands.lua")

-- ##########################
-- ####       UTILS      ####
-- ##########################

-- Removes spells from a table whose ID is not found in the gun_actions table
local function filter_spells(spells, uses_remaining)
  dofile_once("data/scripts/gun/gun_actions.lua")
  if not spell_exist_lookup then
    spell_exist_lookup = {}
    for i, v in ipairs(actions) do
      spell_exist_lookup[v.id] = true
    end
  end
  local out = {}
  local out_uses = {}
  for i, spell in ipairs(spells) do
    if spell == "" or spell_exist_lookup[spell] then
      table.insert(out, spell)
      if uses_remaining then
        table.insert(out_uses, uses_remaining[i])
      end
    end
  end
  return out, out_uses
end

local function string_split(inputstr, sep)
  sep = sep or "%s"
  local t= {}
  local pos = 0
  local function next(s)
    pos = pos + 1
    local out = s:sub(pos, pos)
    if out ~= "" then
      return out
    end
  end
  local cur_str = ""
  local next_char = next(inputstr)
  while next_char do
    if next_char == sep then
      table.insert(t, cur_str)
      next_char = next(inputstr)
      cur_str = ""
    else
      cur_str = cur_str .. next_char
      next_char = next(inputstr)
    end
  end
  table.insert(t, cur_str)
  return t
end

local function test_conditionals(conditions)
  for i, conditon in ipairs(conditions) do
    if not conditon[1] then
      return false, conditon[2]
    end
  end
  return true
end

wand_props = {
  shuffle = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "boolean", "shuffle must be true or false" }
      }
    end,
    default = false,
  },
  spellsPerCast = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "spellsPerCast must be a number" },
        { val > 0, "spellsPerCast must be a number > 0" },
      }
    end,
    default = 1,
  },
  castDelay = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "castDelay must be a number" },
      }
    end,
    default = 20,
  },
  currentCastDelay = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "currentCastDelay must be a number" },
      }
    end,
  },
  rechargeTime = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "rechargeTime must be a number" },
      }
    end,
    default = 40,
  },
  currentRechargeTime = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "currentRechargeTime must be a number" },
      }
    end,
  },
  manaMax = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "manaMax must be a number" },
        { val > 0, "manaMax must be a number > 0" },
      }
    end,
    default = 500,
  },
  mana = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "mana must be a number" },
        { val > 0, "mana must be a number > 0" },
      }
    end,
    default = 500,
  },
  manaChargeSpeed = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "manaChargeSpeed must be a number" },
      }
    end,
    default = 200,
  },
  capacity = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "capacity must be a number" },
        { val >= 0, "capacity must be a number >= 0" },
      }
    end,
    default = 10,
  },
  spread = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "spread must be a number" },
      }
    end,
    default = 10,
  },
  speedMultiplier = {
    validate = function(val)
      return test_conditionals{
        { type(val) == "number", "speedMultiplier must be a number" },
      }
    end,
    default = 1,
  },
}
-- Throws an error if the value doesn't have the correct format or the property doesn't exist
local function validate_property(name, value)
  if wand_props[name] == nil then
    error(name .. " is not a valid wand property.", 4)
  end
  local success, err = wand_props[name].validate(value)
  if not success then
    error(err, 4)
  end
end

--[[
  values is a table that contains info on what values to set
  example:
  values = {
    manaMax = 50,
    rechargeSpeed = 20
  }
  etc
  calls error() if values contains invalid properties
  fills in missing properties with default values
]]

function validate_wand_properties(values)
  if type(values) ~= "table" then
    error("Arg 'values': table expected.", 2)
  end
  -- Check if all passed in values are valid wand properties and have the required type
  for k, v in pairs(values) do
    validate_property(k, v)
  end
  -- Fill in missing properties with default values
  for k,v in pairs(wand_props) do
    values[k] = values[k] or v.default
  end
  return values
end

 function table.contains(table, element)
  for _, value in pairs(table) do
    if value == element then
      return true
    end
  end
  return false
end

-- Returns true if entity is a wand
local function entity_is_wand(entity_id)
	local ability_component = EntityGetFirstComponentIncludingDisabled(entity_id, "AbilityComponent")
	return ComponentGetValue2(ability_component, "use_gun_script") == true
end

local function starts_with(str, start)
  return str:match("^" .. start) ~= nil
end

local function ends_with(str, ending)
  return ending == "" or str:sub(-#ending) == ending
end

-- Parses a serialized wand string into a table with it's properties
--[[local function deserialize_v1(str)
  local values = string_split(str, ";")
  if #values ~= 18 then
    error("Wrong wand import string format")
  end

  local out = {
    props = {
      shuffle = values[2] == "1",
      spellsPerCast = tonumber(values[3]),
      castDelay = tonumber(values[4]),
      rechargeTime = tonumber(values[5]),
      manaMax = tonumber(values[6]),
      mana = tonumber(values[7]),
      manaChargeSpeed = tonumber(values[8]),
      capacity = tonumber(values[9]),
      spread = tonumber(values[10]),
      speedMultiplier = tonumber(values[11])
    },
    spells = string_split(values[12] == "-" and "" or values[12], ","),
    always_cast_spells = string_split(values[13] == "-" and "" or values[13], ","),
    sprite_image_file = values[14],
    offset_x = tonumber(values[15]),
    offset_y = tonumber(values[16]),
    tip_x = tonumber(values[17]),
    tip_y = tonumber(values[18])
  }

  if #out.spells == 1 and out.spells[1] == "" then
    out.spells = {}
  end
  if #out.always_cast_spells == 1 and out.always_cast_spells[1] == "" then
    out.always_cast_spells = {}
  end

  return out
end]]

-- Parses a serialized wand string into a table with it's properties
local function deserialize(str)
  local values = string_split(str, ";")
  if #values ~= 21 then
    error("Wrong wand import string format")
  end

  local out = {
    props = {
      shuffle = values[2] == "1",
      spellsPerCast = tonumber(values[3]),
      castDelay = tonumber(values[4]),
      rechargeTime = tonumber(values[5]),
      manaMax = tonumber(values[6]),
      mana = tonumber(values[7]),
      manaChargeSpeed = tonumber(values[8]),
      capacity = tonumber(values[9]),
      spread = tonumber(values[10]),
      speedMultiplier = tonumber(values[11])
    },
    spells = string_split(values[12] == "-" and "" or values[12], ","),
    spells_uses_remaining = string_split(values[13] == "-" and "" or values[13], ","),
    always_cast_spells = string_split(values[14] == "-" and "" or values[14], ","),
    sprite_image_file = values[15],
    offset_x = tonumber(values[16]),
    offset_y = tonumber(values[17]),
    tip_x = tonumber(values[18]),
    tip_y = tonumber(values[19]),
    name = values[20],
    show_name_in_ui = (tonumber(values[21]) == 1) or false,
  }

  if #out.spells == 1 and out.spells[1] == "" then
    out.spells = {}
  end

  if #out.spells_uses_remaining == 1 and out.spells_uses_remaining[1] == "" then
    out.spells_uses_remaining = {}
  end

  if #out.always_cast_spells == 1 and out.always_cast_spells[1] == "" then
    out.always_cast_spells = {}
  end

  return out
end

-- Parses a serialized wand string into a table with it's properties
local function deserialize_wand(str)
    if not starts_with(str, "EZW") then
        error("Wrong wand import string format")
    end
    local this_version = 2
    local serialized_version = tonumber(str:match("EZWv(%d+)"))
    if serialized_version > this_version then
        return "Serialized wand was made with newer version of EZWand"
    end

    return deserialize(str)

end


local spell_type_bgs = {
	[ACTION_TYPE_PROJECTILE] = "data/ui_gfx/inventory/item_bg_projectile.png",
	[ACTION_TYPE_STATIC_PROJECTILE] = "data/ui_gfx/inventory/item_bg_static_projectile.png",
	[ACTION_TYPE_MODIFIER] = "data/ui_gfx/inventory/item_bg_modifier.png",
	[ACTION_TYPE_DRAW_MANY] = "data/ui_gfx/inventory/item_bg_draw_many.png",
	[ACTION_TYPE_MATERIAL] = "data/ui_gfx/inventory/item_bg_material.png",
	[ACTION_TYPE_OTHER] = "data/ui_gfx/inventory/item_bg_other.png",
	[ACTION_TYPE_UTILITY] = "data/ui_gfx/inventory/item_bg_utility.png",
	[ACTION_TYPE_PASSIVE] = "data/ui_gfx/inventory/item_bg_passive.png",
}

local function get_spell_bg(action_id)
	return spell_type_bgs[spell_lookup[action_id] and spell_lookup[action_id].type] or spell_type_bgs[ACTION_TYPE_OTHER]
end

-- This function is a giant mess, but it works :)
-- wand needs to be of the same format as you get from EZWand.Deserialize():
-- {
--   props = {
--     shuffle = true,
--     spellsPerCast = 1,
--     castDelay = 30,
--     rechargeTime = 30,
--     manaMax = 200,
--     mana = 200,
--     manaChargeSpeed = 20,
--     capacity = 10,
--     spread = 0,
--     speedMultiplier = 1
--   },
--   spells = { "SPELL_ONE", "SPELL_TWO" },
--   always_cast_spells = { "SPELL_ONE", "SPELL_TWO" },
--   sprite_image_file = "data/whatever.png",
--   offset_x = 0,
--   offset_y = 0,
--   tip_x = 0,
--   tip_y = 0,
--   name = "Shootblaster",
--   show_name_in_ui = true
-- }
-- To get this easily you can use EZWand.Deserialize(EZWand(wand):Serialize())
-- Better cache it though, it's not super expensive but...
local function render_tooltip(origin_x, origin_y, wand, gui_)
  origin_x = tonumber(origin_x)
  if not origin_x then
    error("RenderTooltip: Argument x is required and must be a number", 2)
  end
  origin_y = tonumber(origin_y)
  if not origin_y then
    error("RenderTooltip: Argument y is required and must be a number", 2)
  end
  -- gui = gui or GuiCreate()
  gui = gui_ or gui or GuiCreate()
  if not gui_ then
    GuiStartFrame(gui)
  end
  GuiIdPushString(gui, "EZWand_tooltip")
  -- GuiOptionsAdd(gui, GUI_OPTION.NonInteractive)
  if not spell_lookup then
    spell_lookup = {}
    dofile_once("data/scripts/gun/gun_actions.lua")
    for i, action in ipairs(actions) do
      spell_lookup[action.id] = {
        icon = action.sprite,
        type = action.type
      }
    end
  end

  local margin = -3
  local wand_name = "WAND"
  local id = 1
  local function new_id()
    id = id + 1
    return id
  end
  local right = origin_x
  local bottom = origin_y
  local function update_bounds(rot)
    local _, _, _, x, y, w, h = GuiGetPreviousWidgetInfo(gui)
    if rot == -90 then
      local old_w = w
      w = h
      h = old_w
      y = y - h
    end
    right = math.max(right, x + w)
    bottom = math.max(bottom, y + h)
  end
  GuiLayoutBeginHorizontal(gui, origin_x, origin_y, true)
  GuiLayoutBeginVertical(gui, 0, 0)
  local text_lightness = 0.82
  local function gui_text_with_shadow(gui, x, y, text, lightness)
    lightness = lightness or text_lightness
    GuiColorSetForNextWidget(gui, lightness, lightness, lightness, 1)
    GuiText(gui, x, y, text)
    GuiZSetForNextWidget(gui, 8)
    GuiOptionsAddForNextWidget(gui, GUI_OPTION.Layout_NoLayouting)
    GuiColorSetForNextWidget(gui, 0, 0, 0, 0.83)
    local _, _, _, x, y = GuiGetPreviousWidgetInfo(gui)
    GuiText(gui, x, y + 1, text)
  end
  GuiColorSetForNextWidget(gui, text_lightness, text_lightness, text_lightness, 1)
  GuiText(gui, 0, 0, wand_name)
  GuiImage(gui, new_id(), 0, 4, "data/ui_gfx/inventory/icon_gun_shuffle.png", 1, 1, 1)
  GuiImage(gui, new_id(), 0, 1, "data/ui_gfx/inventory/icon_gun_actions_per_round.png", 1, 1, 1)
  GuiImage(gui, new_id(), 0, 1, "data/ui_gfx/inventory/icon_fire_rate_wait.png", 1, 1, 1)
  GuiImage(gui, new_id(), 0, 1, "data/ui_gfx/inventory/icon_gun_reload_time.png", 1, 1, 1)
  GuiImage(gui, new_id(), 0, 1, "data/ui_gfx/inventory/icon_mana_max.png", 1, 1, 1)
  GuiImage(gui, new_id(), 0, 1, "data/ui_gfx/inventory/icon_mana_charge_speed.png", 1, 1, 1)
  GuiImage(gui, new_id(), 0, 1, "data/ui_gfx/inventory/icon_gun_capacity.png", 1, 1, 1)
  GuiImage(gui, new_id(), 0, 1, "data/ui_gfx/inventory/icon_spread_degrees.png", 1, 1, 1)
   -- Saves the position and width of the spread icon so we can draw the spells below it
  local _, _, _, last_icon_x, last_icon_y, last_icon_width, last_icon_height = GuiGetPreviousWidgetInfo(gui)
  GuiLayoutEnd(gui)
  local wand_name_width = GuiGetTextDimensions(gui, wand_name)
  GuiLayoutBeginVertical(gui, 12 - wand_name_width, -3, true)
  GuiText(gui, 0, 0, " ")
  gui_text_with_shadow(gui, 0, 5, GameTextGetTranslatedOrNot("$inventory_shuffle"))
  gui_text_with_shadow(gui, 0, margin, GameTextGetTranslatedOrNot("$inventory_actionspercast"))
  gui_text_with_shadow(gui, 0, margin, GameTextGetTranslatedOrNot("$inventory_castdelay"))
  gui_text_with_shadow(gui, 0, margin, GameTextGetTranslatedOrNot("$inventory_rechargetime"))
  gui_text_with_shadow(gui, 0, margin, GameTextGetTranslatedOrNot("$inventory_manamax"))
  gui_text_with_shadow(gui, 0, margin, GameTextGetTranslatedOrNot("$inventory_manachargespeed"))
  gui_text_with_shadow(gui, 0, margin, GameTextGetTranslatedOrNot("$inventory_capacity"))
  gui_text_with_shadow(gui, 0, margin, GameTextGetTranslatedOrNot("$inventory_spread"))
  GuiLayoutEnd(gui)
  GuiLayoutBeginVertical(gui, -6, -3, true)
  GuiText(gui, 0, 0, " ")
  local most_right_text_x = 0
  local function update_most_right_text_x()
    local _, _, _, x, y, w, h = GuiGetPreviousWidgetInfo(gui)
    most_right_text_x = math.max(most_right_text_x, x + w)
  end
  local function format_cast_delay_and_recharge_time(input)
    local pattern = "%.2f"
    if input % 1 == 0 then
      return input .. ".0 s"
    end
    return (pattern):format(input) .. " s"
  end
  gui_text_with_shadow(gui, 0, 5, GameTextGetTranslatedOrNot(wand.props.shuffle and "$menu_yes" or "$menu_no"), 1)
  local _, _, _, _, no_text_y = GuiGetPreviousWidgetInfo(gui)
  update_most_right_text_x()
  gui_text_with_shadow(gui, 0, margin, ("%.0f"):format(wand.props.spellsPerCast), 1)
  update_most_right_text_x()
  gui_text_with_shadow(gui, 0, margin, format_cast_delay_and_recharge_time(wand.props.castDelay / 60), 1)
  update_most_right_text_x()
  gui_text_with_shadow(gui, 0, margin, format_cast_delay_and_recharge_time(wand.props.rechargeTime / 60), 1)
  update_most_right_text_x()
  gui_text_with_shadow(gui, 0, margin, ("%.0f"):format(wand.props.manaMax), 1)
  update_most_right_text_x()
  gui_text_with_shadow(gui, 0, margin, ("%.0f"):format(wand.props.manaChargeSpeed), 1)
  update_most_right_text_x()
  gui_text_with_shadow(gui, 0, margin, ("%.0f"):format(wand.props.capacity), 1)
  update_most_right_text_x()
  gui_text_with_shadow(gui, 0, margin, ("%.1f DEG"):format(wand.props.spread), 1)
  update_most_right_text_x()
  update_bounds()
  local _, _, _, spread_text_x, spread_text_y, spread_text_width, spread_text_height = GuiGetPreviousWidgetInfo(gui)
  GuiLayoutEnd(gui)
  GuiLayoutEnd(gui)
  local always_cast_spell_icon_scale = 0.711
  local add_some = 0 -- I'm out of creativity for variable names...
  -- Always casts
  if type(wand.always_cast_spells) == "table" and #wand.always_cast_spells > 0 then
    add_some = 3
    local background_scale = 0.768
    GuiLayoutBeginHorizontal(gui, last_icon_x, last_icon_y + last_icon_height + 8, true)
    GuiImage(gui, new_id(), 0, 1, "data/ui_gfx/inventory/icon_gun_permanent_actions.png", 1, 1, 1)
    _, _, _, last_icon_x, last_icon_y, last_icon_width, last_icon_height = GuiGetPreviousWidgetInfo(gui)
    gui_text_with_shadow(gui, 3, 0, GameTextGetTranslatedOrNot("$inventory_alwayscasts"))
    local _, _, _, ac_icon_x, ac_icon_y, ac_icon_width, ac_icon_height = GuiGetPreviousWidgetInfo(gui)
    local last_ac_x, last_ac_y, last_ac_width, last_ac_height
    for i, spell in ipairs(wand.always_cast_spells) do
      if i == 1 then
        update_bounds()
      end
      local item_bg_icon = get_spell_bg(spell)
      local w, h = GuiGetImageDimensions(gui, item_bg_icon, background_scale)
      local x, y
      if i == 1 then
        x, y = ac_icon_x + ac_icon_width + 3, math.floor(ac_icon_y - ac_icon_height / 2 + 2)
      else
        x, y = math.floor(last_ac_x + (last_ac_width - 2)) + 1, last_ac_y
      end
      GuiZSetForNextWidget(gui, 9)
      GuiOptionsAddForNextWidget(gui, GUI_OPTION.Layout_NoLayouting)
      -- Background / Spell type border
      GuiImage(gui, new_id(), x, y, item_bg_icon, 1, background_scale, background_scale)
      _, _, _, last_ac_x, last_ac_y, last_ac_width, last_ac_height = GuiGetPreviousWidgetInfo(gui)
      local _, _, _, x, y, w, h = GuiGetPreviousWidgetInfo(gui)
      GuiOptionsAddForNextWidget(gui, GUI_OPTION.Layout_NoLayouting)
      -- Spell icon
      GuiImage(gui, new_id(), x + 2, y + 2, (spell_lookup[spell] and spell_lookup[spell].icon) or "data/ui_gfx/gun_actions/unidentified.png", 1, always_cast_spell_icon_scale, always_cast_spell_icon_scale)
    end
    GuiLayoutEnd(gui)
  end
  -- /Always casts
  -- Spells
  local spell_icon_scale = 0.70066976733398
  local background_scale = 0.76863774490356
  GuiLayoutBeginHorizontal(gui, last_icon_x, last_icon_y + last_icon_height + 7 + add_some + 0.05, true)
  local row = 0
  for i=1, wand.props.capacity do
    GuiZSetForNextWidget(gui, 9)
    GuiImage(gui, new_id(), -0.3, -0.4, "data/ui_gfx/inventory/inventory_box.png", 0.95, background_scale, background_scale)
    update_bounds()
    local _, _, _, x, y = GuiGetPreviousWidgetInfo(gui)
    x = x + 0.32479339599609
    y = y + 0.4
    local item_bg_icon = get_spell_bg(wand.spells[i])
    GuiZSetForNextWidget(gui, 8.5)
    GuiOptionsAddForNextWidget(gui, GUI_OPTION.Layout_NoLayouting)
    if not wand.spells[i] or wand.spells[i] == "" then
      -- Render an invisible (alpha = 0.0001) item just so it counts for the auto-layout
      GuiImage(gui, new_id(), x - 2, y - 2, item_bg_icon, 0.0001, background_scale, background_scale)
    else
      -- Background / Spell type border
      GuiImage(gui, new_id(), x - 2, y - 2, item_bg_icon, 0.75, background_scale, background_scale)
      GuiZSetForNextWidget(gui, 8)
      GuiOptionsAddForNextWidget(gui, GUI_OPTION.Layout_NoLayouting)
      GuiImage(gui, new_id(), x + 0.11, y, (spell_lookup[wand.spells[i]] and spell_lookup[wand.spells[i]].icon) or "data/ui_gfx/gun_actions/unidentified.png", 0.8, spell_icon_scale, spell_icon_scale)
    end
    -- Start a new row after 10 spells
    if i % 10 == 0 then
      row = row + 1
      GuiLayoutEnd(gui)
      _, _, _, _, last_icon_y, last_icon_width, last_icon_height = GuiGetPreviousWidgetInfo(gui)
      GuiLayoutBeginHorizontal(gui, last_icon_x, y + 14.00, true)
    end
  end
  GuiLayoutEnd(gui)
  local wand_sprite = wand.sprite_image_file
  if wand_sprite and wand_sprite ~= "" then
    -- Render wand sprite centered in the space on the right
    local wand_sprite_width, wand_sprite_height = GuiGetImageDimensions(gui, wand.sprite_image_file, 2)
    GuiOptionsAddForNextWidget(gui, GUI_OPTION.Layout_NoLayouting)
    local horizontal_space = right - most_right_text_x
    local vertical_space = spread_text_y + spread_text_height - no_text_y
    horizontal_space = math.max(horizontal_space, wand_sprite_height + 6)
    local wand_sprite_place_center_x = most_right_text_x + horizontal_space / 2
    local wand_sprite_place_center_y = no_text_y + vertical_space / 2
    local wand_sprite_x = wand_sprite_place_center_x - wand_sprite_height / 2
    local wand_sprite_y = wand_sprite_place_center_y + wand_sprite_width / 2
    GuiImage(gui, new_id(), wand_sprite_x, wand_sprite_y, wand.sprite_image_file, 1, 2, 2, -math.rad(90))
    update_bounds(-90)
  end

  GuiZSetForNextWidget(gui, 10)
  GuiImageNinePiece(gui, new_id(), origin_x - 5, origin_y - 5, right - (origin_x - 5) + 5,  bottom - (origin_y - 5) + 5)
  GuiIdPop(gui)
end

local function refresh_wand_if_in_inventory(wand_id)
  -- Refresh the wand if it's being held by the player
  local parent = EntityGetRootEntity(wand_id)
  if EntityHasTag(parent, "player_unit") then
    local inventory2_comp = EntityGetFirstComponentIncludingDisabled(parent, "Inventory2Component")
    if inventory2_comp then
      ComponentSetValue2(inventory2_comp, "mForceRefresh", true)
      ComponentSetValue2(inventory2_comp, "mActualActiveItem", 0)
    end
  end
end

local function add_spell_at_pos(wand, action_id, pos, uses_remaining)
  local spells_on_wand = wand:GetSpells()
  -- Check if there's space for one more spell
  if wand.capacity == #spells_on_wand then
    return false
  end
  -- Check if there's already a spell at the desired position
  for i, spell in ipairs(spells_on_wand) do
    if spell.inventory_x + 1 == pos then
      return false
    end
  end
  local action_entity_id = CreateItemActionEntity(action_id)
  EntityAddChild(wand.entity_id, action_entity_id)
  EntitySetComponentsWithTagEnabled(action_entity_id, "enabled_in_world", false)
  local item_component = EntityGetFirstComponentIncludingDisabled(action_entity_id, "ItemComponent")
  ComponentSetValue2(item_component, "inventory_slot", pos-1, 0)

  if(uses_remaining) then
    ComponentSetValue2(item_component, "uses_remaining", tonumber(uses_remaining))
  end

  return true
end

-- ##########################
-- ####    UTILS END     ####
-- ##########################

local wand = {}
-- Setter
wand.__newindex = function(table, key, value)
  if rawget(table, "_protected")[key] ~= nil then
    error("Cannot set protected property '" .. key .. "'")
  end
  table:SetProperties({ [key] = value })
end
-- Getter
wand.__index = function(table, key)
  if type(rawget(wand, key)) == "function" then
    return rawget(wand, key)
  end
  if rawget(table, "_protected")[key] ~= nil then
    return rawget(table, "_protected")[key]
  end
  return table:GetProperties({ key })[key]
end

function wand:new(from, rng_seed_x, rng_seed_y, refresh)
    refresh = refresh or false

    -- 'protected' should not be accessed by end users!
    local protected = {}
    local o = {
        _protected = protected
    }
    setmetatable(o, self)
    if type(from) == "table" or from == nil then
        -- Just load some existing wand that we alter later instead of creating one from scratch
        protected.entity_id = EntityLoad("data/entities/items/wand_level_04.xml", rng_seed_x or 0, rng_seed_y or 0)
        protected.ability_component = EntityGetFirstComponentIncludingDisabled(protected.entity_id, "AbilityComponent")
        protected.item_component = EntityGetFirstComponentIncludingDisabled(protected.entity_id, "ItemComponent")
        -- Copy all validated props over or initialize with defaults
        local props = from or {}
        validate_wand_properties(props)
        o:SetProperties(props)
        o:RemoveSpells()
        o:DetachSpells()
    elseif tonumber(from) or type(from) == "number" then
        -- Wrap an existing wand
        protected.entity_id = from
        protected.ability_component = EntityGetFirstComponentIncludingDisabled(protected.entity_id, "AbilityComponent")
        protected.item_component = EntityGetFirstComponentIncludingDisabled(protected.entity_id, "ItemComponent")
    else
        if starts_with(from, "EZW") then
        local values = deserialize_wand(from)
        protected.entity_id = EntityLoad("data/entities/items/wand_level_04.xml", rng_seed_x or 0, rng_seed_y or 0)
        protected.ability_component = EntityGetFirstComponentIncludingDisabled(protected.entity_id, "AbilityComponent")
        protected.item_component = EntityGetFirstComponentIncludingDisabled(protected.entity_id, "ItemComponent")
        validate_wand_properties(values.props)
        o:SetProperties(values.props)
        o:RemoveSpells()
        o:DetachSpells()
        -- Filter spells whose ID no longer exist (for instance when a modded spellpack was disabled)
        values.spells, values.spells_uses_remaining = filter_spells(values.spells, values.spells_uses_remaining)
        values.always_cast_spells = filter_spells(values.always_cast_spells)
        for i, action_id in ipairs(values.spells) do
            if action_id ~= "" then
                local remaining = nil
                if(not refresh)then
                  remaining = values.spells_uses_remaining[i]
                end
                add_spell_at_pos(o, action_id, i, remaining)
            end
        end
        o:AttachSpells(values.always_cast_spells)
        o:SetSprite(values.sprite_image_file, values.offset_x, values.offset_y, values.tip_x, values.tip_y)
        o:SetName(values.name, values.show_name_in_ui)
        -- Load a wand by xml
        elseif ends_with(from, ".xml") then
        local x, y = GameGetCameraPos()
        protected.entity_id = EntityLoad(from, rng_seed_x or x, rng_seed_y or y)
        protected.ability_component = EntityGetFirstComponentIncludingDisabled(protected.entity_id, "AbilityComponent")
        protected.item_component = EntityGetFirstComponentIncludingDisabled(protected.entity_id, "ItemComponent")
        else
        error("Wrong format for wand creation.", 2)
        end
    end

    if not entity_is_wand(protected.entity_id) then
        error("Loaded entity is not a wand.", 2)
    end

    return o
end

local variable_mappings = {
  shuffle = { target = "gun_config", name = "shuffle_deck_when_empty" },
  spellsPerCast = { target = "gun_config", name="actions_per_round"},
  castDelay = { target = "gunaction_config", name="fire_rate_wait"},
  currentCastDelay = { target = "ability_component", name="mNextFrameUsable"},
  rechargeTime = { target = "gun_config", name="reload_time"},
  currentRechargeTime = { target = "ability_component", name="mReloadNextFrameUsable"},
  manaMax = { target = "ability_component", name="mana_max"},
  mana = { target = "ability_component", name="mana"},
  manaChargeSpeed = { target = "ability_component", name="mana_charge_speed"},
  capacity = { target = "gun_config", name="deck_capacity"},
  spread = { target = "gunaction_config", name="spread_degrees"},
  speedMultiplier = { target = "gunaction_config", name="speed_multiplier"},
}

-- Sets the actual property on the corresponding component/object
function wand:_SetProperty(key, value)
  local mapped_key = variable_mappings[key].name
  local target_setters = {
    ability_component = function(key, value)
      if key == "mNextFrameUsable" then
        ComponentSetValue2(self.ability_component, key, GameGetFrameNum() + value)
        ComponentSetValue2(self.ability_component, "mCastDelayStartFrame", GameGetFrameNum())
      elseif key == "mReloadNextFrameUsable" then
        ComponentSetValue2(self.ability_component, key, GameGetFrameNum() + value)
        ComponentSetValue2(self.ability_component, "mReloadFramesLeft", value)
        ComponentSetValue2(self.ability_component, "reload_time_frames", value)
      else
        ComponentSetValue2(self.ability_component, key, value)
      end
    end,
    gunaction_config = function(key, value)
      ComponentObjectSetValue2(self.ability_component, "gunaction_config", key, value)
    end,
    gun_config = function(key, value)
      ComponentObjectSetValue2(self.ability_component, "gun_config", key, value)
    end,
  }
  -- We need a special rule for capacity, since always cast spells count towards capacity, but not in the UI...
  if key == "capacity" then
    local spells, attached_spells = self:GetSpells()
    -- If capacity is getting reduced, remove any spells that don't fit anymore
    local spells_to_remove = {}
    for i=#spells, value+1, -1 do
      table.insert(spells_to_remove, { spells[i].action_id, 1 })
    end
    if #spells_to_remove > 0 then
      self:RemoveSpells(spells_to_remove)
    end
    value = value + #attached_spells
  end
  target_setters[variable_mappings[key].target](mapped_key, value)
end
-- Retrieves the actual property from the component or object
function wand:_GetProperty(key)
  if not variable_mappings[key] then
    error(("EZWand has no property '%s'"):format(key), 4)
  end
  local mapped_key = variable_mappings[key].name
  local target_getters = {
    ability_component = function(key)
      if key == "mNextFrameUsable" or key == "mReloadNextFrameUsable" then
        return (math.max(0, ComponentGetValue2(self.ability_component, key) - GameGetFrameNum()))
      else
        return ComponentGetValue2(self.ability_component, key)
      end
    end,
    gunaction_config = function(key)
      return ComponentObjectGetValue2(self.ability_component, "gunaction_config", key)
    end,
    gun_config = function(key)
      return ComponentObjectGetValue2(self.ability_component, "gun_config", key)
    end,
  }
  local result = target_getters[variable_mappings[key].target](mapped_key)
  -- We need a special rule for capacity, since always cast spells count towards capacity, but not in the UI...
  if key == "capacity" then
    result = result - select(2, self:GetSpellsCount())
  end
  return result
end

function wand:SetProperties(key_values)
  for k,v in pairs(key_values) do
    validate_property(k, v)
    self:_SetProperty(k, v)
  end
end

function wand:GetProperties(keys)
  -- Return all properties when empty
  if keys == nil then
    keys = {}
    for k,v in pairs(wand_props) do
      table.insert(keys, k)
    end
  end
  local result = {}
  for i,key in ipairs(keys) do
    result[key] = self:_GetProperty(key)
  end
  return result
end
-- For making the interface nicer, this allows us to use this one function here for
function wand:_AddSpells(spells, attach)
  -- Check if capacity is sufficient
  local count = 0
  for i, v in ipairs(spells) do
    count = count + v[2]
  end
  local spells_on_wand = self:GetSpells()
  local positions = {}
  for i, v in ipairs(spells_on_wand) do
    positions[v.inventory_x] = true
  end

  if not attach and #spells_on_wand + count > self.capacity then
    error(string.format("Wand capacity (%d/%d) cannot fit %d more spells. ", #spells_on_wand, self.capacity, count), 3)
  end
  local current_position = 0
  for i,spell in ipairs(spells) do
    for i2=1, spell[2] do
      if not attach then
        local action_entity_id = CreateItemActionEntity(spell[1])
        EntityAddChild(self.entity_id, action_entity_id)
        EntitySetComponentsWithTagEnabled(action_entity_id, "enabled_in_world", false)
        local item_component = EntityGetFirstComponentIncludingDisabled(action_entity_id, "ItemComponent")
        while positions[current_position] do
          current_position = current_position + 1
        end
        positions[current_position] = true
        ComponentSetValue2(item_component, "inventory_slot", current_position, 0)
      else
        AddGunActionPermanent(self.entity_id, spell[1])
      end
    end
  end
  refresh_wand_if_in_inventory(self.entity_id)
end

function extract_spells_from_vararg(...)
  local spells = {}
  local spell_args = select("#", ...) == 1 and type(...) == "table" and ... or {...}
  local i = 1
  while i <= #spell_args do
    if type(spell_args[i]) == "table" then
      -- Check for this syntax: { "BOMB", 1 }
      if type(spell_args[i][1]) ~= "string" or type(spell_args[i][2]) ~= "number" then
        error("Wrong argument format at index " .. i .. ". Expected format for multiple spells shortcut: { \"BOMB\", 3 }", 3)
      else
        table.insert(spells, spell_args[i])
      end
    elseif type(spell_args[i]) == "string" then
      local amount = spell_args[i+1]
      if type(amount) ~= "number" then
        amount = 1
        table.insert(spells, { spell_args[i], amount })
      else
        table.insert(spells, { spell_args[i], amount })
        i = i + 1
      end
    else
      error("Wrong argument format.", 3)
    end
    i = i + 1
  end
  return spells
end
-- Input can be a table of action_ids, or multiple arguments
-- e.g.:
-- AddSpells("BLACK_HOLE")
-- AddSpells("BLACK_HOLE", "BLACK_HOLE", "BLACK_HOLE")
-- AddSpells({"BLACK_HOLE", "BLACK_HOLE"})
-- To add multiple spells you can also use this shortcut:
-- AddSpells("BLACK_HOLE", {"BOMB", 5}) this will add 1 blackhole followed by 5 bombs
function wand:AddSpells(...)
  local spells = extract_spells_from_vararg(...)
  self:_AddSpells(spells, false)
end
-- Same as AddSpells but permanently attach the spells
function wand:AttachSpells(...)
  local spells = extract_spells_from_vararg(...)
  self:_AddSpells(spells, true)
end
-- Returns the amount of slots on a wand that are not occupied by a spell
function wand:GetFreeSlotsCount()
  return self.capacity - self:GetSpellsCount()
end
-- Returns: spells_count, always_cast_spells_count
function wand:GetSpellsCount()
	local children = EntityGetAllChildren(self.entity_id)
  if children == nil then
    return 0, 0
  end
  -- Count the number of always cast spells
  local always_cast_spells_count = 0
  for i,spell in ipairs(children) do
    local item_component = EntityGetFirstComponentIncludingDisabled(spell, "ItemComponent")
    if item_component ~= nil and ComponentGetValue2(item_component, "permanently_attached") == true then
      always_cast_spells_count = always_cast_spells_count + 1
    end
  end

	return #children - always_cast_spells_count, always_cast_spells_count
end
-- Returns two values:
-- 1: table of spells with each entry having the format { action_id = "BLACK_HOLE", inventory_x = 1, entity_id = <action_entity_id> }
-- 2: table of attached spells with the same format
-- inventory_x should give the position in the wand slots, 1 = first up to num_slots
-- inventory_x is not working yet
function wand:GetSpells()
	local spells = {}
	local always_cast_spells = {}
	local children = EntityGetAllChildren(self.entity_id)
  if children == nil then
    return spells, always_cast_spells
  end
	for _, spell in ipairs(children) do
		local action_id = nil
		local permanent = false
    local uses_remaining = -1
    local item_action_component = EntityGetFirstComponentIncludingDisabled(spell, "ItemActionComponent")
    if item_action_component then
      action_id = ComponentGetValue2(item_action_component, "action_id")
    end
    local inventory_x, inventory_y = -1, -1
    local item_component = EntityGetFirstComponentIncludingDisabled(spell, "ItemComponent")
    if item_component then
      permanent = ComponentGetValue2(item_component, "permanently_attached")
      inventory_x, inventory_y = ComponentGetValue2(item_component, "inventory_slot")
      uses_remaining = ComponentGetValue2(item_component, "uses_remaining")
    end
    if action_id then
			if permanent == true then
				table.insert(always_cast_spells, { action_id = action_id, entity_id = spell, inventory_x = inventory_x, inventory_y = inventory_y })
			else
				table.insert(spells, { action_id = action_id, entity_id = spell, uses_remaining = uses_remaining, inventory_x = inventory_x, inventory_y = inventory_y })
			end
		end
  end

  local function assign_inventory_x(t)
    local a = {}
    for i, v in ipairs(t) do
      if v.inventory_x > 0 then
        a[v.inventory_x+1] = v
      end
    end
    local inventory_x = 1
    for i, v in ipairs(t) do
      if v.inventory_x == 0 then
        while a[inventory_x] do
          inventory_x = inventory_x + 1
        end
        v.inventory_x = inventory_x-1
        a[inventory_x] = v
      end
    end
    for i = #t, 1, -1 do
      if not t[i].inventory_x then
        table.remove(t, i)
      end
    end
  end
  -- When a wand is spawned its spell's inventory_x is always set to 0, only once the inventory is opened
  -- is inventory_x assigned correctly to all spells, so to fake that we go through all the spells manually
  -- and assign inventory_x to either what it was set as or by the order the entities appear on the wand
  assign_inventory_x(spells)
  table.sort(spells, function(a, b) return a.inventory_x < b.inventory_x end)
	return spells, always_cast_spells
end

function wand:_RemoveSpells(spells_to_remove, detach)
	local spells, attached_spells = self:GetSpells()
  local which = detach and attached_spells or spells
  local spells_to_remove_remaining = {}
  for _, spell in ipairs(spells_to_remove) do
    spells_to_remove_remaining[spell[1]] = (spells_to_remove_remaining[spell[1]] or 0) + spell[2]
  end
  for i, v in ipairs(which) do
    if #spells_to_remove == 0 or spells_to_remove_remaining[v.action_id] and spells_to_remove_remaining[v.action_id] ~= 0 then
      if #spells_to_remove > 0 then
        spells_to_remove_remaining[v.action_id] = spells_to_remove_remaining[v.action_id] - 1
      end
      -- This needs to happen because EntityKill takes one frame to take effect or something
      EntityRemoveFromParent(v.entity_id)
      EntityKill(v.entity_id)
      if detach then
        self.capacity = self.capacity - 1
      end
    end
  end
  refresh_wand_if_in_inventory(self.entity_id)
end
-- action_ids = {"BLACK_HOLE", "GRENADE"} remove all spells of those types
-- If action_ids is empty, remove all spells
-- If entry is in the form of {"BLACK_HOLE", 2}, only remove 2 instances of black hole
function wand:RemoveSpells(...)
  local spells = extract_spells_from_vararg(...)
  self:_RemoveSpells(spells, false)
end
function wand:DetachSpells(...)
  local spells = extract_spells_from_vararg(...)
  self:_RemoveSpells(spells, true)
end

function wand:RemoveSpellAtIndex(index)
  if index+1 > self.capacity then
    return false, "index is bigger than capacity"
  end
  local spells = self:GetSpells()
  for i, spell in ipairs(spells) do
    if spell.inventory_x == index then
      -- This needs to happen because EntityKill takes one frame to take effect or something
      EntityRemoveFromParent(spell.entity_id)
      EntityKill(spell.entity_id)
      return true
    end
  end
  return false, "index at " .. index .. " does not contain a spell"
end

-- Make it impossible to edit the wand
-- freeze_wand prevents spells from being added to the wand or moved
-- freeze_spells prevents the spells from being removed
function wand:SetFrozen(freeze_wand, freeze_spells)
  local item_component = EntityGetFirstComponentIncludingDisabled(self.entity_id, "ItemComponent")
  ComponentSetValue2(item_component, "is_frozen", freeze_wand)
  local spells = self:GetSpells()
  for i, spell in ipairs(spells) do
    local item_component = EntityGetFirstComponentIncludingDisabled(spell.entity_id, "ItemComponent")
    ComponentSetValue2(item_component, "is_frozen", freeze_spells)
  end
end

function wand:SetSprite(item_file, offset_x, offset_y, tip_x, tip_y)
	if self.ability_component then
    ComponentSetValue2(self.ability_component, "sprite_file", item_file)
	end
  local sprite_comp = EntityGetFirstComponentIncludingDisabled(self.entity_id, "SpriteComponent", "item")
  if sprite_comp then
    ComponentSetValue2(sprite_comp, "image_file", item_file)
    ComponentSetValue2(sprite_comp, "offset_x", offset_x)
    ComponentSetValue2(sprite_comp, "offset_y", offset_y)
    EntityRefreshSprite(self.entity_id, sprite_comp)
	end
	local hotspot_comp = EntityGetFirstComponentIncludingDisabled(self.entity_id, "HotspotComponent", "shoot_pos")
  if hotspot_comp then
    ComponentSetValue2(hotspot_comp, "offset", tip_x, tip_y)
	end
end

function wand:GetSprite()
  local sprite_file, offset_x, offset_y, tip_x, tip_y = "", 0, 0, 0, 0
	if self.ability_component then
		sprite_file = ComponentGetValue2(self.ability_component, "sprite_file")
	end
	local sprite_comp = EntityGetFirstComponentIncludingDisabled(self.entity_id, "SpriteComponent", "item")
	if sprite_comp then
    if sprite_file == "" then
      sprite_file = ComponentGetValue2(sprite_comp, "image_file")
    end
    offset_x = ComponentGetValue2(sprite_comp, "offset_x")
    offset_y = ComponentGetValue2(sprite_comp, "offset_y")
	end
	local hotspot_comp = EntityGetFirstComponentIncludingDisabled(self.entity_id, "HotspotComponent", "shoot_pos")
  if hotspot_comp then
    tip_x, tip_y = ComponentGetValue2(hotspot_comp, "offset")
  end
  return sprite_file, offset_x, offset_y, tip_x, tip_y
end

function wand:Clone()
  local new_wand = wand:new(self:GetProperties())
  local spells, attached_spells = self:GetSpells()
  for k, v in pairs(spells) do
    new_wand:AddSpells{v.action_id}
  end
  for k, v in pairs(attached_spells) do
    new_wand:AttachSpells{v.action_id}
  end
  -- TODO: Make this work if sprite_file is an xml
  new_wand:SetSprite(self:GetSprite())
  return new_wand
end

--[[
  These are pulled from data/scripts/gun/procedural/gun_procedural.lua
  because dofiling that file overwrites the init_total_prob function,
  which ruins things in biome scripts
]]
function WandDiff( gun, wand )
	local score = 0
	score = score + ( math.abs( gun.fire_rate_wait - wand.fire_rate_wait ) * 2 )
	score = score + ( math.abs( gun.actions_per_round - wand.actions_per_round ) * 20 )
	score = score + ( math.abs( gun.shuffle_deck_when_empty - wand.shuffle_deck_when_empty ) * 30 )
	score = score + ( math.abs( gun.deck_capacity - wand.deck_capacity ) * 5 )
	score = score + math.abs( gun.spread_degrees - wand.spread_degrees )
	score = score + math.abs( gun.reload_time - wand.reload_time )
	return score
end

function GetWand( gun )
	local best_wand = nil
	local best_score = 1000
	local gun_in_wand_space = {}

	gun_in_wand_space.fire_rate_wait = clamp(((gun["fire_rate_wait"] + 5) / 7)-1, 0, 4)
	gun_in_wand_space.actions_per_round = clamp(gun["actions_per_round"]-1,0,2)
	gun_in_wand_space.shuffle_deck_when_empty = clamp(gun["shuffle_deck_when_empty"], 0, 1)
	gun_in_wand_space.deck_capacity = clamp( (gun["deck_capacity"]-3)/3, 0, 7 ) -- TODO
	gun_in_wand_space.spread_degrees = clamp( ((gun["spread_degrees"] + 5 ) / 5 ) - 1, 0, 2 )
	gun_in_wand_space.reload_time = clamp( ((gun["reload_time"]+5)/25)-1, 0, 2 )

	for k,wand in pairs(wands) do
		local score = WandDiff( gun_in_wand_space, wand )
		if( score <= best_score ) then
			best_wand = wand
			best_score = score
			-- just randomly return one of them...
			if( score == 0 and Random(0,100) < 33 ) then
				return best_wand
			end
		end
	end
	return best_wand
end
--[[ /data/scripts/gun/procedural/gun_procedural.lua ]]

-- Applies an appropriate Sprite using the games own algorithm
function wand:UpdateSprite()
  local gun = {
    fire_rate_wait = self.castDelay,
    actions_per_round = self.spellsPerCast,
    shuffle_deck_when_empty = self.shuffle and 1 or 0,
    deck_capacity = self.capacity,
    spread_degrees = self.spread,
    reload_time = self.rechargeTime,
  }
  local sprite_data = GetWand(gun)
  self:SetSprite(sprite_data.file, sprite_data.grip_x, sprite_data.grip_y,
    (sprite_data.tip_x - sprite_data.grip_x),
    (sprite_data.tip_y - sprite_data.grip_y))
end

function wand:SetName(name, show_in_ui)
  if show_in_ui == nil then
    show_in_ui = true
  end
  show_in_ui = not not show_in_ui
  local item_comp = self.item_component
  if item_comp then
    ComponentSetValue2(item_comp, "item_name", tostring(name))
    ComponentSetValue2(item_comp, "always_use_item_name_in_ui", show_in_ui)
  end
end

-- returns name, show_in_ui
function wand:GetName()
  local item_comp = self.item_component
  if item_comp then
    local name = ComponentGetValue2(item_comp, "item_name")
    local show_in_ui = ComponentGetValue2(item_comp, "always_use_item_name_in_ui")
    return name, show_in_ui
  end
  error("No item component found", 2)
end

function wand:PickUp(entity)
    local item_component = EntityGetFirstComponentIncludingDisabled(self.entity_id, "ItemComponent")
    local preferred_inv = "QUICK"
    if item_component then
      ComponentSetValue2(item_component, "has_been_picked_by_player", true)
      preferred_inv = ComponentGetValue2(item_component, "preferred_inventory")
    end

    local entity_children = EntityGetAllChildren(entity) or {}

    for key, child in pairs( entity_children ) do
      if EntityGetName( child ) == "inventory_"..string.lower(preferred_inv) then
        EntityAddChild( child, self.entity_id)
      end
    end
  
    EntitySetComponentsWithTagEnabled( self.entity_id, "enabled_in_world", false )
    EntitySetComponentsWithTagEnabled( self.entity_id, "enabled_in_hand", false )
    EntitySetComponentsWithTagEnabled( self.entity_id, "enabled_in_inventory", true )
  
    local wand_children = EntityGetAllChildren(self.entity_id) or {}
  
    for k, v in ipairs(wand_children)do
      EntitySetComponentsWithTagEnabled( self.entity_id, "enabled_in_world", false )
    end

    local sprite_particle_emitter_comp = EntityGetFirstComponentIncludingDisabled(self.entity_id, "SpriteParticleEmitterComponent")
    if sprite_particle_emitter_comp ~= nil then
        EntitySetComponentIsEnabled(self.entity_id, sprite_particle_emitter_comp, false)
    end
  
end

function wand:PlaceAt(x, y)
	EntitySetComponentIsEnabled(self.entity_id, self.ability_component, true)
	local hotspot_comp = EntityGetFirstComponentIncludingDisabled(self.entity_id, "HotspotComponent")
	EntitySetComponentIsEnabled(self.entity_id, hotspot_comp, true)
  local item_component = EntityGetFirstComponentIncludingDisabled(self.entity_id, "ItemComponent")
	EntitySetComponentIsEnabled(self.entity_id, item_component, true)
	local sprite_component = EntityGetFirstComponentIncludingDisabled(self.entity_id, "SpriteComponent")
	EntitySetComponentIsEnabled(self.entity_id, sprite_component, true)
  local light_component = EntityGetFirstComponentIncludingDisabled(self.entity_id, "LightComponent")
  EntitySetComponentIsEnabled(self.entity_id, light_component, true)

  ComponentSetValue(item_component, "has_been_picked_by_player", "0")
  ComponentSetValue(item_component, "play_hover_animation", "1")
  ComponentSetValueVector2(item_component, "spawn_pos", x, y)

	local lua_comp = EntityGetFirstComponentIncludingDisabled(self.entity_id, "LuaComponent")
	EntitySetComponentIsEnabled(self.entity_id, lua_comp, true)
	local simple_physics_component = EntityGetFirstComponentIncludingDisabled(self.entity_id, "SimplePhysicsComponent")
  EntitySetComponentIsEnabled(self.entity_id, simple_physics_component, false)
	-- Does this wand have a ray particle effect? Most do, except the starter wands
	local sprite_particle_emitter_comp = EntityGetFirstComponentIncludingDisabled(self.entity_id, "SpriteParticleEmitterComponent")
	if sprite_particle_emitter_comp ~= nil then
		EntitySetComponentIsEnabled(self.entity_id, sprite_particle_emitter_comp, true)
	else
		-- TODO: As soon as there's some way to clone Components or Transplant/Remove+Add to another Entity, copy
		-- the SpriteParticleEmitterComponent of entities/base_wand.xml
  end
end

function wand:PutInPlayersInventory()
  local inventory_id = EntityGetWithName("inventory_quick")
  -- Get number of wands currently already in inventory
  local count = 0
  local inventory_items = EntityGetAllChildren(inventory_id)
  if inventory_items then
    for i,v in ipairs(inventory_items) do
      if entity_is_wand(v) then
        count = count + 1
      end
    end
  end
  local players = EntityGetWithTag("player_unit")
  if count < 4 and #players > 0 then
    local item_component = EntityGetFirstComponentIncludingDisabled(self.entity_id, "ItemComponent")
    if item_component then
      ComponentSetValue2(item_component, "has_been_picked_by_player", true)
    end
    GamePickUpInventoryItem(players[1], self.entity_id, false)
  else
    error("Cannot add wand to players inventory, it's already full.", 2)
  end
end

-- Turns the wand properties etc into a string
-- Output string looks like:
-- EZWv(version);shuffle[1|0];spellsPerCast;castDelay;rechargeTime;manaMax;mana;manaChargeSpeed;capacity;spread;speedMultiplier;
-- SPELL_ONE,SPELL_TWO;ALWAYS_CAST_ONE,ALWAYS_CAST_TWO;sprite.png;offset_x;offset_y;tip_x;tip_y;name;show_name
function wand:Serialize(include_mana, include_offsets)
    include_mana = include_mana or false
    include_offsets = include_offsets or false
    local spells_string = ""
    local spells_uses_string = ""
    local always_casts_string = ""
    local spells, always_casts = self:GetSpells()
    local slots = {}
    for i, spell in ipairs(spells) do
        slots[spell.inventory_x+1] = spell
    end
    for i=1, self.capacity do
        spells_string = spells_string .. (i == 1 and "" or ",") .. (slots[i] and slots[i].action_id or "")

        if(slots[i])then
            local uses_remaining = slots[i].uses_remaining
            if uses_remaining == nil then
                uses_remaining = -1
            end
            spells_uses_string = spells_uses_string .. (i == 1 and "" or ",") .. tostring(uses_remaining)
        else
            spells_uses_string = spells_uses_string .. (i == 1 and "" or ",") .. "0"
        end
    end
    for i, spell in ipairs(always_casts) do
        always_casts_string = always_casts_string .. (i == 1 and "" or ",") .. spell.action_id
    end

    local sprite_image_file, offset_x, offset_y, tip_x, tip_y = self:GetSprite()

    -- Add a workaround for the starter wands which are the only ones with an xml sprite
    -- Modded wands which use xmls won't work sadly
    if sprite_image_file == "data/items_gfx/handgun.xml" then
        sprite_image_file = "data/items_gfx/handgun.png"
        offset_x = 4
        offset_y = 3.5
    end
    if sprite_image_file == "data/items_gfx/bomb_wand.xml" then
        sprite_image_file = "data/items_gfx/bomb_wand.png"
        offset_x = 4
        offset_y = 3.5
    end

    local name, show_name = self:GetName()
    local serialize_version = "2"
    return ("EZWv%s;%d;%d;%d;%d;%d;%d;%d;%d;%d;%d;%s;%s;%s;%s;%d;%d;%d;%d;%s;%d"):format(
        serialize_version,
        self.shuffle and 1 or 0,
        self.spellsPerCast,
        self.castDelay,
        self.rechargeTime,
        self.manaMax,
        include_mana and self.mana or self.manaMax,
        self.manaChargeSpeed,
        self.capacity,
        self.spread,
        self.speedMultiplier,
        spells_string == "" and "-" or spells_string,
        spells_uses_string == "" and "-" or spells_uses_string,
        always_casts_string == "" and "-" or always_casts_string,
        sprite_image_file, include_offsets and offset_x or 0, include_offsets and offset_y or 0, include_offsets and tip_x or 0, include_offsets and tip_y or 0, name, show_name and 1 or 0
    )
end

local function get_held_wand()
	local player = EntityGetWithTag("player_unit")[1]
  if player then
    local inventory2_comp = EntityGetFirstComponentIncludingDisabled(player, "Inventory2Component")
    local active_item = ComponentGetValue2(inventory2_comp, "mActiveItem")
    return entity_is_wand(active_item) and wand:new(active_item)
  end
end

function wand:RenderTooltip(origin_x, origin_y, gui_)
  local success, error_msg = pcall(render_tooltip, origin_x, origin_y, deserialize_wand(self:Serialize()), gui_)
  if not success then
    error(error_msg, 2)
  end
end

local function get_all_wands()
    local wands = {}
      local player = EntityGetWithTag("player_unit")
    if player and player[1] then
      local items = GameGetAllInventoryItems(player[1]) or {}
      for i, item in ipairs(items) do
        if(entity_is_wand(item))then
          table.insert(wands, wand:new(item))
        end
      end
    end
    return wands
end

return setmetatable({}, {
  __call = function(self, from, rng_seed_x, rng_seed_y, refresh)
    return wand:new(from, rng_seed_x, rng_seed_y, refresh)
  end,
  __newindex = function(self)
    error("Can't assign to this object.", 2)
  end,
  __index = function(self, key)
    return ({
      Deserialize = deserialize_wand,
      RenderTooltip = render_tooltip,
      IsWand = entity_is_wand,
      GetHeldWand = get_held_wand,
      GetAllWands = get_all_wands,
    })[key]
  end
})
