local function MUST_IMPLEMENT_SENTINEL()
    error("Attempted to access unimplemented field!", 2)
end

function ZO_VerifyClassImplementation(finalClass, classTraceback)
    -- Detect any instances of ZO_MUST_IMPLEMENT that are not implemented.
    local function VisitClass(currentClass)
        for fieldName, fieldValue in pairs(currentClass) do
            if fieldValue == MUST_IMPLEMENT_SENTINEL and finalClass[fieldName] == MUST_IMPLEMENT_SENTINEL then
                local NO_STACK_TRACE = 0
                error("Class has unimplemented field: " .. fieldName .. "\n" .. classTraceback, NO_STACK_TRACE)
            end
        end

        if currentClass.__parentClasses then
            for _, parentClass in ipairs(currentClass.__parentClasses) do
                VisitClass(parentClass)
            end
        end
    end
    VisitClass(finalClass)
end

local RegisterConcreteClass, RemoveConcreteClass
local SHOULD_VERIFY_CLASSES = GetCVar("EnableLuaClassVerification") == "1"
if SHOULD_VERIFY_CLASSES then
    local g_concreteClassSet = {}
    function RegisterConcreteClass(concreteClass, stackLevel)
        g_concreteClassSet[concreteClass] = debug.traceback(nil, stackLevel + 1)
    end

    function RemoveConcreteClass(concreteClass)
        g_concreteClassSet[concreteClass] = nil
    end

    function ZO_VerifyConcreteClasses()
        for concreteClass, classTraceback in pairs(g_concreteClassSet) do
            ZO_VerifyClassImplementation(concreteClass, classTraceback)
            g_concreteClassSet[concreteClass] = nil
        end
    end

    GetEventManager():RegisterForEvent("BaseObject", EVENT_ADD_ON_LOADED, function()
        ZO_VerifyConcreteClasses()
    end)
else
    -- Do nothing instead of tracking and automatically verifying classes
    function RegisterConcreteClass()
    end
    function RemoveConcreteClass()
    end
    function ZO_VerifyConcreteClasses()
    end
end

ZO_Object = {}
ZO_Object.__index = ZO_Object

function ZO_Object:New(template)
    local class = template or self
    local newObject = setmetatable({}, class)
    return newObject
end

---
-- Call ZO_Object:Subclass() to create a new class that inherits from this one.
--
function ZO_Object:Subclass()
    local newClass = setmetatable({}, self)
    newClass.__index = newClass
    newClass.__parentClasses = { self }
    newClass.__isAbstractClass = false
    if self.__isAbstractClass then
        local PARENT_STACK = 2
        RegisterConcreteClass(newClass, PARENT_STACK) 
    end
    return newClass
end

---
-- Call ZO_Object:MultiSubclass() to create a new class that
-- inherits from multiple parent classes. In situations where the same method is
-- defined on multiple classes, the leftmost class in the argument list takes
-- priority. It is recommended that you override to avoid this!
--
function ZO_Object:MultiSubclass(...)
    local parentClasses = { self, ... }
    local newClass = setmetatable({}, {
        __index = function(table, key)
            for _, parentClassTable in ipairs(parentClasses) do
                local value = parentClassTable[key]
                if value ~= nil then
                    return value
                end
            end
        end
    })
    newClass.__index = newClass
    newClass.__parentClasses = parentClasses
    newClass.__isAbstractClass = false
    for _, parentClass in ipairs(parentClasses) do
        if parentClass.__isAbstractClass then
            local PARENT_STACK = 2
            RegisterConcreteClass(newClass, PARENT_STACK) 
            break
        end
    end
    return newClass
end

---
-- Use MUST_IMPLEMENT to create a field that must be implemented by subclasses.
-- If a concrete class does not implement a field that contains a MUST_IMPLEMENT,
-- it will cause an error after the entire addon has been loaded.
-- Example A: ZO_MyAbstractClass.MyAbstractFunction = ZO_MyAbstractClass:MUST_IMPLEMENT()
-- Example B: ZO_MyAbstractClass:MUST_IMPLEMENT("MyAbstractFunction")
function ZO_Object:MUST_IMPLEMENT(fieldName)
    self:IGNORE_UNIMPLEMENTED()
    if fieldName then
        self[fieldName] = MUST_IMPLEMENT_SENTINEL
    end
    return MUST_IMPLEMENT_SENTINEL
end

---
-- Use IGNORE_UNIMPLEMENTED_FUNCTIONS to mark a class as abstract. Typically used to denote an abstract derived class that
-- does not have any of its own MUST_IMPLEMENT fields, but inherits from another abstract class that does.
-- Example: ZO_MyAbstractDerivedClass:IGNORE_UNIMPLEMENTED()
function ZO_Object:IGNORE_UNIMPLEMENTED()
    self.__isAbstractClass = true
    RemoveConcreteClass(self)
end

---
-- Use STUB to create a field that doesn't have to be implemented by subclasses, but will error if called.
-- Example A: ZO_MyParentClass.MyStubbedFunction = ZO_MyAbstractClass:STUB()
-- Example B: ZO_MyParentClass:STUB("MyStubbedFunction")
local function STUB_SENTINEL()
    assert(false, "Attempted to access unimplemented field!")
end

function ZO_Object:STUB(fieldName)
    if fieldName then
        self[fieldName] = STUB_SENTINEL
    end
    return STUB_SENTINEL
end

do
    -- Indicates whether the class 'currentClass', or any of its parent classes,
    -- is the class 'checkClass'.
    local function IsClassOrSubclass(currentClass, checkClass)
        if currentClass == checkClass then
            return true
        end

        if currentClass.__parentClasses then
            for _, parentClass in ipairs(currentClass.__parentClasses) do
                if IsClassOrSubclass(parentClass, checkClass) then
                    return true
                end
            end
        end

        return false
    end

    -- Indicates whether this object is an instance of the class
    -- 'checkClass' or any class that derives from 'checkClass'.
    function ZO_Object:IsInstanceOf(checkClass)
        return IsClassOrSubclass(self.__index, checkClass)
    end
end

--[[
Here is a simple multiple inheritance example:

local A = ZO_Object:Subclass()

function A:InitializeA()
    self.name = "A"
end

local B = ZO_Object:Subclass()

function B:InitializeB()
    self.text = "B"
end

C = ZO_Object.MultiSubclass(A, B)

function C:New()
    local obj = ZO_Object.New(self)
    obj:Initialize()
    return obj
end

function C:Initialize()
    self:InitializeA()
    self:InitializeB()
end
]]--

function ZO_GenerateDataSourceMetaTableIndexFunction(template)
    return function(tbl, key)
        local value = template[key]
        if value == nil then
            local dataSource = rawget(tbl, "dataSource")
            if dataSource then
                value = dataSource[key]
            end
        end
        return value
    end
end

ZO_DataSourceObject = {}

function ZO_DataSourceObject:New(template)
    template = template or self

    local newObject = setmetatable({}, template.instanceMetaTable)

    local mt = getmetatable(newObject)
    mt.__index = ZO_GenerateDataSourceMetaTableIndexFunction(template)

    return newObject
end

function ZO_DataSourceObject:GetDataSource()
    return self.dataSource
end

function ZO_DataSourceObject:SetDataSource(dataSource)
    self.dataSource = dataSource
end

function ZO_DataSourceObject:Subclass()
    local newTemplate = setmetatable({}, { __index = self })
    newTemplate.instanceMetaTable = { __index = ZO_GenerateDataSourceMetaTableIndexFunction(newTemplate) }
    newTemplate.__parentClasses = { self }
    newTemplate.__isAbstractClass = false
    return newTemplate
end

---
-- ZO_InitializingObject is a new Object definition that more directly encodes
-- the practices most current ZO_Objects are actually using. In most cases, you
-- can directly replace a ZO_Object with a ZO_InitializingObject, and delete the
-- redundant :New() definition that most ZO_Object classes defined.
--
ZO_InitializingObject = {}
zo_mixin(ZO_InitializingObject, ZO_Object)
ZO_InitializingObject.__index = ZO_InitializingObject

---
-- The external constructor for each object.
-- Sample usage:
--     myObject = MyClass:New([arguments])
--
function ZO_InitializingObject:New(...)
    local newObject = setmetatable({}, self)
    newObject:Initialize(...)
    return newObject
end

---
-- Override this initialization function to define how your object should be constructed.
-- Sample usage:
--     function MyClass:Initialize(argument1, argument2)
--         self.myField = argument1
--     end
-- You can still create an InitializingObject that does not define an Initialize method;
-- new instances will simply call this empty, default Initialize method instead.
function ZO_InitializingObject:Initialize()
    -- To be overridden
end

--
-- ZO_DeferredInitializingObject is a ZO_InitializingObject wrapper that standardizes the
-- logic around deferring some of the initialization until later.
-- To use this abstract class:
--   (1) Pass in a ZO_Scene, ZO_SceneFragment or ZO_SceneGroup object.
--   (2) Define a custom OnDeferredInitialize method.
--       ZO_DeferredInitializingObject will call your OnDeferredInitialize method the first
--       time the ZO_Scene/ZO_SceneFragment/ZO_SceneGroup is shown.
--       Note that this is handled automatically and will happen prior to calling the
--       OnShowing method.
ZO_DeferredInitializingObject = {}
zo_mixin(ZO_DeferredInitializingObject, ZO_InitializingObject)
ZO_DeferredInitializingObject.__index = ZO_DeferredInitializingObject

---
-- If you must override the Initialize method, be sure to create a ZO_Scene, ZO_SceneFragment or
-- ZO_SceneGroup object to the pass to the base ZO_DeferredInitializingObject.Initialize method.
-- Sample usage:
--     function MyClass:Initialize(control, argument1, argument2)
--         ZO_DeferredInitializingObject.Initialize(self, ZO_FadeSceneFragment:New(control))
--         self.myField = argument1
--     end
function ZO_DeferredInitializingObject:Initialize(sceneStateObject)
    assert(sceneStateObject)
    self.sceneStateObject = sceneStateObject
    self.sceneStateObject:RegisterCallback("StateChange", function(...) self:OnStateChanged(...) end)
    if self.sceneStateObject:IsInstanceOf(ZO_Scene) then
        self.scene = sceneStateObject
    elseif self.sceneStateObject:IsInstanceOf(ZO_SceneFragment) then
        self.fragment = sceneStateObject
    elseif self.sceneStateObject:IsInstanceOf(ZO_SceneGroup) then
        self.sceneGroup = sceneStateObject
    end
end

function ZO_DeferredInitializingObject:PerformDeferredInitialize()
    if not self.initialized then
        self.initialized = true
        self:OnDeferredInitialize()
    end
end

function ZO_DeferredInitializingObject:OnStateChanged(_, newState)
    if newState == ZO_STATE.SHOWING then
        self:PerformDeferredInitialize()
        self:OnShowing()
    elseif newState == ZO_STATE.SHOWN then
        self:OnShown()
    elseif newState == ZO_STATE.HIDING then
        self:OnHiding()
    elseif newState == ZO_STATE.HIDDEN then
        self:OnHidden()
    end
end

function ZO_DeferredInitializingObject:OnShowing()
    -- To be overridden
end

function ZO_DeferredInitializingObject:OnShown()
    -- To be overridden
end

function ZO_DeferredInitializingObject:OnHiding()
    -- To be overridden
end

function ZO_DeferredInitializingObject:OnHidden()
    -- To be overridden
end

function ZO_DeferredInitializingObject:GetScene()
    return self.scene
end

function ZO_DeferredInitializingObject:GetFragment()
    return self.fragment
end

function ZO_DeferredInitializingObject:GetSceneGroup()
    return self.sceneGroup
end

function ZO_DeferredInitializingObject:IsShowing()
    return self.sceneStateObject:IsShowing()
end

ZO_DeferredInitializingObject:MUST_IMPLEMENT("OnDeferredInitialize")
