// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using ClassicalSharp.Entities;
using ClassicalSharp.GraphicsAPI;
using ClassicalSharp.Map;
using OpenTK;
using BlockID = System.UInt16;
using TexLoc = System.UInt16;

namespace ClassicalSharp.Particles {

	public abstract class Particle {
		public Vector3 Velocity;
		public float Lifetime;
		public byte Size;
		protected Vector3 lastPos, nextPos;
		protected static bool hitTerrain;
		
		// http://www.opengl-tutorial.org/intermediate-tutorials/billboards-particles/billboards/
		public static void DoRender(ref Matrix4 view, ref Vector2 size, ref Vector3 pos, ref TextureRec rec,
		                            PackedCol col, VertexP3fT2fC4b[] vertices, ref int index) {
			float sX = size.X * 0.5f, sY = size.Y * 0.5f;
			Vector3 centre = pos; centre.Y += sY;
			Vector3 a, b;
			
			a.X = view.Row0.X * sX; a.Y = view.Row1.X * sX; a.Z = view.Row2.X * sX; // right * size.X * 0.5f
			b.X = view.Row0.Y * sY; b.Y = view.Row1.Y * sY; b.Z = view.Row2.Y * sY; // up * size.Y * 0.5f
			VertexP3fT2fC4b v; v.Col = col;
			
			v.X = centre.X - a.X - b.X; v.Y = centre.Y - a.Y - b.Y; v.Z = centre.Z - a.Z - b.Z;
			v.U = rec.U1; v.V = rec.V2; vertices[index++] = v;
			
			v.X = centre.X - a.X + b.X; v.Y = centre.Y - a.Y + b.Y; v.Z = centre.Z - a.Z + b.Z;
			              v.V = rec.V1; vertices[index++] = v;

			v.X = centre.X + a.X + b.X; v.Y = centre.Y + a.Y + b.Y; v.Z = centre.Z + a.Z + b.Z;
			v.U = rec.U2;               vertices[index++] = v;
			
			v.X = centre.X + a.X - b.X; v.Y = centre.Y + a.Y - b.Y; v.Z = centre.Z + a.Z - b.Z;
			              v.V = rec.V2; vertices[index++] = v;
		}
		
		public void ResetState(Vector3 pos, Vector3 velocity, double lifetime) {
			lastPos = nextPos = pos;
			Velocity = velocity;
			Lifetime = (float)lifetime;
		}

		protected bool Tick(Game game, float gravity, bool throughLiquids, double delta) {
			lastPos = nextPos;
			BlockID curBlock = GetBlock(game, (int)nextPos.X, (int)nextPos.Y, (int)nextPos.Z);
			float minY = Utils.Floor(nextPos.Y) + BlockInfo.MinBB[curBlock].Y;
			float maxY = Utils.Floor(nextPos.Y) + BlockInfo.MaxBB[curBlock].Y;			
			if (!CanPassThrough(game, curBlock, throughLiquids) && nextPos.Y >= minY && nextPos.Y < maxY && CollideHor(game, curBlock))
				return true;
			
			Velocity.Y -= gravity * (float)delta;
			int startY = (int)Math.Floor(nextPos.Y);
			nextPos += Velocity * (float)delta * 3;
			int endY = (int)Math.Floor(nextPos.Y);
			
			if (Velocity.Y > 0) {
				// don't test block we are already in
				for (int y = startY + 1; y <= endY && TestY(game, y, false, throughLiquids); y++);
			} else {
				for (int y = startY; y >= endY && TestY(game, y, true, throughLiquids); y--);
			}
			
			Lifetime -= (float)delta;
			return Lifetime < 0;
		}	
		
		bool TestY(Game game, int y, bool topFace, bool throughLiquids) {
			if (y < 0) {
				nextPos.Y = lastPos.Y = 0 + Entity.Adjustment;
				Velocity = Vector3.Zero;
				hitTerrain = true;
				return false;
			}
			
			BlockID block = GetBlock(game, (int)nextPos.X, y, (int)nextPos.Z);
			if (CanPassThrough(game, block, throughLiquids)) return true;
			Vector3 minBB = BlockInfo.MinBB[block];
			Vector3 maxBB = BlockInfo.MaxBB[block];
			float collideY = y + (topFace ? maxBB.Y : minBB.Y);
			bool collideVer = topFace ? (nextPos.Y < collideY) : (nextPos.Y > collideY);
			
			if (collideVer && CollideHor(game, block)) {
				float adjust = topFace ? Entity.Adjustment : -Entity.Adjustment;
				nextPos.Y = lastPos.Y = collideY + adjust;
				Velocity = Vector3.Zero;
				hitTerrain = true;
				return false;
			}
			return true;
		}
		
		bool CanPassThrough(Game game, BlockID block, bool throughLiquids) {
			byte draw = BlockInfo.Draw[block];
			return draw == DrawType.Gas || draw == DrawType.Sprite
				|| (throughLiquids && BlockInfo.IsLiquid[block]);
		}
		
		bool CollideHor(Game game, BlockID block) {
			Vector3 min = BlockInfo.MinBB[block] + FloorHor(nextPos);
			Vector3 max = BlockInfo.MaxBB[block] + FloorHor(nextPos);
			return nextPos.X >= min.X && nextPos.Z >= min.Z &&
				nextPos.X < max.X && nextPos.Z < max.Z;
		}
		
		BlockID GetBlock(Game game, int x, int y, int z) {
			if (game.World.IsValidPos(x, y, z))
				return game.World.GetBlock(x, y, z);
			
			if (y >= game.World.Env.EdgeHeight) return Block.Air;
			if (y >= game.World.Env.SidesHeight) return game.World.Env.EdgeBlock;
			return game.World.Env.SidesBlock;
		}
		
		static Vector3 FloorHor(Vector3 v) {
			return new Vector3(Utils.Floor(v.X), 0, Utils.Floor(v.Z));
		}
	}
	
	public sealed class RainParticle : Particle {
		static TextureRec rec = new TextureRec(2/128f, 14/128f, 3/128f, 2/128f);
		
		public bool Tick(Game game, double delta) {
			hitTerrain = false;
			return Tick(game, 3.5f, false, delta) || hitTerrain;
		}
		
		public void Render(Game game, float t, VertexP3fT2fC4b[] vertices, ref int index) {
			Vector3 pos = Vector3.Lerp(lastPos, nextPos, t);
			Vector2 size; size.X = Size * 0.015625f; size.Y = size.X;
			
			int x = Utils.Floor(pos.X), y = Utils.Floor(pos.Y), z = Utils.Floor(pos.Z);
			PackedCol col = game.World.IsValidPos(x, y, z) ? game.Lighting.LightCol(x, y, z) : game.Lighting.Outside;
			DoRender(ref game.Graphics.View, ref size, ref pos, ref rec, col, vertices, ref index);
		}
	}
	
	public sealed class TerrainParticle : Particle {
		internal TextureRec rec;
		internal TexLoc texLoc;
		internal BlockID block;		
		
		public bool Tick(Game game, double delta) {
			return Tick(game, 5.4f, true, delta);
		}
		
		public void Render(Game game, float t, VertexP3fT2fC4b[] vertices, ref int index) {
			Vector3 pos = Vector3.Lerp(lastPos, nextPos, t);
			Vector2 size; size.X = Size * 0.015625f; size.Y = size.X;

			PackedCol col = PackedCol.White;
			if (!BlockInfo.FullBright[block]) {
				int x = Utils.Floor(pos.X), y = Utils.Floor(pos.Y), z = Utils.Floor(pos.Z);
				col = game.World.IsValidPos(x, y, z) ? game.Lighting.LightCol_ZSide(x, y, z) : game.Lighting.OutsideZSide;
			}
			
			if (BlockInfo.Tinted[block]) { col *= BlockInfo.FogCol[block]; }
			DoRender(ref game.Graphics.View, ref size, ref pos, ref rec, col, vertices, ref index);
		}
	}
}