--[[-----------------------------------------------------------------------------
ScrollFrame Container
Plain container that scrolls its content and doesn't grow in height.
-------------------------------------------------------------------------------]]
local Type, Version = "ScrollFrame", 26
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then
  return
end

-- Lua APIs
local pairs, assert, type = pairs, assert, type
local min, max, floor = math.min, math.max, math.floor

-- WoW APIs
local CreateFrame, UIParent = CreateFrame, UIParent

--[[-----------------------------------------------------------------------------
Support functions
-------------------------------------------------------------------------------]]
local function FixScrollOnUpdate(frame)
  frame:SetScript("OnUpdate", nil)
  frame.obj:FixScroll()
end

--[[-----------------------------------------------------------------------------
Scripts
-------------------------------------------------------------------------------]]
local function ScrollFrame_OnMouseWheel(frame, value)
  frame.obj:MoveScroll(value)
end

local function ScrollFrame_OnSizeChanged(frame)
  frame:SetScript("OnUpdate", FixScrollOnUpdate)
end

local function ScrollBar_OnScrollValueChanged(frame, value)
  frame.obj:SetScroll(value)
end

--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
  ["OnAcquire"] = function(self)
    self:SetScroll(0)
    self.scrollframe:SetScript("OnUpdate", FixScrollOnUpdate)
  end,

  ["OnRelease"] = function(self)
    self.status = nil
    for k in pairs(self.localstatus) do
      self.localstatus[k] = nil
    end
    self.scrollframe:SetPoint("BOTTOMRIGHT")
    self.scrollbar:Hide()
    self.scrollBarShown = nil
    self.content.height, self.content.width, self.content.original_width = nil, nil, nil
  end,

  ["SetScroll"] = function(self, value)
    local status = self.status or self.localstatus
    local viewheight = self.scrollframe:GetHeight()
    local height = self.content:GetHeight()
    local offset

    if viewheight > height then
      offset = 0
    else
      offset = floor((height - viewheight) / 1000.0 * value)
    end
    self.content:ClearAllPoints()
    self.content:SetPoint("TOPLEFT", 0, offset)
    self.content:SetPoint("TOPRIGHT", 0, offset)
    status.offset = offset
    status.scrollvalue = value
  end,

  ["MoveScroll"] = function(self, value)
    local status = self.status or self.localstatus
    local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()

    if self.scrollBarShown then
      local diff = height - viewheight
      local delta = 1
      if value < 0 then
        delta = -1
      end
      self.scrollbar:SetValue(min(max(status.scrollvalue + delta * (1000 / (diff / 45)), 0), 1000))
    end
  end,

  ["FixScroll"] = function(self)
    if self.updateLock then
      return
    end
    self.updateLock = true
    local status = self.status or self.localstatus
    local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
    local offset = status.offset or 0
    -- Give us a margin of error of 2 pixels to stop some conditions that i would blame on floating point inaccuracys
    -- No-one is going to miss 2 pixels at the bottom of the frame, anyhow!
    if viewheight < height + 2 then
      if self.scrollBarShown then
        self.scrollBarShown = nil
        self.scrollbar:Hide()
        self.scrollbar:SetValue(0)
        self.scrollframe:SetPoint("BOTTOMRIGHT")
        if self.content.original_width then
          self.content.width = self.content.original_width
        end
        self:DoLayout()
      end
    else
      if not self.scrollBarShown then
        self.scrollBarShown = true
        self.scrollbar:Show()
        self.scrollframe:SetPoint("BOTTOMRIGHT", -20, 0)
        if self.content.original_width then
          self.content.width = self.content.original_width - 20
        end
        self:DoLayout()
      end
      local value = (offset / (viewheight - height) * 1000)
      if value > 1000 then
        value = 1000
      end
      self.scrollbar:SetValue(value)
      self:SetScroll(value)
      if value < 1000 then
        self.content:ClearAllPoints()
        self.content:SetPoint("TOPLEFT", 0, offset)
        self.content:SetPoint("TOPRIGHT", 0, offset)
        status.offset = offset
      end
    end
    self.updateLock = nil
  end,

  ["LayoutFinished"] = function(self, width, height)
    self.content:SetHeight(height or 0 + 20)

    -- update the scrollframe
    self:FixScroll()

    -- schedule another update when everything has "settled"
    self.scrollframe:SetScript("OnUpdate", FixScrollOnUpdate)
  end,

  ["SetStatusTable"] = function(self, status)
    assert(type(status) == "table")
    self.status = status
    if not status.scrollvalue then
      status.scrollvalue = 0
    end
  end,

  ["OnWidthSet"] = function(self, width)
    local content = self.content
    content.width = width - (self.scrollBarShown and 20 or 0)
    content.original_width = width
  end,

  ["OnHeightSet"] = function(self, height)
    local content = self.content
    content.height = height
  end
}
--[[-----------------------------------------------------------------------------
Constructor
-------------------------------------------------------------------------------]]
local function Constructor()
  local frame = CreateFrame("Frame", nil, UIParent)
  local num = AceGUI:GetNextWidgetNum(Type)

  local scrollframe = CreateFrame("ScrollFrame", nil, frame)
  scrollframe:SetPoint("TOPLEFT")
  scrollframe:SetPoint("BOTTOMRIGHT")
  scrollframe:EnableMouseWheel(true)
  scrollframe:SetScript("OnMouseWheel", ScrollFrame_OnMouseWheel)
  scrollframe:SetScript("OnSizeChanged", ScrollFrame_OnSizeChanged)

  local scrollbar = CreateFrame("Slider", ("AceConfigDialogScrollFrame%dScrollBar"):format(num), scrollframe, "UIPanelScrollBarTemplate")
  scrollbar:SetPoint("TOPLEFT", scrollframe, "TOPRIGHT", 4, -16)
  scrollbar:SetPoint("BOTTOMLEFT", scrollframe, "BOTTOMRIGHT", 4, 16)
  scrollbar:SetMinMaxValues(0, 1000)
  scrollbar:SetValueStep(1)
  scrollbar:SetValue(0)
  scrollbar:SetWidth(16)
  scrollbar:Hide()
  -- set the script as the last step, so it doesn't fire yet
  scrollbar:SetScript("OnValueChanged", ScrollBar_OnScrollValueChanged)

  local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
  scrollbg:SetAllPoints(scrollbar)
  scrollbg:SetColorTexture(0, 0, 0, 0.4)

  --Container Support
  local content = CreateFrame("Frame", nil, scrollframe)
  content:SetPoint("TOPLEFT")
  content:SetPoint("TOPRIGHT")
  content:SetHeight(400)
  scrollframe:SetScrollChild(content)

  local widget = {
    localstatus = { scrollvalue = 0 },
    scrollframe = scrollframe,
    scrollbar = scrollbar,
    content = content,
    frame = frame,
    type = Type
  }
  for method, func in pairs(methods) do
    widget[method] = func
  end
  scrollframe.obj, scrollbar.obj = widget, widget

  return AceGUI:RegisterAsContainer(widget)
end

AceGUI:RegisterWidgetType(Type, Constructor, Version)
