--[[
| 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 PLUGIN = PLUGIN
PLUGIN.apartments = PLUGIN.apartments or {}

-- Database formalia
function PLUGIN:DatabaseConnected()
    local query = mysql:Create("ix_apartments_"..game.GetMap())
    query:Create("app_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
    query:Create("app_name", "TEXT")
	query:Create("app_doors", "TEXT")
    query:Create("app_employees", "TEXT")
    query:Create("app_permits", "TEXT")
    query:Create("app_tenants", "TEXT")
    query:Create("app_lastactions", "TEXT")
	query:Create("app_rent", "TEXT")
	query:Create("app_payments", "TEXT")
	query:Create("app_type", "TEXT")
	query:Create("app_noassign", "TEXT")
	query:Create("app_rentdue", "TEXT")
    query:PrimaryKey("app_id")
	query:Callback(function()
		local appsQuery = mysql:Select("ix_apartments_"..game.GetMap())
		appsQuery:Callback(function(appsResult)
			if (!istable(appsResult) or #appsResult == 0) then
				return
			end

			for _, v in pairs(appsResult) do
				local doorsCreationID = util.JSONToTable(v["app_doors"])
				local doors = {}
				for _, v1 in pairs(doorsCreationID) do
					local ent = ents.GetMapCreatedEntity(v1)
					if (!ent) then continue end
					doors[#doors + 1] = ent:EntIndex()

					if (!ix.config.Get("shouldLockDoorsAfterRestart", true) or self.lockedApps) then continue end
					ent:Fire("Lock")
					local partner = ent:GetDoorPartner()
					if (IsValid(partner)) then
						partner:Fire("lock")
					end
				end

				self.apartments[tonumber(v["app_id"])] = {
					name = v["app_name"],
					doors = doors,
					employees = util.JSONToTable(v["app_employees"]),
					permits = util.JSONToTable(v["app_permits"]),
					tenants = util.JSONToTable(v["app_tenants"]),
					lastActions = util.JSONToTable(v["app_lastactions"]),
					rent = v["app_rent"],
					payments = util.JSONToTable(v["app_payments"]),
					type = v["app_type"],
					noAssign = v["app_noassign"],
					rentDue = v["app_rentdue"]
				}

				local employeeList = {}
				for cid, tEmployee in pairs(self.apartments[tonumber(v["app_id"])].employees) do
					employeeList[Schema:ZeroNumber(cid, 5)] = tEmployee
				end

				self.apartments[tonumber(v["app_id"])].employees = employeeList

				local tenantList = {}
				for cid, tTenant in pairs(self.apartments[tonumber(v["app_id"])].tenants) do
					tenantList[Schema:ZeroNumber(cid, 5)] = tTenant
				end

				self.apartments[tonumber(v["app_id"])].tenants = tenantList

				local lastActionList = {}
				for cid, osTime in pairs(self.apartments[tonumber(v["app_id"])].lastActions) do
					lastActionList[Schema:ZeroNumber(cid, 5)] = osTime
				end

				self.apartments[tonumber(v["app_id"])].lastActions = lastActionList

				local paymentList = {}
				for cid, tPaymentInfo in pairs(self.apartments[tonumber(v["app_id"])].payments) do
					paymentList[Schema:ZeroNumber(cid, 5)] = tPaymentInfo
				end

				self.apartments[tonumber(v["app_id"])].payments = paymentList
			end

			self.lockedApps = true
		end)

		appsQuery:Execute()
	end)
    query:Execute()
end

-- A function to create an apartment
function PLUGIN:CreateApartment(client, appName, bNoNotify, sType, callback)
	if self:CheckIfApartmentExists(client, appName) then
		client:NotifyLocalized("Un appartement avec ce nom existe déjà...")
		return false
	end
	local permitsToAssign = {}
	for k, _ in pairs(ix.permits.get()) do
		permitsToAssign[k] = false
	end
    local queryAdd = mysql:Insert("ix_apartments_"..game.GetMap())
		queryAdd:Insert("app_name", appName)
		queryAdd:Insert("app_doors", util.TableToJSON({}))
		queryAdd:Insert("app_employees", util.TableToJSON({}))
		queryAdd:Insert("app_permits", util.TableToJSON(permitsToAssign))
		queryAdd:Insert("app_tenants", util.TableToJSON({}))
		queryAdd:Insert("app_lastactions", util.TableToJSON({}))
		queryAdd:Insert("app_rent", 0)
		queryAdd:Insert("app_payments", util.TableToJSON({}))
		queryAdd:Insert("app_type", sType)
		queryAdd:Insert("app_noassign", string.lower(sType) == "shop" and true or false)
		queryAdd:Insert("app_rentdue", os.time() + (!ix.config.Get("housingTesterMode", false) and 3600 * 24 * 7 * ix.config.Get("housingRentDueInWeeks", 1) or 10))
		queryAdd:Callback(function(result, _, insertID)
			self.apartments[insertID] = {
				name = appName,
				doors = {},
				employees = {},
				permits = permitsToAssign,
				tenants = {},
				lastActions = {},
				rent = 0,
				payments = {},
				type = sType,
				noAssign = string.lower(sType) == "shop" and true or false,
				rentDue = os.time() + (!ix.config.Get("housingTesterMode", false) and 3600 * 24 * 7 * ix.config.Get("housingRentDueInWeeks", 1) or 10)
			}
			if (callback) then
				callback()
			end

			self:SyncApartments(client)
		end)
    queryAdd:Execute()

	if !bNoNotify then
		client:NotifyLocalized("Vous avez ajouté avec succès un "..sType.." avec le nom : "..appName)
	end


end

-- A function to remove an apartment
function PLUGIN:RemoveApartment(client, appName)
	local appID = self:CheckIfApartmentExists(client, appName)
	if appID then
		if self.apartments[appID] then
			for _, doorEntID in pairs(self.apartments[appID].doors) do
				local entDoor = Entity(doorEntID)
				if (IsValid(entDoor) and entDoor:IsDoor()) then
					entDoor:SetNetVar("visible", false)
					entDoor:SetNetVar("name", nil)

					local doorPlugin = ix.plugin.list["doors"]
					if doorPlugin then
						doorPlugin:SaveDoorData()
					end
				end
			end
		end

		self.apartments[appID] = nil

		local queryDelete = mysql:Delete("ix_apartments_"..game.GetMap())
			queryDelete:Where("app_id", appID)
		queryDelete:Execute()

		client:NotifyLocalized("L'appartement a été retiré avec succès: "..appName)
		self:SyncApartments(client)
	else
		client:NotifyLocalized("Cet appartement n'existe pas..")
	end
end

-- A function to add a door to an apartment
function PLUGIN:SetApartmentDoor(client, entDoor, appName)
	local existingAppID = self:CheckIfApartmentExists(client, appName)

	if !existingAppID then
		if ix.config.Get("shouldCreateNonExistingApartment", true) then
			client:NotifyLocalized("Cet appartement n'existait pas. Création d'un appartement non-prioritaire dont le nom est "..appName)
			self:CreateApartment(client, appName, true, "normal", function()
				PLUGIN:SetApartmentDoor(client, entDoor, appName)
			end)
			return
		else
			client:NotifyLocalized("Cet appartement n'existe pas.")
			return
		end
	end

	if self:CheckIfDoorAlreadyAdded(client, entDoor) then
		return false
	end

	self.apartments[existingAppID].doors[#self.apartments[existingAppID].doors + 1] = entDoor:EntIndex()

	local doors = {}
	for _, v in pairs(self.apartments[existingAppID].doors) do
		if (IsValid(Entity(v))) then
			local id = Entity(v):MapCreationID()
			if (id == -1) then continue end
			doors[#doors + 1] = id
		end
	end

	if (IsValid(entDoor) and entDoor:IsDoor()) then
		entDoor:SetNetVar("visible", true)

		if (appName:find("%S")) then
			entDoor:SetNetVar("name", appName)
		end

		local doorPlugin = ix.plugin.list["doors"]
		if doorPlugin then
			doorPlugin:SaveDoorData()
		end
	end

    local addDoor = mysql:Update("ix_apartments_"..game.GetMap())
        addDoor:Where("app_id", existingAppID)
		addDoor:Update("app_doors", util.TableToJSON(doors))
    addDoor:Execute()

	client:NotifyLocalized("Cette porte a été ajoutée avec succès à "..appName)
	self:SyncApartments(client)
end

-- A function to remove an door from an apartment
function PLUGIN:RemoveApartmentDoor(client, entDoor)
	for appKey, tInfo in pairs(self.apartments) do
		for doorKey, doorIndex in pairs(tInfo.doors) do
			if doorIndex == entDoor:EntIndex() then
				table.remove(self.apartments[appKey].doors, doorKey)

				local doors = {}
				for _, v in pairs(self.apartments[appKey].doors) do
					if (IsValid(Entity(v))) then
						local id = Entity(v):MapCreationID()
						if (id == -1) then continue end
						doors[#doors + 1] = id
					end
				end

				if (IsValid(entDoor) and entDoor:IsDoor()) then
					entDoor:SetNetVar("visible", false)
					entDoor:SetNetVar("name", nil)

					local doorPlugin = ix.plugin.list["doors"]
					if doorPlugin then
						doorPlugin:SaveDoorData()
					end
				end

				local removeDoor = mysql:Update("ix_apartments_"..game.GetMap())
					removeDoor:Where("app_id", appKey)
					removeDoor:Update("app_doors", util.TableToJSON(doors))
				removeDoor:Execute()

				client:NotifyLocalized("Vous avez réussi à retirer cette porte de "..tInfo.name)
				self:SyncApartments(client)
				return true
			end
		end
	end

	client:NotifyLocalized("Cette porte n'a pas pu être trouvée assignée à un appartement...")
	return false
end

-- A function to set the rent of an apartment
function PLUGIN:SetApartmentRent(client, appID, entDoor, newRent)
	if (appID) then
		self.apartments[appID].rent = newRent
		if PLUGIN.apartments[appID].type == "shop" then
			for _, shopTerminal in pairs(ents.FindByClass("ix_shopterminal")) do
				shopTerminal:UpdateScreen()
			end
		end
		self:UpdateApartment(appID)
		return true
	end

	if (entDoor) then
		local fetchedID = self:CheckIfDoorAlreadyAdded(client, entDoor, true)
		if (fetchedID) then
			self.apartments[fetchedID].rent = newRent
			self:UpdateApartment(fetchedID)
			client:NotifyLocalized("Prix du loyer "..self.apartments[fetchedID].name.." changé pour "..tostring(newRent).."!")
		else
			client:NotifyLocalized("Cette porte n'est ajoutée à aucun appartement !")
		end
	end
end

-- A function to remove a tenant from an apartment
function PLUGIN:RemoveTenant(client, item, appID, bNoNotify)
	local itemCID = (!isnumber(tonumber(item)) and item:GetData("cid") or item)

	if !self.apartments[appID] then
		if !bNoNotify then
			client:NotifyLocalized("L'appartement n'a pas été trouvé.")
		end

		return
	end

	if !self.apartments[appID].tenants[itemCID] then
		if !bNoNotify then
			client:NotifyLocalized("Impossible de trouver un appartement avec ce CID comme locataire.")

			if !isnumber(tonumber(item)) then
				client:NotifyLocalized("Suppression de l'item attribué à l'appartement.")
				item:SetData("apartment", nil)
			end
		end

		return
	end

	self:LookUpCardItemIDByCID(tostring(itemCID), function(result)
		local idCardID = result[1].idcard or false
		local charID
		if !result then return end
		if !ix.item.instances[idCardID] then
			ix.item.LoadItemByID(idCardID, false, function(card)
				if !card then return end
				charID = card:GetData("owner")
			end)
		else
			charID = ix.item.instances[idCardID]:GetData("owner")
		end

		local permitsToApply = {}
		for k, v in pairs(self.apartments[appID].permits) do
			if v then
				permitsToApply[k] = !v
			end
		end

		local character = ix.char.loaded[charID]
		if character then
			for k, v in pairs(permitsToApply) do
				character:SetPermit(k, v)
			end
		else
			local query = mysql:Select("ix_characters_data")
				query:Where("key", "genericdata")
				query:Where("id", tostring(charID))
				query:Select("data")
				query:Callback(function(genResult)
					if (!istable(genResult) or #genResult == 0) then
						return
					end
					local genericData = util.JSONToTable(genResult[1]["data"])

					for k, v in pairs(permitsToApply) do
						genericData.permits[k] = v
					end
					local removePermits = mysql:Update("ix_characters_data")
					removePermits:Where("id", tostring(charID))
						removePermits:Where("key", "genericdata")
						removePermits:Update("data", util.TableToJSON(genericData))
					removePermits:Execute()
				end)
			query:Execute()
		end
	end)
	self.apartments[appID].tenants[itemCID] = nil
	self.apartments[appID].lastActions[itemCID] = nil
	if !isnumber(tonumber(item)) then
		item:SetData("apartment", nil)
	end

	if !bNoNotify then
		client:NotifyLocalized("Suppression du locataire "..itemCID.." de l'appartement.")
	end

	if self.apartments[appID].type == "shop" and table.IsEmpty(self.apartments[appID].tenants) then
		for _, shopTerminal in pairs(ents.FindByClass("ix_shopterminal")) do
			shopTerminal:UpdateScreen()
		end
	end

	self:UpdateApartment(appID)

	if !isnumber(tonumber(item)) then return end

	PLUGIN:LookUpCardItemIDByCID(tostring(itemCID), function(result)
		local idCardID = result[1].idcard or false
		if !result then return end

		if !ix.item.instances[idCardID] then
			ix.item.LoadItemByID(idCardID, false, function(cid)
				if !cid then return end
				cid:LoadOwnerGenericData(PLUGIN.RemoveTenantHousing, false, appID)
			end)
		else
			ix.item.instances[idCardID]:LoadOwnerGenericData(PLUGIN.RemoveTenantHousing, false, appID)
		end
	end)
end

function PLUGIN:RemoveEmployee(client, item, appID, bNoNotify)
	local itemCID = (!isnumber(tonumber(item)) and item:GetData("cid") or item)
	if !self.apartments[appID] then
		if !bNoNotify then
			client:NotifyLocalized("Could not find the apartment.")
		end

		return
	end

	if !self.apartments[appID].employees[itemCID] then
		if !bNoNotify then
			client:NotifyLocalized("Could not find an apartment with this CID as employee.")
		end

		return
	end

	self:LookUpCardItemIDByCID(tostring(itemCID), function(result)
		local idCardID = result[1].idcard or false
		local charID
		if !result then return end
		if !ix.item.instances[idCardID] then
			ix.item.LoadItemByID(idCardID, false, function(card)
				if !card then return end
				charID = card:GetData("owner")
			end)
		else
			charID = ix.item.instances[idCardID]:GetData("owner")
		end

		local permitsToApply = {}
		for k, v in pairs(self.apartments[appID].permits) do
			if v then
				permitsToApply[k] = !v
			end
		end

		local character = ix.char.loaded[charID]
		if character then
			for k, v in pairs(permitsToApply) do
				character:SetPermit(k, v)
			end
		else
			local query = mysql:Select("ix_characters_data")
				query:Where("key", "genericdata")
				query:Where("id", tostring(charID))
				query:Select("data")
				query:Callback(function(gResult)
					if (!istable(gResult) or #gResult == 0) then
						return
					end
					local genericData = util.JSONToTable(gResult[1]["data"])

					for k, v in pairs(permitsToApply) do
						genericData.permits[k] = v
					end
					local removePermits = mysql:Update("ix_characters_data")
					removePermits:Where("id", tostring(charID))
						removePermits:Where("key", "genericdata")
						removePermits:Update("data", util.TableToJSON(genericData))
					removePermits:Execute()
				end)
			query:Execute()
		end
	end)

	self.apartments[appID].employees[itemCID] = nil
	self.apartments[appID].lastActions[itemCID] = nil

	if !bNoNotify then
		client:NotifyLocalized("Removed the employee "..itemCID.." from the apartment.")
	end

	self:UpdateApartment(appID)

	if !isnumber(tonumber(item)) then return end
end

-- A function to add a or remove a key to an apartment
function PLUGIN:AddKeyToApartment(client, item, apartment, bRemove, bNoNotify, bEmployee)
	local itemCID = (!isnumber(tonumber(item)) and item:GetData("cid") or item)
	local itemID = (!isnumber(tonumber(item)) and item:GetID() or true)

	if !self.apartments[apartment] then return end
	if !itemCID then return end

	if bRemove and !bEmployee then
		self:RemoveTenant(client, item, apartment)
		return
	elseif bRemove and bEmployee then
		self:RemoveEmployee(client, item, apartment)
		return
	end

	local assignedToShop = false
	local assignedToApp = false
	for appID, tApartment in pairs(self.apartments) do
		if tApartment.tenants[itemCID] or tApartment.employees[itemCID] then
			if tApartment.type == "shop" then
				assignedToShop = appID
			else
				assignedToApp = appID
			end
		end
	end

	if (self.apartments[apartment].type == "shop" and assignedToShop) then
		if !bNoNotify then
			client:NotifyLocalized("Le locataire "..itemCID.." est déjà attribué au magasin. "..self.apartments[assignedToShop].name)
		end

		return false
	end

	if (self.apartments[apartment].type != "shop" and assignedToApp) then
		if !bNoNotify then
			client:NotifyLocalized("Le locataire "..itemCID.." est déjà affecté à l'appartement "..self.apartments[assignedToApp].name)
		end

		return false
	end
	if !bEmployee then
		self.apartments[apartment].tenants[itemCID] = {key = itemID, autoPay = true}
		self.apartments[apartment].lastActions[itemCID] = os.time()
	else
		self.apartments[apartment].employees[itemCID] = {key = itemID}
		self.apartments[apartment].lastActions[itemCID] = os.time()
	end

	if !isnumber(tonumber(item)) and !bEmployee then
		item:SetData("apartment", apartment)
	end

	if !bNoNotify and !bEmployee then
		client:NotifyLocalized("Ajout du locataire "..itemCID.." au "..(self.apartments[apartment].type == "shop" and "shop " or "apartment ")..self.apartments[apartment].name)
	elseif !bNoNotify and bEmployee then
		client:NotifyLocalized("Ajout de l'employé "..itemCID.." au "..(self.apartments[apartment].type == "shop" and "shop " or "apartment ")..self.apartments[apartment].name)
	end

	self:UpdateApartment(apartment)

	return true
end

function PLUGIN:CheckIfCIDExistsLoaded(cid)
	local target = false

	-- Check if CID exists
	local cidCheck = tonumber(cid) != nil and string.utf8len( tostring(cid) ) <= 5
	-- Check for cached characters
	for _, v in pairs(ix.char.loaded) do
		local targetCid = v.GetCid and v:GetCid()
		local name = v.GetName and v:GetName()
		if (cidCheck and targetCid) then
			if string.match( targetCid, cid ) then
				target = v
				break
			end
		elseif name and string.find(name, cid) then
			target = v
			break
		end
	end

	return target
end

function PLUGIN:CheckIfCIDExistsDatabase(cid)
	local target = false
	cid = tostring(cid)

	if (tonumber(cid) != nil and string.utf8len( cid ) <= 5) then
		local query = mysql:Select("ix_characters")
		query:Select("name")
		query:Where("cid", cid)
		query:Where("schema", Schema and Schema.folder or "helix")
		query:Callback(function(result)
			if (!istable(result) or #result == 0) then
				return
			end

			target = result
		end)
		query:Execute()
	end

	return target
end

-- A function to set the CID of a key
function PLUGIN:SetKeyCID(client, item, cid)
	if !cid then
		if item:GetData("apartment") then
			client:NotifyLocalized("Vous devez d'abord supprimer l'appartement attribué.")
			return false
		end

		client:NotifyLocalized("CID supprimé de la clé.")
		item:SetData("cid", nil)
		return
	end

	if !self:CheckIfCIDExistsLoaded(cid) then
		if !self:CheckIfCIDExistsDatabase(cid) then
			client:NotifyLocalized("Ce CID n'existe pas !")
			return
		end
	end

	item:SetData("cid", cid)
	client:NotifyLocalized(" CID : "..cid.." assigné à la clé.")
end

-- A function for the key item to unlock/lock an apartment
function PLUGIN:UnlockLockApartment(keyItem, bUnlock, entDoor)
	local appID = keyItem:GetData("apartment", false)
	local client = keyItem:GetOwner()
	if !appID then return false end

	if self:GetKeyHasAccess(keyItem, entDoor) then
		if (IsValid(client) and client:GetPos():Distance(entDoor:GetPos()) > 96) then
			client:NotifyLocalized("Vous n'êtes pas assez près de la porte !")
			return
		end

		if (entDoor:IsDoor()) then
			local partner = entDoor:GetDoorPartner()
			if entDoor.locked then return end

			if (!bUnlock) then
				if (IsValid(partner)) then
					partner:Fire("lock")
				end

				entDoor:Fire("lock")
				client:EmitSound("doors/door_latch3.wav")

				hook.Run("PlayerLockedDoor", client, entDoor, partner)
			else
				if (IsValid(partner)) then
					partner:Fire("unlock")
				end

				entDoor:Fire("unlock")
				client:EmitSound("doors/door_latch1.wav")

				hook.Run("PlayerUnlockedDoor", client, entDoor, partner)
			end
		end
	end
end

function PLUGIN:GetPriorityApartmentAccess(genericData)
	local loyaltyStatus = genericData.loyaltyStatus
	local tier = tonumber(self:GetNumbersFromText(loyaltyStatus))
	local tierNeeded = tonumber(self:GetNumbersFromText(ix.config.Get("priorityHousingTierNeeded", "Collaborateur de Rang 1 (VERT)")))

	if genericData.loyaltyStatus == "H.A.A" then
		return true
	end

	if (!tier or !isnumber(tier)) then
		return false
	end

	if (isnumber(tier) and tier < tierNeeded) then
		return false
	end

	return true
end

-- A function to assign an apartment
function PLUGIN.AssignApartment(idCard, genericData, client, foundApartment)
	if idCard:GetCredits() < ix.config.Get("costForAnApartment", 35) then
		netstream.Start(client, "SendHousingErrorMessage", "PAS ASSEZ DE CRÉDITS")
		return
	end

	if !foundApartment then
		netstream.Start(client, "SendHousingErrorMessage", "AUCUN LOGEMENT TROUVÉ")
		return
	end

	if foundApartment == "priority" then
		if PLUGIN:GetPriorityApartmentAccess(genericData) then
			foundApartment = PLUGIN:GetWhichApartmentToAssign(foundApartment)
			if !foundApartment then
				netstream.Start(client, "SendHousingErrorMessage", "AUCUN LOGEMENT TROUVÉ")
				return
			end
		end
	end

	local cid = idCard:GetData("cid", false)
	if !cid then return false end

	for appID, v in pairs(PLUGIN.apartments) do
		if v.type == "shop" then continue end
		if v.tenants[cid] then
			PLUGIN:RemoveTenant(client, cid, appID, true)
		end
	end

	genericData.housing = foundApartment
	local combineutilities = ix.plugin.list["combineutilities"]
	if combineutilities and combineutilities.UpdateGenericData then
		combineutilities:UpdateGenericData(genericData)
	end

	local character = ix.char.loaded[genericData.id]
	if character then
		if !character:GetPurchasedItems()["apartmentkey"] then
			character:SetPurchasedItems("apartmentkey", 1)
		else
			client:NotifyLocalized("Il y a déjà une clé qui attend au terminal de dépôt pour ce CID. Il a été mis à jour.")
		end
	end

	idCard:TakeCredits(ix.config.Get("costForAnApartment", 35), "Logement", "Appartement acheté")
	ix.city.main:AddCredits(ix.config.Get("costForAnApartment", 35))
	PLUGIN:AddKeyToApartment(client, cid, foundApartment, false, true)
	PLUGIN:SyncToOneApartments(client, false, foundApartment, genericData)
end

-- A function to update the assigned itemID of a CID tenant
function PLUGIN:UpdateItemIDTenant(cid, itemID, type)
	for appID, v in pairs(self.apartments) do
		if type:find("shop") and v.type != "shop" then continue end
		if type:find("apartment") and v.type == "shop" then continue end
		if !v.tenants[cid] then continue end
		if v.tenants[cid].key == itemID then break end

		self.apartments[appID].tenants[cid].key = itemID
		self:UpdateApartment(appID)
		break
	end
end
function PLUGIN:UpdateItemIDEmployee(cid, itemID, type)
	for appID, v in pairs(self.apartments) do
		if type:find("shop") and v.type != "shop" then continue end
		if type:find("apartment") and v.type == "shop" then continue end
		if !v.employees[cid] then continue end
		if v.employees[cid].key == itemID then break end
		self.apartments[appID].employees[cid].key = itemID
		self:UpdateApartment(appID)
		break
	end
end
-- A function to handle the apartment assignment request
function PLUGIN:HandleAssignment(client, cid, type)
	cid = self:HasCIDInInventory(client, cid)
	if !cid then return false end

	local foundApartment = type
	if type != "priority" then
		foundApartment = self:GetWhichApartmentToAssign(type)
	end

	cid:LoadOwnerGenericData(self.AssignApartment, false, client, foundApartment)
end

function PLUGIN:CheckTenantInteraction()
	local interactionWeeks = ix.config.Get("tenantDoorInteractionCheck", 2)
	for appID, tApartment in pairs(self.apartments) do
		for cid, time in pairs(tApartment.lastActions) do
			if (os.time() < (tonumber(time) + 3600 * 24 * 7 * interactionWeeks)) then continue end
			self:RemoveTenant(false, cid, appID, true)
		end
	end
end

function PLUGIN:LookUpCardItemIDByCID(cid, callback)
	local query = mysql:Select("ix_characters")
	query:Select("idcard")
	query:WhereIn("cid", cid)
	query:Callback(function(result)
		if (!istable(result) or #result == 0) then
			return
		end

		if (callback) then
			callback(result)
		end
	end)

	query:Execute()
end

function PLUGIN:PayRentOnCheck(idCard, individualRent, cid, appID)
	local credits = idCard and idCard.GetCredits and idCard:GetCredits()
	local madePayment = false

	if (!idCard or (idCard and !credits)) then
		return false
	end

	if (idCard and credits) and (credits >= individualRent) then
		idCard:TakeCredits(individualRent, "Logement", "Location")
		ix.city.main:AddCredits(individualRent)
		if self.apartments[appID].payments[cid] then
			self.apartments[appID].payments[cid] = {amount = individualRent + self.apartments[appID].payments[cid].amount, date = os.time()}
		else
			self.apartments[appID].payments[cid] = {amount = individualRent, date = os.time()}
		end

		madePayment = cid
	else
		self:RemoveTenant(false, cid, appID, true)
	end

	return madePayment
end

function PLUGIN:CheckRentPayments()
	for appID, tApartment in pairs(self.apartments) do
		if os.time() < tonumber(tApartment.rentDue) then continue end
		local remainingRent = self:GetRemainingRent(tApartment)
		if !remainingRent then return end
		if remainingRent <= 0 then self:ResetRent(appID) continue end

		local tenantCount = table.Count(tApartment.tenants)
		local individualRent = math.ceil( tApartment.rent / tenantCount )

		for tenantCID, _ in pairs(tApartment.tenants) do
			local madePayment = false
			if tApartment.tenants[tenantCID].autoPay then
				self:LookUpCardItemIDByCID(tenantCID, function(result)
					local idCardID = result[1].idcard or false
					if !idCardID then
						ix.log.AddRaw("[LOGEMENT] "..tenantCID.." a été retiré de l'appartement "..appID..".")
						self:RemoveTenant(false, tenantCID, appID, true)
						return
					end

					if !ix.item.instances[tonumber(idCardID)] then
						ix.item.LoadItemByID(tonumber(idCardID), false, function(item)
							if !item then
								ix.log.AddRaw("[LOGEMENT] "..tenantCID.." La carte CID n'a pas été trouvée lors du traitement du paiement du loyer hors ligne pour le logement : "..appID..".")
								return
							end
							madePayment = self:PayRentOnCheck(ix.item.instances[tonumber(idCardID)], individualRent, tenantCID, appID)
						end)
					else
						madePayment = self:PayRentOnCheck(ix.item.instances[tonumber(idCardID)], individualRent, tenantCID, appID)
					end

					timer.Simple(10, function()
						if !madePayment then
							ix.log.AddRaw("[LOGEMENT] "..tenantCID.." n'a pas effectué de paiement automatique, vérifier les paiements non automatiques avant de désassigné le logement : "..appID..".")
							self:CheckNoAutoPayments(tApartment, appID, tenantCID)
						end
					end)
				end)
			end
		end

		timer.Simple(6, function()
			self:ResetRent(appID)
		end)
	end
end

function PLUGIN.RemovePermitsGeneric(idCard, genericData)
	genericData.permits = {}

	local combineutilities = ix.plugin.list["combineutilities"]
	if combineutilities and combineutilities.UpdateGenericData then
		combineutilities:UpdateGenericData(genericData)
	end
end

function PLUGIN:RemovePermits(tenantCID)
	tenantCID = Schema:ZeroNumber(tenantCID, 5)

	PLUGIN:LookUpCardItemIDByCID(tostring(tenantCID), function(result)
		local idCardID = result[1].idcard or false
		if !result then return end

		if !ix.item.instances[idCardID] then
			ix.item.LoadItemByID(idCardID, false, function(item)
				if !item then return end
				item:LoadOwnerGenericData(PLUGIN.RemovePermitsGeneric, false)
			end)
		else
			ix.item.instances[idCardID]:LoadOwnerGenericData(PLUGIN.RemovePermitsGeneric, false)
		end
	end)
end

function PLUGIN:CheckNoAutoPayments(tApartment, appID, tenantCID)
	local tenantCount = table.Count(tApartment.tenants)
	local individualRent = math.ceil( tApartment.rent / tenantCount )

	if !tApartment.payments[tenantCID] then
		ix.log.AddRaw("[LOGEMENT] "..tenantCID.." n'a effectué aucun paiement manuel, supprimant l'identification du logement: "..appID..".. dev check: "..tonumber(isnumber(tenantCID)))
		self:RemoveTenant(false, tenantCID, appID, true)

		if tApartment.type == "shop" then
			self:RemovePermits(tenantCID)
		end

		return
	end

	for paymentCID, tPayments in pairs(tApartment.payments) do
		if tPayments.paidAmount and individualRent then
			if tPayments.paidAmount >= individualRent then continue end
		end

		if !tApartment.tenants[paymentCID] then continue end

		if tApartment.type == "shop" then
			self:RemovePermits(paymentCID)
		end

		ix.log.AddRaw("[LOGEMENT] "..paymentCID.." n'a pas payé le loyer au dessus du seuil minimum, le retirant du commerce : "..appID..".. dev check: "..isnumber(paymentCID))
		self:RemoveTenant(false, paymentCID, appID, true)
	end
end

function PLUGIN:ResetRent(appID)
	self.apartments[appID].payments = {}
	self.apartments[appID].rentDue = self.apartments[appID].rentDue + (!ix.config.Get("housingTesterMode", false) and 3600 * 24 * 7 * ix.config.Get("housingRentDueInWeeks", 1) or 10)
	self:UpdateApartment(appID)
end

function PLUGIN:PreCharacterDeleted(client, character)
	local cid = character:GetCid()
	if !cid then return end

	for appID, tApartment in pairs(self.apartments) do
		if !tApartment.tenants[cid] then continue end

		self:RemoveTenant(client, cid, appID, true)
	end
end

function PLUGIN:OnCharacterBanned(character)
	local cid = character:GetCid()
	if !cid then return end

	for appID, tApartment in pairs(self.apartments) do
		if !tApartment.tenants[cid] then continue end

		self:RemoveTenant(false, cid, appID, true)
	end
end

function PLUGIN:OnCharacterBannedByID(charID)
	if (!ix.plugin.list.cid) then return end
	if !ix.plugin.list.cid.GenerateCID then return end
	local cid = ix.plugin.list.cid:GenerateCID(charID)

	for appID, tApartment in pairs(self.apartments) do
		if !tApartment.tenants[cid] then continue end

		self:RemoveTenant(false, cid, appID, true)
	end
end

do
	timer.Create("ixHousingCheckInteractionAndRent", ix.config.Get("housingCheckInteractionAndRentTimer", 20) * 60, 0, function()
		PLUGIN:CheckTenantInteraction()
		PLUGIN:CheckRentPayments()
	end)

	function PLUGIN.UpdateInteractionTimer(oldVal, newVal)
		timer.Adjust("ixHousingCheckInteractionAndRent", newVal * 60)
	end
end