package xaero.rotatenjump.game.object;

import java.util.ArrayList;
import java.util.List;

import xaero.rotatenjump.GameView;
import xaero.rotatenjump.game.Game;
import xaero.rotatenjump.game.graphics.Graphics;
import xaero.rotatenjump.misc.Transformations;

/**
 * Created by xaero on 06/07/2017.
 */

public class Player extends EntityWithVelocity {
    //private static final int MAX_COLLISION_RUNS = 20;
    public static final float PLAYER_RADIUS = 10;
    private static final float COLLISION_SCALE = 5f;//the bigger the scale the smaller the area is used for filtering entities for collision
    //private static final Model STAR_MODEL = new NinjaStar(PLAYER_RADIUS);
    private static final float[] CIRCLE_COLOUR = {0.2f, 0.2f, 0.2f, 0.3f};

    private Platform attachedTo;
    private float attachXOffset;
    private float attachYOffset;
    private float platformRotationOnAttach;
    private float rotationOnAttach;
    //actual coordinates of where the player got attached, needed for proper animation (the player hit the platform mid-rotation (of the platform) (due to collisionPartial)), needs to be moved to the hit spot first
    //this way you will see a proper representation of what happened during the tick calculation
    //after hitting the platform mid rotation the player is instantly moved by the rest of the rotation, so without this it would just linearly move to the already rotated spot
    private float attachX;
    private float attachY;
    private float attachCollisionPartial = -1;
    private float nextAttachCollisionPartial = -1;

    private float lastRotationRender;
    protected float rotation;

    private float rotationRender;//new value
    public boolean shouldDeattach;
    private boolean dead = false;

    public Player(){
        super(0, 0, PLAYER_RADIUS * 2, PLAYER_RADIUS * 2, 0);
        attachedTo = null;
    }

    public void moveTo(float x, float y){
        posX = posXRender = lastPosXRender = x;
        posY = posYRender = lastPosXRender = y;
    }

    @Override
    public void preUpdate() {
        super.preUpdate();
    }

    /*
     * Reusable objects for better performance
     */
    private ArrayList<Coin> totalEnemiesHit = new ArrayList<>();
    private ArrayList<Coin> subTickEnemiesHit = new ArrayList<>();
    private List<Entity> platformsBuffer = new ArrayList<>();//used for storing filtered results
    private List<Entity> othersBuffer = new ArrayList<>();//used for storing filtered results
    private List<Entity> collectablesBuffer = new ArrayList<>();//used for storing filtered results
    private float[][] points = new float[4][2];

    @Override
    public void update(Game game){
        super.update(game);
        if(shouldDeattach){
            deattach();
            shouldDeattach = false;
        }
        if(dead)
            return;
        if(!GameView.DEBUG) {
            if (attachedTo == null) {
                velocityX += game.getDirectedGravityX();//gravity
                velocityY += game.getDirectedGravityY();//gravity
                float velocity = fixVelocity();//applies max velocity constraint
                totalEnemiesHit.clear();
                Entity otherCollision = null;
                int runs = (int)Math.max(velocity, 1);
                /*
                 * Find the "visible" entities
                 */
                game.visibleOnlyFilter(true, game.getPlatforms(), posX, posY, COLLISION_SCALE, 0, 0, platformsBuffer);
                game.visibleOnlyFilter(true, game.getOthers(), posX, posY, COLLISION_SCALE, 0, 0, othersBuffer);
                game.visibleOnlyFilter(true, game.getCoins(), posX, posY, COLLISION_SCALE, 0,0, collectablesBuffer);
                /*
                 * Check for collisions
                 */
                for (int i = 0; i < runs; i++) {
                    rotation -= 30.0f / runs;
                    posX += velocityX / runs;
                    posY += velocityY / runs;
                    getFourPoints(0, points);
                    float collisionPartial = (i + 1f) / runs;
                    Platform col = (Platform)findCollision(platformsBuffer, points, collisionPartial);
                    if (col != null)
                        attach(game, col, collisionPartial);
                    totalEnemiesHit.addAll(getHitCoins(game, totalEnemiesHit, points, collisionPartial));//different because we should be able to hit multiple coins
                    otherCollision = findCollision(othersBuffer, points, collisionPartial);
                    if (col != null)
                        break;
                }
                for (Coin e : totalEnemiesHit)
                    e.getHit(game);
                if (otherCollision != null)
                    otherCollision.collide(this, points);
            }
            if(attachedTo != null)
                attachedTo.house(game, this);
        }
    }

    private ArrayList<Coin> getHitCoins(Game game, ArrayList<Coin> soFar, float[][] points, float collisionPartial) {
        subTickEnemiesHit.clear();
        for(int i = 0; i < collectablesBuffer.size(); i++){
            Coin e = (Coin) collectablesBuffer.get(i);
            if(soFar.contains(e))
                continue;
            if(e.isCollidingWith(this, (float)Math.sqrt(Math.pow(e.posX - posX, 2) + Math.pow(e.posY - posY, 2)), points, collisionPartial))
                subTickEnemiesHit.add(e);
        }
        return subTickEnemiesHit;
    }

    public Entity findCollision(List<Entity> entities, float[][] points, float collisionPartial){
        Entity closest = null;
        double closestDistance = -1;
        double closestDistanceE = -1;
        for(int i = 0; i < entities.size(); i++) {
            Entity e = entities.get(i);
            if(e.goThrough > 0)
                continue;
            double d = Math.sqrt(Math.pow(posX - e.getPosX(collisionPartial), 2) + Math.pow(posY - e.getPosY(collisionPartial), 2));
            double dE = d;
            if(e instanceof Wall){
                if (e.isCollidingWith(this, (float)d, points, collisionPartial)) {
                    return e;
                }
            } else {
                if(e instanceof Platform)
                    dE -= ((Platform)e).getSize();
                if (closest == null || dE < closestDistanceE){
                    closest = e;
                    closestDistance = d;
                    closestDistanceE = dE;
                }
            }
        }
        if(closest != null && closest.isCollidingWith(this, (float)closestDistance, points, collisionPartial))//circles would collide
            return closest;
        return null;
    }

    public void addVelocity(float x, float y){
        velocityX += x;
        velocityY += y;
    }

    public Platform getAttached(){
        return attachedTo;
    }

    public void attach(Game game, Platform p, float collisionPartial){
        velocityX = 0;
        velocityY = 0;
        attachedTo = p;
        attachXOffset = posX - p.getPosX(collisionPartial);
        attachYOffset = posY - p.getPosY(collisionPartial);
        platformRotationOnAttach = p.getRotation(collisionPartial);
        rotationOnAttach = rotation;
        nextAttachCollisionPartial = collisionPartial;
        attachX = posX;
        attachY = posY;
        //nextPartialTickMultiplier = 1f / collisionPartial;
        p.onAttach(game, this);
    }

    public void deattach(){
        if(attachedTo != null) {
            attachedTo.onDettach(this);
            attachedTo.goThrough = 3;
            attachedTo = null;
        }
    }

    @Override
    public void refresh() {
        super.refresh();
        lastRotationRender = rotationRender;
        rotationRender = rotation;
        attachCollisionPartial = nextAttachCollisionPartial;
        nextAttachCollisionPartial = -1;
        //partialTickMultiplier = nextPartialTickMultiplier;
        //nextPartialTickMultiplier = 1;
    }

    private float toPosX;//to not create them each render + to use by the camera
    private float toPosY;
    private float toRotation;
    private float usedPartialTicks;
    public float getToPosX() {
        return toPosX;
    }
    public float getToPosY() {
        return toPosY;
    }
    public float getToRotation() {
        return toRotation;
    }
    public float getUsedPartialTicks() {
        return usedPartialTicks;
    }

    public void calculateRenderValues(float partialTicks){
        toPosX = posXRender;
        toPosY = posYRender;
        toRotation = rotationRender;
        usedPartialTicks = partialTicks;
        if(attachCollisionPartial != -1){
            if(partialTicks <= attachCollisionPartial) {
                toPosX = attachX;
                toPosY = attachY;
                toRotation = rotationOnAttach;
                usedPartialTicks = partialTicks / attachCollisionPartial;
            } else {
                lastPosXRender = attachX;
                lastPosYRender = attachY;
                lastRotationRender = rotationOnAttach;
                usedPartialTicks = (partialTicks - attachCollisionPartial) / (1f - attachCollisionPartial);
            }
        }
    }

    @Override
    public void render(Game game, float partialTicks){
        //values already calculated for camera, so second time not needed
        if(dead)
            return;
        if(attachedTo == null || attachCollisionPartial != -1 && partialTicks <= attachCollisionPartial) {
            Graphics.color = CIRCLE_COLOUR;
            Graphics.CIRCLE.draw(lastPosXRender + (toPosX - lastPosXRender) * usedPartialTicks, lastPosYRender + (toPosY - lastPosYRender) * usedPartialTicks, lastRotationRender + (toRotation - lastRotationRender) * usedPartialTicks, 0, 0, PLAYER_RADIUS, PLAYER_RADIUS, 1);
        }
        Graphics.color = Graphics.DKGRAY;
        Graphics.modelsLoadedFromFiles[5].draw(lastPosXRender + (toPosX - lastPosXRender) * usedPartialTicks, lastPosYRender + (toPosY - lastPosYRender) * usedPartialTicks, lastRotationRender + (toRotation - lastRotationRender) * usedPartialTicks, 0, 0, PLAYER_RADIUS, PLAYER_RADIUS, PLAYER_RADIUS);
        //STAR_MODEL.draw(lastPosX + (toPosX - lastPosX) * usedPartialTicks, lastPosY + (toPosY - lastPosY) * usedPartialTicks, lastRotation + (toRotation - lastRotation) * usedPartialTicks, 0, 0, 1 , 1, 1);
    }

    public void getFourPoints(float error, float[][] dest){
        for(int i = 0; i < 4; i++)
            Transformations.rotate(Player.PLAYER_RADIUS - error /*so that it goes a little deeper than 1 pixel*/, 0, i * 90 + rotation, dest[i]);//gets one of 4 player points
    }

    public void setDead(boolean h){
        dead = h;
    }

    public boolean isDead(){
        return dead;
    }

    public float getRotation(){
        return rotation;
    }

    public float getAttachXOffset(){
        return attachXOffset;
    }

    public float getAttachYOffset(){
        return attachYOffset;
    }

    public float getPlatformRotationOnAttach(){
        return platformRotationOnAttach;
    }

    public float getRotationOnAttach(){
        return rotationOnAttach;
    }

}
