
-- declare colour codes for console messages
local GREEN   = "|cff00ff00"
local YELLOW  = "|cffffff00"
local RED     = "|cffff0000"
local WHITE   = "|cffffffff"

gfxToggle = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceDB-2.0", "AceConsole-2.0")
local L = AceLibrary("AceLocale-2.2"):new("gfxToggle")

local dewdrop = AceLibrary("Dewdrop-2.0")
local tablet = AceLibrary("Tablet-2.0")
local ldb = LibStub:GetLibrary("LibDataBroker-1.1")
local dataobj = ldb:NewDataObject("gfxToggle2", { type = "data source", icon = "Interface\\AddOns\\gfxToggle2\\icon" } )

-- Keybinding
BINDING_HEADER_GFX		= L["gfxToggle2"]
BINDING_NAME_GFX_TOGGLE	= L["Toggle graphics"]
BINDING_NAME_GFX_AUTO	= L["Auto-Mode"]

local gfxToggle = gfxToggle
gfxToggle.printFrame = DEFAULT_CHAT_FRAME

local tooltip, options

local defaultSets = {
	["Low"] = {
		short = "L",
		colour = GREEN,
		db = {
			baseMip = 1,
			farclip = 177,
			environmentDetail = 0.5,
			groundEffectDensity = 16,
			groundEffectDist = 70,
			particleDensity = 0.1,
			projectedTextures = 0,
			weatherDensity = 0,
			shadowMode = 0,
			ffxDeath = 0,
			ffxGlow = 0,
		},
	},
	["Medium"] = {
		short = "M",
		colour = YELLOW,
		db = {
			baseMip = 1,
			farclip = 617,
			environmentDetail = 1,
			groundEffectDensity = 40,
			groundEffectDist = 100,
			particleDensity = 0.5,
			projectedTextures = 0,
			weatherDensity = 1,
			shadowMode = 2,
			ffxDeath = 1,
			ffxGlow = 1,
		},
	},
	["High"] = {
		short = "H",
		colour = RED,
		db = {
			baseMip = 0,
			farclip = 1057,
			environmentDetail = 1.25,
			groundEffectDensity = 56,
			groundEffectDist = 130,
			particleDensity = 0.8,
			projectedTextures = 1,
			weatherDensity = 3,
			shadowMode = 4,
			ffxDeath = 1,
			ffxGlow = 1,
		},
	},
	["Normal"] = {
		short = "N",
		colour = WHITE,
	}
}

local function GetDisplayName(name, short)
	if name and defaultSets[name] then
		local colour = defaultSets[name].colour
		if short then name = defaultSets[name].short end
		return colour..L[name].."|r"
	end
	return name
end

local function Capitalize(s)
	for w in string.gmatch(s, "%S+") do
		if strlen(w) > 2 then
			s = gsub(s, w, strupper(strsub(w, 1, 1))..strsub(w, 2))
		end
	end
	return s
end

do
local self = gfxToggle

options = { desc = L["gfxToggle2 Options"], type = "group", args = {
	save = {
		guiHidden = true,
		name = L["Save"],
		desc = L["Saves your custom settings."],
		usage = L["Change settings in WoW Graphic-Options (World Appearance, Shaders and Misc). After that click on the Ok-Button and save settings."],
		type = "text",
		get = false,
		set = function(v) self:Save(v) end,
		validate = {"low", "med", "high"},
	},
	spacer = { type = "header", order = 80 },
	guisave = {
		cmdHidden = true,
		name = L["Save"], order = 250,
		desc = L["Change settings in WoW Graphic-Options (World Appearance, Shaders and Misc). After that click on the Ok-Button and save settings."],
		type = "group",
		args = {
			low = {
				name = L["Low"], order = 1,
				type = "execute",
				desc = L["Saves settings for: "]..GetDisplayName("Low"),
				func = function(v) self:Save("low") end,
			},
			med = {
				name = L["Medium"], order = 2,
				type = "execute",
				desc = L["Saves settings for: "]..GetDisplayName("Medium"),
				func = function(v) self:Save("med") end,
			},
			high = {
				name = L["High"], order = 3,
				type = "execute",
				desc = L["Saves settings for: "]..GetDisplayName("High"),
				func = function(v) self:Save("high") end,
			},
		},
	},
	auto = {
		name = L["Auto"], order = 600,
		desc = L["Checks your zone database and toggles the saved settings automatically."],
		type = "toggle",
		get = function() return self.data.autoMode end,
		set = function(v) self.data.autoMode = v; self:UpdateDisplay(); self:AutoReg(); end,
	},
	medium = {
		name = L["Medium"], order = 1000,
		desc = string.format(L["Does allow the use of the settings: %s."], GetDisplayName("Medium")),
		type = "toggle",
		get = function() return self.data.useMedium end,
		set = function(v) self.data.useMedium = v end,
	},
	toggle = {
		guiHidden = true,
		name = L["Toggle"], order = 500,
		desc = string.format(L["Toggles between %s, %s (if turned on by '/gfxt medium') and %s."],
			GetDisplayName("Low"), GetDisplayName("Medium"), GetDisplayName("High")),
		type = "execute",
		func = "Toggle",
	},
	shorttext = {
		cmdHidden = true,
		name = L["Short Text"], order = 1300,
		desc = L["Toggles text display"],
		type = "toggle",
		get = function() return self.data.shortText end,
		set = function(v) self.data.shortText = v self:UpdateDisplay() end,
	},
	addl = {
		guiHidden = true,
		name = L["Add "]..GetDisplayName("Low"),
		desc = L["Adds zone for setting: "]..GetDisplayName("Low"),
		usage = '<'..L["this"]..'> '..L["Adds the current zone. "]..'<'..L["name"]..'> '..L["Adds the specified zone."],
		type = "text",
		get = false,
		set = function(i) self:Add(i, "low") end,
	},
	addm = {
		guiHidden = true,
		name = L["Add "]..GetDisplayName("Medium"),
		desc = L["Adds zone for setting: "]..GetDisplayName("Medium"),
		usage = '<'..L["this"]..'> '..L["Adds the current zone. "]..'<'..L["name"]..'> '..L["Adds the specified zone."],
		type = "text",
		get = false,
		set = function(i) self:Add(i, "med") end,
	},
	guiadd = {
		cmdHidden = true,
		type = "group",
		name = L["Add "]..L["zone"], order = 50,
		desc = L["Adds the specified zone."],
		args = {
			gaddl = {
				name = L["Low"], order = 1,
				desc = L["Adds zone for setting: "]..GetDisplayName("Low"),
				usage = '<'..L["name"]..'> '..L["Adds the specified zone."],
				type = "text",
				error = '<'..L["name"]..'> '..L["Adds the specified zone."],
				get = false,
				set = function(v) self:Add(v, "low") end,
				validate = function(v) return v ~= "" and string.gsub(v, " ", "") ~= "" end,
			},
			gaddm = {
				name = L["Medium"], order = 2,
				desc = L["Adds zone for setting: "]..GetDisplayName("Medium"),
				usage = '<'..L["name"]..'> '..L["Adds the specified zone."],
				type = "text",
				get = false,
				set = function(v) self:Add(v, "med") end,
				validate = function(v) return v ~= "" and string.gsub(v, " ", "") ~= "" end,
			},
		},
	},
	del = {
		guiHidden = true,
		name = L["Delete "]..L["zone"],
		desc = L["Deletes a zone from database."],
		usage = '<'..L["this"]..'> '..L["Deletes current zone."]..' <'..L["name"]..'> '
			..L["Deletes specified zone."]..' <'..L["All"]..'> '..L["Deletes all zones."],
		type = "text",
		get = false,
		set = function(i) self:Del(i) end,
	},
	guidel = {
		cmdHidden = true,
		name = L["Delete "]..L["zone"], order = 60,
		desc = L["Deletes a zone from database."],
		type = "group",
		args = {
			allzones = {
				name = L["All"], order = 3,
				desc = L["Deletes all zones."],
				type = "execute",
				func = function() StaticPopup_Show("GFXTOGGLE_ZONES_RESET") end,
			},
		},
	},
	spacer2 = { type = "header", order = 350 },
	list = {
		guiHidden = true,
		name = L["List"], order = 400,
		desc = L["Lists all saved zones."],
		type = "execute",
		func = "List",
	},
	reset = {
		name = L["Reset"], order = 2000,
		desc = L["Resets all settings to their defaults."],
		type = "execute",
		func = function() StaticPopup_Show("GFXTOGGLE_OPTIONS_RESET") end,
	},
	mute = {
		name = L["Mute"], order = 800,
		desc = L["Toggles screen messages."],
		type = "toggle",
		get = function() return self.data.mute end,
		set = function(v) self.data.mute = v end,
	},
	restartGX = {
		name = L["RestartGx"], order = 1100,
		desc = L["Allows re-initializing of the graphics driver (needed for VSync)."],
		usage = L["To switch VSync, Tripplebuffer and Smooth Mouse the game has to restart the graphics. "..
			"While it does the restart, it'll switch to the desktop and back to game after a duration of 1-2 seconds. "..
			"By using this command you allow switching these settings. Default is: |cffff5050Off|r."],
		type = "toggle",
		get = function() return self.data.gxRestart end,
		set = function(v) self.data.gxRestart = v end,
	},
	delay = {
		name = L["Delay"], order = 700,
		desc = L["Sets a delay for the Auto-Mode. (Default: [|cfff5f5305|r] seconds)"],
		type = "range",
		get = function() return self.data.delay end,
		set = function(v)
			self.data.delay = v
		end,
		min = 1, max = 60, step = 0.5,
	},
} }
end

function gfxToggle:OnInitialize()
	do
		local version
		--@non-debug@
		version = "2.3.0-beta2"
		--@end-non-debug@
		if version then
			self.version = version
		end
	end

	self:RegisterDB("gfxToggleDB")
	self:RegisterDefaults('profile', {
		zones = {},
		sets = {},
		delay = 5,
		mute = false,
	} )

	self.data = self.db.profile

	self:RegisterChatCommand({ "/gfxt", "/gfxtoggle" }, options)
	self.OnMenuRequest = options

	StaticPopupDialogs["GFXTOGGLE_OPTIONS_RESET"] = {
		text = string.format(L["Are you sure you want to reset all your %s for %s"], L["settings"], self.title),
		button1 = TEXT(ACCEPT),
		button2 = TEXT(CANCEL),
		OnAccept = function() self:Reset()	end,
		showAlert = 1,
		timeout = 0,
		exclusive = 1,
		whileDead = 1,
		interruptCinematic = 1
	}

	StaticPopupDialogs["GFXTOGGLE_ZONES_RESET"] = {
		text = string.format(L["Are you sure you want to reset all your %s for %s"], L["zones"], self.title),
		button1 = TEXT(ACCEPT),
		button2 = TEXT(CANCEL),
		OnAccept = function() self:Del("all") end,
		showAlert = 1,
		timeout = 0,
		exclusive = 1,
		whileDead = 1,
		interruptCinematic = 1
	}

	-- convert legacy zone list
	if self.data.zonesDB and not next(self.data.zones) then
		local zones = self.data.zones
		for k, v in pairs(self.data.zonesDB) do
			if v == "L" then
				zones[k] = "Low"
			elseif v == "M" then
				zones[k] = "Medium"
			end
		end
		self.data.zonesDB = nil
	end

	if ApplyToForehead then
		self:Print(L["Attention! Don't touch the RestartGx toggle if you're running WoW on a linux box. It might crash your client!"])
	end
end

function gfxToggle:OnEnable()
	self:AutoReg()
	self:UpdateDisplay()
end

function gfxToggle:AutoReg()
	if self.data.autoMode then
		self:RegisterEvent("ZONE_CHANGED_NEW_AREA")
		self:ScheduleEvent("GFXT_ZONE_CHANGED", self.ZONE_CHANGED_NEW_AREA, self.data.delay, self)
	elseif self:IsEventRegistered("ZONE_CHANGED_NEW_AREA") then
		self:UnregisterEvent("ZONE_CHANGED_NEW_AREA")
	end
end

function gfxToggle:AutoKey()
	self.data.autoMode = not self.data.autoMode

	if self.data.autoMode then
		self:PrintMessage(string.format(L["Auto-Mode is set to: %s"], GREEN..L["On"].."|r"))
	else
		self:PrintMessage(string.format(L["Auto-Mode is set to: %s"], RED..L["Off"].."|r"))
	end

	self:UpdateDisplay()
	self:AutoReg()
end

function gfxToggle:Reset()
	self.data.sets = {}
	self:Print(L["All settings have been reset to their defaults."])
	self:UpdateDisplay()
end

function gfxToggle:ZONE_CHANGED_NEW_AREA()
	if self:IsEventScheduled("GFXT_ZONE_CHANGED") then self:CancelScheduledEvent("GFXT_ZONE_CHANGED"); end
	local gzt = GetZoneText()

	if gzt == "" then
		self:ScheduleEvent("GFXT_ZONE_CHANGED", self.ZONE_CHANGED_NEW_AREA, self.data.delay, self)
		return
	end

	for k, v in pairs(self.data.zones) do
		if strlower(k) == strlower(gzt) then
			if self.data.currentSet == v then return; end
			self:ScheduleEvent("GFXT_LOADSET", self.LoadSet, self.data.delay, self, v)
			return
		end
	end

	if self.data.currentSet ~= "High" then
		self:ScheduleEvent("GFXT_LOADSET", self.LoadSet, self.data.delay, self, "High")
	end
end

function gfxToggle:Add(i, w)
	local z

	if i == "this" then
		z = strlower(GetZoneText())
	else
		z = strlower(i)
	end

	for k in pairs(self.data.zones) do
		if strlower(k) == z then
			self:Print(L["Chat_Exists"])
			return
		end
	end

	if w == "low" then
		self.data.zones[z] = "Low"
	elseif w == "med" then
		self.data.zones[z] = "Medium"
		if not self.data.useMedium then self:Print(L["Chat_MedAdd"]) end
	end

	self:Print("'"..Capitalize(z)..L["Chat_Added"])

	if z == strlower(GetZoneText()) then
		if w == "low" then
			self:LoadSet("Low")
		elseif w == "med" then
			self:LoadSet("Medium")
		end
	end

	self:UpdateDisplay()
end

function gfxToggle:Del(i)
	if strlower(i) == "all" then
		self.data.zones = {}
		self:Print(L["Chat_DelAll"])
		return
	end

	local z, f

	if i == "this" then
		z = strlower(GetZoneText())
	else
		z = strlower(i)
	end

	for v in pairs(self.data.zones) do
		if strlower(v) == z then
			f = strlower(v)
		end
	end

	if not f then
		self:Print(L["Chat_NotThere"])
		return
	end

	self:Print("'"..Capitalize(z)..L["Chat_Deleted"])
	self.data.zones[f] = nil

	if z == strlower(GetZoneText()) then
		self:LoadSet("High")
	end

	self:UpdateDisplay()
end

function gfxToggle:List()
	local dbLow, dbMed = {}, {}
	local c = 0

	if next(self.data.zones) then
		for k, v in pairs(self.data.zones) do
			if v == "Low" then
				tinsert(dbLow, Capitalize(k))
			elseif v == "Medium" then
				tinsert(dbMed, Capitalize(k))
			end

			c = c + 1
		end

		if next(dbLow) ~= nil then
			table.sort(dbLow)
			self:Print(L["Zones: "]..GetDisplayName("Low"))
			for k, v in ipairs(dbLow) do
				self:Print(v)
			end
		end

		if next(dbMed) ~= nil then
			table.sort(dbMed)
			self:Print(L["Zones: "]..GetDisplayName("Medium"))
			for k, v in ipairs(dbMed) do
				self:Print(v)
			end
		end

		if c == 1 then
			self:Print(c..L["Chat_FoundOne"])
		else
			self:Print(c..L["Chat_Found"])
		end
	else
		print(L["Chat_NoEntry"])
	end
end

function gfxToggle:Toggle()
	if self.data.useMedium then
		if self.data.currentSet == "Low" then
			self:LoadSet("Medium", true)
		elseif self.data.currentSet == "Medium" then
			self:LoadSet("High", true)
		elseif self.data.currentSet == "High" then
			self:LoadSet("Low", true)
		end
	else
		if self.data.currentSet == "Low" then
			self:LoadSet("High", true)
		else
			self:LoadSet("Low", true)
		end
	end
end

local function SetCVar2(db, var)
	local val = db[var]
	if not val then return; end
	local old = GetCVar(var)
	if old ~= val then
		SetCVar(var, val)
		return true
	end
end

function gfxToggle:LoadSet(set, force)
	local db, msg

	if self.data.sets[set] then
		db = self.data.sets[set]
		msg = L["GFX set to:"].." "..GetDisplayName(set)
	else
		db = defaultSets[set].db
		msg = GetDisplayName(set).." "..L["(default) \nUse '/gfxt save' to save custom settings."]
	end

	SetCVar2(db, "farclip")
	SetCVar2(db, "baseMip")
	SetCVar2(db, "environmentDetail")
	SetCVar2(db, "groundEffectDensity")
	SetCVar2(db, "groundEffectDist")
	SetCVar2(db, "particleDensity")
	SetCVar2(db, "projectedTextures")
	SetCVar2(db, "weatherDensity")
	SetCVar2(db, "shadowMode")
	SetCVar2(db, "ffxDeath")
	SetCVar2(db, "ffxGlow")

	local restart

	restart = SetCVar2(db, "gxVSync") or restart
	restart = SetCVar2(db, "gxTripleBuffer") or restart
	restart = SetCVar2(db, "gxFixLag") or restart

	if restart and (force or self.data.gxRestart) then
		RestartGx()
	end

	self:PrintMessage(msg)
	self.data.currentSet = set
	self:UpdateDisplay()
end

function gfxToggle:Save(v)
	local db = {
		baseMip = GetCVar("baseMip"),
		farclip = GetCVar("farclip"),
		environmentDetail = GetCVar("environmentDetail"),
		groundEffectDensity = GetCVar("groundEffectDensity"),
		groundEffectDist = GetCVar("groundEffectDist"),
		particleDensity = GetCVar("particleDensity"),
		projectedTextures = GetCVar("projectedTextures"),
		weatherDensity = GetCVar("weatherDensity"),
		shadowMode = GetCVar("shadowMode"),
		ffxDeath = GetCVar("ffxDeath"),
		ffxGlow = GetCVar("ffxGlow"),
		gxVSync = GetCVar("gxVSync"),
		gxTripleBuffer = GetCVar("gxTripleBuffer"),
		gxFixLag = GetCVar("gxFixLag"),
	}

	if strlower(v) == "low" then
		self.data.sets["Low"] = db
	elseif strlower(v) == "med" then
		self.data.sets["Medium"] = db
	elseif strlower(v) == "high" then
		self.data.sets["High"] = db
	end

	if not self.data.mute then
		if strlower(v) == "low" then
			self:Print("%s %s", L["Saved settings for: "], GetDisplayName("Low"))
		elseif strlower(v) == "med" then
			self:Print("%s %s", L["Saved settings for: "], GetDisplayName("Medium"))
		elseif strlower(v) == "high" then
			self:Print("%s %s", L["Saved settings for: "], GetDisplayName("High"))
		end
	else
		return
	end

	self:UpdateDisplay()
end

local function GetBarAnchor(frame)
	local x, y = frame:GetCenter()
	if not x or not y then return "TOPLEFT", "BOTTOMLEFT"; end
	local cx, cy = UIParent:GetWidth() / 3, UIParent:GetHeight() / 2

	if x < cx then
		if y < cy then return "BOTTOMLEFT", "TOPLEFT"
		else return "TOPLEFT", "BOTTOMLEFT"; end
	elseif x > 2 * cx then
		if y < cy then return "BOTTOMRIGHT", "TOPRIGHT"
		else return "TOPRIGHT", "BOTTOMRIGHT"; end
	else
		if y < cy then return "BOTTOM", "TOP"
		else return "TOP", "BOTTOM"; end
	end
end

function dataobj:OnEnter()
	if dewdrop:IsOpen(this) then return; end

	if not tablet:IsRegistered(self) then
		tablet:Register(self,
		    'children', function()
				gfxToggle:ShowTooltip()
			end,
			'point', GetBarAnchor,
			'clickable', true,
			'dontHook', true
		)
	end

	tooltip = self
	tablet:Open(self)
end

function dataobj:OnClick(button)
	tablet:Close()

	if button == "LeftButton" then
		if dewdrop:IsOpen(self) then dewdrop:Close(); end
		if IsShiftKeyDown() then gfxToggle:AutoKey() else gfxToggle:Toggle() end
	elseif button == "RightButton" then
		if not dewdrop:IsRegistered(self) then
			dewdrop:Register(self,
				'children', options,
				'point', GetBarAnchor,
				'dontHook', true
			)
		end

		if dewdrop:IsOpen(self) then
			dewdrop:Close()
		elseif self:IsShown() then
			dewdrop:Open(self)
		end
	end
end

function gfxToggle:UpdateDisplay()
	local auto

	if self.data.autoMode then
		auto = self.data.shortText and L["AUTO_MODE"] or L["Auto"]
	else
		auto = self.data.shortText and L["MANUAL_MODE"] or L["Manual"]
	end

	local text

	if defaultSets[self.data.currentSet] then
		text = self.data.currentSet
	else
		text = "Normal"
	end

	text = GetDisplayName(text, self.data.shortText)

	dataobj.text = string.format("%s"..WHITE.." - %s|r", text, auto)
end

function gfxToggle:ShowTooltip()
	local z = GetZoneText()
	local dbl, dbm = {}, {}
	local settings, r,g,b, fl, fm

	if next(self.data.zones) then
		for k, v in pairs(self.data.zones) do
			if strlower(k) == strlower(z) then
				if v == "Low" then
					fl = true
				elseif v == "Medium" then
					fm = true
				end
			end

			if v == "Low" then
				tinsert(dbl, Capitalize(k))
			else
				tinsert(dbm, Capitalize(k))
			end
		end

		if fl then
			settings = L["Low"]
			r,g,b = 0,1,0
		elseif fm then
			settings = L["Medium"]
			r,g,b = 1,1,0
		else
			settings = L["High"]
			r,g,b = 1,0,0
		end
	end

	tablet:SetTitle("gfxToggle2")

	local cat = tablet:AddCategory(
		'text', L["Toggle graphics"],
		'columns', 1
	)

	cat:AddLine(
		'text', L["Low"],
		'textR', 0,
		'textG', 1,
		'textB', 0,
		'func', function() self:LoadSet("Low", true) end
	)

	cat:AddLine(
		'text', L["Medium"],
		'textR', 1,
		'textG', 1,
		'textB', 0,
		'func', function() self:LoadSet("Medium", true) end
	)

	cat:AddLine(
		'text', L["High"],
		'textR', 1,
		'textG', 0,
		'textB', 0,
		'func', function() self:LoadSet("High", true) end
	)

	cat = tablet:AddCategory(
		'text', L["Current zone:"],
		'columns', 2,
		'child_textR', 1,
		'child_textG', 1,
		'child_textB', 0
	)

	cat:AddLine(
		'text', z,
		'text2', settings,
		'text2R', r,
		'text2G', g,
		'text2B', b,
		'func', function()
			if IsShiftKeyDown() then
				self:Add("this", "med")
			else
				self:Add("this", "low")
			end
		end
	)

	if next(dbl) ~= nil then
		table.sort(dbl)

		cat = tablet:AddCategory(
			'text', L["Zones: "]..GetDisplayName("Low"),
			'child_textR',1,
			'child_textG',1,
			'child_textB',0
		)

		for k, v in ipairs(dbl) do
			cat:AddLine(
				'text', v,
				'func', 'Del',
				'arg1', self,
				'arg2', v
			)
		end
	end

	if next(dbm) ~= nil then
		table.sort(dbm)

		cat = tablet:AddCategory(
			'text', L["Zones: "]..GetDisplayName("Medium"),
			'child_textR',1,
			'child_textG',1,
			'child_textB',0
		)

		for k, v in ipairs(dbm) do
			cat:AddLine(
				'text', v,
				'func', 'Del',
				'arg1', self,
				'arg2', v
			)
		end
	end

	tablet:SetHint(L["Left-click on current zone adds zone to 'Low', Shift + left-click adds zone to 'Medium'. " ..
		"Left-click on 'Low' or 'Medium' zone deletes it."])
end

function gfxToggle:PrintMessage(msg)
	if not self.data.mute then
		self:CustomPrint(1, 1, 1, UIErrorsFrame, nil, " ", msg)
	end
end
