--[[-------------------------------------------------------------------------
  James Whitehead II grants anyone the right to use this work for any purpose,
  without any conditions, unless such conditions are required by law.
---------------------------------------------------------------------------]]

local major = 'DongleStub'
local minor = tonumber(string.match('$Revision: 313 $', '(%d+)') or 1)

local g = getfenv(0)

if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then
  local lib = setmetatable({}, {
    __call = function(t, k)
      if type(t.versions) == 'table' and t.versions[k] then
        return t.versions[k].instance
      else
        error("Cannot find a library with name '" .. tostring(k) .. "'", 2)
      end
    end,
  })

  function lib:IsNewerVersion(major, minor)
    local versionData = self.versions and self.versions[major]

    -- If DongleStub versions have differing major version names
    -- such as DongleStub-Beta0 and DongleStub-1.0-RC2 then a second
    -- instance will be loaded, with older logic.  This code attempts
    -- to compensate for that by matching the major version against
    -- "^DongleStub", and handling the version check correctly.

    if major:match('^DongleStub') then
      local oldmajor, oldminor = self:GetVersion()
      if self.versions and self.versions[oldmajor] then
        return minor > oldminor
      else
        return true
      end
    end

    if not versionData then
      return true
    end
    local oldmajor, oldminor = versionData.instance:GetVersion()
    return minor > oldminor
  end

  local function NilCopyTable(src, dest)
    for k, v in pairs(dest) do
      dest[k] = nil
    end
    for k, v in pairs(src) do
      dest[k] = v
    end
  end

  function lib:Register(newInstance, activate, deactivate)
    assert(
      type(newInstance.GetVersion) == 'function',
      "Attempt to register a library with DongleStub that does not have a 'GetVersion' method."
    )

    local major, minor = newInstance:GetVersion()
    assert(
      type(major) == 'string',
      'Attempt to register a library with DongleStub that does not have a proper major version.'
    )
    assert(
      type(minor) == 'number',
      'Attempt to register a library with DongleStub that does not have a proper minor version.'
    )

    -- Generate a log of all library registrations
    if not self.log then
      self.log = {}
    end
    table.insert(self.log, string.format('Register: %s, %s', major, minor))

    if not self:IsNewerVersion(major, minor) then
      return false
    end
    if not self.versions then
      self.versions = {}
    end

    local versionData = self.versions[major]
    if not versionData then
      -- New major version
      versionData = {
        ['instance'] = newInstance,
        ['deactivate'] = deactivate,
      }

      self.versions[major] = versionData
      if type(activate) == 'function' then
        table.insert(self.log, string.format('Activate: %s, %s', major, minor))
        activate(newInstance)
      end
      return newInstance
    end

    local oldDeactivate = versionData.deactivate
    local oldInstance = versionData.instance

    versionData.deactivate = deactivate

    local skipCopy
    if type(activate) == 'function' then
      table.insert(self.log, string.format('Activate: %s, %s', major, minor))
      skipCopy = activate(newInstance, oldInstance)
    end

    -- Deactivate the old libary if necessary
    if type(oldDeactivate) == 'function' then
      local major, minor = oldInstance:GetVersion()
      table.insert(self.log, string.format('Deactivate: %s, %s', major, minor))
      oldDeactivate(oldInstance, newInstance)
    end

    -- Re-use the old table, and discard the new one
    if not skipCopy then
      NilCopyTable(newInstance, oldInstance)
    end
    return oldInstance
  end

  function lib:GetVersion()
    return major, minor
  end

  local function Activate(new, old)
    -- This code ensures that we'll move the versions table even
    -- if the major version names are different, in the case of
    -- DongleStub
    if not old then
      old = g.DongleStub
    end

    if old then
      new.versions = old.versions
      new.log = old.log
    end
    g.DongleStub = new
  end

  -- Actually trigger libary activation here
  local stub = g.DongleStub or lib
  lib = stub:Register(lib, Activate)
end
