package projectzombie.entity.player;

import java.util.ArrayList;

import org.lwjgl.opengl.GL33;

import bdf.types.BdfNamedList;
import bdf.types.BdfObject;
import gl_engine.MathHelpers;
import gl_engine.matrix.Matrix4;
import gl_engine.vec.Vec2d;
import gl_engine.vec.Vec2i;
import gl_engine.vec.Vec3d;
import projectzombie.Main;
import projectzombie.display.DisplayLighting;
import projectzombie.entity.Entity;
import projectzombie.entity.EntityAlive;
import projectzombie.entity.EntityHasArmor;
import projectzombie.entity.EntityHasClothing;
import projectzombie.entity.EntityHasInventory;
import projectzombie.entity.EntityHasTasks;
import projectzombie.entity.EntityItem;
import projectzombie.entity.particle.ParticleBreak;
import projectzombie.init.Items;
import projectzombie.init.Models;
import projectzombie.init.Tiles;
import projectzombie.inventory.Inventory;
import projectzombie.inventory.InventoryArmor;
import projectzombie.inventory.InventoryClothing;
import projectzombie.items.ItemTool;
import projectzombie.items.modifier.ItemModifierMeta;
import projectzombie.model.Model;
import projectzombie.model.ModelPlayer;
import projectzombie.settings.Cheats;
import projectzombie.task.Task;
import projectzombie.task.TaskDeathScreen;
import projectzombie.tiles.TileSapling;
import projectzombie.util.ItemStack;
import projectzombie.util.TileState;
import projectzombie.world.chunk.Chunk;
import projectzombie.world.layer.Layer;

public class EntityPlayer extends Entity implements
	EntityAlive, EntityHasInventory, EntityHasArmor, EntityHasClothing, EntityHasTasks
{
	public boolean MOVE_FORWARD = false;
	public boolean MOVE_BACKWARD = false;
	public boolean MOVE_LEFT = false;
	public boolean MOVE_RIGHT = false;
	
	public boolean moving = false;
	
	public ModelPlayer MODEL = Models.ENTITY_PLAYER_W;
	
	private double health_max = 1000;
	private double health = health_max;
	private double temperature = 0.5;
	private double hydration = 1;
	private double break_progress = 0;
	private Vec2i break_pos = new Vec2i(0, 0);
	public boolean dead = false;
	public boolean in_animation = false;
	
	private ArrayList<Task> tasks;
	private Inventory inventory;
	private InventoryArmor armor;
	private InventoryClothing clothing;
	
	public int inventory_hand = 0;
	
	public double angle;
	
	private Vec2i last_chunk;
	
	public EntityPlayer(BdfObject bdf) {
		BdfClassLoad(bdf);
	}
	
	@Override
	public void BdfClassLoad(BdfObject bdf) {
		super.BdfClassLoad(bdf);
		BdfNamedList nl = bdf.getNamedList();
		health = nl.get("health").getDouble();
		dead = nl.get("dead").getBoolean();
		inventory = new Inventory(nl.get("inventory"));
		armor = new InventoryArmor(nl.get("armor"));
		clothing = new InventoryClothing(nl.get("clothing"));
		angle = nl.get("angle").getDouble();
		temperature = nl.get("temperature").getDouble();
		hydration = nl.get("hydration").getDouble();
		in_animation = nl.get("inAnimation").getBoolean();
		moving = nl.get("moving").getBoolean();
		inventory_hand = nl.get("hand").getInteger();
		
		tasks = new ArrayList<Task>();
		
		Task[] tasks = Task.loadTasks(this, nl.get("tasks"));
		
		for(int i=0;i<tasks.length;i++) {
			this.tasks.add(tasks[i]);
		}
		
		if(hydration > 1) {
			hydration = 1;
		}
	}
	
	public int getAmmo() {
		return inventory.getItemCount(new ItemStack(Items.AMMO, 1));
	}
	
	@Override
	public void BdfClassSave(BdfObject bdf) {
		super.BdfClassSave(bdf);
		BdfNamedList nl = bdf.getNamedList();
		nl.set("health", bdf.newObject().setDouble(health));
		nl.set("dead", bdf.newObject().setBoolean(dead));
		inventory.BdfClassSave(nl.get("inventory"));
		armor.BdfClassSave(nl.get("armor"));
		clothing.BdfClassSave(nl.get("clothing"));
		nl.set("angle", bdf.newObject().setDouble(angle));
		nl.set("temperature", bdf.newObject().setDouble(temperature));
		nl.set("hydration", bdf.newObject().setDouble(hydration));
		nl.set("tasks", Task.saveTasks(bdf.newObject(), tasks.toArray(new Task[0])));
		nl.set("inAnimation", bdf.newObject().setBoolean(in_animation));
		nl.set("moving", bdf.newObject().setBoolean(moving));
		nl.set("hand", bdf.newObject().setInteger(inventory_hand));
	}
	
	public EntityPlayer() {
		super(new Vec3d(0, 0, 0), new Vec3d(0, 0, 0));
		
		this.angle = 45;
		
		// Set some settings
		hitbox = 0.5;
		isSolid = true;
		emitsLight = true;
		
		tasks = new ArrayList<Task>();
		
		// Create the inventory
		inventory = new Inventory(42);
		armor = new InventoryArmor();
		clothing = new InventoryClothing();
	}
	
	@Override
	public double getLightLevel()
	{
		ItemStack item = inventory.getItem(inventory_hand);
		
		if(!item.isEmpty()) {
			return getLightWithHeight(item.item.getLightLevel(item));
		}
		
		return 0;
	}
	
	
	
	@Override
	public void tick(Chunk chunk, Layer layer)
	{
		chunk = layer.getChunk(getPos().xz());
		
		if(chunk != null && chunk.c_pos != null && (last_chunk == null || !chunk.c_pos.equal(last_chunk))) {
			last_chunk = chunk.c_pos;
			DisplayLighting.setDirty();
		}
		
		// Handle player deaths
		if(health <= 0 && !dead)
		{
			chunk.spawnEntity(new ParticleBreak(getPos(), getVelocity(), getModel()));
			
			if(Cheats.god_mode) {
				this.resetHealth();
			}
			
			else {
				dead = true;
				health = 0;
				addTask(new TaskDeathScreen(this));
			}
		}
		
		for(int i=0;i<tasks.size();i++) {
			Task task = tasks.get(i);
			if(task.isDone()) {
				tasks.remove(i);
				i -= 1;
			} else {
				task.update();
			}
		}
		
		// Is the player dead or in an animation
		if(dead || in_animation) return;
		
		// Call super
		super.tick(chunk, layer);
		
		double temp_diff = MathHelpers.biggest(
				layer.layergen.getTemperature(layer, getPos().xz()),
				chunk.getLightLevel(getPos().xz().toInt()) * 0.6) - temperature;
		
		temperature += temp_diff / 1000;
		hydration -= MathHelpers.smoothStep(Math.abs(temperature - 0.4) * 0.75 + 0.1) / 4000;
		
		if(temperature < 0.3) {
			health -= 2 * (0.3 - temperature);
		}
		
		if(temperature > 0.7) {
			health -= 4 * (temperature - 0.7);
		}
		
		if(hydration <= 0) {
			hydration = 0;
			health -= 0.1;
		}
		
		// Rotate left
		if(MOVE_LEFT) {
			this.angle -= 1;
		}
		
		// Rotate right
		if(MOVE_RIGHT) {
			this.angle += 1;
		}
		
		this.angle = MathHelpers.mod(this.angle, 360);
		
		// Move forward
		if(MOVE_FORWARD) {
			this.moveTowards(this.angle);
		}
		
		// Move backward
		if(MOVE_BACKWARD) {
			this.moveTowards(this.angle + 180);
		}
	}
	
	@Override
	public void kill()
	{
		// Is god mode inactive; kill the player
		if(Cheats.god_mode) {
			return;
		}
		
		chunk.spawnEntity(new ParticleBreak(getPos(), getVelocity(), getModel()));
		
		dead = true;
	}
	
	public void moveTowards(double angle, double speed) {
		if(dead || in_animation) return;
		super.push(MathHelpers.moveTowards2(speed, Math.toRadians(angle)).xny());
	}
	
	public void moveTowards(double angle) {
		this.moveTowards(angle, 0.08);
	}
	
	public void leftClick() {
		if(dead || in_animation) return;
		
		Layer layer = Main.world.getLayer();
		ItemStack is = inventory.getItem(inventory_hand);
		Vec2d place_pos = getPos().xz().add(MathHelpers.moveTowards2(0.5, Math.toRadians(angle)));
		
		if(is.isEmpty() || !is.item.onPlayerLeftClick(is, Main.world.getLayer(), this, place_pos))
		{
			boolean isTool = is.item instanceof ItemTool;
			ItemTool tool = isTool ? (ItemTool) is.item : null;
			double speed = isTool ? ((ItemTool) is.item).toolSpeed(is) : 1;
			Vec2i pos = place_pos.toInt();
			
			for(int ti=0;ti<2;ti++)
			{
				TileState ts;
				
				if(ti == 0) {
					ts = layer.getFrontTile(pos);
				} else {
					ts = layer.getBackTile(pos);
				}
				
				if(ts.tile.canTileBreak(ts, is, tool))
				{
					break_progress += speed;
					
					if(!pos.equal(break_pos)) {
						break_progress = 0;
						break_pos = pos;
					}
					
					if(break_progress > ts.tile.hardness)
					{
						ItemStack[] stacks = ts.tile.getTileDrops(ts);
						
						for(ItemStack stack : stacks) {
							layer.spawnEntity(new EntityItem(
									pos.toDouble().add(new Vec2d(0.5, 0.5)).xny(),
									new Vec3d(0, 0, 0), stack));
						}
						
						if(ts.tile.doBreak(layer, chunk, ts, pos)) {
							if(ti == 0) {
								layer.breakFrontTile(pos);
							} else {
								layer.breakBackTile(pos);
							}
						}
						
						break_progress = 0;
						
						if(tool != null) {
							tool.toolOnUse(is);
						}
					}
					
					break;
				}
			}
		}
	}
	
	public void rightClick() {
		if(dead || in_animation) return;
		
		ItemStack is = inventory.getItem(inventory_hand);
		Vec2d movement = MathHelpers.moveTowards2(1, Math.toRadians(angle));
		Vec2d place_pos = getPos().xz().add(movement);
		
		if(!activateTile(getPos().xz().add(movement.multiply(0.5)).toInt()) && !is.isEmpty()) {
			is.item.onPlayerRightClick(is, Main.world.getLayer(), this, place_pos);
		}
	}

	@Override
	public void addHealth(double amount) {
		health += amount;
		if(health > health_max) health = health_max;
	}

	@Override
	public void addDamage(double amount) {
		//amount = amount / (defence_level / 2.5 + 1);
		health -= amount;
	}
	
	@Override
	public void addFireDamage(double amount) {
		addDamage(amount);
	}
	
	@Override
	public void addBlastDamage(double amount) {
		addDamage(amount);
	}

	@Override
	public double getHealth() {
		return health;
	}

	@Override
	public void resetHealth() {
		health = health_max;
	}

	@Override
	public void clearHealth() {
		health = 0;
	}

	@Override
	public double maxHealth() {
		return health_max;
	}

	@Override
	public void setHealth(double health) {
		this.health = health;
	}

	@Override
	public Inventory getInventory() {
		return inventory;
	}
	
	public void dropItem()
	{
		ItemStack i = inventory.getItem(inventory_hand);
		
		if(!i.isEmpty())
		{
			ItemStack stack = i.copy();
			stack.count = 1;
			
			EntityItem e = new EntityItem(getPos(), getVelocity(), stack, Math.toRadians(angle));
			Main.world.getLayer().spawnEntity(e);
			i.count -= 1;
		}
	}
	
	public void render()
	{
		Layer layer = Main.world.getLayer();
		Model model = getModel();
		model.setModel(Matrix4.translate(0, getPos().y, 0));
		model.render();
		
		ItemStack holding = inventory.getItem(inventory_hand);
		
		if(holding != null && !holding.isEmpty())
		{
			Vec3d ppos = getPos();
			
			Model model_place = holding.item.getPlaceModel(holding);
			Model model_spawn = holding.item.getSpawnModel(holding);
			Vec2d pos = ppos.xz().add(MathHelpers.moveTowards2(1, Math.toRadians(angle)));
			boolean render_place_model = model_place != null && layer.getFrontTile(pos.toInt()).tile == Tiles.VOID;
			
			if(render_place_model || model_spawn != null)
			{
				GL33.glUniform4f(Main.window.glsl_color, 0.5f, 0.75f, 0.5f, 1);
			}
			
			if(render_place_model)
			{
				model_place.setModel(Matrix4.translate(Math.floor(pos.x) - ppos.x + 0.5, 0, Math.floor(pos.y) - ppos.z + 0.5));
				model_place.render();
			}
			
			if(model_spawn != null)
			{
				model_spawn.setModel(Matrix4.translate(pos.x - ppos.x, 0, pos.y - ppos.z));
				model_spawn.render();
			}
			
			if(render_place_model || model_spawn != null)
			{
				GL33.glUniform4f(Main.window.glsl_color, 1, 1, 1, 1);
			}
		}
	}
	
	@Override
	public int bloodParticles() {
		return 12;
	}

	@Override
	public Model getModel() {
		return moving ? MODEL.model_back_moving : MODEL.model_back_still;
	}
	
	public double getTemperature() {
		return temperature;
	}

	public double getHydration() {
		return hydration;
	}
	
	public void setInAnimation(boolean status)
	{
		in_animation = status;
		hasGravity = !status;
		isSolid = !status;
		moving = status;
	}
	
	@Override
	public void addTask(Task task) {
		tasks.add(task);
	}
	
	public void rehydrate()
	{
		hydration += 0.001;
		
		if(hydration > 1) {
			hydration = 1;
		}
	}

	@Override
	public InventoryClothing getInventoryClothing() {
		return clothing;
	}

	@Override
	public InventoryArmor getInventoryArmor() {
		return armor;
	}
}
