--***********************************************************
--**                    THE INDIE STONE                    **
--***********************************************************

-----------------------------------------------------------------------
--                          FARMING MOD                              --
--                      CODE BY ROBERT JOHNSON                       --
--                       TEXTURES BY THUZTOR                         --
-----------------------------------------------------------------------
--                          OFFICIAL TOPIC                           --
--  http://www.theindiestone.com/community/viewtopic.php?f=33&t=8675 --
--                                                                   --
-----------------------------------------------------------------------

if isClient() then return end

require "Map/SGlobalObjectSystem"

SFarmingSystem = SGlobalObjectSystem:derive("SFarmingSystem")

function SFarmingSystem:new()
	local o = SGlobalObjectSystem.new(self, "farming")

	 -- The value of this may have been read from gos_farming.bin already.
	o.hoursElapsed = o.hoursElapsed or 0

	o.hourElapsedForWater = 0
	o.previousHourHealth = 0

	local gameTime = GameTime:getInstance()
	local sec = math.floor(gameTime:getTimeOfDay() * 3600)
	o.previousHour = math.floor(sec / 3600)

	return o
end

function SFarmingSystem:initSystem()
	SGlobalObjectSystem.initSystem(self)

	-- Specify GlobalObjectSystem fields that should be saved.
	self.system:setModDataKeys({'hoursElapsed'})
	
	-- Specify GlobalObject fields that should be saved.
	self.system:setObjectModDataKeys({
		'state', 'nbOfGrow', 'typeOfSeed', 'fertilizer', 'mildewLvl',
		'aphidLvl', 'fliesLvl', 'waterLvl', 'waterNeeded', 'waterNeededMax',
		'lastWaterHour', 'nextGrowing', 'hasSeed', 'hasVegetable',
		'health', 'badCare', 'exterior', 'spriteName', 'objectName'})

	self:convertOldModData()
end

function SFarmingSystem:getInitialStateForClient()
	return { hoursElapsed = self.hoursElapsed }
end

function SFarmingSystem:newLuaObject(globalObject)
	return SPlantGlobalObject:new(self, globalObject)
end

function SFarmingSystem:isValidModData(modData)
	return modData and modData.state and modData.nbOfGrow and modData.health
end

function SFarmingSystem:isValidIsoObject(isoObject)
	return isoObject:hasModData() and self:isValidModData(isoObject:getModData())
end

-- Take plant data previously stored in GameTime.getModData() and put it into
-- the new GlobalObjectSystem.
function SFarmingSystem:convertOldModData()
	-- If the gos_xxx.bin file existed, don't touch GameTime modData in case mods are using it.
	if self.system:loadedWorldVersion() ~= -1 then return end
	
	local modData = GameTime:getInstance():getModData()
	if modData.farming and modData.farming.plants then
		self:noise('converting old-style GameTime modData')
		for _,plant in pairs(modData.farming.plants) do
			if not self.system:getObjectAt(plant.x, plant.y, plant.z) then
				local globalObject = self.system:newObject(plant.x, plant.y, plant.z)
				for k,v in pairs(plant) do
					globalObject:getModData()[k] = v
				end
			end
		end
		modData.farming.plants = nil
		for k,v in pairs(modData.farming) do
			noise("copied "..tostring(k).."="..tostring(v))
			self[k] = v
		end
		modData.farming = nil
		self:noise('converted '..self.system:getObjectCount()..' plants')
	end
end

function SFarmingSystem:OnClientCommand(command, playerObj, args)
	SFarmingSystemCommands[command](playerObj, args)
end

function SFarmingSystem:EveryTenMinutes()
	local sec = math.floor(getGameTime():getTimeOfDay() * 3600)
	local currentHour = math.floor(sec / 3600)
	local day = getGameTime():getDay()
	-- an hour has passed
	if currentHour ~= self.previousHour then
		self.hoursElapsed = self.hoursElapsed + 1
		self.previousHour = currentHour
		self.hourElapsedForWater = self.hourElapsedForWater + 1
		-- every 2 hours, we lover the water lvl of all plant by 1
		-- we also gonna up our disease lvl
		local hourForWater = 5;
		if SandboxVars.PlantResilience == 1 then -- very high
			hourForWater = 12;
		elseif SandboxVars.PlantResilience == 2 then -- high
			hourForWater = 8;
		elseif SandboxVars.PlantResilience == 4 then -- low
			hourForWater = 3;
		elseif SandboxVars.PlantResilience == 5 then -- very low
			hourForWater = 2;
		end
		if self.hourElapsedForWater >= hourForWater then
			self:lowerWaterLvlAndUpDisease()
			self.hourElapsedForWater = 0
		end
		-- change health of the plant every 3 hours
		self.previousHourHealth = self.previousHourHealth + 1
		if self.previousHourHealth == 2 then
			self:changeHealth()
			self.previousHourHealth = 0
		end
		self:sendCommand('hoursElapsed', { hoursElapsed = self.hoursElapsed })
	end
--[[
	-- a day as passed, maybe npc will water the plant for us
	if self.currentDay ~= day then
		self.currentDay = day
--		self:automateNpc()
	end
--]]
	self:checkPlant()
end

function SFarmingSystem:lowerWaterLvlAndUpDisease()
	for i=1,self:getLuaObjectCount() do
		local luaObject = self:getLuaObjectByIndex(i)
		luaObject:lowerWaterLvl()
		luaObject:upDisease()
	end
end

-- up or low the health of our plant, depending on weather, if they are well watered, etc.
function SFarmingSystem:changeHealth()
	for i=1,self:getLuaObjectCount() do
		local luaObject = self:getLuaObjectByIndex(i)
		-- change with weather
		local weather = getWorld():getWeather()
		if "sunny" == weather then -- if it's sunny
			if luaObject.exterior then
				luaObject.health = luaObject.health + 1
			else
				luaObject.health = luaObject.health + 0.25
			end
		end
		-- lower health if temperature is not high
		if season.currentTemp <= 10 then
			luaObject.health = luaObject.health - 0.25;
		end
		-- change with water
		local water = farming_vegetableconf.calcWater(luaObject.waterNeeded, luaObject.waterLvl)
		local waterMax = farming_vegetableconf.calcWater(luaObject.waterLvl, luaObject.waterNeededMax)
		if water >= 0 and waterMax >= 0 then
			luaObject.health = luaObject.health + 0.4
		elseif water == -1 then -- we low health by 0.2
			luaObject.health = luaObject.health - 0.2
		elseif water == -2 then -- low health by 0.5
			luaObject.health = luaObject.health - 0.5
		elseif waterMax == -1 and luaObject.health > 20  then
			luaObject.health = luaObject.health - 0.2
		elseif waterMax == -2 and luaObject.health > 20  then
			luaObject.health = luaObject.health - 0.5
		end
	end
end

function SFarmingSystem:checkPlant()
	for i=1,self:getLuaObjectCount() do
		local luaObject = self:getLuaObjectByIndex(i)
		local square = luaObject:getSquare()
		if square then luaObject.exterior = square:isOutside() end
		-- we may destroy our plant if someone walk onto it
		self:destroyOnWalk(luaObject)
		-- Something can grow up !
		if luaObject.nextGrowing ~= nil then
			if self.hoursElapsed >= luaObject.nextGrowing then
				self:growPlant(luaObject, nil, true)
			end
		end
		-- if the plant still alive
		if luaObject.state ~= "plow" and luaObject:isAlive() then
			-- check the last water hour of all our plant, if it's more than 76 hours the plant start to lose health
			-- if it's raining we up a little the water lvl of the plant
			if RainManager.isRaining() then
				if luaObject.exterior then
					luaObject.waterLvl = luaObject.waterLvl + 3
					if luaObject.waterLvl > 100 then
						luaObject.waterLvl = 100
					end
					luaObject.lastWaterHour = self.hoursElapsed
				end
			-- if it's sunny, we lower a bit our water lvl
			elseif season.weather == "sunny" then
				luaObject.waterLvl = luaObject.waterLvl - 0.1
				if luaObject.waterLvl < 0 then
					luaObject.waterLvl = 0
				end
			end
		end
		-- add the icon if we have the required farming xp and if we're close enough of the plant
		luaObject:addIcon()
		luaObject:checkStat()
		luaObject:saveData()
	end
end

-- grow the plant
function SFarmingSystem:growPlant(luaObject, nextGrowing, updateNbOfGrow)
	if(luaObject.state == "seeded") then
		local new = luaObject.nbOfGrow <= 0
		if(luaObject.typeOfSeed == "Carrots") then
			luaObject = farming_vegetableconf.growCarrots(luaObject, nextGrowing, updateNbOfGrow)
		elseif(luaObject.typeOfSeed == "Broccoli") then
			luaObject = farming_vegetableconf.growBroccoli(luaObject, nextGrowing, updateNbOfGrow)
		elseif(luaObject.typeOfSeed == "Strawberry plant") then
			luaObject = farming_vegetableconf.growStrewberries(luaObject, nextGrowing, updateNbOfGrow)
		elseif(luaObject.typeOfSeed == "Radishes") then
			luaObject = farming_vegetableconf.growRedRadish(luaObject, nextGrowing, updateNbOfGrow)
		elseif(luaObject.typeOfSeed == "Tomato") then
			luaObject = farming_vegetableconf.growTomato(luaObject, nextGrowing, updateNbOfGrow)
		elseif(luaObject.typeOfSeed == "Potatoes") then
			luaObject = farming_vegetableconf.growPotato(luaObject, nextGrowing, updateNbOfGrow)
		elseif(luaObject.typeOfSeed == "Cabbages") then
			luaObject = farming_vegetableconf.growCabbage(luaObject, nextGrowing, updateNbOfGrow)
		end
		-- maybe this plant gonna be disease
		if not new and luaObject.nbOfGrow > 0 then
			self:diseaseThis(luaObject, true)
		end
		luaObject.nbOfGrow = luaObject.nbOfGrow + 1
	end
end

function SFarmingSystem:harvest(luaObject, player)
	local props = farming_vegetableconf.props[luaObject.typeOfSeed]
	local numberOfVeg = getVegetablesNumber(props.minVeg, props.maxVeg, props.minVegAutorized, props.maxVegAutorized, luaObject)
	if player then
		player:sendObjectChange('addItemOfType', { type = props.vegetableName, count = numberOfVeg })
	end

	if luaObject.hasSeed and player then
		player:sendObjectChange('addItemOfType', { type = props.seedName, count = (props.seedPerVeg * numberOfVeg) })
	end

	luaObject.hasVegetable = false
	luaObject.hasSeed = false

	-- the strawberrie don't disapear, it goes on phase 2 again
	if luaObject.typeOfSeed == "Strawberry plant" then
		luaObject.nbOfGrow = 1
		luaObject.fertilizer = 0;
		self:growPlant(luaObject, nil, true)
		luaObject:saveData()
	else
		self:removePlant(luaObject)
	end
end

-- test if the plant gonna be disease
function SFarmingSystem:diseaseThis(luaObject, checkClosePlant)
	-- if we don't already have mildew
	if(luaObject.mildewLvl == 0) then
		luaObject:mildew()
	end
	if(luaObject.aphidLvl == 0) then
		luaObject:aphid()
	end
	if(luaObject.fliesLvl == 0) then
		luaObject:flies()
	end
	if(checkClosePlant ==  true) then
		-- we gonna check all the plant near this one, if one is infected, maybe this one would be infected too !
		self:diseaseClosePlant(luaObject)
	end
end

-- fetch all the plant near our current disease plant, so maybe they gonna have a disease too
function SFarmingSystem:diseaseClosePlant(luaObject)
	local adjacent = self.system:getObjectsAdjacentTo(luaObject.x, luaObject.y, luaObject.z)
	for i=1,adjacent:size() do
		local luaObject2 = adjacent:get(i-1):getModData()
		-- a close plant is infected
		if luaObject2.aphidLvl > 0 or luaObject2.mildewLvl > 0 then
			-- we gonna re check if our plant can be infected
			self:diseaseThis(luaObject, false)
		end
	end
	-- This returns the ArrayList to a pool for reuse.  There's no harm if
	-- you forget to call it.
	self.system:finishedWithList(adjacent)
end

-- if zombie or npc walk over your plant, you have 1 to 10 risk that your plant is destroyed
function SFarmingSystem:destroyOnWalk(luaObject)
	local square = luaObject:getSquare()
	if not square then return end
	if luaObject.state == "plow" or luaObject.state == "destroy" then return end
	-- if zombie walk on our plant !
	for i=1,square:getMovingObjects():size() do
		local movingObject = square:getMovingObjects():get(i-1)
		if instanceof(movingObject, "IsoZombie") then
			-- too bad ! :)
			if ZombRand(11) == 5 then
				luaObject:destroyThis()
			end
		end
	end
end

-- get the health of the new plant depending on the moon
function SFarmingSystem:getHealth()
	-- it's better to plant seed during ascending phase of the moon
	if season.moonCycle >= 4 and season.moonCycle < 18 then -- ascending moon health between 47 and 53
		return ZombRand(47, 54)
	elseif season.moonCycle >= 18 and season.moonCycle <= 21 then -- full moon, the best ! health between 57 and 64
		return ZombRand(57, 64)
	else -- descending moon, the worst, health between 37 and 44
		return ZombRand(37, 44)
	end
end

-- plow the land
function SFarmingSystem:plow(square)
	self:removeTallGrass(square)
	local luaObject = self:newLuaObjectOnSquare(square)
	luaObject:initNew()
	luaObject.exterior = square:isOutside()
	luaObject:addObject()
	self:noise('plowed '..luaObject.x..','..luaObject.y)
	self:noise("#plants="..self:getLuaObjectCount())
end

function SFarmingSystem:removePlant(luaObject)
	if not luaObject or luaObject.luaSystem ~= self then return end
	-- This calls removeLuaObject(luaObject) as a side effect of the OnObjectAboutToBeRemoved event
	luaObject:removeObject()
--	FarmingSystem.instance:removeLuaObject(plant)
end

function SFarmingSystem:removeTallGrass(sq)
	-- remove vegetation
	for i=sq:getObjects():size(),1,-1 do
		o = sq:getObjects():get(i-1)
		-- FIXME: blends_grassoverlays tiles should have 'vegitation' flag
		if o:getSprite() and (
				o:getSprite():getProperties():Is(IsoFlagType.canBeRemoved) or
				(o:getSprite():getProperties():Is(IsoFlagType.vegitation) and o:getType() ~= IsoObjectType.tree) or
				(o:getSprite():getName() and luautils.stringStarts(o:getSprite():getName(), "blends_grassoverlays"))) then
			sq:transmitRemoveItemFromSquare(o)
		end
	end
end

function SFarmingSystem:receiveCommand(playerObj, command, args)
	SFarmingSystemCommands[command](playerObj, args)
end

SGlobalObjectSystem.RegisterSystemClass(SFarmingSystem)

local function EveryTenMinutes()
	SFarmingSystem.instance:EveryTenMinutes()
end

Events.EveryTenMinutes.Add(EveryTenMinutes)

