package games.colonier.ld44.screen;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Cursor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.utils.Align;
import games.colonier.ld44.Colors;
import games.colonier.ld44.GameScreen;
import games.colonier.ld44.LudumGame;
import games.colonier.ld44.chemistry.Atom;
import games.colonier.ld44.chemistry.Connection;
import games.colonier.ld44.logic.Difficulty;
import games.colonier.ld44.logic.GameState;
import games.colonier.ld44.menu.ButtonList;
import games.colonier.ld44.music.Musics;
import games.colonier.ld44.text.Fonts;

import java.util.*;

public class MenuGameScreen extends AbstractGameScreen {

    private TextButton startGameBtn;
    private TextButton creditBtn;
    private TextButton musicBtn;
    private TextButton difficultyBtn;

    private ButtonList buttonList;

    private Stage stage;
    private FrameBuffer backgroundFBO;

    private List<FlyingMolecule> flyingMolecules = new ArrayList<> ();

    private float cameraAnimationTimer = 0.0f;
    private Vector2 cameraLerpTowards = new Vector2 ();

    Pixmap arrowCursorPixmap;
    Pixmap pointerCursorPixmap;

    Cursor arrowCursor;
    Cursor pointerCursor;

    public MenuGameScreen (LudumGame ludumGame) {
        super (ludumGame);
    }

    private FlyingMolecule randomMolecule () {

        Random random = new Random ();

        List<FlyingAtom> atoms = new ArrayList<> ();
        int numAtoms = random.nextInt (3) + 3;

        for (int j = 0; j < numAtoms; j++) {

            FlyingAtom atom = new FlyingAtom (
                    j,
                    Atom.Type.values ()[random.nextInt (Atom.Type.values ().length)]
            );
            atoms.add (atom);

        }

        Set<Connection> visitedConnections = new HashSet<> ();

        for (int j = 0; j < numAtoms; j++) {

            int nTries = 0;
            do {

                if (nTries++ >= 100) {
                    break;
                }

                int id1 = random.nextInt (atoms.size ());
                int id2 = random.nextInt (atoms.size ());

                if (id1 != id2) {

                    Connection c = new Connection (id1, id2);
                    if (visitedConnections.add (c)) {

                        FlyingAtom a1 = atoms.get (id1);
                        FlyingAtom a2 = atoms.get (id2);

                        a1.neighbours.add (a2);
                        a2.neighbours.add (a1);

                        break;

                    }

                }

            } while (true);

        }

        atoms.removeIf (a -> a.neighbours.isEmpty ());

        FlyingMolecule fm = new FlyingMolecule (atoms);

        fm.position.set (
                random.nextFloat () * resolution.x,
                random.nextFloat () * resolution.y
        );
        fm.velocity.set (
                random.nextFloat () * 128f - 64f,
                random.nextFloat () * 128f - 64f
        );
        fm.angularVelocity = random.nextFloat () * 60.0f - 30.0f;

        return fm;

    }

    @Override
    protected void onInit () {

        cameraAnimationTimer = 0f;
        cameraLerpTowards.set (
                Gdx.graphics.getWidth () / 2f,
                Gdx.graphics.getHeight () / 2f
        );

        arrowCursorPixmap = new Pixmap (Gdx.files.internal ("arrowCursor.png"));
        pointerCursorPixmap = new Pixmap (Gdx.files.internal ("pointerCursor.png"));

        arrowCursor = Gdx.graphics.newCursor (arrowCursorPixmap, 0, 0);
        pointerCursor = Gdx.graphics.newCursor (pointerCursorPixmap, 0, 0);

        stage = new Stage ();
        Gdx.input.setInputProcessor (stage);

        Skin style = new Skin (Gdx.files.internal ("ui.json"));

        startGameBtn = new TextButton ("Start", style);
        creditBtn = new TextButton ("Credit", style);
        musicBtn = new TextButton ("Music: On", style);
        difficultyBtn = new TextButton ("Difficulty: " + Difficulty.getChosen ().name, style);

        musicBtn.addListener (new InputListener () {
            boolean musicIsPlaying = Musics.getInstance ().isPlaying ();
            String musicBtnText = musicIsPlaying ? "On" : "Off";

            @Override
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                musicIsPlaying = !musicIsPlaying;
                musicBtnText = musicIsPlaying ? "On" : "Off";
                musicBtn.setText ("Music: " + musicBtnText);
                Musics.getInstance ().setPlay (musicIsPlaying);
                return true;
            }
        });

        difficultyBtn.addListener (
                new InputListener () {
                    @Override
                    public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                        difficultyBtn.setText ("Difficulty: " + Difficulty.increase ().name);
                        return true;
                    }
                }
        );

        buttonList = new ButtonList(stage, startGameBtn, difficultyBtn, creditBtn, musicBtn);

        stage.addListener(new InputListener() {
            @Override
            public boolean keyDown(InputEvent event, int keycode) {

                Vector2 before = new Vector2 (buttonList.getCurrentButton ().getX (), buttonList.getCurrentButton ().getY ());

                if ( keycode == Input.Keys.DOWN ) {
                    buttonList.incremPosition();

                }

                if ( keycode == Input.Keys.UP ) {
                    buttonList.decremPosition();
                }

                if ( keycode == Input.Keys.ENTER ) {
                    InputEvent ie1 = new InputEvent ();
                    ie1.setButton (Input.Buttons.LEFT);
                    ie1.setType (InputEvent.Type.touchDown);
                    buttonList.getCurrentButton ().fire (
                            ie1
                    );
                    buttonList.getCurrentButton ().setChecked (true);
                }

                Vector2 diff = new Vector2 (
                        buttonList.getCurrentButton ().getX () - before.x,
                        buttonList.getCurrentButton ().getY () - before.y
                );

                cameraLerpTowards.set (
                        resolution.x / 2f,
                        cameraLerpTowards.y + diff.y
                );
                cameraAnimationTimer = 0f;

                return true;
            }
        });


        Random random = new Random ();

        for (int i = 0; i < 15; i++) {
            flyingMolecules.add (randomMolecule ());
        }

    }

    @Override
    protected void onResize (float w, float h) {

        if (backgroundFBO != null) {
            backgroundFBO.dispose ();
        }

        backgroundFBO = new FrameBuffer (Pixmap.Format.RGBA8888, (int) w, (int) h, false);

    }

    @Override
    protected void update (float delta) {

        cameraAnimationTimer = MathUtils.clamp (cameraAnimationTimer + delta, 0.0f, 1.0f);
        stage.getCamera ().position.lerp (
                new Vector3 (cameraLerpTowards.x, cameraLerpTowards.y, 0.0f),
                cameraAnimationTimer
        );

        stage.act (delta);

        Gdx.graphics.setCursor (arrowCursor);
        if (startGameBtn.isOver () || creditBtn.isOver () || musicBtn.isOver ()) {
            Gdx.graphics.setCursor (pointerCursor);
        }

        for (FlyingMolecule m : flyingMolecules) {
            m.update (delta);
        }

        flyingMolecules.removeIf (
                fm -> {

                    float minX = fm.position.x + fm.atoms.get (0).position.x;
                    float maxX = fm.position.x + fm.atoms.get (0).position.x;
                    float minY = fm.position.y + fm.atoms.get (0).position.y;
                    float maxY = fm.position.y + fm.atoms.get (0).position.y;

                    for (FlyingAtom a : fm.atoms) {

                        if (fm.position.x + a.position.x < minX) {
                            minX = fm.position.x + a.position.x;
                        }
                        if (fm.position.x + a.position.x > maxX) {
                            maxX = fm.position.x + a.position.x;
                        }
                        if (fm.position.y + a.position.y < minY) {
                            minY = fm.position.y + a.position.y;
                        }
                        if (fm.position.y + a.position.y > maxY) {
                            maxY = fm.position.y + a.position.y;
                        }

                    }

                    return maxX < -resolution.x * 0.5f
                            || minX > resolution.x * 1.5f
                            || maxY < -resolution.y * 0.5f
                            || minY > resolution.y * 1.5f;

                }
        );

        for (int i = flyingMolecules.size (); i < 15; i++) {
            flyingMolecules.add (randomMolecule ());
        }

    }

    @Override
    protected void onDispose () {

        stage.dispose ();
        backgroundFBO.dispose ();

    }

    @Override
    public void render (float delta) {

        Gdx.gl.glClearColor (
                Colors.Background.r,
                Colors.Background.g,
                Colors.Background.b,
                Colors.Background.a
        );
        Gdx.gl.glClear (Gdx.gl.GL_COLOR_BUFFER_BIT | Gdx.gl.GL_DEPTH_BUFFER_BIT);

        backgroundFBO.begin ();

        Gdx.gl.glClearColor (0f, 0f, 0f, 0f);
        Gdx.gl.glClear (Gdx.gl.GL_COLOR_BUFFER_BIT);

        Gdx.gl.glEnable (Gdx.gl.GL_BLEND);

        shapeRenderer.begin (ShapeRenderer.ShapeType.Filled);

        for (FlyingMolecule m : flyingMolecules) {

            m.draw (shapeRenderer);

        }

        shapeRenderer.end ();

        backgroundFBO.end ();

        shapeRenderer.begin (ShapeRenderer.ShapeType.Filled);

        shapeRenderer.setColor (Colors.TextDark.r, Colors.TextDark.g, Colors.TextDark.b, 0.3f);
        shapeRenderer.roundedRect (resolution.x / 2f - resolution.x / 4f, resolution.y / 2f - 34f, resolution.x / 2f, 64f, 10.0f);

        shapeRenderer.end ();

        spriteBatch.begin ();
        spriteBatch.enableBlending ();
        spriteBatch.setColor (1f, 1f, 1f, 0.4f);
        spriteBatch.draw (
                backgroundFBO.getColorBufferTexture (),
                0, 0, resolution.x, resolution.y
        );
        // spriteBatch.disableBlending ();

        spriteBatch.setColor (1f, 1f, 1f, 1f);
        Fonts.f128 ().setColor (Color.WHITE);
        Fonts.f128 ().draw (
                spriteBatch,
                "ChemisTree",
                0.0f, resolution.y - 32f + 16f * (float) Math.sin (getScreenTime ()),
                resolution.x, Align.center, true
        );

        spriteBatch.end ();

        stage.draw ();

    }

    @Override
    public GameScreen next () {
        if(startGameBtn.isChecked()) {
            Gdx.graphics.setCursor (arrowCursor);
            GameState.getInstance ().load ();
            return new MoleculeEditorGameScreen(ludumGame);
        }
        if(creditBtn.isChecked()) {
            Gdx.graphics.setCursor(arrowCursor);
            return new CreditGameScreen(ludumGame);
        }
        return null;
    }

    private static final class FlyingAtom {

        public final int id;
        public final Atom.Type atomType;
        public final Vector2 position = new Vector2 ();
        public final List<FlyingAtom> neighbours = new ArrayList<> ();
        public FlyingAtom bfsFrom;

        private FlyingAtom (int id, Atom.Type atomType) {
            this.id = id;
            this.atomType = atomType;
        }

        @Override
        public boolean equals (Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass () != o.getClass ()) {
                return false;
            }
            FlyingAtom that = (FlyingAtom) o;
            return id == that.id &&
                    atomType == that.atomType;
        }

        @Override
        public int hashCode () {
            return Objects.hash (id, atomType);
        }

    }

    private static final class FlyingMolecule {

        public final List<FlyingAtom> atoms = new ArrayList<> ();
        public final Vector2 position = new Vector2 ();
        public final Vector2 velocity = new Vector2 ();
        public float angularVelocity = 0.0f;
        public float rotation = 0.0f;
        public float transparency = 0.0f;
        private Set<Connection> drawnConnections = new HashSet<> ();

        private void initMolecule () {

            Deque<FlyingAtom> bfsQueue = new ArrayDeque<> ();
            Set<FlyingAtom> bfsVisited = new HashSet<> ();

            bfsQueue.addLast (atoms.get (0));
            bfsVisited.add (bfsQueue.getFirst ());

            Random random = new Random ();

            while (!bfsQueue.isEmpty ()) {

                FlyingAtom atom = bfsQueue.removeFirst ();
                atom.position.set (
                        atom.bfsFrom != null
                                ? atom.bfsFrom.position
                                : Vector2.Zero
                ).add (
                        random.nextBoolean () ? 32f : -32f,
                        random.nextBoolean () ? 32f : -32f
                ).add (
                        (random.nextFloat ()) * 128f - 64f,
                        (random.nextFloat ()) * 128f - 64f
                );

                for (FlyingAtom neighbour : atom.neighbours) {

                    if (bfsVisited.add (neighbour)) {
                        neighbour.bfsFrom = atom;
                        bfsQueue.addLast (neighbour);
                    }

                }

            }

        }

        private FlyingMolecule (List<FlyingAtom> list) {
            this.atoms.addAll (list);
            initMolecule ();
        }

        public void update (float delta) {

            position.add (velocity.cpy ().scl (delta));
            rotation += angularVelocity * delta;
            transparency += delta;
            if (transparency >= 1.0f) {
                transparency = 1.0f;
            }

        }

        public void draw (ShapeRenderer shapeRenderer) {

            drawnConnections.clear ();

            shapeRenderer.translate (position.x, position.y, 0.0f);
            shapeRenderer.rotate (0.0f, 0.0f, 1.0f, rotation);

            for (FlyingAtom atom : atoms) {

                for (FlyingAtom neighbour : atom.neighbours) {

                    Connection connection = new Connection (atom.id, neighbour.id);
                    if (drawnConnections.add (connection)) {
                        shapeRenderer.setColor (new Color (0, 0, 0, transparency));
                        shapeRenderer.rectLine (atom.position, neighbour.position, 2.0f);
                    }

                }

            }

            for (FlyingAtom atom : atoms) {
                shapeRenderer.setColor (
                        new Color (
                                atom.atomType.color.r, atom.atomType.color.g, atom.atomType.color.b,
                                transparency
                        )
                );
                shapeRenderer.circle (
                        atom.position.x,
                        atom.position.y,
                        16.0f
                );
            }

            shapeRenderer.rotate (0.0f, 0.0f, 1.0f, -rotation);
            shapeRenderer.translate (-position.x, -position.y, 0.0f);

        }

    }

}
