package com.homescreenarcade.pinball.fields;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.homescreenarcade.pinball.Ball;
import com.homescreenarcade.pinball.BaseFieldDelegate;
import com.homescreenarcade.pinball.Field;
import com.homescreenarcade.pinball.elements.DropTargetGroupElement;
import com.homescreenarcade.pinball.elements.FieldElement;
import com.homescreenarcade.pinball.elements.RolloverGroupElement;
import com.homescreenarcade.pinball.elements.SensorElement;
import com.homescreenarcade.pinball.elements.WallElement;

public class Field2Delegate extends BaseFieldDelegate {

    static final double TAU = 2*Math.PI; // pi is wrong.

    static class RotatingGroup {
        String[] elementIDs;
        double centerX, centerY;
        double radius;
        double rotationSpeed;
        double startAngle;
        double currentAngle;
        double angleIncrement;

        public RotatingGroup(String[] ids, double cx, double cy, double radius, double startAngle, double speed) {
            this.elementIDs = ids;
            this.centerX = cx;
            this.centerY = cy;
            this.radius = radius;
            this.rotationSpeed = speed;
            this.startAngle = this.currentAngle = startAngle;
            this.angleIncrement = TAU / ids.length;
        }

        /**
         * Creates a RotatingGroup by computing the distance and angle to center from the first
         * element ID in the ids array.
         */
        public static RotatingGroup create(Field field, String[] ids, double cx, double cy, double speed) {
            FieldElement element = field.getFieldElementById(ids[0]);
            Body body = element.getBodies().get(0);
            Vector2 position = body.getPosition();
            double radius = Math.hypot(position.x - cx, position.y - cy);
            double angle = Math.atan2(position.y - cy, position.x - cx);
            return new RotatingGroup(ids, cx, cy, radius, angle, speed);
        }

        public void applyRotation(Field field, double dt) {
            currentAngle += dt*rotationSpeed;
            if (currentAngle>TAU) currentAngle -= TAU;
            if (currentAngle<0) currentAngle += TAU;
            for(int i=0; i<elementIDs.length; i++) {
                double angle = currentAngle + angleIncrement*i;

                FieldElement element = field.getFieldElementById(elementIDs[i]);
                Body body = element.getBodies().get(0);
                double x = centerX + radius*Math.cos(angle);
                double y = centerY + radius*Math.sin(angle);
                body.setTransform((float)x, (float)y, body.getAngle());
            }
        }


    }

    RotatingGroup[] rotatingGroups;

    RotatingGroup createRotatingGroup(Field field, String centerID, String[] ids, double speed) {
        FieldElement centerElement = field.getFieldElementById(centerID);
        Vector2 centerPosition = centerElement.getBodies().get(0).getPosition();
        return RotatingGroup.create(field, ids, centerPosition.x, centerPosition.y, speed);
    }

    void setupRotatingGroups(Field field) {
        // Read rotation params from variables defined in the field.
        float b1Speed = ((Number)field.getValueWithKey("RotatingBumper1Speed")).floatValue();
        float b2Speed = ((Number)field.getValueWithKey("RotatingBumper2Speed")).floatValue();
        float b2cx = ((Number)field.getValueWithKey("RotatingBumper2CenterX")).floatValue();
        float b2cy = ((Number)field.getValueWithKey("RotatingBumper2CenterY")).floatValue();
        String[] group1Ids = {
                "RotatingBumper1A", "RotatingBumper1B", "RotatingBumper1C", "RotatingBumper1D"
        };
        rotatingGroups = new RotatingGroup[] {
                createRotatingGroup(field, "CenterBumper1", group1Ids, b1Speed),
                RotatingGroup.create(field, new String[] {"RotatingBumper2A", "RotatingBumper2B"},
                        b2cx, b2cy, b2Speed)
        };
    }

    @Override
    public void tick(Field field, long nanos) {
        if (rotatingGroups==null) {
            setupRotatingGroups(field);
        }

        double seconds = nanos/1e9;
        for(int i=0; i<rotatingGroups.length; i++) {
            rotatingGroups[i].applyRotation(field, seconds);
        }
    }

    private void restoreLeftBallSaver(Field field) {
        ((WallElement)field.getFieldElementById("BallSaver-left")).setRetracted(false);
    }

    private void restoreRightBallSaver(Field field) {
        ((WallElement)field.getFieldElementById("BallSaver-right")).setRetracted(false);
    }

    void startMultiball(final Field field) {
        field.showGameMessage("Multiball!", 2000);
        restoreLeftBallSaver(field);
        restoreRightBallSaver(field);

        Runnable launchBall = new Runnable() {
            @Override
            public void run() {
                if (field.getBalls().size()<3) field.launchBall();
            }
        };
        field.scheduleAction(1000, launchBall);
        field.scheduleAction(3500, launchBall);
    }

    /** Always return true so the rotating bumpers animate smoothly */
    @Override public boolean isFieldActive(Field field) {
        return true;
    }

    @Override public void allRolloversInGroupActivated(Field field, RolloverGroupElement rolloverGroup) {
        // Rollover groups increment field multiplier when all rollovers are activated.
        rolloverGroup.setAllRolloversActivated(false);
        field.getGameState().incrementScoreMultiplier();
        field.showGameMessage(((int)field.getGameState().getScoreMultiplier()) + "x Multiplier", 1500);
    }

    @Override public void processCollision(Field field, FieldElement element, Body hitBody, Ball ball) {
        // When center red bumper is hit, start multiball if all center rollovers are lit,
        // otherwise retract left barrier.
        String elementID = element.getElementId();
        if ("CenterBumper1".equals(elementID)) {
            WallElement barrier = (WallElement)field.getFieldElementById("LeftTubeBarrier");
            RolloverGroupElement multiballRollovers =
                    (RolloverGroupElement)field.getFieldElementById("ExtraBallRollovers");

            if (multiballRollovers.allRolloversActive()) {
                barrier.setRetracted(false);
                startMultiball(field);
                multiballRollovers.setAllRolloversActivated(false);
            }
            else {
                // don't retract during multiball
                if (field.getBalls().size()==1) {
                    barrier.setRetracted(true);
                }
            }
        }
    }

    @Override
    public void allDropTargetsInGroupHit(Field field, DropTargetGroupElement targetGroup) {
        // activate ball saver for left and right groups, "increment" multiball rollover for left/right/center column
        int startRolloverIndex = -1;
        String id = targetGroup.getElementId();
        if ("DropTargetLeft".equals(id)) {
            restoreLeftBallSaver(field);
            field.showGameMessage("Left Save Enabled", 1500);
            startRolloverIndex = 0;
        }
        else if ("DropTargetRight".equals(id)) {
            restoreRightBallSaver(field);
            field.showGameMessage("Right Save Enabled", 1500);
            startRolloverIndex = 2;
        }
        else if ("DropTargetTopLeft".equals(id)) {
            startRolloverIndex = 1;
        }

        // activate next rollover for appropriate column if possible
        if (startRolloverIndex>=0) {
            RolloverGroupElement multiballRollovers = (RolloverGroupElement)field.getFieldElementById("ExtraBallRollovers");
            int numRollovers = multiballRollovers.numberOfRollovers();
            while (startRolloverIndex < numRollovers) {
                if (!multiballRollovers.isRolloverActiveAtIndex(startRolloverIndex)) {
                    multiballRollovers.setRolloverActiveAtIndex(startRolloverIndex, true);

                    if (multiballRollovers.allRolloversActive()) {
                        field.showGameMessage("Shoot Red Bumper", 1500);
                    }
                    break;
                }
                else {
                    startRolloverIndex += 3;
                }
            }
        }

    }

    // support for enabling launch barrier after ball passes by it and hits sensor, and disabling for new ball or new game
    void setLaunchBarrierEnabled(Field field, boolean enabled) {
        WallElement barrier = (WallElement)field.getFieldElementById("LaunchBarrier");
        barrier.setRetracted(!enabled);
    }

    @Override
    public void ballInSensorRange(final Field field, SensorElement sensor, Ball ball) {
        String sensorID = sensor.getElementId();
        // enable launch barrier
        if ("LaunchBarrierSensor".equals(sensorID)) {
            setLaunchBarrierEnabled(field, true);
        }
        else if ("LaunchBarrierRetract".equals(sensorID)) {
            setLaunchBarrierEnabled(field, false);
        }
        else if ("LeftTubeSensor".equals(sensorID)) {
            if (ball.getLinearVelocity().y > 0) {
                // ball going up, retract barrier after delay
                field.scheduleAction(1000, new Runnable() {
                    @Override
                    public void run() {
                        WallElement barrier = (WallElement)field.getFieldElementById("LeftTubeBarrier");
                        barrier.setRetracted(false);
                    }
                });
            }
        }
    }

    @Override
    public void gameStarted(Field field) {
        setLaunchBarrierEnabled(field, false);
    }

    @Override
    public void ballLost(Field field) {
        setLaunchBarrierEnabled(field, false);
    }
}