--***********************************************************
--**                    ROBERT JOHNSON                     **
--***********************************************************

-- this class extend ISBuildingObject, it's a class to help you drag around/place an item in the world
RainCollectorBarrel = ISBuildingObject:derive("RainCollectorBarrel");
-- list of our barrel in the world
RainCollectorBarrel.barrels = {};
RainCollectorBarrel.waterScale = 4
RainCollectorBarrel.smallWaterMax = 40 * RainCollectorBarrel.waterScale
RainCollectorBarrel.largeWaterMax = 100 * RainCollectorBarrel.waterScale

RainCollectorBarrel.wantNoise = true
local noise = function(msg)
	if (RainCollectorBarrel.wantNoise) then print('rain barrel: '..msg) end
end

function RainCollectorBarrel:create(x, y, z, north, sprite)
	local cell = getWorld():getCell();
	self.sq = cell:getGridSquare(x, y, z);
	self.javaObject = IsoThumpable.new(cell, self.sq, sprite, north, self);
	buildUtil.setInfo(self.javaObject, self);
	buildUtil.consumeMaterial(self);
	-- the barrel have 200 base health + 100 per carpentry lvl
	self.javaObject:setMaxHealth(self:getHealth());
	self.javaObject:setHealth(self.javaObject:getMaxHealth());
	-- the sound that will be played when our barrel will be broken
	self.javaObject:setBreakSound("breakdoor");
	-- add the item to the ground
    self.sq:AddSpecialObject(self.javaObject);
	-- add some xp for because you successfully build the barrel
	buildUtil.addWoodXp(self);
	-- IsoObjects with 'waterAmount' 
    self.javaObject:getModData()["waterMax"] = self.waterMax;
    self.javaObject:getModData()["waterAmount"] = 0;
    self.javaObject:setSpecialTooltip(true)
	local barrel = {};
	barrel.x = self.sq:getX();
	barrel.y = self.sq:getY();
	barrel.z = self.sq:getZ();
	barrel.waterAmount = 0;
	barrel.waterMax = self.waterMax
	barrel.exterior = self.sq:isOutside()
	table.insert(RainCollectorBarrel.barrels, barrel);
	self.javaObject:transmitCompleteItemToServer();
--~ 	print("add a barrel at : " .. x .. "," .. y);
end

function RainCollectorBarrel:new(player, sprite, waterMax)
	-- OOP stuff
	-- we create an item (o), and return it
	local o = {};
	setmetatable(o, self);
	self.__index = self;
	o:init();
	-- the number of sprites can be up to 4, one for each direction, you ALWAYS need at least 2 sprites, south (Sprite) and north (NorthSprite)
	-- here we're not gonna be able to rotate our building (it's a barrel, so every face are the same), so we set that the south sprite = north sprite
	o:setSprite(sprite);
	o:setNorthSprite(sprite);
	o.name = "Rain Collector Barrel";
    o.waterMax = waterMax;
	o.player = player;
	-- the barrel will be dismantable (come from IsoThumpable stuff, check buildUtil.setInfo to see wich options are available)
	o.dismantable = true;
	-- you can't barricade it
	o.canBarricade = false;
	-- the item will block all the square where it placed (not like a wall for example)
	o.blockAllTheSquare = true;
	return o;
end

-- return the health of the new barrel, it's 200 + 100 per carpentry lvl
function RainCollectorBarrel:getHealth()
	return 200 + buildUtil.getWoodHealth(self);
end

-- our barrel can be placed on this square ?
-- this function is called everytime you move the mouse over a grid square, you can for example not allow building inside house..
function RainCollectorBarrel:isValid(square)
	if not square then return false end
	if square:isSolid() or square:isSolidTrans() then return false end
	if square:HasStairs() then return false end
	if square:HasTree() then return false end
	if not square:getMovingObjects():isEmpty() then return false end
	if not square:TreatAsSolidFloor() then return false end
	if not self:haveMaterial(square) then return false end
	for i=1,square:getObjects():size() do
		local obj = square:getObjects():get(i-1)
		if self:getSprite() == obj:getTextureName() then return false end
    end
    if buildUtil.stairIsBlockingPlacement( square, true ) then return false; end
	if square:isVehicleIntersecting() then return false end
	return true
end

-- called after render the ghost objects
-- the ISBuildingObject only render 1 sprite (north, south...), for example for stairs I can render the 2 others tile for stairs here
-- if I return false, the ghost render will be in red and I couldn't build the item
function RainCollectorBarrel:render(x, y, z, square)
	ISBuildingObject.render(self, x, y, z, square)
end

function RainCollectorBarrel.isRainCollectorBarrelObject(object)
	return object ~= nil and object:getName() == "Rain Collector Barrel"
end

function RainCollectorBarrel.findObject(square)
	if not square then return nil end
	for i=0,square:getSpecialObjects():size()-1 do
		local obj = square:getSpecialObjects():get(i)
		if RainCollectorBarrel.isRainCollectorBarrelObject(obj) then
			return obj
		end
	end
	return nil
end

-- every 10 minutes we check if it's raining, to fill our water barrel
function RainCollectorBarrel.checkRain()
    if isClient() then return; end
	if RainManager.isRaining() then
		for iB,vB in ipairs(RainCollectorBarrel.barrels) do
			if vB.waterAmount < vB.waterMax then
				local square = getCell():getGridSquare(vB.x, vB.y, vB.z);
				if square then vB.exterior = square:isOutside() end
				if vB.exterior then
					vB.waterAmount = math.min(vB.waterMax, vB.waterAmount + 1 * RainCollectorBarrel.waterScale)
					local obj = RainCollectorBarrel.findObject(square)
					if obj then -- object might have been destroyed
						noise('added rain to barrel at '..vB.x..","..vB.y..","..vB.z..' waterAmount='..vB.waterAmount)
						obj:setWaterAmount(vB.waterAmount)
						obj:transmitModData()
					end
				end
			end
		end
	end
end

function RainCollectorBarrel.LoadGridsquare(square)
    if isClient() then return; end
	-- does this square have a rain barrel ?
	for i=0,square:getSpecialObjects():size() - 1 do
		local obj = square:getSpecialObjects():get(i)
		if RainCollectorBarrel.isRainCollectorBarrelObject(obj) then
			RainCollectorBarrel.loadRainBarrel(obj)
			break
		end
	end
end

function RainCollectorBarrel.loadGridsquareJavaHook(sq, object)
	if isClient() then return; end
	RainCollectorBarrel.loadRainBarrel(object)
end

-- load the barrel
function RainCollectorBarrel.loadRainBarrel(barrelObject)
	if not barrelObject or not barrelObject:getSquare() then return end
	local square = barrelObject:getSquare()
	local barrel = nil;
	-- check if we don't already have this barrel in our map (the streaming of the map make the gridsquare to reload every time)
	for i,v in ipairs(RainCollectorBarrel.barrels) do
		if v.x == square:getX() and v.y == square:getY() and v.z == square:getZ() then
			barrel = v;
			break;
		end
	end
	if not barrel then -- if we don't have the barrel, it's basically when you load your saved game the first time
		barrel = {};
		barrel.x = square:getX();
		barrel.y = square:getY();
		barrel.z = square:getZ();
		barrel.waterAmount = barrelObject:getWaterAmount()
		barrel.waterMax = barrelObject:getModData()["waterMax"]
		-- compatibility, waterAmount stored in IsoGridSquare modData
		if square:getModData()["waterAmount"] then
			barrel.waterAmount = tonumber(square:getModData()["waterAmount"])
			square:getModData()["waterAmount"] = nil
			square:getModData()["waterMax"] = nil
			square:getModData()["alwaysTakeWater"] = nil
			square:getModData()["waterbarrel"] = nil
		end
		-- sanity check + compatibility with waterMax stored in IsoGridSquare modData
		if not barrel.waterMax then
			local spriteName = nil
			if barrelObject:getSprite() then spriteName = barrelObject:getSprite():getName() end
			if spriteName == "carpentry_02_54" or spriteName == "carpentry_02_55" then
				barrel.waterMax = RainCollectorBarrel.smallWaterMax
			elseif spriteName == "carpentry_02_52" or spriteName == "carpentry_02_53" then
				barrel.waterMax = RainCollectorBarrel.largeWaterMax
			else
				barrelObject:setSprite("carpentry_02_54")
				barrel.waterMax = RainCollectorBarrel.smallWaterMax
			end
			barrelObject:getModData()["waterMax"] = barrel.waterMax
		end
		table.insert(RainCollectorBarrel.barrels, barrel);
		noise("new barrel created " .. barrel.x .. "," .. barrel.y .. " with " .. barrel.waterAmount .. " water");
	else
		noise("found existed barrel " .. barrel.x .. "," .. barrel.y .. " with " .. barrel.waterAmount);
		barrelObject:setWaterAmount(barrel.waterAmount)
	end

	barrelObject:setSpecialTooltip(true)
	
	-- ISTakeWaterAction was fixed to consider storage capacity of water containers.
	-- Update old rainbarrels with 40/100 capacity to 160/400 capacity.
	if barrel.waterMax == 40 then barrel.waterMax = RainCollectorBarrel.smallWaterMax end
	if barrel.waterMax == 100 then barrel.waterMax = RainCollectorBarrel.largeWaterMax end
	barrelObject:getModData()["waterMax"] = barrel.waterMax

	barrel.exterior = square:isOutside()

	RainCollectorBarrel.changeSprite(barrel, barrelObject);
end

function RainCollectorBarrel.changeSprite(barrel, barrelObject)
	local spriteName = nil
	if barrel.waterMax == RainCollectorBarrel.smallWaterMax then
		if barrel.waterAmount >= barrel.waterMax * 0.75 then
			spriteName = "carpentry_02_55"
		else
			spriteName = "carpentry_02_54"
		end
	elseif barrel.waterMax == RainCollectorBarrel.largeWaterMax then
		if barrel.waterAmount >= barrel.waterMax * 0.75 then
			spriteName = "carpentry_02_53"
		else
			spriteName = "carpentry_02_52"
		end
	end
	if spriteName and (not barrelObject:getSprite() or spriteName ~= barrelObject:getSprite():getName()) then
		noise('sprite changed to '..spriteName..' at '..barrel.x..','..barrel.y..','..barrel.z)
		barrelObject:setSprite(spriteName)
		barrelObject:transmitUpdatedSpriteToClients()
	end
end

RainCollectorBarrel.OnWaterAmountChange = function(object, prevAmount)
    if isClient() then return end
	if not object then return end
	for iB,vB in ipairs(RainCollectorBarrel.barrels) do
		if vB.x == object:getX() and vB.y == object:getY() and vB.z == object:getZ() then
			noise('waterAmount changed to '..object:getWaterAmount()..' at '..vB.x..','..vB.y..','..vB.z)
			vB.waterAmount = object:getWaterAmount()
			RainCollectorBarrel.changeSprite(vB, object)
			break
		end
	end
end

RainCollectorBarrel.OnClientCommand = function(module, command, player, args)
	if module ~= 'object' then
		return
	end
	-- This takeWater command works for *any* object that the player takes
	-- water from, including sinks and rain barrels.
	if command == 'takeWater' then
		local gs = getCell():getGridSquare(args.x, args.y, args.z)
		if gs then
			for i=0,gs:getObjects():size()-1 do
				local obj = gs:getObjects():get(i)
				if obj:useWater(args.units) > 0 then
					obj:transmitModData()
					break
				end
			end
		end
	end
end

-- Called when the client adds an object to the map (which it shouldn't be able to)
RainCollectorBarrel.OnObjectAdded = function(object)
	if isClient() then return end
	if RainCollectorBarrel.isRainCollectorBarrelObject(object) then
		RainCollectorBarrel.loadRainBarrel(object)
	end
end

function RainCollectorBarrel.OnDestroyIsoThumpable(thump, player)
	if isClient() then return end
	if not thump:getSquare() or not RainCollectorBarrel.isRainCollectorBarrelObject(thump) then
		return
	end
	local sq = thump:getSquare()
	if not sq then return end
	for iB,vB in ipairs(RainCollectorBarrel.barrels) do
		if vB.x == sq:getX() and vB.y == sq:getY() and vB.z == sq:getZ() then
			noise('destroyed at '..vB.x..','..vB.y..','..vB.z)
			table.remove(RainCollectorBarrel.barrels, iB)
			break
		end
	end
end

function RainCollectorBarrel.DoSpecialTooltip(tooltipUI, square)
	local playerObj = getSpecificPlayer(0)
	if not playerObj or playerObj:getZ() ~= square:getZ() or
			playerObj:DistToSquared(square:getX() + 0.5, square:getY() + 0.5) > 2 * 2 then
		return
	end
	
	local barrel = RainCollectorBarrel.findObject(square)
	if not barrel or not barrel:getModData()["waterMax"] then return end

	local smallFontHgt = getTextManager():getFontFromEnum(UIFont.Small):getLineHeight()
	tooltipUI:setHeight(6 + smallFontHgt + 6 + smallFontHgt + 12)

	local textX = 12
	local textY = 6 + smallFontHgt + 6

	local barX = textX + getTextManager():MeasureStringX(UIFont.Small, getText("IGUI_invpanel_Remaining")) + 12
	local barWid = 80
	local barHgt = 4
	local barY = textY + (smallFontHgt - barHgt) / 2 + 2

	tooltipUI:setWidth(barX + barWid + 12)
	tooltipUI:DrawTextureScaledColor(nil, 0, 0, tooltipUI:getWidth(), tooltipUI:getHeight(), 0, 0, 0, 0.75)
	tooltipUI:DrawTextCentre(getText("ContextMenu_Rain_Collector_Barrel"), tooltipUI:getWidth() / 2, 6, 1, 1, 1, 1)
	tooltipUI:DrawText(getText("IGUI_invpanel_Remaining") .. ":", textX, textY, 1, 1, 1, 1)

	local f = barrel:getWaterAmount() / barrel:getModData()["waterMax"]
	local fg = { r=0.0, g=0.6, b=0.0, a=0.7 }
	if f < 0.0 then f = 0.0 end
	if f > 1.0 then f = 1.0 end
	local done = math.floor(barWid * f)
	if f > 0 then done = math.max(done, 1) end
	tooltipUI:DrawTextureScaledColor(nil, barX, barY, done, barHgt, fg.r, fg.g, fg.b, fg.a)
	local bg = {r=0.15, g=0.15, b=0.15, a=1.0}
	tooltipUI:DrawTextureScaledColor(nil, barX + done, barY, barWid - done, barHgt, bg.r, bg.g, bg.b, bg.a)
end

-- every 10 minutes we check if it's raining, to fill our water barrel
Events.EveryTenMinutes.Add(RainCollectorBarrel.checkRain);

-- when we load a gridsquare we check if there's any rain barrel on it, to add it on our map (so we can update the rain)
--Events.LoadGridsquare.Add(RainCollectorBarrel.LoadGridsquare);

Events.OnWaterAmountChange.Add(RainCollectorBarrel.OnWaterAmountChange)
Events.OnClientCommand.Add(RainCollectorBarrel.OnClientCommand)
Events.OnObjectAdded.Add(RainCollectorBarrel.OnObjectAdded)
Events.OnDestroyIsoThumpable.Add(RainCollectorBarrel.OnDestroyIsoThumpable);
--Events.DoSpecialTooltip.Add(RainCollectorBarrel.DoSpecialTooltip)

