require("core.assert")
local CLASSES = {}
local CLASSMAP = {}
_G.__CLASSES__ = CLASSES
if not _G.__profilemarkers then
  _G.__profilemarkers = {}
end
local function initializer(self, CLASS)
  local PARENT = rawget(CLASS, "PARENT")
  if PARENT then
    initializer(self, PARENT)
  end
  local initlist = rawget(CLASS, "initlist")
  for _, l in ipairs(initlist) do
    for k, v in pairs(l) do
      self[k] = v
    end
  end
end
local function constructor(self, CLASS, ...)
  local manual_ctor = rawget(CLASS, "manual_init")
  local PARENT = rawget(CLASS, "PARENT")
  if manual_ctor then
    manual_ctor(self, PARENT and constructor, PARENT, ...)
    return
  elseif PARENT then
    constructor(self, PARENT, ...)
  end
  local ctor = rawget(CLASS, "init")
  if ctor then
    ctor(self, ...)
  end
end
local internal_register_profile_marker = engine.RegisterProfileMarkerForFunction
local internal_lazy_init_profile_markers = engine.ManualInitProfileMarkers
local register_profile_marker = internal_register_profile_marker or function(fn, LEAFNAME, k1, k2)
  if not _G.__profilemarkers[fn] then
    local fmt = k2 and "%s.%s:%s" or "%s:%s"
    local s = string.format(fmt, LEAFNAME, k1, k2)
    _G.__profilemarkers[fn] = s
  end
end
local lazy_init_profile_markers
lazy_init_profile_markers = internal_lazy_init_profile_markers or function(CLASS)
  local PARENT = rawget(CLASS, "PARENT")
  if PARENT and not rawget(PARENT, "has_profile_markers") then
    lazy_init_profile_markers(PARENT)
  end
  engine.PushProfileMarker("lazy_init_profile_markers")
  for k, v in pairs(CLASS) do
    if k ~= "CLASS" and k ~= "__index" then
      if type(v) == "function" then
        register_profile_marker(v, CLASS.LEAFNAME, k)
      elseif type(v) == "table" then
        for k2, v2 in pairs(v) do
          if type(v2) == "function" then
            register_profile_marker(v2, CLASS.LEAFNAME, k, k2)
          end
        end
      end
    end
  end
  rawset(CLASS, "has_profile_markers", true)
  engine.PopProfileMarker()
end
local internal_bless = engine.Bless
local bless = internal_bless or function(instance, CLASS)
  if not rawget(CLASS, "has_profile_markers") then
    lazy_init_profile_markers(CLASS)
  end
  local self = setmetatable(instance, CLASS)
  return self
end
local in_place_new = function(instance, CLASS, ...)
  local self = bless(instance, CLASS)
  initializer(self, CLASS, ...)
  constructor(self, CLASS, ...)
  return self
end
local instance_create = function(CLASS, ...)
  return in_place_new({}, CLASS, ...)
end
local function subclass_init(CLASS, PARENT)
  local GRANDPARENT = rawget(PARENT, "PARENT")
  if GRANDPARENT then
    subclass_init(CLASS, GRANDPARENT)
  end
  local onSubclass = getmetatable(PARENT).onSubclass
  if onSubclass then
    onSubclass(CLASS)
  end
end
local attach_parent = function(CLASS, PARENT)
  local EXISTING_PARENT = rawget(CLASS, "PARENT")
  assert(EXISTING_PARENT == nil, "Cannot parent class '%s' to '%s'; it is already parented to '%s'", rawget(CLASS, "CLASSNAME"), rawget(PARENT, "CLASSNAME"), EXISTING_PARENT and rawget(EXISTING_PARENT, "CLASSNAME"))
  local MT = getmetatable(CLASS)
  CLASS.PARENT = PARENT
  MT.__index = PARENT
  subclass_init(CLASS, PARENT)
end
local lookup_class = function(name)
  local CLASS = CLASSES[name]
  assert(CLASS, "Unable to find class named '%s'", name)
  return CLASS
end
local extend = function(CLASS, t)
  local initlist
  for k, v in pairs(t) do
    local tt = type(v)
    if tt == "function" then
      CLASS[k] = v
    elseif tt == "table" then
      assert(rawget(v, "CLASSNAME") == nil, "Can't initialize class '%s' member '%s' with class of type '%s'", rawget(CLASS, "CLASSNAME"), k, rawget(v, "CLASSNAME"))
      local target = rawget(CLASS, k) or {}
      CLASS[k] = target
      for innerk, innerv in pairs(v) do
        local innertt = type(innerv)
        assert(innertt == "function", "Can't initialize event %s.%s.%s with a '%s', expected a function", rawget(CLASS, "CLASSNAME"), k, innerk, innertt)
        target[innerk] = innerv
      end
    else
      initlist = initlist or {}
      initlist[k] = v
    end
  end
  if initlist then
    table.insert(rawget(CLASS, "initlist"), initlist)
  end
  return CLASS
end
local hook_subclass = function(CLASS, fn)
  local MT = getmetatable(CLASS)
  MT.onSubclass = fn
end
local namespaced_name = function(name)
  return table.concat(name, ".")
end
local class = function(name, parent)
  local name_path
  local leaf_name = name
  if type(name) == "table" then
    if 1 < #name then
      name_path = name
      name = namespaced_name(name)
      leaf_name = name_path[#name_path]
      table.remove(name_path)
    else
      name = name[1]
      leaf_name = name
    end
  end
  local CLASS = CLASSES[name]
  if not CLASS then
    local MT = {}
    CLASS = {}
    CLASS.CLASSNAME = name
    CLASS.NAMESPACE = name_path
    CLASS.LEAFNAME = leaf_name
    CLASS.initlist = {}
    CLASS.__index = CLASS
    CLASS.CLASS = CLASS
    function CLASS.New(...)
      return instance_create(CLASS, ...)
    end
    MT.__concat = extend
    setmetatable(CLASS, MT)
    CLASSES[name] = CLASS
    CLASSMAP[CLASS] = name
  end
  if parent then
    attach_parent(CLASS, parent)
  end
  return CLASS
end
local iskindof = function(instance, CLASS)
  local currentClass = instance.CLASS
  repeat
    if CLASS == currentClass then
      return true
    end
    currentClass = rawget(currentClass, "PARENT")
  until currentClass == nil
  return false
end
local isClass = function(obj)
  return CLASSES[obj.CLASSNAME] ~= nil
end
local isInstance = function(obj)
  local CLASS = obj.CLASS
  if not CLASS then
    return
  end
  return CLASSMAP[CLASS] ~= nil
end
local getClass = function(instance)
  assert(isInstance(instance))
  return instance.CLASS
end
local getParentClass = function(cls)
  assert(isClass(cls))
  return cls.PARENT
end
return {
  initializer = initializer,
  constructor = constructor,
  lookup = lookup_class,
  extend = extend,
  onSubclass = hook_subclass,
  Class = class,
  iskindof = iskindof,
  InPlaceNew = in_place_new,
  bless = bless,
  isClass = isClass,
  isInstance = isInstance,
  getClass = getClass,
  getParentClass = getParentClass,
  lazy_init_profile_markers = lazy_init_profile_markers,
  register_profile_marker = register_profile_marker
}
