local registredOpcodes = nil

local ServerPackets = {
	DailyRewardCollectionState = 0xDE,
	OpenRewardWall = 0xE2,
	CloseRewardWall = 0xE3,
	DailyRewardBasic = 0xE4,
	DailyRewardHistory = 0xE5,
	RestingAreaState = 0xA9,
	BestiaryData = 0xd5,
	BestiaryOverview = 0xd6,
	BestiaryMonsterData = 0xd7,
	BestiaryCharmsData = 0xd8,
	BestiaryTracker = 0xd9,
	BestiaryTrackerTab = 0xB9,
	OpenStashSupply = 0x29,
	UpdateLootTracker = 0xCF,
	UpdateTrackerAnalyzer = 0xCC,
	UpdateSupplyTracker = 0xCE,
	KillTracker = 0xD1,
	SpecialContainer = 0x2A,
	isUpdateCoinBalance = 0xF2,
	UpdateCoinBalance = 0xDF,
	PartyAnalyzer = 0x2B,
	GameNews = 0x98,
	ClientCheck = 0x63,
	LootStats = 0xCF,
	LootContainer = 0xC0,
	TournamentLeaderBoard = 0xC5,
	CyclopediaCharacterInfo = 0xDA,
	Tutorial = 0xDC,
	Highscores = 0xB1,
	Inspection = 0x76,
	TeamFinderList = 0x2D,
	TeamFinderLeader = 0x2C
}

-- Server Types
local DAILY_REWARD_TYPE_ITEM = 1
local DAILY_REWARD_TYPE_STORAGE = 2
local DAILY_REWARD_TYPE_PREY_REROLL = 3
local DAILY_REWARD_TYPE_XP_BOOST = 4

-- Client Types
local DAILY_REWARD_SYSTEM_SKIP = 1
local DAILY_REWARD_SYSTEM_TYPE_ONE = 1
local DAILY_REWARD_SYSTEM_TYPE_TWO = 2
local DAILY_REWARD_SYSTEM_TYPE_OTHER = 1
local DAILY_REWARD_SYSTEM_TYPE_PREY_REROLL = 2
local DAILY_REWARD_SYSTEM_TYPE_XP_BOOST = 3

function init()
  connect(g_game, { onEnterGame = registerProtocol,
                    onPendingGame = registerProtocol,
                    onGameEnd = unregisterProtocol })
  if g_game.isOnline() then
    registerProtocol()
  end
end

function terminate()
  disconnect(g_game, { onEnterGame = registerProtocol,
                    onPendingGame = registerProtocol,
                    onGameEnd = unregisterProtocol })
                    
  unregisterProtocol()
end

function registerProtocol()
  if registredOpcodes ~= nil or not g_game.getFeature(GameTibia12Protocol) then
    return
  end
  
  registredOpcodes = {}

  registerOpcode(ServerPackets.TeamFinderLeader, function(protocol, msg)
	local bool = msg:getU8() -- reset
	if bool > 0 then
		return -- Server internal changes
	end

	msg:getU16() -- Min level
	msg:getU16() -- Max level
	msg:getU8() -- Vocation flag
	msg:getU16() -- Slots
	msg:getU16() -- Free slots
	msg:getU32() -- Timestamp
	local type = msg:getU8() -- Team type
	msg:getU16() -- Type flag
	if type == 2 then
		msg:getU16() -- Hunt area
	end

	local size = msg:getU16() -- Members size
	for i = 1, size do
		msg:getU32() -- Character id
		msg:getString() -- Character name
		msg:getU16() -- Character level
		msg:getU8() -- Vocation
		msg:getU8() -- Member type (Leader == 3)
	end
  end)

  registerOpcode(ServerPackets.TeamFinderList, function(protocol, msg)
	msg:getU8()
	local size = msg:getU32() -- List size
	for i = 1, size do
		msg:getU32() -- Leader Id
		msg:addString() -- Leader name
		msg:getU16() -- Min level
		msg:getU16() -- Max level
		msg:getU8() -- Vocations flag
		msg:getU16() -- Slots
		msg:getU16() -- Used slots
		msg:getU32() -- Timestamp
		local type = msg:getU8() -- Team type [1]: Boss, [2]: Hunt and [3]: Quest
		msg:getU16() -- Type flag
		if type == 2 then
			msg:getU16() -- Hunt area
		end
		msg:getU8() -- Player status
	end
  end)

  registerOpcode(ServerPackets.Inspection, function(protocol, msg)
	local bool = msg:getU8() -- IsPlayer
	if g_game.getClientVersion() >= 1230 then
		msg:getU8()
	end
	local size = msg:getU8() -- List
	for i = 1, size do
		if bool > 0 then
			msg:getU8()
		end
		msg:getString() -- Name
		readAddItem(msg)
		local size_2 = msg:getU8() -- Imbuements
		for u = 1, size_2 do
			msg:getU16() -- Imbue
		end
		local size_3 = msg:getU8() -- Details
		for j = 1, size_3 do
			msg:getString() -- Name
			msg:getString() -- Description
		end
	end

	if bool > 0 then
		msg:getString() -- Player name
		local outfit = msg:getU16() -- lookType
		if outfit ~= 0 then
			msg:getU8() -- lookHead
			msg:getU8() -- lookBody
			msg:getU8() -- lookLegs
			msg:getU8() -- lookFeet
			msg:getU8() -- lookAddons
		else
			msg:getU16() -- lookTypeEx
		end
		local size_4 = msg:getU8() -- Detail
		for l = 1, size_4 do
			msg:getString() -- Name
			msg:getString() -- Description
		end
	end
  end)

  registerOpcode(ServerPackets.Highscores, function(protocol, msg)
	msg:getU8()
	local size = msg:getU8() -- Worlds
	for i = 1, size do
		msg:getString() --  World name
	end
	msg:getString() -- Selected world
	local size_2 = msg:getU8() -- Vocations
	for u = 1, size_2 do
		msg:getU32() -- Id
		msg:getString() -- Name
	end
	msg:getU32() -- Vocation selected Id
	local size_3 = msg:getU8() -- Categories
	for j = 1, size_3 do
		msg:getU8() -- Id
		msg:getString() -- Name
	end
	msg:getU8() -- Category selected Id
	msg:getU16() -- Pages
	msg:getU16() -- Selected page
	local size_4 = msg:getU8() -- Entries
	for l = 1, size_4 do
		msg:getU32() -- Rank
		msg:getString() -- Character name
		msg:getString() -- Character title
		msg:getU8() -- Vocation
		msg:getString() -- World
		msg:getU16() -- Level
		msg:getU8() -- Is player? then highlight
		msg:getU64() -- Points
	end
	msg:getU8()
	msg:getU8()
	msg:getU8()
	msg:getU32() -- Last update
  end)

  registerOpcode(ServerPackets.Tutorial, function(protocol, msg)
	msg:getU8() -- Tutorial id
  end)

  registerOpcode(ServerPackets.CyclopediaCharacterInfo, function(protocol, msg)
	local type = msg:getU8()
	if g_game.getClientVersion() >= 1215 then
		local error = msg:getU8()
		if error > 0 then
			-- [1] 'No data available at the moment.'
			-- [2] 'You are not allowed to see this character's data.'
			-- [3] 'You are not allowed to inspect this character.'
		end
	end
	if type == 0 then -- Basic Information
		msg:getString() -- Player name
		msg:getString() -- Vocation
		msg:getU16() -- Level
		local outfit = msg:getU16() -- lookType
		if outfit ~= 0 then
			msg:getU8() -- lookHead
			msg:getU8() -- lookBody
			msg:getU8() -- lookLegs
			msg:getU8() -- lookFeet
			msg:getU8() -- lookAddons
		else
			msg:getU16() -- lookTypeEx
		end
		msg:getU8() -- Hide stamina
		if g_game.getClientVersion() >= 1220 then
			msg:getU8() -- Personal habs
			msg:getString() -- Title
		end
	elseif type == 1 then -- Character Stats
		msg:getU64() -- Experience
		msg:getU16() -- Level
		msg:getU8() -- LevelPercent
		msg:getU16() -- BaseXpGain
		msg:getU32() -- Tournament
		msg:getU16() -- Grinding
		msg:getU16() -- Store XP
		msg:getU16() -- Hunting
		msg:getU16() -- Store XP Time
		msg:getU8() -- Show store XP button (bool)
		msg:getU16() -- Health
		msg:getU16() -- Health max
		msg:getU16() -- Mana
		msg:getU16() -- Mana max
		msg:getU8() -- Soul
		msg:getU16() -- Stamina
		msg:getU16() -- Food
		msg:getU16() -- Offline training
		msg:getU16() -- Speed
		msg:getU16() -- Speed base
		msg:getU32() -- Capacity bonus
		msg:getU32() -- Capacity
		msg:getU32() -- Capacity max
		local size = msg:getU8() -- Skills
		for i = 1, size do
			msg:getU8() -- Skill id
			msg:getU16() -- Skill level
			msg:getU16() -- Base skill
			msg:getU16() -- Base skill
			msg:getU16() -- Skill percent
		end
		if g_game.getClientVersion() < 1215 then
			msg:getU16()
			msg:getString() -- Player name
			msg:getString() -- Vocation
			msg:getU16() -- Level
			local outfit = msg:getU16() -- lookType
			if outfit ~= 0 then
				msg:getU8() -- lookHead
				msg:getU8() -- lookBody
				msg:getU8() -- lookLegs
				msg:getU8() -- lookFeet
				msg:getU8() -- lookAddons
			else
				msg:getU16() -- lookTypeEx
			end
		end
	elseif type == 2 then -- Combat Stats
		msg:getU16() -- Critical chance base
		msg:getU16() -- Critical chance bonus
		msg:getU16() -- Critical damage base
		msg:getU16() -- Critical damage bonus
		msg:getU16() -- Life leech chance base
		msg:getU16() -- Life leech chance bonus
		msg:getU16() -- Life leech amount base
		msg:getU16() -- Life leech amount bonus
		msg:getU16() -- Mana leech chance base
		msg:getU16() -- Mana leech chance bonus
		msg:getU16() -- Mana leech amount base
		msg:getU16() -- Mana leech amount bonus
		msg:getU8() -- Blessing amount
		msg:getU8() -- Blessing max
		msg:getU16() -- Attack
		msg:getU8() -- Attack type
		msg:getU8() -- Convert damage
		msg:getU8() -- Convert damage type
		msg:getU16() -- Armor
		msg:getU16() -- Defense
		local size = msg:getU8() -- Reductions
		for i = 1, size do
			msg:getU8() -- Element
			msg:getU8() -- Percent
		end
	elseif type == 3 then -- Recent Deaths
		msg:getU16() -- Page
		msg:getU16() -- Page max
		local size = msg:getU16()
		for i = 1, size do
			msg:getU32() -- Timestamp
			msg:getString() -- Cause
		end
	elseif type == 4 then -- Recent PvP Kills
		msg:getU16() -- Page
		msg:getU16() -- Page max
		local size = msg:getU16()
		for i = 1, size do
			msg:getU32() -- Timestamp
			msg:getString() -- Description
			msg:getU8() -- Status
		end
	elseif type == 5 then -- Achievements
		msg:getU16() -- Points
		msg:getU16() -- Secret max
		local size = msg:getU16() -- Unlocked
		for i = 1, size do
			msg:getU16() -- Id
			msg:getU32() -- Timestamp
			local size_2 = msg:getU8() -- Is secret
			if size_2 > 0 then
				msg:getString() -- Name
				msg:getString() -- Description
				msg:getU8() -- Grade
			end
		end
	elseif type == 6 then -- Item Summary
		local size = msg:getU16() -- Item list size
		for i = 1, size do
			msg:getU16() -- Item client Id
			msg:getU32() -- Item count
		end
	elseif type == 7 then -- Outfits and Mounts
		local size = msg:getU16() -- Outfit list size
		for i = 1, size do
			msg:getU16() -- Id
			msg:getString() -- Name
			msg:getU8() -- Addon
			msg:getU8() -- Category 0 = Standard, 1 = Quest, 2 = Store
			msg:getU32() -- Is current ? then 1000 or 0
		end
		msg:getU8() -- lookHead
		msg:getU8() -- lookBody
		msg:getU8() -- lookLegs
		msg:getU8() -- lookFeet

		local size_2 = msg:getU16() -- Mount list size
		for u = 1, size_2 do
			msg:getU16() -- Id
			msg:getString() -- Name
			msg:getU8() -- Addon
			msg:getU8() -- Category 0 = Standard, 1 = Quest, 2 = Store
			msg:getU32() -- Is current ? then 1000 or 0
		end
		if g_game.getClientVersion() >= 1260 then
			msg:getU8() -- Mount lookHead
			msg:getU8() -- Mount lookBody
			msg:getU8() -- Mount lookLegs
			msg:getU8() -- Mount lookFeet
		end
	elseif type == 8 then -- Store Summary
		msg:getU32() -- Store XP boost time
		msg:getU32() -- Daily reward XP boost time
		local size = msg:getU8() -- Blessings
		for i = 1, size do
			msg:getString() -- Name
			msg:getU8() -- Amount
		end
		msg:getU8() -- Prey slots
		msg:getU8() -- Prey wildcard
		msg:getU8() -- Instant reward
		msg:getU8() -- Charm expansion
		msg:getU8() -- Hireling
		local size_2 = msg:getU8() -- Hireling jogs
		for u = 1, size_2 do
			msg:getU8() -- Job id
		end
		local size_3 = msg:getU8() -- Hireling outfit
		for j = 1, size_3 do
			msg:getU8() -- Outfit id
		end
		msg:getU16() -- House items
	elseif type == 9 then -- Inspect
		local size = msg:getU8() -- Items
		for i = 1, size do
			msg:getU8() -- Slot index
			msg:getString() -- Item name
			readAddItem(msg)
			local size_2 = msg:getU8() -- Imbuements
			for u = 1, size_2 do
				msg:getU16() -- Imbue
			end
			local size_3 = msg:getU8() -- Detail
			for j = 1, size_3 do
				msg:getString() -- Name
				msg:getString() -- Description
			end
		end
		msg:getString() -- Player name
		local outfit = msg:getU16() -- lookType
		if outfit ~= 0 then
			msg:getU8() -- lookHead
			msg:getU8() -- lookBody
			msg:getU8() -- lookLegs
			msg:getU8() -- lookFeet
			msg:getU8() -- lookAddons
		else
			msg:getU16() -- lookTypeEx
		end
		local size_4 = msg:getU8() -- Player detail
		for k = 1, size_4 do
			msg:getString() -- Name
			msg:getString() -- Description
		end
	elseif type == 10 then -- Badges
		local bool = msg:getU8() -- Show account
		if bool > 0 then
			msg:getU8() -- Is online
			msg:getU8() -- Is premium
			msg:getString() -- Loyality title
			local size = msg:getU8() -- Badges
			for i = 1, size do
				msg:getU32() -- Id
				msg:getString() -- Name
			end
		end
	elseif type == 11 then -- Titles
		msg:getU8() -- Title
		local size = msg:getU8() -- Titles
		for i = 1, size do
			msg:getU8() -- Id
			msg:getString() -- Name
			msg:getString() -- Description
			msg:getU8() -- Permanent
			msg:getU8() -- Unlocked
		end
	end
  end)

  registerOpcode(ServerPackets.TournamentLeaderBoard, function(protocol, msg)
	msg:getU16()
	local capacity = msg:getU8() -- Worlds
	for i = 1, capacity do
		msg:getString() -- World name
	end

	msg:getString() -- World selected
	msg:getU16() -- Refresh rate
	msg:getU16() -- Current page
	msg:getU16() -- Total pages
	local size = msg:getU8() -- Players on page
	for u = 1, size do
		msg:getU32() -- Rank
		msg:getU32() -- Previous rank
		msg:getString() -- Name
		msg:getU8() -- Vocation
		msg:getU64() -- Points
		msg:getU8() -- Rank chance direction (arrow0
		msg:getU8() -- Rank chance bool
	end
	msg:getU8()
	msg:getString() -- Rewards
  end)

  registerOpcode(ServerPackets.LootContainer, function(protocol, msg)
	msg:getU8() -- Fallback
	local size = msg:getU8() -- Quickloot size
	for i = 1, size do
		msg:getU8() -- Category Id
		msg:getU16() -- Client Id
	end
  end)

  registerOpcode(ServerPackets.LootStats, function(protocol, msg)
	readAddItem(msg)
	msg:getString() -- Item name
  end)

  registerOpcode(ServerPackets.ClientCheck, function(protocol, msg)
	local size = msg:getU32() -- Data size
	for i = 1, size do
		msg:getU8() -- Data
	end
  end)

  registerOpcode(ServerPackets.GameNews, function(protocol, msg)
	msg:getU32() -- Category
	msg:getU8() -- Page
  end)

  registerOpcode(ServerPackets.PartyAnalyzer, function(protocol, msg)
	msg:getU32() -- Timestamp
	msg:getU32() -- Party leader id
	msg:getU8() -- Price type (client/market)
	local size = msg:getU8() -- Party size
	for i = 1, size do
		msg:getU32() -- Player ID
		msg:getU8() -- (Highlight text bool)

		msg:getU64() -- Loot count
		msg:getU64() -- Supply count
		msg:getU64() -- Impact count
		msg:getU64() -- Heal count
	end

	msg:getU8()
	local size_2 = msg:getU8() -- Size
	for u = 1, size_2 do
		msg:getU32() -- Player ID
		msg:getString() -- Player name
	end
  end)

  registerOpcode(ServerPackets.UpdateCoinBalance, function(protocol, msg)
	msg:getU8() -- Is updating
	msg:getU32() -- Normal coin
	msg:getU32() -- Transferable coin
	if g_game.getClientVersion() >= 1220 then
		msg:getU32() -- Reserved auction coin
		msg:getU32() -- Tournament coin
	end
  end)

  registerOpcode(ServerPackets.isUpdateCoinBalance, function(protocol, msg)
	msg:getU8() -- Is updating
  end)

  registerOpcode(ServerPackets.SpecialContainer, function(protocol, msg)
	local supplyStashMenu = msg:getU8() -- ('Stow item', 'Stow container' ...)
	local marketMenu = msg:getU8() -- ('Show in market')
  end)

  registerOpcode(ServerPackets.KillTracker, function(protocol, msg)
	msg:getString() -- Name
	msg:getU16() -- lookType
	msg:getU8() -- lookHead
	msg:getU8() -- lookBody
	msg:getU8() -- lookLegs
	msg:getU8() -- lookFeet
	msg:getU8() -- lookAddons
	local size = msg:getU8() -- Corpse size
	if size > 0 then
		for i = 1, size do
			readAddItem(msg)
		end
	end
  end)

  registerOpcode(ServerPackets.UpdateSupplyTracker, function(protocol, msg)
	msg:getU16() -- Item client ID
  end)

  registerOpcode(ServerPackets.UpdateTrackerAnalyzer, function(protocol, msg)
	local type = msg:getU8()
	msg:getU32() -- Amount
	if type > 0 then -- ANALYZER_DAMAGE_DEALT
		msg:getU8() -- Element
		if type > 1 then -- 
			msg:getString() -- Target
		end
	end
  end)
  
  registerOpcode(ServerPackets.UpdateLootTracker, function(protocol, msg)
	readAddItem(msg)
	msg:getString() -- Item name
  end)
  
  registerOpcode(ServerPackets.OpenStashSupply, function(protocol, msg)
    local count = msg:getU16() -- List size
    for i = 1, count do
      msg:getU16() -- Item client ID
      msg:getU32() -- Item count
    end

	msg:getU16() -- Stash size left (total - used)
  end)

  registerOpcode(ServerPackets.OpenRewardWall, function(protocol, msg)
    msg:getU8()
    msg:getU32()
    msg:getU8()
    local taken = msg:getU8()
    if taken > 0 then
      msg:getString()
    end
    msg:getU32()
    msg:getU16()
    msg:getU16()
  end)

  registerOpcode(ServerPackets.CloseRewardWall, function(protocol, msg)

  end)

  registerOpcode(ServerPackets.DailyRewardBasic, function(protocol, msg)
    local count = msg:getU8()
    for i = 1, count do
      readDailyReward(msg)
      readDailyReward(msg)
    end
    local maxBonus = msg:getU8()
    for i = 1, maxBonus do
      msg:getString()
      msg:getU8()
    end
    msg:getU8()
  end)

  registerOpcode(ServerPackets.DailyRewardHistory, function(protocol, msg)
    local count = msg:getU8()
    for i=1,count do
      msg:getU32()
      msg:getU8()
      msg:getString()
      msg:getU16()
    end
  end)
  
  registerOpcode(ServerPackets.BestiaryTrackerTab, function(protocol, msg)
    local count = msg:getU8()
    for i = 1, count do
      msg:getU16()
      msg:getU32()
      msg:getU16()
      msg:getU16()
      msg:getU16()
      msg:getU8()
    end
  end)
  
  
end

function readAddItem(msg)
	msg:getU16() -- Item client ID
 
	if g_game.getClientVersion() < 1150 then
		msg:getU8() -- Unmarked
	end

	local var = msg:getU8()
	if g_game.getClientVersion() > 1150 then
		if var == 1 then
			msg:getU32() -- Loot flag
		end

		if g_game.getClientVersion() >= 1260 then
			local isQuiver = msg:getU8()
			if isQuiver == 1 then
				msg:getU32() -- Quiver count
			end
		end
	else
		msg:getU8()
	end
end

function unregisterProtocol()
  if registredOpcodes == nil then
    return
  end
  for _, opcode in ipairs(registredOpcodes) do
    ProtocolGame.unregisterOpcode(opcode)
  end
  registredOpcodes = nil
end

function registerOpcode(code, func)
  if registredOpcodes[code] ~= nil then
    error("Duplicated registed opcode: " .. code)
  end
  registredOpcodes[code] = func
  ProtocolGame.registerOpcode(code, func)
end

function readDailyReward(msg)
	local systemType = msg:getU8()
	if (systemType == 1) then
    msg:getU8()
    local count = msg:getU8()
    for i = 1, count do
      msg:getU16()
      msg:getString()				
      msg:getU32()
    end
	elseif (systemType == 2) then
    msg:getU8()
    local type = msg:getU8()
    
		if (type == DAILY_REWARD_SYSTEM_TYPE_PREY_REROLL) then
      msg:getU8()
		elseif (type == DAILY_REWARD_SYSTEM_TYPE_XP_BOOST) then
      msg:getU16()
		end
	end
end
