package sig.objects;

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

import sig.RabiClone;
import sig.engine.Action;
import sig.engine.Alpha;
import sig.engine.Panel;
import sig.engine.Rectangle;
import sig.engine.Sprite;
import sig.engine.Transform;
import sig.engine.objects.AnimatedObject;
import sig.map.DataTile;
import sig.map.Map;
import sig.map.Tile;
import sig.map.View;
import sig.objects.actor.PhysicsObject;
import sig.objects.actor.State;
import sig.objects.weapons.KnifeSwing;
import sig.utils.TimeUtils;

public class Player extends PhysicsObject{
    final static boolean LEFT = false;
    final static boolean RIGHT = true;
    final static long jump_fall_AnimationWaitTime = TimeUtils.millisToNanos(200);
    final static long slide_AnimationWaitTime = TimeUtils.millisToNanos(100);
    final static long slide_duration = TimeUtils.millisToNanos(700);
    final static long bellySlideDuration = TimeUtils.millisToNanos(400);
    final static long weaponSwingAnimationTime = TimeUtils.millisToNanos(333);
    final static long weaponComboWaitTime = TimeUtils.millisToNanos(60);
    final static double finalComboJumpBackSpeedX = -185;
    final static double finalComboJumpBackSpeedY = -110;

    List<PhysicsObject> collisionBatch = new ArrayList<PhysicsObject>();

    long weaponSwingTime = 0;

    State prvState = state;

    final double viewBoundaryX = RabiClone.BASE_WIDTH / 3;
    final double viewBoundaryY = RabiClone.BASE_HEIGHT / 3;
    View lastCameraView = View.FIXED;
    boolean facing_direction = RIGHT;
    boolean landedBellySlide=false;

    long spacebarPressed = RabiClone.TIME;
    long jump_slide_fall_StartAnimationTimer = -1;
    long slide_time = -1;
    long jumpHoldTime = TimeUtils.millisToNanos(150);
    long bellySlideTime = -1;

    final static long slideBufferTime = TimeUtils.millisToNanos(200);
    long slidePressed = -1;

    boolean onSlope = false;

    public Player(Panel panel) {
        super(Sprite.ERINA, 5, panel);
        setX(RabiClone.BASE_WIDTH / 2 - getAnimatedSpr().getWidth() / 2);
        setY(RabiClone.BASE_HEIGHT * (2 / 3d) - getAnimatedSpr().getHeight() / 2);
        setAccelerationLimits_UseDefaultStrategy();
        setVelocityLimits_UseDefaultStrategy();
        setGroundDrag_UseDefaultStrategy();
        setGroundFriction_UseDefaultStrategy();
        setAirDrag_UseDefaultStrategy();
        setAirFriction_UseDefaultStrategy();
        setSlidingVelocity_UseDefaultStrategy();
        setSlidingAcceleration_UseDefaultStrategy();
        setJumpVelocity_UseDefaultStrategy();
        setMaxJumpCount_UseDefaultStrategy();
        setGravity_UseDefaultStrategy();
    }

    @Override
    public Rectangle setCollisionBounds() {
        return new Rectangle(10,6,12,25);
    }

    @Override
    public void update(double updateMult) {
        super.update(updateMult);
        handleCameraRoomMovement();
        handleCollisionBatch();
        handleEventCollisions();

        switch (state) {
            case ATTACK:
                if (RabiClone.TIME - weaponSwingTime > weaponSwingAnimationTime) {
                    state=State.IDLE;
                }
                break;
            case ATTACK2:
                if (RabiClone.TIME - weaponSwingTime > weaponSwingAnimationTime) {
                    state=State.IDLE;
                }
                break;
            case ATTACK3:
                if (RabiClone.TIME - weaponSwingTime > weaponSwingAnimationTime) {
                    state=State.IDLE;
                }
                break;
            case FALLING:
                if (prvState != State.FALLING) {
                    jump_slide_fall_StartAnimationTimer = RabiClone.TIME;
                    setAnimatedSpr(Sprite.ERINA_JUMP_FALL1);
                    spacebarPressed = 0;
                }
                if (RabiClone.TIME - jump_slide_fall_StartAnimationTimer > jump_fall_AnimationWaitTime) {
                    setAnimatedSpr(Sprite.ERINA_JUMP_FALL);
                    jump_slide_fall_StartAnimationTimer = -1;
                }
                break;
            case IDLE:
                if (RabiClone.TIME - slidePressed <= slideBufferTime && state!=State.BELLYSLIDE) {
                    performSlide();
                    break;
                }

                jump_velocity = PhysicsObject.JUMP_VELOCITY;
                horizontal_friction = PhysicsObject.GROUND_FRICTION;
                jump_slide_fall_StartAnimationTimer = -1;

                if (x_velocity != 0) {
                    setAnimatedSpr(Sprite.ERINA_WALK);
                    if (x_velocity > 0) {
                        facing_direction = RIGHT;
                    } else if (x_velocity < 0) {
                        facing_direction = LEFT;
                    }
                } else {
                    setAnimatedSpr(Sprite.ERINA);
                }
                break;
            case JUMP:
                if (jump_slide_fall_StartAnimationTimer == -1) {
                    jump_slide_fall_StartAnimationTimer = RabiClone.TIME;
                    setAnimatedSpr(Sprite.ERINA_JUMP_RISE1);
                }
                if (RabiClone.TIME - jump_slide_fall_StartAnimationTimer > jump_fall_AnimationWaitTime) {
                    setAnimatedSpr(Sprite.ERINA_JUMP_RISE);
                }
                break;
            case SLIDE:
                horizontal_friction = 0;
                if (jump_slide_fall_StartAnimationTimer == -1) {
                    jump_slide_fall_StartAnimationTimer = RabiClone.TIME;
                    setAnimatedSpr(Sprite.ERINA_SLIDE1);
                }
                if (RabiClone.TIME - jump_slide_fall_StartAnimationTimer > slide_AnimationWaitTime) {
                    setAnimatedSpr(Sprite.ERINA_SLIDE);
                }
                if (RabiClone.TIME - slide_time > slide_duration) {
                    if (KeyHeld(Action.MOVE_LEFT)) {
                        facing_direction = LEFT;
                    }
                    if (KeyHeld(Action.MOVE_RIGHT)) {
                        facing_direction = RIGHT;
                    }
                    state = State.IDLE;
                }
                if (KeyHeld(Action.MOVE_LEFT) && !KeyHeld(Action.MOVE_RIGHT)) {
                    if (facing_direction == LEFT && x_velocity > -sliding_velocity * 1.5 ||
                            facing_direction == RIGHT && x_velocity > sliding_velocity * 0.5) {
                        x_velocity -= sliding_acceleration * updateMult;
                    }
                } else if (KeyHeld(Action.MOVE_RIGHT) && !KeyHeld(Action.MOVE_LEFT)) {
                    if (facing_direction == LEFT && x_velocity < -sliding_velocity * 0.5 ||
                            facing_direction == RIGHT && x_velocity < sliding_velocity * 1.5) {
                        x_velocity += sliding_acceleration * updateMult;
                    }
                }
                break;
            case BELLYSLIDE:{
                horizontal_friction = 0;
                if (y_velocity>0) {
                   bellySlideTime=RabiClone.TIME; 
                }
                if (groundCollision&&!landedBellySlide){
                    landedBellySlide=true;
                    x_velocity = sliding_velocity*(facing_direction?1:-1);
                }
                if (RabiClone.TIME - bellySlideTime > bellySlideDuration) {
                    if (KeyHeld(Action.MOVE_LEFT)) {
                        facing_direction = LEFT;
                    }
                    if (KeyHeld(Action.MOVE_RIGHT)) {
                        facing_direction = RIGHT;
                    }
                    state = State.IDLE;
                }
                if (KeyHeld(Action.MOVE_LEFT) && !KeyHeld(Action.MOVE_RIGHT)) {
                    if (facing_direction == LEFT && x_velocity > -sliding_velocity * 1.5 ||
                            facing_direction == RIGHT && x_velocity > sliding_velocity * 0.5) {
                        x_velocity -= sliding_acceleration * updateMult;
                    }
                } else if (KeyHeld(Action.MOVE_RIGHT) && !KeyHeld(Action.MOVE_LEFT)) {
                    if (facing_direction == LEFT && x_velocity < -sliding_velocity * 0.5 ||
                            facing_direction == RIGHT && x_velocity < sliding_velocity * 1.5) {
                        x_velocity += sliding_acceleration * updateMult;
                    }
                }
            }break;
            case STAGGER:
                break;
            case UNCONTROLLABLE:
                break;
            default:
                break;
        }
        prvState = state;
        if (KeyHeld(Action.JUMP) && RabiClone.TIME - spacebarPressed < jumpHoldTime
        && state!=State.ATTACK2&&state!=State.ATTACK3&&state!=State.BELLYSLIDE) {
            y_velocity = jump_velocity;
        }
    }

    private void handleEventCollisions() {
        int x = (int)getX()/Tile.TILE_WIDTH;
        int y = (int)getY()/Tile.TILE_HEIGHT;
        if (x>=0&&x<Map.MAP_WIDTH&&y>=0&&y<Map.MAP_HEIGHT) {
            char dataTileValue = RabiClone.CURRENT_MAP.getDataTileRawValue((int)getX()/Tile.TILE_WIDTH, (int)getY()/Tile.TILE_HEIGHT);
            if (dataTileValue<8192&&dataTileValue!=0) {
                if (!RabiClone.CURRENT_MAP.getDataTile(x, y).getEvent().performCollision((int)getX(), (int)getY())) {
                    RabiClone.CURRENT_MAP.ModifyDataTile(x, y, DataTile.NULL);
                }
            }
        }
    }

    private void handleCollisionBatch() {
        for (int p=0;p<collisionBatch.size();p++) {
            PhysicsObject pobj = collisionBatch.get(p);
            if(facing_direction){
                if (state!=State.UNCONTROLLABLE) {
                    setUncontrollable(0.2);
                }
                pobj.setStagger(0.5);
                pobj.setInvulnerability(0.6);
                pobj.x_velocity = -300;
                pobj.y_velocity = -120;
            }else{
                if (state!=State.UNCONTROLLABLE) {
                    setUncontrollable(0.2);
                }
                pobj.setStagger(0.5);
                pobj.setInvulnerability(0.6);
                pobj.x_velocity = 300;
                pobj.y_velocity = -120;
            }
        }
        collisionBatch.clear();
    }

    @Override
    public void KeyReleased(Action a) {
        if (a == Action.JUMP) {
            spacebarPressed = 0;
        }
        if (state != State.SLIDE&&state!=State.BELLYSLIDE) {
            if ((a == Action.MOVE_LEFT) && (KeyHeld(Action.MOVE_RIGHT))) {
                facing_direction = RIGHT;
            } else if ((a == Action.MOVE_RIGHT) && (KeyHeld(Action.MOVE_LEFT))) {
                facing_direction = LEFT;
            }
        }
    }

    @Override
    @SuppressWarnings("incomplete-switch")
    public void KeyPressed(Action a) {
        switch (state) {
            case ATTACK:
                if (a==Action.ATTACK&&RabiClone.TIME-weaponSwingTime>weaponComboWaitTime) {
                    state=State.ATTACK2;
                    weaponSwingTime=RabiClone.TIME;
                }
                break;
            case ATTACK2:
                if (a==Action.ATTACK&&RabiClone.TIME-weaponSwingTime>weaponComboWaitTime) {
                    state=State.ATTACK3;
                    weaponSwingTime=RabiClone.TIME;
                }
                break;
            case ATTACK3:
                if (a==Action.ATTACK&&RabiClone.TIME-weaponSwingTime>weaponComboWaitTime) {
                    state=State.ATTACK4;
                    weaponSwingTime=RabiClone.TIME;
                    y_velocity = finalComboJumpBackSpeedY;
                    x_velocity = finalComboJumpBackSpeedX*(facing_direction?1:-1);
                }
                break;
            case IDLE:
                if (a == Action.SLIDE || a == Action.FALL) {
                    performSlide();
                }
                break;
            case FALLING:
                if (a == Action.SLIDE || a == Action.FALL) {
                    slidePressed = RabiClone.TIME;
                    // System.out.println("Queue up slide.");
                }
            case JUMP:
                if (jumpCount > 0 && (a == Action.JUMP)) {
                    jumpCount = 0;
                    y_velocity = jump_velocity;
                    spacebarPressed = RabiClone.TIME;
                }
                break;
            case SLIDE:
                break;
            case STAGGER:
                break;
            case UNCONTROLLABLE:
                break;
            default:
                break;
        }
        if (a==Action.FALL&&(state==State.JUMP||state==State.FALLING)&&!isUnderwater()) {
            state=State.BELLYSLIDE;
            y_velocity=y_velocity_limit;
            x_velocity=60*(facing_direction?1:-1);
            bellySlideTime=RabiClone.TIME;
            landedBellySlide=false;
            spacebarPressed = 0;
        } else
        if (a == Action.ATTACK&&(state==State.IDLE||state==State.FALLING||state==State.JUMP)&&(RabiClone.TIME-weaponSwingTime>=weaponSwingAnimationTime)) {
            RabiClone.OBJ.add(new KnifeSwing(Sprite.KNIFE_SWING,40,RabiClone.p,this));
            state=State.ATTACK;
            weaponSwingTime=RabiClone.TIME;
        }
        if (groundCollision||isUnderwater()) {
            if (jumpCount>0&&a==Action.JUMP&&state!=State.ATTACK2&&state!=State.ATTACK3) {
                state = State.JUMP;
                jumpCount--;
                y_velocity = jump_velocity;
                spacebarPressed = RabiClone.TIME;
                //System.out.println("Jump");
            }
        }
        if (state != State.SLIDE&&state!=State.BELLYSLIDE) {
            switch (a) {
                case MOVE_LEFT:
                    facing_direction = LEFT;
                    break;
                case MOVE_RIGHT:
                    facing_direction = RIGHT;
                    break;
            }
        }
    }

    private void performSlide() {
        slide_time = RabiClone.TIME;
        if (facing_direction) {
            x_velocity = sliding_velocity;
        } else {
            x_velocity = -sliding_velocity;
        }
        state = State.SLIDE;
    }

    private void handleCameraRoomMovement() {
        int tileX = (int) (getX()) / Tile.TILE_WIDTH;
        int tileY = (int) (getY()) / Tile.TILE_HEIGHT;
        double newX = RabiClone.level_renderer.getX(), newY = RabiClone.level_renderer.getY(); // By default we use a
                                                                                               // fixed view.
        View currentView = RabiClone.CURRENT_MAP.getView(tileX, tileY);
        if (currentView != lastCameraView) {
            lastCameraView = currentView;
            newX = (tileX / Tile.TILE_SCREEN_COUNT_X) * Map.MAP_WIDTH;
            newY = (tileY / Tile.TILE_SCREEN_COUNT_Y) * Map.MAP_HEIGHT;
        } else
            switch (currentView) {
                case LIGHT_FOLLOW:
                    if (getX() - RabiClone.level_renderer.getX() < viewBoundaryX) {
                        newX = getX() - viewBoundaryX;
                        int newTileX = (int) (newX) / Tile.TILE_WIDTH;
                        View newView = RabiClone.CURRENT_MAP.getView(newTileX, tileY);
                        if (newView != currentView) {
                            newX = (tileX / Tile.TILE_SCREEN_COUNT_X) * Map.MAP_WIDTH;
                        }
                    } else if (getX() - RabiClone.level_renderer.getX() > RabiClone.BASE_WIDTH - viewBoundaryX) {
                        newX = getX() - (RabiClone.BASE_WIDTH - viewBoundaryX);
                        int newTileX = (int) (getX() + viewBoundaryX) / Tile.TILE_WIDTH;
                        View newView = RabiClone.CURRENT_MAP.getView(newTileX, tileY);
                        if (newView != currentView) {
                            newX = (tileX / Tile.TILE_SCREEN_COUNT_X) * Map.MAP_WIDTH;
                        }
                    }
                    if (getY() - RabiClone.level_renderer.getY() < viewBoundaryY) {
                        newY = getY() - viewBoundaryY;
                        int newTileY = (int) (getY() - viewBoundaryY) / Tile.TILE_HEIGHT;
                        View newView = RabiClone.CURRENT_MAP.getView(tileX, newTileY);
                        if (newView != currentView) {
                            newY = (tileY / Tile.TILE_SCREEN_COUNT_Y) * Map.MAP_HEIGHT;
                        }
                    } else if (getY() - RabiClone.level_renderer.getY() > RabiClone.BASE_HEIGHT - viewBoundaryY) {
                        newY = getY() - (RabiClone.BASE_HEIGHT - viewBoundaryY);
                        int newTileY = (int) (getY() + viewBoundaryY) / Tile.TILE_HEIGHT;
                        View newView = RabiClone.CURRENT_MAP.getView(tileX, newTileY);
                        if (newView != currentView) {
                            newY = (tileY / Tile.TILE_SCREEN_COUNT_Y) * Map.MAP_HEIGHT;
                        }
                    }
                    break;
                case LIGHT_HORIZONTAL_FOLLOW:
                    if (getX() - RabiClone.level_renderer.getX() < viewBoundaryX) {
                        newX = getX() - viewBoundaryX;
                        int newTileX = (int) (newX) / Tile.TILE_WIDTH;
                        View newView = RabiClone.CURRENT_MAP.getView(newTileX, tileY);
                        if (newView != currentView) {
                            newX = (tileX / Tile.TILE_SCREEN_COUNT_X) * Map.MAP_WIDTH;
                        }
                    } else if (getX() - RabiClone.level_renderer.getX() > RabiClone.BASE_WIDTH - viewBoundaryX) {
                        newX = getX() - (RabiClone.BASE_WIDTH - viewBoundaryX);
                        int newTileX = (int) (getX() + viewBoundaryX) / Tile.TILE_WIDTH;
                        View newView = RabiClone.CURRENT_MAP.getView(newTileX, tileY);
                        if (newView != currentView) {
                            newX = (tileX / Tile.TILE_SCREEN_COUNT_X) * Map.MAP_WIDTH;
                        }
                    }
                    newY = (tileY / Tile.TILE_SCREEN_COUNT_Y) * Map.MAP_HEIGHT;
                    break;
                case LIGHT_VERTICAL_FOLLOW:
                    newX = (tileX / Tile.TILE_SCREEN_COUNT_X) * Map.MAP_WIDTH;
                    if (getY() - RabiClone.level_renderer.getY() < viewBoundaryY) {
                        newY = getY() - viewBoundaryY;
                        int newTileY = (int) (getY() - viewBoundaryY) / Tile.TILE_HEIGHT;
                        View newView = RabiClone.CURRENT_MAP.getView(tileX, newTileY);
                        if (newView != currentView) {
                            newY = (tileY / Tile.TILE_SCREEN_COUNT_Y) * Map.MAP_HEIGHT;
                        }
                    } else if (getY() - RabiClone.level_renderer.getY() > RabiClone.BASE_HEIGHT - viewBoundaryY) {
                        newY = getY() - (RabiClone.BASE_HEIGHT - viewBoundaryY);
                        int newTileY = (int) (getY() + viewBoundaryY) / Tile.TILE_HEIGHT;
                        View newView = RabiClone.CURRENT_MAP.getView(tileX, newTileY);
                        if (newView != currentView) {
                            newY = (tileY / Tile.TILE_SCREEN_COUNT_Y) * Map.MAP_HEIGHT;
                        }
                    }
                    break;
                case FIXED:
                    break;
                default:
                    break;
            }
        RabiClone.level_renderer.setX(newX < 0 ? 0 : newX);
        RabiClone.level_renderer.setY(newY < 0 ? 0 : newY);
    }

    @Override
    public void collisionEvent(AnimatedObject obj) {
        if (state==State.BELLYSLIDE) {
            if(obj instanceof PhysicsObject){
                PhysicsObject pobj = (PhysicsObject)obj;
                if(!pobj.isInvulnerable()){
                    collisionBatch.add(pobj);
                }
            }
        }
    }

    @Override
    public void draw(byte[] p) {
    }

    @Override
    public String toString() {
        return "Player [facing_direction=" + (facing_direction ? "RIGHT" : "LEFT") + ", groundCollision="
                + groundCollision + ", jumpCount="
                + jumpCount + ", x_velocity=" + x_velocity + ", y_velocity=" + y_velocity + ", x=" + getX() + ", y="
                + getY() + "]";
    }

    @Override
    public Transform getSpriteTransform() {
        return facing_direction?Transform.HORIZONTAL:Transform.NONE;
    }

    @Override
    public boolean rightKeyHeld() {
        return state!=State.ATTACK2&&state!=State.ATTACK3&&KeyHeld(Action.MOVE_RIGHT);
    }

    @Override
    public boolean leftKeyHeld() {
        return state!=State.ATTACK2&&state!=State.ATTACK3&&KeyHeld(Action.MOVE_LEFT);
    }

    public double getYVelocity() {
        return y_velocity;
    }

    @Override
    public boolean isFriendlyObject() {
        return true;
    }
    
}
