package scene3d;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.Ray;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.EventListener;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.SnapshotArray;

public class Stage3d extends InputAdapter implements Disposable {
	private float width, height;
	private final ModelBatch modelBatch;
	private Environment environment;

	private PerspectiveCamera camera;

	private final Group3d root;
	private Actor3d scrollFocus;
	private Actor3d keyboardFocus;
	
	public Touchable touchable = Touchable.disabled;
	private int selecting = -1;
    
    private boolean canHit = false;


	/** Creates a stage with a {@link #setViewport(float, float, boolean) viewport} equal to the device screen resolution. The stage
	 * will use its own {@link SpriteBatch}. */
	public Stage3d () {
		this(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
	}

	/** Creates a stage with the specified {@link #setViewport(float, float, boolean) viewport} that doesn't keep the aspect ratio.
	 * The stage will use its own {@link SpriteBatch}, which will be disposed when the stage is disposed. */
	public Stage3d (float width, float height) {
		this(width, height, false);
	}

	public Stage3d (float width, float height, boolean keepAspectRatio) {
		this.width = width;
		this.height = height;

		root = new Group3d();
		root.setStage3d(this);

		modelBatch = new ModelBatch();

		camera =  new Camera3d();
		environment = new Environment();
		environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.9f, 0.9f, 0.9f, 1f));
		environment.add(new DirectionalLight().set(0.8f, 0f, 0f, -1f, -0.8f, -0.2f));

		setViewport(width, height, keepAspectRatio);
	}
	
	public Stage3d (float width, float height, PerspectiveCamera camera) {
		this.width = width;
		this.height = height;
		root = new Group3d();
		root.setStage3d(this);
		modelBatch = new ModelBatch();
		this.camera = camera;
	}
	
	public Stage3d (float width, float height, PerspectiveCamera camera, Environment environment) {
		this.width = width;
		this.height = height;
		root = new Group3d();
		root.setStage3d(this);
		modelBatch = new ModelBatch();
		this.camera = camera;
		this.environment = environment;
	}


	public void setViewport (float width, float height) {
		setViewport(width, height, false, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
	}

	/** Sets up the stage size using a viewport that fills the entire screen.
	 * @see #setViewport(float, float, boolean, float, float, float, float) */
	public void setViewport (float width, float height, boolean keepAspectRatio) {
		setViewport(width, height, keepAspectRatio, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
	}

	/** Sets up the stage size and viewport. The viewport is the glViewport position and size, which is the portion of the screen
	 * used by the stage. The stage size determines the units used within the stage, depending on keepAspectRatio:
	 * <p>
	 * If keepAspectRatio is false, the stage is stretched to fill the viewport, which may distort the aspect ratio.
	 * <p>
	 * If keepAspectRatio is true, the stage is first scaled to fit the viewport in the longest dimension. Next the shorter
	 * dimension is lengthened to fill the viewport, which keeps the aspect ratio from changing. The {@link #getGutterWidth()} and
	 * {@link #getGutterHeight()} provide access to the amount that was lengthened.
	 * @param viewportX The top left corner of the viewport in glViewport coordinates (the origin is bottom left).
	 * @param viewportY The top left corner of the viewport in glViewport coordinates (the origin is bottom left).
	 * @param viewportWidth The width of the viewport in pixels.
	 * @param viewportHeight The height of the viewport in pixels. */
	public void setViewport (float stageWidth, float stageHeight, boolean keepAspectRatio, float viewportX, float viewportY,
			float viewportWidth, float viewportHeight) {
		if (keepAspectRatio) {
			if (viewportHeight / viewportWidth < stageHeight / stageWidth) {
				float toViewportSpace = viewportHeight / stageHeight;
				float toStageSpace = stageHeight / viewportHeight;
				float deviceWidth = stageWidth * toViewportSpace;
				float lengthen = (viewportWidth - deviceWidth) * toStageSpace;
				this.width = stageWidth + lengthen;
				this.height = stageHeight;
			} else {
				float toViewportSpace = viewportWidth / stageWidth;
				float toStageSpace = stageWidth / viewportWidth;
				float deviceHeight = stageHeight * toViewportSpace;
				float lengthen = (viewportHeight - deviceHeight) * toStageSpace;
				this.height = stageHeight + lengthen;
				this.width = stageWidth;
			}
		} else {
			this.width = stageWidth;
			this.height = stageHeight;
		}
		camera.viewportWidth = this.width;
		camera.viewportHeight = this.height;
	}

	public void draw(){ 
		camera.update();
		if (!root.isVisible()) return;
		modelBatch.begin(camera);
		root.draw(modelBatch, environment);
		modelBatch.end();
	}

	/** Calls {@link #act(float)} with {@link Graphics#getDeltaTime()}. */
	public void act () {
		act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f));
	}

	/** Calls the {@link Actor#act(float)} method on each actor in the stage. Typically called each frame. This method also fires
	 * enter and exit events.
	 * @param delta Time in seconds since the last frame. */
	public void act(float delta) {
		root.act(delta);
	}

	/** Adds an actor to the root of the stage.
	 * @see Group#addActor(Actor)
	 * @see Actor#remove() */
	public void addActor3d(Actor3d actor) {
		root.addActor3d(actor);
	}

	/** Adds an action to the root of the stage.
	 * @see Group#addAction3d(Action) */
	public void addAction3d(Action3d action) {
		root.addAction3d(action);
	}

	/** Returns the root's child actors.
	 * @see Group#getChildren() */
	public Array<Actor3d> getActors3d() {
		return root.getChildren();
	}

	/** Adds a listener to the root.
	 * @see Actor#addListener(EventListener) */
	public boolean addListener (Event3dListener listener) {
		return root.addListener(listener);
	}

	/** Removes a listener from the root.
	 * @see Actor#removeListener(EventListener) */
	public boolean removeListener (Event3dListener listener) {
		return root.removeListener(listener);
	}

	/** Removes the root's children, actions, and listeners. */
	public void clear () {
		unfocusAll();
		root.dispose();
		root.clear();
	}

	/** Removes the touch, keyboard, and scroll focused actors. */
	public void unfocusAll () {
		scrollFocus = null;
		keyboardFocus = null;
		//cancelTouchFocus();
	}

	/** Removes the touch, keyboard, and scroll focus for the specified actor and any descendants. */
	public void unfocus(Actor3d actor) {
		if (scrollFocus != null && scrollFocus.isDescendantOf(actor)) scrollFocus = null;
		if (keyboardFocus != null && keyboardFocus.isDescendantOf(actor)) keyboardFocus = null;
	}

	/** Sets the actor that will receive key events.
	 * @param actor May be null. */
	public void setKeyboardFocus (Actor3d actor) {
		if (keyboardFocus == actor) return;
	}

	/** Gets the actor that will receive key events.
	 * @return May be null. */
	public Actor3d getKeyboardFocus () {
		return keyboardFocus;
	}

	/** Sets the actor that will receive scroll events.
	 * @param actor May be null. */
	public void setScrollFocus(Actor3d actor) {
		if (scrollFocus == actor) return;
	}

	/** Gets the actor that will receive scroll events.
	 * @return May be null. */
	public Actor3d getScrollFocus () {
		return scrollFocus;
	}

	public ModelBatch getModelBatch () {
		return modelBatch;
	}

	public PerspectiveCamera getCamera () {
		return camera;
	}

	/** Sets the stage's camera. The camera must be configured properly or {@link #setViewport(float, float, boolean)} can be called
	 * after the camera is set. {@link Stage#draw()} will call {@link Camera#update()} and use the {@link Camera#combined} matrix
	 * for the SpriteBatch {@link SpriteBatch#setProjectionMatrix(com.badlogic.gdx.math.Matrix4) projection matrix}. */
	public void setCamera (PerspectiveCamera camera) {
		this.camera = camera;
	}

	/** Returns the root group which holds all actors in the stage. */
	public Group3d getRoot () {
		return root;
	}
	
	public void setEnvironment(Environment environment){
		this.environment = environment;
	}

	public Environment getEnvironment(){
		return environment;
	}
	
	public void enableHit(){
		canHit = true;
	}
	
	public void disableHit(){
		canHit = false;
	}

	@Override
	public boolean touchDown(int screenX, int screenY, int pointer, int button) {
		if(canHit){
			Actor3d actor3d = getObject(screenX, screenY);
			selecting = actor3d != null?1:-1;
			if(actor3d != null && actor3d.getName() != null)
				Gdx.app.log("", ""+actor3d.getName());
		}
        return selecting > 0;
		//return false;
	}

	@Override
	public boolean touchUp(int screenX, int screenY, int pointer, int button) {
		if (selecting >= 0) {
	         //setSelected(getObject(screenX, screenY));
	         selecting = -1;
	         return true;
	    }
	    return false;
		//if(touchable == Touchable.enabled)
		//	hit(screenX, screenY);
		//return false;
	}
	
	@Override
    public boolean touchDragged (int screenX, int screenY, int pointer) {
        return selecting >= 0;
    }
 
	Vector3 position = new Vector3();
	int result = -1;
    float distance = -1;
    
    public Actor3d getObject(int screenX, int screenY) {
    	 Actor3d temp = null;
    	 SnapshotArray<Actor3d> children = root.getChildren();
    	 Actor3d[] actors = children.begin();
         for(int i = 0, n = children.size; i < n; i++){
        	 temp = hit3d(screenX, screenY, actors[i]);
        	 if(actors[i] instanceof Group3d)
        		 temp = hit3d(screenX, screenY, (Group3d)actors[i]);
         }
         children.end();
         return temp;
    }
    
    public Actor3d hit3d(int screenX, int screenY, Actor3d actor3d) {
        Ray ray = camera.getPickRay(screenX, screenY);
        float distance = -1;
        final float dist2 = actor3d.intersects(ray);
        if (dist2 >= 0f && (distance < 0f || dist2 <= distance)) { 
            distance = dist2;
            return actor3d;
        }
        return null;
    }
    
    public Actor3d hit3d(int screenX, int screenY, Group3d group3d) {
    	 Actor3d temp = null;
    	 SnapshotArray<Actor3d> children = group3d.getChildren();
    	 Actor3d[] actors = children.begin();
         for(int i = 0, n = children.size; i < n; i++){
        	 temp = hit3d(screenX, screenY, actors[i]);
        	 if(actors[i] instanceof Group3d)
        		 temp = hit3d(screenX, screenY, (Group3d)actors[i]);
         }
         children.end();
         return temp;
    }

	@Override
	public void dispose() {
		modelBatch.dispose();
		clear();
	}
}