--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]


local ix = ix
local string = string
local os = os
local CurTime = CurTime
local IsValid = IsValid
local netstream = netstream

local PLUGIN = PLUGIN

util.AddNetworkString("changeLockAccess")
util.AddNetworkString("changeLockAccessCmru")

ix.log.AddType("creditIDCardSetCredits", function(client, itemId, amount)
	return string.format("%s a défini les crédits le crédits de #%d à %d.", client:SteamName(), itemId, amount)
end)

-- CID GENERATION
local prime = 99787 -- prime % 4 = 3! DO NOT CHANGE EVER
local offset = 320 -- slightly larger than sqrt(prime) is ok. DO NOT CHANGE EVER
local block = 1000
function PLUGIN:GenerateCid(id)
	id = (id + offset) % prime

	local cid = 0

	for _ = 1, math.floor(id/block) do
		cid = (cid + (id * block) % prime) % prime
	end

	cid = (cid + (id * (id % block) % prime)) % prime

	if (2 * id < prime) then
		return Schema:ZeroNumber(cid, 5)
	else
		return Schema:ZeroNumber(prime - cid, 5)
	end
end

ix.log.AddType("creditTransaction", function(client, senderCard, receiverCard, amount, posDevice, reason, senderCID, receiverCID)
	local senderCardCid
	local receiverCardCid

	if !senderCID then
		senderCardCid = senderCard:GetData("cid", "INVALIDE")
	else
		senderCardCid = (tostring(senderCard:GetData("cid", "00000")) == "00000" and senderCID or senderCard:GetData("cid", "INVALIDE"))
	end

	if !receiverCID then
		receiverCardCid = receiverCard:GetData("cid", "INVALIDE")
	else
		receiverCardCid = (tostring(receiverCard:GetData("cid", "00000") ) == "00000" and receiverCID or receiverCard:GetData("cid", "INVALIDE"))
	end

	local senderName = senderCard:GetData("name", "INCONNU")

	if (IsValid(client) and client:HasActiveCombineMask()) then
		senderName = "IDENTITÉ CONFIDENTIELLE"
		senderCardCid = "CONFIDENTIELLE"
	end

	local insert = mysql:Insert("ix_cid_transactions")
		insert:Insert("datetime", os.time())
		insert:Insert("sender_name", senderName)
		insert:Insert("sender_cid", senderCardCid)
		insert:Insert("sender_cardid", senderCard:GetID())
		insert:Insert("receiver_name", receiverCard:GetData("name", "INCONNU"))
		insert:Insert("receiver_cid", receiverCardCid)
		insert:Insert("receiver_cardid", receiverCard:GetID())
		insert:Insert("amount", amount)
		if (reason and reason != "") then
			insert:Insert("reason", string.len(reason) > 250 and string.sub(reason, 1, 250) or reason)
		else
			insert:Insert("reason", "aucune raison donnée")
		end

		if (posDevice) then
			insert:Insert("pos", posDevice:GetID())
			insert:Insert("read", 0)
			insert:Callback(function(result, _, insertID)
				posDevice:AddTransactionID(insertID)
			end)
		else
			insert:Insert("pos", 0)
			insert:Insert("read", 1)
		end
	insert:Execute()

	return string.format("%s (CID : %s - Objet : %s) a envoyé %d crédits à %s (CID : %s - Objet : %s) via un %s.",
		senderCard:GetData("name"),
		senderCardCid,
		senderCard:GetID(),
		amount,
		receiverCard:GetData("name"),
		receiverCardCid,
		receiverCard:GetID(),
		posDevice and string.format("Terminal POS pour '%s'", reason) or "Terminal"
	)
end)

function PLUGIN:DatabaseConnected()
	local query = mysql:Create("ix_cid_transactions")
		query:Create("id", "INT UNSIGNED NOT NULL AUTO_INCREMENT")
		query:Create("datetime", "INT NOT NULL")
		query:Create("sender_name", "VARCHAR(255) NOT NULL")
		query:Create("sender_cid", "VARCHAR(255) NOT NULL")
		query:Create("sender_cardid", "INT NOT NULL")
		query:Create("receiver_name", "VARCHAR(255) NOT NULL")
		query:Create("receiver_cid", "VARCHAR(255) NOT NULL")
		query:Create("receiver_cardid", "INT NOT NULL")
		query:Create("amount", "INT NOT NULL")
		query:Create("pos", "INT NOT NULL")
		query:Create("read", "TINYINT(1) NOT NULL")
		query:Create("reason", "VARCHAR(255) DEFAULT NULL")
		query:PrimaryKey("id")
	query:Execute()
end

function PLUGIN:SelectTransactions(client, key, value, maxAge, bNoIncludeRead, customCallback)
	if (client.ixNextTransactionSelect and client.ixNextTransactionSelect > CurTime()) then
		client.ixNextTransactionSelect = CurTime() + 1
		return
	end
	client.ixNextTransactionSelect = CurTime() + 1

	maxAge = maxAge or 7
	local query = mysql:Select("ix_cid_transactions")
		if (key == "cid") then
			query.whereList[#query.whereList + 1] = "(`sender_cid` = '"..query:Escape(value).."' OR `receiver_cid` = '"..query:Escape(value).."')"
		else
			query:Where(key, value)
		end
		query:WhereGTE("datetime", os.time() - maxAge * 24 * 3600)
		if (bNoIncludeRead) then
			query:Where("read", 0)
		end
		query:Select("id")
		query:Select("datetime")
		query:Select("sender_name")
		query:Select("sender_cid")
		query:Select("receiver_name")
		query:Select("receiver_cid")
		query:Select("amount")
		query:Select("reason")
		if (key == "pos") then
			query:Select("pos")
			query:Select("read")
		end
		query:Callback(function(result)
			if (!IsValid(client)) then return end
			if customCallback then
				customCallback(result)
				return
			end

			netstream.Start(client, "ixCreditTransactionLog", result, key == "pos" and !bNoIncludeRead)
		end)
		query:OrderByDesc("datetime")
	query:Execute()
end

function PLUGIN:MarkTransactionsRead(id, bAllFromPos, bRead, pos)
	local query = mysql:Update("ix_cid_transactions")
		if (bAllFromPos) then
			query:Where("pos", id)
		else
			query:Where("pos", pos)
			query:Where("id", id)
		end
		query:Update("read", bRead and 1 or 0)
	query:Execute()
end

netstream.Hook("ixTransactionSetRead", function(client, id, bAllFromPos, bRead, pos)
	local inventory = client:GetCharacter():GetInventory()
	local item = inventory:GetItemByID(pos or id)
	if (!item or (item:GetData("cardIDLock") and !inventory:GetItemByID(item:GetData("cardID")))) then
		return
	end

	PLUGIN:MarkTransactionsRead(id, bAllFromPos, bRead, pos)
end)

function PLUGIN:CreateIDCard(character, inventory, credits)
	local genericdata = character:GetGenericdata()
	local age, height, eyeColor = character:GetAge(), character:GetHeight(), character:GetEyeColor()
	local data = {
		owner = character:GetID(),
		cid = character:GetCid(),
		name = character:GetName(),
		geneticDesc = age.." | "..height.." | YEUX "..eyeColor.."",
		active = true,
		credits = credits or 0
	}

	if (character:GetFaction() == FACTION_VORT) then
		data.geneticDesc = age.." | "..height

		if (istable(genericdata)) then
			genericdata.cid = data["cid"]
			character:SetGenericdata(genericdata)
			character:Save()
		end
	end

	local success, error = inventory:Add("id_card", 1, data)

	if (!success) then
		character:GetPlayer():NotifyLocalized(error or "unknownError")
	end
end

-- Generic transaction stuff
local function DoCreditTransaction(recipientCard, sendingCard, transactionAmount, sending, recipient, bPosDevice, reason, senderCid, receiverCid)
	if (!recipientCard or !sendingCard) then
		if (IsValid(sending)) then sending:NotifyLocalized("posError") end
		if (IsValid(recipient)) then recipient:NotifyLocalized("posError") end

		return
	end

	local amount = transactionAmount
	local senderCard = sendingCard
	local receiverCard = recipientCard
	local receiver = recipient
	local sender = sending
	local amountPercent = amount / 100
	local vat = ix.config.Get("transactionVatPercent", 2)
	vat = math.Round(amountPercent * vat)

	if (amount < 0) then
		amount = amount * -1

		senderCard = recipientCard
		receiverCard = sendingCard
		sender = recipient
		receiver = sending
	end

	if (senderCard:HasCredits(amount + vat)) then
		senderCard:TakeCredits(amount + vat)
		ix.city.main:AddCredits(vat)
		receiverCard:GiveCredits(amount)

		if (IsValid(sender)) then
			sender:NotifyLocalized("posTransactionSuccess")

			netstream.Start(sender, "TerminalUpdateCredits", amount)
		end

		if (IsValid(receiver)) then receiver:NotifyLocalized("posTransactionSuccess") end
		ix.log.Add(sending, "creditTransaction", sendingCard, recipientCard, transactionAmount, bPosDevice, reason, senderCid, receiverCid)
	else
		if (IsValid(sender)) then sender:NotifyLocalized("transactionNoMoney") end
		if (IsValid(receiver)) then receiver:NotifyLocalized("transactionNoMoney") end
	end
end

function PLUGIN:CreditTransaction(receiverCardID, senderCardID, amount, sender, receiver, bPosDevice, reason, senderCid, receiverCid)
	senderCid = senderCid or false
	receiverCid = receiverCid or false

	if (ix.config.Get("creditsNoConnection")) then
		if (IsValid(sender)) then sender:NotifyLocalized("errorNoConnection") end
		if (IsValid(receiver)) then receiver:NotifyLocalized("errorNoConnection") end
		return
	end


	if (!receiverCardID or !senderCardID) then
		if (IsValid(sender)) then sender:NotifyLocalized("posError") end
		if (IsValid(receiver)) then receiver:NotifyLocalized("posError") end
		return
	end

	if (!ix.item.instances[receiverCardID]) then
		ix.item.LoadItemByID(receiverCardID, nil, function(receiverCard)
			if (ix.item.instances[senderCardID]) then
				DoCreditTransaction(receiverCard, ix.item.instances[senderCardID], amount, sender, receiver, bPosDevice, reason, senderCid, receiverCid)
			else
				ix.item.LoadItemByID(senderCardID, function(senderCard)
					DoCreditTransaction(receiverCard, senderCard, amount, sender, receiver, bPosDevice, reason, senderCid, receiverCid)
				end)
			end
		end)

		return
	end

	if (!ix.item.instances[senderCardID]) then
		ix.item.LoadItemByID(senderCardID, function(senderCard)
			DoCreditTransaction(ix.item.instances[receiverCardID], ix.item.instances[senderCardID], amount, sender, receiver, bPosDevice, reason, senderCid, receiverCid)
		end)

		return
	end

	DoCreditTransaction(ix.item.instances[receiverCardID], ix.item.instances[senderCardID], amount, sender, receiver, bPosDevice, reason, senderCid, receiverCid)
end

-- CHAR funcs
do
	local CHAR = ix.meta.character

	function CHAR:CreateIDCard(credits)
		PLUGIN:CreateIDCard(self, self:GetInventory(), credits)
	end

	function CHAR:GetCredits()
		local itemID = self:GetIdCard()
		if (!itemID or !ix.item.instances[itemID]) then
			return 0
		end

		return ix.item.instances[itemID]:GetCredits()
	end

	function CHAR:HasCredits(amount)
		local itemID = self:GetIdCard()
		if (!itemID or !ix.item.instances[itemID]) then
			return false
		end

		return amount <= ix.item.instances[itemID]:GetCredits()
	end

	function CHAR:SetCredits(amount)
		local itemID = self:GetIdCard()
		if (!itemID or !ix.item.instances[itemID]) then
			return false
		end

		ix.item.instances[itemID]:SetCredits(amount)

		return true
	end

	function CHAR:GiveCredits(amount, sender, reason)
		local itemID = self:GetIdCard()
		if (!itemID or !ix.item.instances[itemID]) then
			return false
		end

		return ix.item.instances[itemID]:GiveCredits(amount, sender, reason)
	end

	function CHAR:TakeCredits(amount, receiver, reason)
		local itemID = self:GetIdCard()
		if (!itemID or !ix.item.instances[itemID]) then
			return false
		end

		return ix.item.instances[itemID]:TakeCredits(amount, receiver, reason)
	end
end

local playerMeta = FindMetaTable("Player")

function playerMeta:SelectCIDCard(onSuccess, onFail)
	local items = self:GetCharacter():GetInventory():GetItemsByUniqueID("id_card")
	local count = table.Count(items)

	if (count == 1) then
		onSuccess(items[1])
	elseif (count == 0) then
		if (onFail) then
			onFail()
		end
	else
		self.ixSelectCIDSuccess = onSuccess

		if (onFail) then
			self.ixSelectCIDFail = onFail
		end

		netstream.Start(self, "ixSelectCID", isfunction(onFail))
	end
end
