if not WeakAuras.IsLibsOK() then return end
local AddonName = ...
local Private = select(2, ...)

local SharedMedia = LibStub("LibSharedMedia-3.0");
local L = WeakAuras.L;

-- Default settings
local default = {
  model_path = "Creature/Arthaslichking/arthaslichking.m2",
  modelIsUnit = false,
  model_x = 0,
  model_y = 0,
  model_z = 0,
  width = 200,
  height = 200,
  sequence = 1,
  advance = false,
  rotation = 0,
  scale = 1,
  selfPoint = "CENTER",
  anchorPoint = "CENTER",
  anchorFrameType = "SCREEN",
  xOffset = 0,
  yOffset = 0,
  frameStrata = 1,
  border = false,
  borderColor = {1.0, 1.0, 1.0, 0.5},
  backdropColor = {1.0, 1.0, 1.0, 0.5},
  borderEdge = "None",
  borderOffset = 5,
  borderInset = 11,
  borderSize = 16,
  borderBackdrop = "Blizzard Tooltip"
};

Private.regionPrototype.AddAlphaToDefault(default)

local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20;

local properties = {
  width = {
    display = L["Width"],
    setter = "SetRegionWidth",
    type = "number",
    min = 1,
    softMax = screenWidth,
    bigStep = 1,
    default = 32
  },
  height = {
    display = L["Height"],
    setter = "SetRegionHeight",
    type = "number",
    min = 1,
    softMax = screenHeight,
    bigStep = 1,
    default = 32
  },
}

Private.regionPrototype.AddProperties(properties, default);

local function GetProperties(data)
  return properties;
end

local regionFunctions = {
  Update = function() end,
  SetAlpha = function(self, alpha)
    self.alpha = alpha
    if self.model then
      self.model:SetAlpha(alpha)
    end
  end
}

-- Called when first creating a new region/display
local function create(parent)
  -- Main region
  local region = CreateFrame("Frame", nil, UIParent);
  region.regionType = "model"
  region:SetMovable(true);
  region:SetResizable(true);
  region:SetMinResize(1, 1);

  -- Border region
  local border = CreateFrame("Frame", nil, region);
  region.border = border;

  Private.regionPrototype.create(region);

  for k, v in pairs (regionFunctions) do
    region[k] = v
  end

  -- Return complete region
  return region;
end

local function CreateModel()
  return CreateFrame("PlayerModel", nil, UIParent)
end

-- Keep the two model apis separate
local poolModelApi = CreateObjectPool(CreateModel)
local poolUnitApi = CreateObjectPool(CreateModel)

local function ConfigureModel(region, model, data)
  model.modelIsUnit = data.modelIsUnit

  model:ClearAllPoints()
  model:SetAllPoints(region)
  model:SetParent(region)
  --model:SetKeepModelOnHide(true)
  model:Show()

  -- Adjust model
  WeakAuras.SetModel(model, data.model_path, data.modelIsUnit, data.modelDisplayInfo)
  model:SetPosition(data.model_z, data.model_x, data.model_y);
  model:SetFacing(rad(region.rotation));

  model:SetScript("OnShow", function()
    WeakAuras.SetModel(model, data.model_path, data.modelIsUnit, data.modelDisplayInfo)
    model:SetPosition(data.model_z, data.model_x, data.model_y);
    model:SetFacing(rad(region.rotation));
  end)

  if data.modelIsUnit then
    model:RegisterEvent("UNIT_MODEL_CHANGED");
    if (data.model_path == "target") then
      model:RegisterEvent("PLAYER_TARGET_CHANGED");
    elseif (data.model_path == "focus") then
      model:RegisterEvent("PLAYER_FOCUS_CHANGED");
    end
    model:SetScript("OnEvent", function(self, event, unitId)
      Private.StartProfileSystem("model");
      if (event ~= "UNIT_MODEL_CHANGED" or UnitIsUnit(unitId, data.model_path)) then
        WeakAuras.SetModel(model, data.model_path, data.modelIsUnit, data.modelDisplayInfo)
      end
      Private.StopProfileSystem("model");
    end
    );
  else
    model:UnregisterEvent("UNIT_MODEL_CHANGED");
    model:UnregisterEvent("PLAYER_TARGET_CHANGED");
    model:UnregisterEvent("PLAYER_FOCUS_CHANGED");
    model:SetScript("OnEvent", nil);
  end

  -- Enable model animation
  if(data.advance) then
    local elapsed = 0;
    model:SetScript("OnUpdate", function(self, elaps)
      Private.StartProfileSystem("model");
      elapsed = elapsed + (elaps * 1000);
      model:SetSequenceTime(data.sequence, elapsed);
      Private.StopProfileSystem("model");
    end)
  else
    model:SetScript("OnUpdate", nil)
  end
end

local function AcquireModel(region, data)
  local pool = data.modelIsUnit and poolUnitApi or poolModelApi
  local model = pool:Acquire()
  ConfigureModel(region, model, data)
  return model
end

local function ReleaseModel(model)
  model:SetAlpha(1)
  --model:SetKeepModelOnHide(false)
  model:Hide()
  model:UnregisterEvent("UNIT_MODEL_CHANGED");
  model:UnregisterEvent("PLAYER_TARGET_CHANGED");
  model:UnregisterEvent("PLAYER_FOCUS_CHANGED");
  model:SetScript("OnEvent", nil);
  local pool = model.modelIsUnit and poolUnitApi or poolModelApi
  pool:Release(model)
end

-- Modify a given region/display
local function modify(parent, region, data)
  Private.regionPrototype.modify(parent, region, data);
  -- Localize
  local border = region.border;

  if region.model then
    ReleaseModel(region.model)
    region.model = nil
  end

  -- Reset position and size
  region:SetWidth(data.width);
  region:SetHeight(data.height);
  region.width = data.width;
  region.height = data.height;
  region.scalex = 1;
  region.scaley = 1;

  -- Update border
  if data.border then
    border:SetBackdrop({
      edgeFile = SharedMedia:Fetch("border", data.borderEdge),
      edgeSize = data.borderSize,
      bgFile = SharedMedia:Fetch("background", data.borderBackdrop),
      insets = {
        left     = data.borderInset,
        right    = data.borderInset,
        top      = data.borderInset,
        bottom   = data.borderInset,
      },
    });
    border:SetBackdropBorderColor(data.borderColor[1], data.borderColor[2], data.borderColor[3], data.borderColor[4]);
    border:SetBackdropColor(data.backdropColor[1], data.backdropColor[2], data.backdropColor[3], data.backdropColor[4]);

    border:SetPoint("bottomleft", region, "bottomleft", -data.borderOffset, -data.borderOffset);
    border:SetPoint("topright",   region, "topright",    data.borderOffset,  data.borderOffset);

    border:Show();
  else
    border:Hide();
  end
  -- Rescale model display
  function region:Scale(scalex, scaley)
    if(scalex < 0) then
      region.mirror_h = true;
      scalex = scalex * -1;
    else
      region.mirror_h = nil;
    end
    region:SetWidth(region.width * scalex);
    if(scaley < 0) then
      scaley = scaley * -1;
      region.mirror_v = true;
    else
      region.mirror_v = nil;
    end
    region:SetHeight(region.height * scaley);
  end

  function region:SetRegionWidth(width)
    region.width = width;
    region:Scale(region.scalex, region.scaley);
  end

  function region:SetRegionHeight(height)
    region.height = height;
    region:Scale(region.scalex, region.scaley);
  end

  -- Rotate model
  function region:SetAnimRotation(degrees)
    region.animRotation = degrees
    region:UpdateEffectiveRotation()
  end

  function region:SetRotation(degrees)
    region.rotation = degrees
    region:UpdateEffectiveRotation()
  end

  function region:UpdateEffectiveRotation()
    region.effectiveRotation = region.animRotation or region.rotation
    if region.model then
      region.model:SetFacing(rad(region.effectiveRotation))
    end
  end

  region:SetRotation(data.rotation)

  -- Get model rotation
  function region:GetBaseRotation()
    return region.rotation
  end

  function region:PreShow()
    if not region.model then
      region.model = AcquireModel(self, data)
    else
      ConfigureModel(region, region.model, data)
    end
    if type(data.alpha) == "number" then
      region:SetAlpha(data.alpha)
    end
  end

  function region:PreHide()
    if region.model then
      ReleaseModel(region.model)
      region.model = nil
    end
  end

  Private.regionPrototype.modifyFinish(parent, region, data);
end

-- Work around for movies and world map hiding all models
do
  function Private.PreShowModels(self, event)
    Private.StartProfileSystem("model");
    for id, data in pairs(Private.regions) do
      Private.StartProfileAura(id);
      if data.region and data.region.toShow then
        if (data.regionType == "model") then
          data.region:PreShow();
        end
      end
      Private.StopProfileAura(id);
    end
    for model in pairs(Private.barmodels) do
      model:PreShow()
    end
    Private.StopProfileSystem("model");
  end
end

local function validate(data)
  Private.EnforceSubregionExists(data, "subbackground")
end

-- Register new region type with WeakAuras
Private.RegisterRegionType("model", create, modify, default, GetProperties, validate);
