// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using ClassicalSharp.Map;
using BlockID = System.UInt16;
using BlockRaw = System.Byte;

namespace ClassicalSharp.Singleplayer {

	public class FoliagePhysics {
		Game game;
		PhysicsBase physics;
		World map;
		Random rnd = new Random();
		
		public FoliagePhysics(Game game, PhysicsBase physics) {
			this.game = game;
			this.physics = physics;
			map = game.World;
			
			physics.OnPlace[Block.Sapling] = HandleSapling;
			physics.OnRandomTick[Block.Sapling] = HandleSapling;
			physics.OnRandomTick[Block.Dirt] = HandleDirt;
			physics.OnRandomTick[Block.Grass] = HandleGrass;
			
			physics.OnRandomTick[Block.Dandelion] = HandleFlower;
			physics.OnRandomTick[Block.Rose] = HandleFlower;
			physics.OnRandomTick[Block.RedMushroom] = HandleMushroom;
			physics.OnRandomTick[Block.BrownMushroom] = HandleMushroom;
		}
		
		void HandleSapling(int index, BlockRaw block) {
			int x = index % map.Width;
			int y = (index / map.Width) / map.Length;
			int z = (index / map.Width) % map.Length;
			
			BlockRaw below = Block.Air;
			if (y > 0) below = map.blocks[index - map.OneY];
			if (below == Block.Grass) GrowTree(x, y, z);
		}
		
		void HandleDirt(int index, BlockRaw block) {
			int x = index % map.Width;
			int y = (index / map.Width) / map.Length;
			int z = (index / map.Width) % map.Length;
			
			if (game.Lighting.IsLit(x, y, z))
				game.UpdateBlock(x, y, z, Block.Grass);
		}
		
		void HandleGrass(int index, BlockRaw block) {
			int x = index % map.Width;
			int y = (index / map.Width) / map.Length;
			int z = (index / map.Width) % map.Length;
			
			if (!game.Lighting.IsLit(x, y, z))
				game.UpdateBlock(x, y, z, Block.Dirt);
		}
		
		void HandleFlower(int index, BlockRaw block) {
			int x = index % map.Width;
			int y = (index / map.Width) / map.Length;
			int z = (index / map.Width) % map.Length;
			
			if (!game.Lighting.IsLit(x, y, z)) {
				game.UpdateBlock(x, y, z, Block.Air);
				physics.ActivateNeighbours(x, y, z, index);
				return;
			}
			
			BlockRaw below = Block.Dirt;
			if (y > 0) below = map.blocks[index - map.Width * map.Length];
			if (!(below == Block.Dirt || below == Block.Grass)) {
				game.UpdateBlock(x, y, z, Block.Air);
				physics.ActivateNeighbours(x, y, z, index);
			}
		}
		
		void HandleMushroom(int index, BlockRaw block) {
			int x = index % map.Width;
			int y = (index / map.Width) / map.Length;
			int z = (index / map.Width) % map.Length;
			
			if (game.Lighting.IsLit(x, y, z)) {
				game.UpdateBlock(x, y, z, Block.Air);
				physics.ActivateNeighbours(x, y, z, index);
				return;
			}
			
			BlockRaw below = Block.Stone;
			if (y > 0) below = map.blocks[index - map.OneY];
			if (!(below == Block.Stone || below == Block.Cobblestone)) {
				game.UpdateBlock(x, y, z, Block.Air);
				physics.ActivateNeighbours(x, y, z, index);
			}
		}
		
		// Algorithm source: Looking at the trees generated by the default classic server.
		// Hence, the random thresholds may be slightly off.
		public void GrowTree(int x, int y, int z) {
			int trunkH = rnd.Next(1, 4);
			game.UpdateBlock(x, y, z, Block.Air);

			// Can the new tree grow?
			if (!CheckBounds(x, x, y, y + trunkH - 1, z, z) ||
			   !CheckBounds(x - 2, x + 2, y + trunkH, y + trunkH + 1, z - 2, z + 2) ||
			   !CheckBounds(x - 1, x + 1, y + trunkH + 2, y + trunkH + 3, z - 1, z + 1)) {
				game.UpdateBlock(x, y, z, Block.Sapling);
				return;
			}
			
			// Leaves bottom layer
			y += trunkH;
			for (int zz = -2; zz <= 2; zz++) {
				for (int xx = -2; xx <= 2; xx++) {
					if (Math.Abs(xx) == 2 && Math.Abs(zz) == 2) {
						if (rnd.Next(0, 5) < 4) game.UpdateBlock(x + xx, y, z + zz, Block.Leaves);
						if (rnd.Next(0, 5) < 2) game.UpdateBlock(x + xx, y + 1, z + zz, Block.Leaves);
					} else {
						game.UpdateBlock(x + xx, y, z + zz, Block.Leaves);
						game.UpdateBlock(x + xx, y + 1, z + zz, Block.Leaves);
					}
				}
			}
			
			// Leaves top layer
			y += 2;
			for (int zz = -1; zz <= 1; zz++) {
				for (int xx = -1; xx <= 1; xx++) {
					if (xx == 0 || zz == 0) {
						game.UpdateBlock(x + xx, y, z + zz, Block.Leaves);
						game.UpdateBlock(x + xx, y + 1, z + zz, Block.Leaves);
					} else if (rnd.Next(0, 5) == 0) {
						game.UpdateBlock(x + xx, y, z + zz, Block.Leaves);
					}
				}
			}
			
			// Base trunk
			y -= 2 + trunkH;
			for (int yy = 0; yy < trunkH + 3; yy++)
				game.UpdateBlock(x, y + yy, z, Block.Log);
		}
		
		bool CheckBounds(int x1, int x2, int y1, int y2, int z1, int z2) {
			for (int y = y1; y <= y2; y++)
				for (int z = z1; z <= z2; z++)
					for (int x = x1; x <= x2; x++)
			{
				if (!map.IsValidPos(x, y, z)) return false;
				
				BlockID block = map.GetBlock(x, y, z);
				if (!(block == 0 || block == Block.Leaves)) return false;
			}
			return true;
		}
	}
}