local Type, Version = "WITScrollFrame", 24
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, abs = math.min, math.max, math.floor, math.abs

-- 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 VScrollbar_OnScrollValueChanged(frame, value)
	frame.obj:SetScroll(value, frame.obj.hScrollbar:GetValue())
end

local function HScrollbar_OnScrollValueChanged(frame, value)
	frame.obj:SetScroll(frame.obj.vScrollbar:GetValue(), value)
end

--[[-----------------------------------------------------------------------------
Methods
-------------------------------------------------------------------------------]]
local methods = {
	["OnAcquire"] = function(self) 
		self:SetScroll(0, 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.vScrollbar:Hide()
		self.vScrollbarShown = nil
		self.hScrollbar:Hide()
		self.hScrollbarShown = nil
		self.content.height, self.content.width = nil, nil
	end,

	["SetScroll"] = function(self, vvalue, hvalue)
		local status = self.status or self.localstatus
		local viewheight = self.scrollframe:GetHeight()
        local viewwidth = self.scrollframe:GetWidth()
		local height = self.content:GetHeight()
        local width = self.content:GetWidth()
		local voffset, hoffset

		if viewheight > height then
			voffset = 0
		else
			voffset = floor((height - viewheight) / 1000.0 * vvalue)
		end

        if viewwidth > width then
			hoffset = 0
		else
			hoffset = floor((width - viewwidth) / 1000.0 * hvalue)
		end

		self.content:ClearAllPoints()
		self.content:SetPoint("TOPLEFT", -hoffset, voffset)
		--self.content:SetPoint("TOPRIGHT", -hoffset, voffset)
		status.voffset = voffset
        status.hoffset = hoffset
		status.vscrollvalue = vvalue
        status.hscrollvalue = hvalue
	end,

	["MoveScroll"] = function(self, value)
		local status = self.status or self.localstatus
		local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
		
		if self.vScrollbarShown then
			local diff = height - viewheight
			local delta = 1
			if value < 0 then
				delta = -1
			end
			self.vScrollbar:SetValue(min(max(status.vscrollvalue + 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 width, viewwidth = self.scrollframe:GetWidth(), self.content:GetWidth()

		local voffset = status.voffset or 0
        local hoffset = status.hoffset or 0
		local curvaluev = self.vScrollbar:GetValue()
        local curvalueh = self.hScrollbar:GetValue()

        local showVScrollbar = false
        local showHScrollbar = false
        local refreshLayout = false

		if viewheight >= height + ((viewwidth >= width + 2) and not self.hScrollbarShown and 22 or 2) then
            showVScrollbar = true
		end

		if viewwidth >= width + (showVScrollbar and not self.vScrollbarShown and 22 or 2) then
            showHScrollbar = true
		end

        if (self.vScrollbarShown ~= showVScrollbar) or (self.hScrollbarShown ~= showHScrollbar) then
		    self.vScrollbarShown = showVScrollbar
		    self.vScrollbar:SetShown(showVScrollbar)

            if not showVScrollbar then
		        self.vScrollbar:SetValue(0)
            end

		    self.hScrollbarShown = showHScrollbar
		    self.hScrollbar:SetShown(showHScrollbar)

            if not showHScrollbar then
		        self.hScrollbar:SetValue(0)
            end

            self.scrollframe:SetPoint("BOTTOMRIGHT", showVScrollbar and -20 or 0, showHScrollbar and 20 or 0)
            self:DoLayout()
		end

        if showVScrollbar or showHScrollbar then
			local vvalue = 0
            local hvalue = 0

            if showVScrollbar then
                vvalue = (voffset / (viewheight - height) * 1000)
                if vvalue > 1000 then vvalue = 1000 end
            end

            if showHScrollbar then
                hvalue = (hoffset / (viewwidth - width) * 1000)
                if hvalue > 1000 then hvalue = 1000 end
            end
			
			self.vScrollbar:SetValue(vvalue)
            self.hScrollbar:SetValue(hvalue)
			self:SetScroll(vvalue, hvalue)
        end
		self.updateLock = nil
	end,

	["LayoutFinished"] = function(self, width, height)
        self.content:SetWidth(width or 0 + 20)
		self.content:SetHeight(height or 0 + 20)
		self.scrollframe:SetScript("OnUpdate", FixScrollOnUpdate)
	end,

	["SetStatusTable"] = function(self, status)
		assert(type(status) == "table")
		self.status = status
        status.vscrollvalue = status.vscrollvalue or 0
        status.hscrollvalue = status.hscrollvalue or 0
	end,

	["OnWidthSet"] = function(self, width)
		local content = self.content
		content.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 vScrollbar = CreateFrame("Slider", ("AceConfigDialogScrollFrame%dVScrollbar"):format(num), scrollframe)
	vScrollbar:SetPoint("TOPLEFT", scrollframe, "TOPRIGHT", 4, 0)
	vScrollbar:SetPoint("BOTTOMLEFT", scrollframe, "BOTTOMRIGHT", 4, 0)
	vScrollbar:SetMinMaxValues(0, 1000)
	vScrollbar:SetValueStep(1)
	vScrollbar:SetValue(0)
	vScrollbar:SetWidth(16)
	vScrollbar:Hide()
	-- set the script as the last step, so it doesn't fire yet
	vScrollbar:SetScript("OnValueChanged", VScrollbar_OnScrollValueChanged)

    local scrollbgtv = vScrollbar:CreateTexture(nil, "BACKGROUND")
    scrollbgtv:SetTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
    vScrollbar:SetThumbTexture(scrollbgtv)

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

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

    local scrollbgth = hScrollbar:CreateTexture(nil, "BACKGROUND")
    scrollbgth:SetTexture("Interface\\Buttons\\UI-ScrollBar-Knob")
    hScrollbar:SetThumbTexture(scrollbgth)

	local scrollbgh = hScrollbar:CreateTexture(nil, "BACKGROUND")
	scrollbgh:SetAllPoints(hScrollbar)
	scrollbgh:SetColorTexture(0, 0, 0, 0.4)

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

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

	return AceGUI:RegisterAsContainer(widget)
end

AceGUI:RegisterWidgetType(Type, Constructor, Version)
