--[[
	Mortha - sklep za punkty w oknie gry.

	Zadnych komend na czacie i zadnych komunikatow w konsoli: okno rozmawia
	z serwerem rozszerzonym opcodem OTClienta 0x64 (obsluga po stronie serwera:
	data/creaturescripts/scripts/mortha_shop.lua).

		wysylamy   list          poproszenie o saldo i liste ofert
		           buy:<nr>      kupno oferty
		dostajemy  points:<N>
		           offers:<nr>;<itemid>;<ile>;<koszt>;<nazwa>|...
		           ok:<nr>;<saldo>
		           err:<kod>

	Modul NIE ma wlasnej listy ofert - jedynym zrodlem jest serwer, ktory czyta
	ja z tego samego miejsca co strona WWW.

	Dlaczego lista powstaje dopiero po odpowiedzi serwera (a nie w init):
	widget Item przyjmuje ID KLIENTA i przy ustawianiu sprawdza je w Tibia.dat
	(otclient-src/src/client/item.cpp, Item::setId -> g_things.isValidDatId).
	Tibia.dat wczytuje modul game_things dopiero na onClientVersionChange,
	czyli przy logowaniu (client_entergame/entergame.lua). Poprzednia wersja
	budowala wiersze juz w init(), wiec kazde ID bylo wtedy "nieprawidlowe",
	ladowalo zero do m_clientId i Item::draw wychodzil od razu - stad brak
	ikonek. Teraz wiersze powstaja w grze, kiedy dat jest juz wczytany.
]]

local OPCODE = 0x64

local STATUS_CZAS = 6000       -- ms, po tylu znika komunikat w oknie
local KOLOR_OK = '#78c078'
local KOLOR_BLAD = '#d47f7f'
local KOLOR_ZWYKLY = '#9aa4b6'

morthashopWindow = nil
morthashopButton = nil

local offers = {}
local points = nil             -- nil = jeszcze nie wiemy
local confirmBox = nil
local statusEvent = nil

local BLEDY = {
	nopoints    = 'Za malo punktow na to zamowienie.',
	noroom      = 'Brak miejsca w plecaku - punkty wrocily na konto.',
	badoffer    = 'Tej oferty juz nie ma. Lista zostala odswiezona.',
	unavailable = 'Sklep jest chwilowo niedostepny. Sprobuj za chwile.',
}

-- ---------------------------------------------------------------- pomocnicze

local function setStatus(text, color)
	if not morthashopWindow then return end

	local label = morthashopWindow:recursiveGetChildById('statusLabel')
	if not label then return end

	label:setText(text or '')
	label:setColor(color or KOLOR_ZWYKLY)

	if statusEvent then
		removeEvent(statusEvent)
		statusEvent = nil
	end
	if text and text ~= '' then
		statusEvent = scheduleEvent(function()
			statusEvent = nil
			if morthashopWindow then
				local l = morthashopWindow:recursiveGetChildById('statusLabel')
				if l then l:setText('') end
			end
		end, STATUS_CZAS)
	end
end

local function send(text)
	local protocol = g_game.getProtocolGame()
	if not protocol then
		return false
	end
	protocol:sendExtendedOpcode(OPCODE, text)
	return true
end

local function findOffer(nr)
	for _, offer in ipairs(offers) do
		if offer.nr == nr then
			return offer
		end
	end
	return nil
end

-- --------------------------------------------------------------------- widok

local function updatePoints()
	if not morthashopWindow then return end

	-- Uwaga: getChildById patrzy TYLKO na bezposrednie dzieci, a etykieta
	-- siedzi w pasku pointsBar. Kiedys wywalalo to caly modul (razem z ikona
	-- w gornym menu), dlatego wszedzie recursiveGetChildById.
	local label = morthashopWindow:recursiveGetChildById('pointsLabel')
	if label then
		label:setText(points and tostring(points) or '?')
	end

	local list = morthashopWindow:recursiveGetChildById('offerList')
	if not list then return end

	for _, row in ipairs(list:getChildren()) do
		local cost = row.offerCost or 0
		-- Dopoki nie znamy salda, nie blokujemy niczego - inaczej okno przez
		-- moment wygladaloby, jakby gracza nie bylo stac na nic.
		row:setEnabled(points == nil or points >= cost)
	end
end

local function rebuildList()
	if not morthashopWindow then return end

	local list = morthashopWindow:recursiveGetChildById('offerList')
	if not list then return end

	list:destroyChildren()

	for _, offer in ipairs(offers) do
		local row = g_ui.createWidget('MorthaShopOffer', list)
		row:setId('offer' .. offer.nr)
		row.offerCost = offer.cost
		row.offerNr = offer.nr

		local box = row:getChildById('itemBox')
		box:setItemId(offer.item)
		-- Kolejnosc ma znaczenie: setItemCount dziala dopiero, gdy przedmiot
		-- juz istnieje. Liczba rysuje sie na ikonie tylko dla stackowalnych.
		box:setItemCount(offer.count)

		local name = offer.name
		if offer.count > 1 then
			name = offer.count .. 'x ' .. name
		end
		row:getChildById('offerName'):setText(name)
		row:getChildById('offerCost'):setText(offer.cost .. ' pkt')

		row.onClick = function()
			buy(offer.nr)
		end
	end

	updatePoints()
end

-- -------------------------------------------------------------------- zakupy

function buy(nr)
	local offer = findOffer(nr)
	if not offer or confirmBox then
		return
	end

	local close = function()
		if confirmBox then
			confirmBox:destroy()
			confirmBox = nil
		end
	end

	-- UIMessageBox NIE zamyka sie sam po klknieciu - trzeba go zniszczyc
	-- recznie. tr() sam wola string.format, wiec argumenty ida DO tr, a nie
	-- do osobnego string.format wokol niego (wczesniej to wywalalo blad
	-- "bad argument #2 to 'format'" i zakup nigdy nie dochodzil do skutku).
	confirmBox = displayGeneralBox(tr('Potwierdz zakup'),
		tr('Kupic %s za %d pkt?', offer.name, offer.cost), {
		{ text = tr('Kup'), callback = function()
			close()
			if not send('buy:' .. nr) then
				setStatus(tr('Brak polaczenia z serwerem.'), KOLOR_BLAD)
			end
		end },
		{ text = tr('Anuluj'), callback = close },
	}, nil, close)
end

function refresh()
	if not g_game.isOnline() then
		return
	end
	if not send('list') then
		setStatus(tr('Brak polaczenia z serwerem.'), KOLOR_BLAD)
		return
	end

	setStatus(tr('Pobieram oferty...'))
	-- Gdy serwer nie odpowiada (np. nie ma jeszcze wgranej obslugi sklepu),
	-- puste okno niczego nie tlumaczy - wtedy mowimy o tym wprost.
	scheduleEvent(function()
		if #offers == 0 then
			setStatus(tr('Serwer nie odeslal ofert. Sklep moze byc niedostepny.'), KOLOR_BLAD)
		end
	end, 4000)
end

-- ------------------------------------------------------------------ protokol

local function parseOffers(data)
	local list = {}
	for part in data:gmatch('[^|]+') do
		local nr, item, count, cost, name = part:match('^(%d+);(%d+);(%d+);(%d+);(.*)$')
		if nr then
			table.insert(list, {
				nr = tonumber(nr),
				item = tonumber(item),
				count = tonumber(count),
				cost = tonumber(cost),
				name = name,
			})
		end
	end
	return list
end

local function onExtendedOpcode(protocol, opcode, buffer)
	if buffer:sub(1, 7) == 'points:' then
		points = tonumber(buffer:sub(8)) or 0
		updatePoints()

	elseif buffer:sub(1, 7) == 'offers:' then
		offers = parseOffers(buffer:sub(8))
		rebuildList()
		setStatus('')

	elseif buffer:sub(1, 3) == 'ok:' then
		local nr, saldo = buffer:sub(4):match('^(%d+);(%-?%d+)$')
		if nr then
			local offer = findOffer(tonumber(nr))
			points = tonumber(saldo)
			updatePoints()
			setStatus(tr('Kupiono: %s. Przedmiot jest w plecaku.',
				offer and offer.name or tr('przedmiot')), KOLOR_OK)
		end

	elseif buffer:sub(1, 4) == 'err:' then
		local kod = buffer:sub(5)
		setStatus(tr(BLEDY[kod] or 'Zakup sie nie udal.'), KOLOR_BLAD)
	end
end

-- --------------------------------------------------------------------- modul

function toggle()
	if not morthashopWindow then return end

	if morthashopWindow:isVisible() then
		morthashopWindow:hide()
		morthashopButton:setOn(false)
	else
		morthashopWindow:show()
		morthashopWindow:raise()
		morthashopWindow:focus()
		morthashopButton:setOn(true)
		setStatus('')
		refresh()
	end
end

local function onGameEnd()
	offers = {}
	points = nil
	if morthashopWindow then
		local list = morthashopWindow:recursiveGetChildById('offerList')
		if list then list:destroyChildren() end
		morthashopWindow:hide()
	end
	if morthashopButton then
		morthashopButton:setOn(false)
	end
end

function init()
	morthashopWindow = g_ui.displayUI('morthashop')
	morthashopWindow:hide()

	ProtocolGame.registerExtendedOpcode(OPCODE, onExtendedOpcode)
	connect(g_game, { onGameEnd = onGameEnd })

	morthashopButton = modules.client_topmenu.addRightGameToggleButton(
		'morthashopButton', tr('Sklep'), '/images/topbuttons/morthashop', toggle)
end

function terminate()
	ProtocolGame.unregisterExtendedOpcode(OPCODE)
	disconnect(g_game, { onGameEnd = onGameEnd })

	if statusEvent then
		removeEvent(statusEvent)
		statusEvent = nil
	end
	if confirmBox then
		confirmBox:destroy()
		confirmBox = nil
	end
	if morthashopButton then morthashopButton:destroy() end
	if morthashopWindow then morthashopWindow:destroy() end

	morthashopButton = nil
	morthashopWindow = nil
end
