local WEAK_MT = {}
local object_savestate = setmetatable({}, WEAK_MT)
_G.__object_savestate = object_savestate
local save_system_callbacks = setmetatable({}, WEAK_MT)
_G.__save_system_callbacks = save_system_callbacks
local load_system_callbacks = setmetatable({}, WEAK_MT)
_G.__load_system_callbacks = load_system_callbacks
local save_object_callbacks = setmetatable({}, WEAK_MT)
_G.__save_object_callbacks = save_object_callbacks
local load_object_callbacks = setmetatable({}, WEAK_MT)
_G.__load_object_callbacks = load_object_callbacks
local create_path = function(obj, ...)
  local n = select("#", ...)
  local parent = obj
  for i = 1, n do
    local k = select(i, ...)
    obj = parent[k]
    if not obj then
      obj = setmetatable({}, WEAK_MT)
      parent[k] = obj
    end
    parent = obj
  end
  return obj
end
local GetSaveState = function(keyObject, ...)
  local savestate = object_savestate[keyObject] or setmetatable({}, WEAK_MT)
  object_savestate[keyObject] = savestate
  if (...) then
    savestate = create_path(savestate, ...)
  end
  return savestate
end
local Save = function()
  local hasSaveState = next(object_savestate)
  if hasSaveState then
    for callbackFn, keyObjectList in pairs(save_system_callbacks) do
      local _, ok = next(keyObjectList)
      if ok then
        callbackFn()
      end
    end
    for keyObject, callbackList in pairs(save_object_callbacks) do
      for fn, callbackObjects in pairs(callbackList) do
        for callbackObject in pairs(callbackObjects) do
          fn(keyObject, callbackObject)
        end
      end
    end
  end
  return hasSaveState and object_savestate or nil
end
local IsTableEmpty = function(T)
  local count = 0
  for _ in pairs(T) do
    count = count + 1
    if 0 < count then
      return false
    end
  end
  return true
end
local Restore = function(savestate)
  if not savestate then
    return
  end
  if IsTableEmpty(savestate) then
    return
  end
  object_savestate = setmetatable(savestate, WEAK_MT)
  _G.__object_savestate = object_savestate
  for callbackFn, keyObjectList in pairs(load_system_callbacks) do
    local _, ok = next(keyObjectList)
    if ok then
      callbackFn()
    end
  end
  for keyObject, callbackList in pairs(load_object_callbacks) do
    for fn, callbackObjects in pairs(callbackList) do
      for callbackObject in pairs(callbackObjects) do
        fn(keyObject, callbackObject)
      end
    end
  end
end
local AddSaveSystemCallback = function(keyObject, fn)
  local callbackList = create_path(save_system_callbacks, fn)
  callbackList[keyObject] = true
end
local AddLoadSystemCallback = function(keyObject, fn)
  local callbackList = create_path(load_system_callbacks, fn)
  callbackList[keyObject] = true
end
local AddSaveObjectCallback = function(keyObject, saveObject, fn)
  local callbackList = create_path(save_object_callbacks, keyObject, fn)
  callbackList[saveObject] = true
end
local AddLoadObjectCallback = function(keyObject, saveObject, fn)
  local callbackList = create_path(load_object_callbacks, keyObject, fn)
  callbackList[saveObject] = true
end
return {
  AddSaveSystemCallback = AddSaveSystemCallback,
  AddLoadSystemCallback = AddLoadSystemCallback,
  AddSaveObjectCallback = AddSaveObjectCallback,
  AddLoadObjectCallback = AddLoadObjectCallback,
  CreateSaveState = GetSaveState,
  GetSaveState = GetSaveState,
  Save = Save,
  Restore = Restore
}
