-------------------------------------------------------------
-- Halo Class Library
-- Object Oriented Halo Memory Tables
--
-- Version 0.1.0
--
-- Started by Kirby_422
-- Last modified by Kirby_422
-- Last modified date: January 12th 2018
--
-------- Change Log ---------
-- January 12th - Kirby_422
--  - Created. 
--  - Defined some Object types based off of the Halo Memory Address pastebin.
-------- Coding Standards --------------
-- Generally all things should have Getters and Setters (clearly there are exceptions, such as tag_name)
--  - Setters
--    Setters are expected to preform validation, and if the changes will not be made due to invalid something, return FALSE. If the changes are made, return the value given.
--    Certain setters have offset versions of themselves aswell, such as position; 
--      I could go player_bob:set_position("x","+",50) to move the players X 50WU. 
--      Setters like this, might also have variants that exist, such as :set_position_x rather than having to list the X as a parameter.
-------------------------------------------------------------
require 'class'
require "halo_classes.tags"
require "halo_classes.players"
require "halo_classes.objects"

function read_vector3d(address)
  return read_f32(address), read_f32(address + 0x4), read_f32(address+0x8)
end
function write_vector3d(address, x, y, z)
  write_f32(address,x)
  write_f32(address+0x4,y)
  write_f32(address+0x8,z)
end
function read_widestring(Address, Size)
    local str = ""
    for i=0,Size-1 do
        str = str .. string.char(read_byte(Address + i*2))
    end
    return str
end

function write_widestring(Address, String, Size)
    for i=0,Size-1 do
        if(i < #String) then
            local str_byte = String:byte(i+1)
            write_byte(Address + i*2, str_byte)
            write_byte(Address + i*2 + 1, 0)
        else
            write_byte(Address + i*2, 0)
            write_byte(Address + i*2 + 1, 0)
        end
    end
end
__helper_animation_map = {
 [0] = "Idle", 
 [1] = "Gesture",
 [2] = "Turn Left",
 [3] = "Turn Right",
 [4] = "Move Front",
 [5] = "Move Back",
 [6] = "Move Left",
 [7] = "Move Right",
 [8] = "Stunned Front",
 [9] = "Stunned Back",
 [10] = "Stunned Left",
 [11] = "Stunned Right",
 [12] = "Slide Front",
 [13] = "Slide Back",
 [14] = "Slide Left",
 [15] = "Slide Right",
 [16] = "Ready",
 [17] = "Put Away",
 [18] = "Aim Still",
 [19] = "Aim Move",
 [20] = "Airborne",
 [21] = "Land Soft",
 [22] = "Land Hard",
 [23] = "UNKNOWN", 
 [24] = "Airborne Dead",
 [25] = "Landing Dead",
 [26] = "Seat Enter",
 [27] = "Seat Exit",
 [28] = "Custom Animation",
 [29] = "Impulse",
 [30] = "Melee",
 [31] = "Melee Airborne",
 [32] = "Melee Continuous",
 [33] = "Grenade Toss",
 [34] = "Resurrect Front",
 [35] = "Ressurect Back",
 [36] = "Feeding",
 [37] = "Surprise Front",
 [38] = "Surprise Back",
 [39] = "Leap Start",
 [40] = "Leap Airborne",
 [41] = "Leap Melee",
 [42] = "Unused",
 [43] = "Berserk",
}
function __helper_animation_get_int_from_string(str)
  str = string.lower(str)
  for k,v in pairs(__helper_animation_map) do
    if (string.lower(v) == str) then return k end
  end
  return 0
end
HaloPC = class(function(halo,custom_edition,verbose) --TODO
  halo.ontick_updates = {}
  halo.pretick_updates = {}
  halo.onframe_updates = {}
  halo.onrcon_updates = {}
  halo.on_revert = {}
  halo.on_checkpoint = {}
  halo.previous_ticks = 0
  halo.last_save_hash = 0
  halo.last_save_tick = ticks()
  if (custom_edition ~= false) then --nil or true
    halo.game = "CE"
  else
    halo.game = "PC"
  end
  if (verbose ~= true) then --Only recognize true as true
    halo.verbose = false
  else
    halo.verbose = true
  end
end)
function HaloPC:get_game_reverted()
  return self.previous_ticks > ticks()
end
function HaloPC:just_saved()
  return self.last_save_hash ~= read_u32(0x0018DCC0)
end
function HaloPC:get_game()
  return self.game
end
function HaloPC:Print(str)
  --filler;null, should be overwritten
end
function HaloPC:__add_update(update_table,new_function)
  if (type(new_function) == "function") then
    table.insert(update_table, new_function)
	self:Print("Function added")
	return true
  end
  self:Print("Failed to add Function")
  return false
end
function HaloPC:__updates(update_table)
  for _,v in pairs(update_table) do
    v()
  end
end

function HaloPC:add_ontick_update(new_function)
  return self:__add_update(self.ontick_updates,new_function)
end
function HaloPC:add_pretick_update(new_function)
  return self:__add_update(self.pretick_updates,new_function)
end
function HaloPC:add_onframe_update(new_function)
  return self:__add_update(self.onframe_updates,new_function)
end
function HaloPC:add_onrcon_update(new_function)
  return self:__add_update(self.onrcon_updates,new_function)
end
function HaloPC:add_on_revert(new_function)
  return self:__add_update(self.on_revert,new_function)
end
function HaloPC:add_on_checkpoint(new_function)
  return self:__add_update(self.on_checkpoint,new_function)
end
function HaloPC:ontick_update()
  self:__updates(self.ontick_updates)
  --Finally, update whether we've reverted
  self.previous_ticks = ticks()
  --Update suspected save function
  local save_hash = read_u32(0x0018DCC0)
  if (self.last_save_hash ~= save_hash) then
    self.last_save_hash = save_hash
	self.last_save_tick = ticks()
  end
end
function HaloPC:pretick_update()
  --Preform revert/checkpoint actions if needed.
  if (self:just_saved()) then self:__updates(self.on_checkpoint) end
  if (self:get_game_reverted()) then self:__updates(self.on_revert) end
  
  --Perform regular pretick updates.
  self:__updates(self.pretick_updates)
end
function HaloPC:onframe_update()
  self:__updates(self.onframe_updates)
end
function HaloPC:onrcon_update()
  self:__updates(self.onrcon_updates)
end

function HaloPC:search_object_table(tag_name,tag_type)
	local rtrn = {}
	local object_table = read_u32(read_u32(0x401194))
	local object_count = read_u16(object_table + 0x2E)
	local first_object = read_u32(object_table + 0x34)
	for i=0,object_count-1 do
		local ID = read_u16(first_object + i*12)*0x10000 + i
		local object = load_object(ID)--read_u32(first_object + i * 0xC + 0x8))
		if object ~= nil then
		  --cout(object:get_name() .. ":"..object:get_type())
			if (object:get_name() == tag_name and tag_type == object:get_type()) then
			  table.insert(rtrn, object)
			end
		end
	end
	return rtrn
end
function HaloPC:search_tag_table(tag_type)
	rtrn = {}
	tag_count = read_u32(0x4044000C)
	for i = 0, (tag_count - 1) do
		tag_class = string.reverse(string.sub(string.lower(read_string8(0x40440028 + (i * 0x20))), 1, 4))
		meta = read_u32(0x40440028 + (i * 0x20) + 0xC)
		tag_name = read_string8(read_u32(0x40440028 + (i * 0x20) + 0x10))
		tag_name_alt = read_widestring(0x40440028 + (i * 0x20) + 0x10, 100)
		Halo:Print(meta .. ":" .. tag_name)
		if (tag_class == tag_type) then table.insert(rtrn, detect_tag(get_tag("jpt!", tag_name))) end
	end
	--0x40440028
	return rtrn
end

function HaloPC:Get_Object(object_id)
  obj = load_object(object_id)
  if (obj == nil) then 
	self:Print(string.format("Failed to find object with ID 0x%x",object_id))
  else
    self:Print(string.format("Found object with ID 0x%x and type %i",object_id,obj:get_type()))
	self:Print("  with name "..obj:get_name())
  end
  return obj
end
Halo = HaloPC(GAME_CE,OUTPUT_VERBOSE)
require "halo_classes.scripts"
if (Halo.verbose) then
	if (clua_version ~= nil) then --CHIMERA
	  if (clua_version < 2) then
	    function HaloPC:Print(str)
		  cout(str)
	    end
	  else
	    function HaloPC:Print(str)
		  console_out(str)
	    end
	  end
	else
	  if (api_version ~= nil) then --SAPP
		function HaloPC:Print(str)
		  cprint(str)
		end
	  end
	end
end