-- This file is holding seamless translation of older versions of data to newer
-- versions of data

TFA.LatestDataVersion = 1

TFA.DataVersionMapping = {
	[0] = {
		{
			old_path = "DrawCrosshairIS",
			new_path = "DrawCrosshairIronSights",
		},

		{
			old_path = "FiresUnderwater",
			new_path = "Primary.FiresUnderwater",
		},

		{
			old_path = "PenetrationMaterials",
			new_path = "Primary.PenetrationMaterials",
		},

		{
			old_path = "MaxPenetrationCounter",
			new_path = "Primary.MaxSurfacePenetrationCount",
		},

		{
			old_path = "MaxPenetration",
			new_path = "Primary.MaxSurfacePenetrationCount",
		},

		{
			old_path = "IronRecoilMultiplier",
			new_path = "Primary.IronRecoilMultiplier",
		},

		{
			old_path = "MoveSpeed",
			new_path = "RegularMoveSpeedMultiplier",
		},

		{
			old_path = "IronSightsMoveSpeed",
			new_path = "AimingDownSightsSpeedMultiplier",
		},

		{
			old_path = "Shotgun",
			new_path = "LoopedReload",
		},

		{
			old_path = "ShellTime",
			new_path = "LoopedReloadInsertTime",
		},

		{
			old_path = "CrouchPos",
			new_path = "CrouchViewModelPosition",
		},

		{
			old_path = "CrouchAng",
			new_path = "CrouchViewModelAngle",
		},

		{
			old_path = "data.ironsights",
			new_path = "Secondary.IronSightsEnabled",
			upgrade = function(value) return value == 1 end,
			downgrade = function(value) return value and 1 or 0 end,
		},

		{
			old_path = "Secondary.IronFOV",
			new_path = "Secondary.OwnerFOV",
		},

		{
			old_path = "IronViewModelFOV",
			new_path = "Secondary.ViewModelFOV",
		},

		{
			old_path = "DoProceduralReload",
			new_path = "IsProceduralReloadBased",
		},

		{
			old_path = "ProceduralReloadEnabled",
			new_path = "IsProceduralReloadBased",
		},

		{
			old_path = "Akimbo",
			new_path = "IsAkimbo",
		},

		{
			old_path = "AkimboHUD",
			new_path = "EnableAkimboHUD",
		},

		{
			old_path = "IronInSound",
			new_path = "Secondary.IronSightsInSound",
		},

		{
			old_path = "IronOutSound",
			new_path = "Secondary.IronSightsOutSound",
		},

		{
			old_path = "DisableChambering",
			new_path = "Primary.DisableChambering",
		},

		{
			old_path = "DisplayFalloff",
			new_path = "Primary.DisplayFalloff",
		},

		{
			old_path = "SpreadBiasYaw",
			new_path = "Primary.SpreadBiasYaw",
		},

		{
			old_path = "SpreadBiasPitch",
			new_path = "Primary.SpreadBiasPitch",
		},

		{
			old_path = "VMPos",
			new_path = "ViewModelPosition",
		},

		{
			old_path = "VMAng",
			new_path = "ViewModelAngle",
		},

		{
			old_path = "VMPos_Additive",
			new_path = "AdditiveViewModelPosition",
		},

		{
			old_path = "RunSightsPos",
			new_path = "SprintViewModelPosition",
		},

		{
			old_path = "RunSightsAng",
			new_path = "SprintViewModelAngle",
		},

		{
			old_path = "IronSightsPos",
			new_path = "IronSightsPosition",
		},

		{
			old_path = "IronSightsAng",
			new_path = "IronSightsAngle",
		},

		{
			old_path = "Bodygroups_V",
			new_path = "ViewModelBodygroups",
		},

		{
			old_path = "Bodygroups_W",
			new_path = "WorldModelBodygroups",
		},

		{
			old_path = "CenteredPos",
			new_path = "CenteredViewModelPosition",
		},

		{
			old_path = "CenteredAng",
			new_path = "CenteredViewModelAngle",
		},

		{
			old_path = "Offset",
			new_path = "WorldModelOffset",
		},

		{
			old_path = "ProceduralHolsterPos",
			new_path = "ProceduralHolsterPosition",
		},

		{
			old_path = "ProceduralHolsterAng",
			new_path = "ProceduralHolsterAngle",
		},

		{
			old_path = "VElements",
			new_path = "ViewModelElements",
		},

		{
			old_path = "WElements",
			new_path = "WorldModelElements",
		},
	}
}

local function identity(...) return ... end

for version = 0, #TFA.DataVersionMapping do
	for i, data in ipairs(TFA.DataVersionMapping[version]) do
		if not isfunction(data.upgrade) then data.upgrade = identity end
		if not isfunction(data.downgrade) then data.downgrade = identity end
	end
end

TFA.PathParseCache = {}
TFA.PathParseCacheTR = {}
TFA.StatPathRemapCache = {}
TFA.PathParseCacheDirect = {}
TFA.PathParseChildren = {}

local PathParseCache = TFA.PathParseCache
local PathParseCacheTR = TFA.PathParseCacheTR
local PathParseCacheDirect = TFA.PathParseCacheDirect
local StatPathRemapCache = TFA.StatPathRemapCache
local PathParseChildren = TFA.PathParseChildren
local string_Explode = string.Explode
local ipairs = ipairs
local pairs = pairs
local string_sub = string.sub
local tonumber = tonumber
local table_Copy = table.Copy
local table_concat = table.concat
local istable = istable
local string_format = string.format

local function doDowngrade(path, migrations)
	for i, data in ipairs(migrations) do
		if data.new_path == path then
			return data.old_path, data.upgrade
		elseif path:StartWith(data.new_path) and path[#data.new_path + 1] == '.' then
			return data.old_path .. path:sub(#data.new_path + 1), data.upgrade
		end
	end

	return path
end

local function doUpgrade(path, migrations)
	for i, data in ipairs(migrations) do
		if data.old_path == path then
			return data.new_path, data.downgrade
		elseif path:StartWith(data.old_path) and path[#data.old_path + 1] == '.' then
			return data.new_path .. path:sub(#data.old_path + 1), data.downgrade
		end
	end

	return path
end

function TFA.RemapStatPath(path, path_version, structure_version)
	local cache_path = path

	if path_version == nil then path_version = 0 end
	if structure_version == nil then structure_version = 0 end

	-- version do not match
	if path_version ~= structure_version then
		cache_path = string_format("%d_%d_%s", path_version, structure_version, path)
	end

	local get_cache = StatPathRemapCache[cache_path]
	if get_cache ~= nil then return get_cache end

	if cache_path ~= path then
		-- downgrade path
		if path_version > structure_version then
			for version = path_version, structure_version, -1 do
				local mapping = TFA.DataVersionMapping[version]

				if istable(mapping) then
					path = doDowngrade(path, mapping)
				end
			end
		else -- upgrade path
			for version = path_version, structure_version do
				local mapping = TFA.DataVersionMapping[version]

				if istable(mapping) then
					path = doUpgrade(path, mapping)
				end
			end
		end
	end

	StatPathRemapCache[cache_path] = path
	return StatPathRemapCache[cache_path]
end

function TFA.GetStatPathChildren(path, path_version, structure_version)
	-- version do not match
	if path_version ~= structure_version then
		path = TFA.RemapStatPath(path, path_version, structure_version)
	end

	if not PathParseChildren[path] then
		TFA.GetStatPath(path, path_version, structure_version)
	end

	return PathParseChildren[path].list
end

local function concat_to(tab, to)
	local str = tab[1]

	for i = 2, to do
		str = str .. '.' .. tab[i]
	end

	return str
end

local function concat_from(tab, from)
	local str = tab[from]

	for i = from + 1, #tab do
		str = str .. '.' .. tab[i]
	end

	return str
end

function TFA.GetStatPath(path, path_version, structure_version, no_translate)
	local cache_path = path

	if path_version == nil then path_version = 0 end
	if structure_version == nil then structure_version = 0 end

	-- version do not match
	if path_version ~= structure_version then
		cache_path = string_format("%d_%d_%s", path_version, structure_version, path)
	end

	local _PathParseCache = no_translate and PathParseCacheTR or PathParseCache
	local get_cache = _PathParseCache[cache_path]
	if get_cache ~= nil then return get_cache[1], get_cache[2], get_cache[3] end

	local fn, fnGet

	if cache_path ~= path then
		-- downgrade
		if path_version > structure_version then
			for version = path_version, structure_version, -1 do
				local mapping = TFA.DataVersionMapping[version]

				if istable(mapping) then
					path, fnGet = doDowngrade(path, mapping)

					if fnGet and fnGet ~= identity then
						if not fn then
							fn = fnGet
						else
							local _fn = fn
							function fn(...) return fnGet(_fn(...)) end
						end
					end
				end
			end
		else -- upgrade
			for version = path_version, structure_version do
				local mapping = TFA.DataVersionMapping[version]

				if istable(mapping) then
					path, fnGet = doUpgrade(path, mapping)

					if fnGet and fnGet ~= identity then
						if not fn then
							fn = fnGet
						else
							local _fn = fn
							function fn(...) return fnGet(_fn(...)) end
						end
					end
				end
			end
		end
	end

	get_cache = string_Explode(".", path, false)

	do
		local children = PathParseChildren[get_cache[1]]

		if not children then
			children = {
				list = {get_cache[1]},
				children = {}
			}

			PathParseChildren[get_cache[1]] = children
		end

		local childrens = {children}

		for i = 2, #get_cache do
			local obj = get_cache[i]
			local path2 = concat_to(get_cache, i)

			for i3 = 1, #childrens do
				local list = childrens[i3].list
				local hit = false

				for i2 = 1, #list do
					if list[i2] == path2 then
						hit = true
						break
					end
				end

				if not hit then
					table.insert(list, path2)
				end
			end

			if not children.children[obj] then
				children.children[obj] = {
					list = {path2},
					children = {}
				}
			end

			if not PathParseChildren[path2] then
				PathParseChildren[path2] = {
					list = {path2},
					children = {}
				}
			end

			children = children.children[obj]
			table.insert(childrens, children)
			table.insert(childrens, PathParseChildren[path2])
		end
	end

	if not no_translate then
		if get_cache[1] == "Primary" then
			get_cache[1] = "Primary_TFA"
		elseif get_cache[1] == "Secondary" then
			get_cache[1] = "Secondary_TFA"
		end
	end

	for k, v in ipairs(get_cache) do
		get_cache[k] = tonumber(v) or v
	end

	_PathParseCache[cache_path] = {get_cache, path, fn or identity}
	return get_cache, path, fn or identity
end

function TFA.GetStatPathRaw(path)
	local get_cache = PathParseCacheDirect[path]
	if get_cache ~= nil then return get_cache end

	local t_stbl = string_Explode(".", path, false)

	for k, v in ipairs(t_stbl) do
		t_stbl[k] = tonumber(v) or v
	end

	PathParseCacheDirect[path] = t_stbl
	return t_stbl
end

local GetStatPathRaw = TFA.GetStatPathRaw

do
	local function get(self, path)
		local value = self[path[1]]

		for i = 2, #path do
			if not istable(value) then return end
			value = value[path[i]]
		end

		return value
	end

	local function set(self, path, val)
		if #path == 1 then
			if self[path[1]] == nil then
				self[path[1]] = val
			end

			return
		end

		local value = self[path[1]]

		if value == nil then
			self[path[1]] = {}
			value = self[path[1]]
		end

		for i = 2, #path - 1 do
			if not istable(value) then return end
			if value[path[i]] == nil then value[path[i]] = {} end
			value = value[path[i]]
		end

		if istable(value) and value[path[#path]] == nil then
			value[path[#path]] = val
		elseif not istable(value) then
			print('[TFA Base] unable to fill gap for older version in meta structure of ' .. table_concat(path, '.'))
		end
	end

	function TFA.FillMissingMetaValues(SWEP)
		for version = TFA.LatestDataVersion, 0, -1 do
			local mapping = TFA.DataVersionMapping[version]

			if istable(mapping) then
				for i, data in ipairs(mapping) do
					local getVal = get(SWEP, GetStatPathRaw(data.new_path))

					if getVal ~= nil then
						set(SWEP, GetStatPathRaw(data.old_path), data.downgrade(getVal))
					end
				end
			end
		end
	end
end

if CLIENT then
	concommand.Add("cl_tfa_data_translatestat", function(ply, cmd, args, argStr)
		if #args <= 0 then
			print("Usage: " .. cmd .. " <stat name>")

			return
		end

		for _, arg in ipairs(args) do
			if string.StartsWith(arg, "SWEP.") or string.StartsWith(arg, "self.") then
				arg = string.sub(arg, 6)
			end

			local _, path, _ = TFA.GetStatPath(arg, 0, TFA.LatestDataVersion)
			if path then
				print("SWEP." .. arg .. " => SWEP." .. path .. " (at data version " .. TFA.LatestDataVersion .. ")")
			else
				print("Unable to lookup updated stat path for " .. arg)
			end
		end
	end)
end
