package studio.junk.jam.coloritum;

import com.badlogic.ashley.core.Component;
import com.badlogic.ashley.core.Entity;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;

import groovy.lang.Closure;
import groovy.lang.DelegatesTo;
import studio.junk.jam.coloritum.entities.components.Collision;
import studio.junk.jam.coloritum.entities.components.Name;
import studio.junk.jam.coloritum.entities.components.TilePosition;

/**
 * Script base for entity type creators.
 * <p>
 * This class is used as base class for groovy scripts.
 * 
 * @author cryptjar
 */
public abstract class EntityTypeScript extends NamedScript implements EntityType {

	/**
	 * Defines the closure environment for defining a entity.
	 * 
	 * @author cryptjar
	 */
	protected class EntitySpec {
		final Entity ent;

		public EntitySpec(Entity entity) {
			ent = entity;

			add(new Name(scriptName));
		}

		/**
		 * Adds a entity to the underling entity.
		 * 
		 * @param c
		 *            the component to add
		 */
		public void add(Component c) {
			ent.add(c);
		}

		/**
		 * Creates a circular Box2D entry and adds Collision and Position
		 * component.
		 * 
		 * @param pos
		 *            the position of the entity
		 * @param radius
		 *            the radius of the entity
		 */
		public void addStatic(Vector2 pos, float radius) {
			BodyDef def = new BodyDef();

			def.type = BodyDef.BodyType.StaticBody;
			def.position.set(pos).add(0.5f, 0.5f);

			Body body = getBox2D().createBody(def);

			body.setUserData(ent);

			FixtureDef df = new FixtureDef();
			df.shape = new CircleShape();
			df.shape.setRadius(radius);

			body.createFixture(df);

			df.shape.dispose();

			add(new Collision(body));
			add(new TilePosition(body.getWorldCenter()));

		}

		public void addStaticBox(Vector2 pos, Vector2 size) {
			addStaticBox(pos, size, 0);
		}

		/**
		 * Creates a circular Box2D entry and adds Collision and Position
		 * component.
		 * 
		 * @param pos
		 *            the position of the entity
		 * @param radius
		 *            the radius of the entity
		 */
		public void addStaticBox(Vector2 pos, Vector2 size, float angle) {
			BodyDef def = new BodyDef();

			def.type = BodyDef.BodyType.StaticBody;
			def.position.set(pos).add(0.5f, 0.5f);
			def.angle = angle;


			Body body = getBox2D().createBody(def);

			body.setUserData(ent);

			FixtureDef df = new FixtureDef();
			PolygonShape ps = new PolygonShape();
			ps.setAsBox(size.x / 2, size.y / 2);
			df.shape = ps;

			body.createFixture(df);

			df.shape.dispose();

			add(new Collision(body));
			add(new TilePosition(body.getWorldCenter()));

		}

		/**
		 * Creates a circular Box2D entry and adds Collision, Position and
		 * Velocity component.
		 * 
		 * @param pos
		 *            the position of the entity
		 * @param radius
		 *            the radius of the entity
		 */
		public void addDynamic(Vector2 pos, Vector2 velo, float radius) {
			BodyDef def = new BodyDef();

			def.type = BodyDef.BodyType.DynamicBody;
			def.bullet = true;
			def.position.set(pos).add(0.5f, 0.5f);
			def.linearVelocity.set(velo);

			Body body = getBox2D().createBody(def);

			body.setUserData(ent);

			FixtureDef df = new FixtureDef();
			df.isSensor = true;
			df.shape = new CircleShape();
			df.shape.setRadius(radius);

			body.createFixture(df);

			df.shape.dispose();

			add(new Collision(body));
			add(new TilePosition(body.getWorldCenter()));

		}

		/**
		 * Creates a circular Box2D entry and adds Collision, Position and
		 * Velocity component.
		 * 
		 * @param pos
		 *            the position of the entity
		 * @param radius
		 *            the radius of the entity
		 */
		public void addDynamicBox(Vector2 pos, Vector2 size, Vector2 velo, boolean rotating) {
			BodyDef def = new BodyDef();

			def.fixedRotation = !rotating;

			def.type = BodyDef.BodyType.DynamicBody;
			def.bullet = true;
			def.position.set(pos).add(0.5f, 0.5f);
			def.linearVelocity.set(velo);

			Body body = getBox2D().createBody(def);

			FixtureDef df = new FixtureDef();
			df.isSensor = true;
			PolygonShape ps = new PolygonShape();
			ps.setAsBox(size.x, size.y);
			df.shape = ps;

			body.createFixture(df);

			body.setUserData(ent);

			df.shape.dispose();

			add(new Collision(body));
			add(new TilePosition(body.getWorldCenter()));

		}
	}

	/**
	 * Gets the texture of the given name.
	 * 
	 * @param name
	 *            the asset name of the texture
	 * @return the texture
	 */
	protected Animation getAnim(String name) {
		Object obj = GameState.state.assets.get(name);

		if (obj == null) {
			System.err.println("In " + scriptName + ": asset " + name + " does not exist.");
			return null;
		} else if (!(obj instanceof Animation)) {
			System.err.println("In " + scriptName + ": asset " + name + " is no texture.");
			return null;
		}

		return (Animation) obj;
	}

	/**
	 * Gets the texture of the given name.
	 * 
	 * @param name
	 *            the asset name of the texture
	 * @return the texture
	 */
	protected TextureRegion getTexture(String name) {
		Object obj = GameState.state.assets.get(name);

		if (obj == null) {
			System.err.println("In " + scriptName + ": asset " + name + " does not exist.");
			return null;
		} else if (!(obj instanceof TextureRegion)) {
			System.err.println("In " + scriptName + ": asset " + name + " is no texture.");
			return null;
		}

		return (TextureRegion) obj;
	}

	/**
	 * Creates a new entity with a closure.
	 * 
	 * @param cl
	 *            the closure which defines the entity with the
	 *            {@link EntitySpec}
	 * @return the entity specified by the closure
	 */
	protected Entity entity(@DelegatesTo(EntitySpec.class) Closure<?> cl) {
		Entity e = new Entity();

		Closure<?> code = cl.rehydrate(new EntitySpec(e), this, this);
		code.setResolveStrategy(Closure.DELEGATE_ONLY);
		code.call();

		return e;
	}

	/**
	 * Gets the Box2D world.
	 * 
	 * @return Box2D world
	 */
	protected World getBox2D() {
		return GameState.state.world;
	}

	@Override
	public Entity create(String name, Object... args) {
		setProperty("args", args);
		Entity e = (Entity) run();
		e.add(new Name(name));

		GameState.state.engine.addEntity(e);

		return e;
	}

}
