package games.colonier.ld44.screen;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Circle;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
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.chemistry.Molecule;
import games.colonier.ld44.logic.GameState;
import games.colonier.ld44.renderer.AtomsRenderer;
import games.colonier.ld44.renderer.RoundedShapeRenderer;
import games.colonier.ld44.renderer.TutorialRenderer;
import games.colonier.ld44.text.Fonts;
import games.colonier.ld44.util.ColorUtil;

import java.util.*;

public class MoleculeEditorGameScreen extends AbstractGameScreen implements InputProcessor {

    GlyphLayout finishGlyphLayout;

    Atom connectFromAtom = null;
    Atom grabbedAtom = null;
    Vector2 dragStart = new Vector2 ();

    private TutorialRenderer tutorialRenderer;

    private AtomsRenderer atomsRenderer = new AtomsRenderer ();

    private Set<Connection> visitedConnections = new HashSet<> ();
    private Molecule molecule;
    private int lastId = 0;
    private InputAction inputAction = InputAction.NoAction;

    private Circle redToolbarCircle = new Circle ();
    private Circle greenToolbarCircle = new Circle ();
    private Circle blueToolbarCircle = new Circle ();
    private Rectangle finishButtonRectangle = new Rectangle ();
    private Rectangle trashRectangle = new Rectangle();
    private boolean finished = false;
    private boolean escape;

    private Rectangle objectivePanelRect = new Rectangle ();
    private Rectangle objectivePanelInsideRect = new Rectangle ();

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

    @Override
    protected void onInit () {

        molecule = GameState.getInstance ().getCurrentMolecule ();
        lastId = molecule.atoms.stream ().mapToInt (a -> a.id).max ().orElse (0) + 1;
        Gdx.input.setInputProcessor (this);

        finishGlyphLayout = new GlyphLayout (
                Fonts.f32 (),
                "Test"
        );

        tutorialRenderer = new TutorialRenderer ();
        tutorialRenderer.texts.addAll (
                Arrays.asList (
                        "Welcome to our game!",
                        "You are a chemist, who is tasked with a medicine project. You received a sample of a highly " +
                                "dangerous substance, and need to create a chemical that is able to neutralize the " +
                                "material.",
                        "In this world, molecules are made of three different atoms: " +
                                ColorUtil.markup (Colors.AtomRed) + "red" + ColorUtil.markup (Colors.LightGray) + " atoms (R), " +
                                ColorUtil.markup (Colors.AtomGreen) + "green" + ColorUtil.markup (Colors.LightGray) + " atoms (G) and " +
                                ColorUtil.markup (Colors.AtomBlue) + "blue" + ColorUtil.markup (Colors.LightGray) + " atoms (B).",
                        "Another important chemical rule of this world is that any atoms may make connection with any " +
                                "other atoms, excluding themselves. Every atom can also have an infinite amount of unique " +
                                "atomic connections.",
                        "You can neutralize the material, by analyzing it first, finding the symptoms it causes and their " +
                                "location in the molecule. After that, you must construct a molecule that is able to " +
                                "destroy groups of atoms in the material in a way, that the material becomes safe. ",
                        "Once you achieve that all symptoms of the material have been removed, your medicine gets a " +
                                "severity score. Since modifying a material might cause new side effects to come, you " +
                                "also created new symptoms for the users of your chemical. ",
                        "Every symptom has a severity score. " +
                                "The severity score of a molecule is the sum of the severity scores of the symptoms " +
                                "it causes.",
                        "You can destroy atom groups using the following rules: \n" +
                                "(R)-(R) destroys (G)-(B)\n" +
                                "(R)-(G) destroys (B)-(B)\n" +
                                "(R)-(B) destroys (R)-(R)\n" +
                                "(G)-(G) destroys (R)-(G)\n" +
                                "(G)-(B) destroys (R)-(B)\n" +
                                "(B)-(B) destroys (G)-(G)",
                        "If there are multiple atom groups that would be destroyed during the reaction, a random one is " +
                                "chosen. This does not mean, that you can only destroy one group of each kind. Using more " +
                                "of the same structures in the chemical lets the reaction repeat, and destroy another " +
                                "group randomly. Using the same amount of these structures as the groups in the other " +
                                "material leads to that kind of group completely getting destroyed. ",
                        "In the molecule editor, you can create the different kinds of atoms via clicking on their " +
                                "button on the left of the screen, and dragging them into the window. ",
                        "You can connect to atoms via holding shift, while dragging out the connection line between the " +
                                "atoms that you want to connect. To destroy a connection, repeat the same shift-drag " +
                                "action. You can move atoms just by dragging them, and dragging them into the trashcan " +
                                "found on the left side leads to their deletion.",
                        "When your chemical is ready, click the \"Test\" button found at the bottom. This sends out a " +
                                "sample to many test participants, and you will get the cumulated report of the testing. " +
                                "Keep in mind, that due to many factors, a certain noise may be present in this sample. " +
                                "In order to completely see behind the scenes, you need to analyze the sample. You can " +
                                "do this by clicking the \"Analyze\" button on the test results screen.",
                        "If your chemical shows promise, that is it neutralizes the dangerous material, and has a " +
                                "predicted severity score below 6, you may release it as a product. If your product is " +
                                "in fact up to these standards, you won, otherwise, you should get back to design.",
                        "Thank you for reading, and playing our game! :)"
                )
        );

        if (GameState.getInstance ().isFirstRunStarted ()) {
            tutorialRenderer.finished = true;
        }

        GameState.getInstance ().setFirstRunStarted (true);

    }

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

        /*
        objectivePanelWidth = 256f;
        objectivePanelHeight = 128f;

        CompoundMolecularRule cmr = (CompoundMolecularRule) GameState.getInstance ().getDiseaseRule ();
        for (MoleculeRule mr : cmr.rules) {

            String s = RuleTextifier.textify (mr);
            s = s.replaceAll (
                    "red",
                    ColorUtil.markup (Colors.AtomRed) + "red" + ColorUtil.markup (Colors.TextDark)
            );
            s = s.replaceAll (
                    "green",
                    ColorUtil.markup (Colors.Green) + "green" + ColorUtil.markup (Colors.TextDark)
            );
            s = s.replaceAll (
                    "blue",
                    ColorUtil.markup (Colors.AtomBlue) + "blue" + ColorUtil.markup (Colors.TextDark)
            );

            // TODO: Optimize!!!
            GlyphLayout gl = new GlyphLayout (
                    Fonts.f12 (),
                    s,
                    Colors.TextDark,
                    objectivePanelWidth - 32f - 32f,
                    Align.topLeft,
                    true
            );

            objectivePanelHeight += gl.height + 32f;

        }

        objectivePanelHeight += 32f;
        */

    }

    @Override
    protected void update (float delta) {

    }

    @Override
    protected void onDispose () {

    }

    private void renderObjectivePanel () {

    }

    @Override
    public void render (float delta) {

        renderObjectivePanel ();

        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);

        visitedConnections.clear ();

        // Draw UI

        shapeRenderer.begin (ShapeRenderer.ShapeType.Filled);

        shapeRenderer.setColor (Colors.LightPanelShadow);
        shapeRenderer.rect (
                24.0f, 8.0f, 128.0f, Gdx.graphics.getHeight () - 24.0f
        );

        shapeRenderer.setColor (Colors.LightPanelBackground);
        shapeRenderer.rect (
                16.0f, 16.0f, 128.0f, Gdx.graphics.getHeight () - 24.0f
        );

       // Trash

        shapeRenderer.setColor(Colors.DarkGray);
        shapeRenderer.roundedRect(50.0f,
                32.0f,
                60.0f, 80.0f, 8.0f);
        shapeRenderer.roundedRect(50.0f - 8.0f,
                80.0f + 32.0f + 2.0f,
                76.0f, 8.0f, 3.0f);

        shapeRenderer.setColor(Colors.LightPanelBackground);
        shapeRenderer.rect(50.0f - 8.0f,
                80.0f + 32.0f + 1.0f,
                76.0f, 3.0f);


        shapeRenderer.roundedRect(50.0f + 10.0f,
                32.0f + 8.0f,
                8.0f, 80.0f - 16.0f, 3.0f);
        shapeRenderer.roundedRect(50.0f + 26.0f,
                32.0f + 8.0f,
                8.0f, 80.0f - 16.0f, 3.0f);
        shapeRenderer.roundedRect(50.0f + 42.0f,
                32.0f + 8.0f,
                8.0f, 80.0f - 16.0f, 3.0f);

        shapeRenderer.end();

        Gdx.gl.glLineWidth(2.5f);

        shapeRenderer.begin(RoundedShapeRenderer.ShapeType.Line);

        shapeRenderer.setColor(Colors.DarkGray);
        shapeRenderer.roundedRect(50.0f + 10.0f,
                80.0f + 32.0f + 6.0f,
                40.0f, 10.0f, 2.0f);

        shapeRenderer.end();

        Gdx.gl.glLineWidth(1.0f);

        trashRectangle.set(50.0f - 8.0f,
                32.0f,
                76.0f,
                96.0f);


        shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);

        shapeRenderer.setColor (Colors.AtomRedDark);
        shapeRenderer.circle (
                16.0f + 128.0f / 2.0f + 4.0f,
                Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f - 4.0f,
                32.0f
        );

        shapeRenderer.setColor (Colors.AtomRed);
        shapeRenderer.circle (16.0f + 128.0f / 2.0f, Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f, 32.0f);
        redToolbarCircle.set (16.0f + 128.0f / 2.0f, Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f, 32.0f);


        shapeRenderer.setColor (Colors.AtomGreenDark);
        shapeRenderer.circle (
                16.0f + 128.0f / 2.0f + 4.0f,
                Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f - 48.0f * 2.0f - 4.0f,
                32.0f
        );

        shapeRenderer.setColor (Colors.AtomGreen);
        shapeRenderer.circle (
                16.0f + 128.0f / 2.0f,
                Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f - 48.0f * 2.0f,
                32.0f
        );
        greenToolbarCircle.set (
                16.0f + 128.0f / 2.0f,
                Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f - 48.0f * 2.0f,
                32.0f
        );


        shapeRenderer.setColor (Colors.AtomBlueDark);
        shapeRenderer.circle (
                16.0f + 128.0f / 2.0f + 4.0f,
                Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f - 48.0f * 2.0f - 48.0f * 2.0f - 4.0f,
                32.0f
        );

        shapeRenderer.setColor (Colors.AtomBlue);
        shapeRenderer.circle (
                16.0f + 128.0f / 2.0f,
                Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f - 48.0f * 2.0f - 48.0f * 2.0f,
                32.0f
        );
        blueToolbarCircle.set (
                16.0f + 128.0f / 2.0f,
                Gdx.graphics.getHeight () - 24.0f - 16.0f - 24.0f - 48.0f * 2.0f - 48.0f * 2.0f,
                32.0f
        );


        shapeRenderer.setColor (Colors.LightPanelShadow);
        shapeRenderer.roundedRect (
                Gdx.graphics.getWidth () / 2.0f - 128.0f + 8.0f,
                8.0f,
                256.0f, 80.0f, 10.0f
        );

        shapeRenderer.setColor (Colors.Green);
        shapeRenderer.roundedRect (
                Gdx.graphics.getWidth () / 2.0f - 128.0f,
                16.0f,
                256.0f, 80.0f, 10.0f
        );
        finishButtonRectangle.set (
                Gdx.graphics.getWidth () / 2.0f - 128.0f,
                16.0f,
                256.0f, 80.0f
        );

        float opWidth = Math.max (
                GameState.getInstance ().getDiseaseMolecule ().boundingBox.width + 32f,
                256f
        );

        objectivePanelRect.set (
                resolution.x - 32f - opWidth,
                32f,
                opWidth,
                resolution.y - 64f
        );
        objectivePanelInsideRect.set (
                objectivePanelRect.x + 16f,
                objectivePanelRect.y + 16f,
                objectivePanelRect.width - 32f,
                objectivePanelRect.height - 32f
        );

        shapeRenderer.setColor (Colors.BluePanelBackground);
        shapeRenderer.roundedRect (
                objectivePanelRect.x, objectivePanelRect.y,
                objectivePanelRect.width, objectivePanelRect.height, 10.0f
        );

        visitedConnections.clear ();

        float offX = (objectivePanelInsideRect.width - molecule.boundingBox.width) / 2.0f - molecule.boundingBox.x;
        float offY = (objectivePanelInsideRect.height - molecule.boundingBox.height) / 2.0f - molecule.boundingBox.y;


        for (Atom atom : GameState.getInstance ().getDiseaseMolecule ().atoms) {

            for (Atom neighbour : atom.connectedTo) {

                Connection c = new Connection (atom.id, neighbour.id);
                if (visitedConnections.add (c)) {
                    shapeRenderer.setColor (Color.BLACK);
                    shapeRenderer.rectLine (
                            atom.drawPosition.x + objectivePanelInsideRect.x + offX,
                            atom.drawPosition.y + objectivePanelInsideRect.y + offY,
                            neighbour.drawPosition.x + objectivePanelInsideRect.x + offX,
                            neighbour.drawPosition.y + objectivePanelInsideRect.y + offY,
                            2.0f
                    );
                }

            }

        }

        for (Atom atom : GameState.getInstance ().getDiseaseMolecule ().atoms) {

            shapeRenderer.setColor (Color.BLACK);
            shapeRenderer.circle (
                    atom.drawPosition.x + objectivePanelInsideRect.x + offX,
                    atom.drawPosition.y + objectivePanelInsideRect.y + offY,
                    10f
            );

            shapeRenderer.setColor (atom.type.color);
            shapeRenderer.circle (
                    atom.drawPosition.x + objectivePanelInsideRect.x + offX,
                    atom.drawPosition.y + objectivePanelInsideRect.y + offY,
                    8f
            );

        }

        shapeRenderer.end ();

        atomsRenderer.renderAtoms (
                shapeRenderer,
                molecule.atoms,
                true,
                null,
                16f
        );

        shapeRenderer.begin (ShapeRenderer.ShapeType.Filled);

        if (inputAction == InputAction.ConnectAtoms) {
            shapeRenderer.setColor (Color.BLACK);
            shapeRenderer.rectLine (dragStart, mouse, 2.0f);
        }

        shapeRenderer.end ();

        spriteBatch.begin ();

        Fonts.f32 ()
                .setColor (Colors.TextLight);
        Fonts.f32 ()
                .draw (
                        spriteBatch,
                        "Test",
                        Gdx.graphics.getWidth () / 2.0f - finishGlyphLayout.width / 2.0f,
                        16.0f + 40.0f + finishGlyphLayout.height / 2.0f
                );

        Fonts.f32 ().draw (
                spriteBatch,
                "Objectives",
                objectivePanelInsideRect.x,
                objectivePanelInsideRect.y + objectivePanelInsideRect.height,
                objectivePanelInsideRect.width,
                Align.center, true
        );

        spriteBatch.end ();

        if (!tutorialRenderer.finished) {
            tutorialRenderer.render (shapeRenderer, spriteBatch, delta, resolution);
        }
    }

    @Override
    public GameScreen next () {
        if (finished) {
            return new TestResultsGameScreen (ludumGame);
        }
        if(escape) {
            return new MenuGameScreen(ludumGame);
        }
        return null;
    }

    @Override
    public boolean keyDown (int keycode) {

        if (tutorialRenderer.finished) {
            if (keycode == Input.Keys.SHIFT_LEFT) {
                inputAction = InputAction.AboutToConnectAtoms;
            }

            if (keycode == Input.Keys.ESCAPE) {
                escape = true;
            }
        }

        return false;
    }

    @Override
    public boolean keyUp (int keycode) {

        if (tutorialRenderer.finished) {
            if (keycode == Input.Keys.SHIFT_LEFT
                    && (inputAction == InputAction.ConnectAtoms || inputAction == InputAction.AboutToConnectAtoms)) {
                inputAction = InputAction.NoAction;
            }

            if (keycode == Input.Keys.R) {

                Atom atom = new Atom (lastId++, Atom.Type.Red);
                atom.drawPosition.set (0.0f, 0.0f);
                molecule.atoms.add (atom);

            }

            if (keycode == Input.Keys.G) {

                Atom atom = new Atom (lastId++, Atom.Type.Green);
                atom.drawPosition.set (0.0f, 0.0f);
                molecule.atoms.add (atom);

            }

            if (keycode == Input.Keys.B) {

                Atom atom = new Atom (lastId++, Atom.Type.Blue);
                atom.drawPosition.set (0.0f, 0.0f);
                molecule.atoms.add (atom);

            }
        }

        return false;
    }

    @Override
    public boolean keyTyped (char character) {
        return false;
    }

    @Override
    public boolean touchDown (int screenX, int screenY, int pointer, int button) {

        Vector2 mouse = screenToMouse (screenX, screenY);
        if (tutorialRenderer.finished) {

            dragStart.set (mouse);
            Vector2 v = new Vector2 ();

            if (button == Input.Buttons.RIGHT) {

                Atom toRemove = null;

                for (Atom atom : molecule.atoms) {

                    v.set (mouse).sub (atom.drawPosition);
                    if (v.len () <= 16.0f) {

                        toRemove = atom;
                        break;

                    }

                }

                if (toRemove != null) {
                    toRemove.disconnectAll ();
                    molecule.atoms.remove (toRemove);
                }

            }

            if (inputAction == InputAction.NoAction) {

                if (redToolbarCircle.contains (mouse)) {

                    Atom atom = new Atom (lastId++, Atom.Type.Red);
                    atom.drawPosition.set (mouse);
                    molecule.atoms.add (atom);

                } else if (greenToolbarCircle.contains (mouse)) {

                    Atom atom = new Atom (lastId++, Atom.Type.Green);
                    atom.drawPosition.set (mouse);
                    molecule.atoms.add (atom);

                } else if (blueToolbarCircle.contains (mouse)) {

                    Atom atom = new Atom (lastId++, Atom.Type.Blue);
                    atom.drawPosition.set (mouse);
                    molecule.atoms.add (atom);

                }

                for (Atom atom : molecule.atoms) {

                    v.set (mouse).sub (atom.drawPosition);
                    if (v.len () <= 16.0f) {

                        grabbedAtom = atom;
                        inputAction = InputAction.GrabAtom;
                        break;

                    }

                }

            } else if (inputAction == InputAction.AboutToConnectAtoms) {

                for (Atom atom : molecule.atoms) {

                    v.set (mouse).sub (atom.drawPosition);
                    if (v.len () <= 16.0f) {

                        connectFromAtom = atom;
                        inputAction = InputAction.ConnectAtoms;
                        break;

                    }

                }

            }

        }

        return false;
    }

    @Override
    public boolean touchUp (int screenX, int screenY, int pointer, int button) {

        Vector2 mouse = screenToMouse (screenX, screenY);

        if (tutorialRenderer.finished) {

            if (finishButtonRectangle.contains (mouse)) {
                finished = true;
                return false;
            }
            Vector2 v = new Vector2 ();
            if(grabbedAtom != null && trashRectangle.contains(mouse)) {
                Atom toRemove = null;

                for (Atom atom : molecule.atoms) {

                    v.set (mouse).sub (atom.drawPosition);
                    if (v.len () <= 16.0f) {

                        toRemove = atom;
                        break;

                    }

                }

                if (toRemove != null) {
                    toRemove.disconnectAll ();
                    molecule.atoms.remove (toRemove);
                }
            }

        switch (inputAction) {

                case NoAction:
                    break;
                case GrabAtom:

                    dragStart.set (0, 0);
                    grabbedAtom = null;
                    inputAction = InputAction.NoAction;

                    break;
                case ConnectAtoms:
                v = new Vector2 ();
                for (Atom atom : molecule.atoms) {

                        v.set (mouse).sub (atom.drawPosition);
                        if (v.len () <= 16.0f) {

                            if (atom.isConnected (connectFromAtom)) {
                                atom.disconnect (connectFromAtom);
                                break;
                            }

                            if (!atom.equals (connectFromAtom)) {

                                atom.connect (connectFromAtom);
                                break;

                            }


                        }

                    }

                    inputAction = InputAction.AboutToConnectAtoms;

                    break;
            }

        } else {

            tutorialRenderer.click (mouse);

        }

        // inputAction = InputAction.NoAction;

        return false;
    }

    @Override
    public boolean touchDragged (int screenX, int screenY, int pointer) {

        Vector2 mouse = screenToMouse (screenX, screenY);

        if (tutorialRenderer.finished) {

            if (inputAction == InputAction.GrabAtom) {
                if (grabbedAtom != null) {

                    grabbedAtom.drawPosition.set (mouse);

                }
            }

        }

        return false;
    }

    @Override
    public boolean mouseMoved (int screenX, int screenY) {
        return false;
    }

    @Override
    public boolean scrolled (int amount) {
        return false;
    }

    enum InputAction {

        NoAction,
        GrabAtom,
        AboutToConnectAtoms,
        ConnectAtoms

    }

}
