package games.colonier.ld44.chemistry.rule;

import games.colonier.ld44.chemistry.Atom;
import games.colonier.ld44.chemistry.Molecule;
import games.colonier.ld44.chemistry.MoleculeRule;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

public class StructuralMolecularRule implements MoleculeRule {

    public final String expression;

    public StructuralMolecularRule (String expression) {
        this.expression = expression;
    }

    private String command (Atom a1, Atom a2) {
        return a1.type.name ().substring (0, 1).toLowerCase () + a2.type.name ().substring (0, 1).toLowerCase ();
    }

    private List<Edge> add (List<Edge> list, Edge edge) {

        List<Edge> result = new ArrayList<> ();
        if (list != null) {
            result.addAll (list);
        }
        result.add (edge);
        return result;

    }

    public List<Edge> firstMatch (Molecule molecule) {

        List<String> commands = new ArrayList<> ();
        for (int i = 0; i < expression.length () / 2; i++) {
            commands.add (expression.substring (2 * i, 2 * (i + 1)));
        }

        int currentCommand = 0;

        Deque<Walk> walks = new ArrayDeque<> ();

        for (Atom a1 : molecule.atoms) {
            for (Atom a2 : a1.connectedTo) {

                if (command (a1, a2).equalsIgnoreCase (commands.get (currentCommand))) {
                    walks.addLast (
                            new Walk (
                                    add (null, new Edge (a1, a2)),
                                    a1,
                                    a2,
                                    currentCommand + 1,
                                    currentCommand + 1 == commands.size ()
                            )
                    );
                }

            }
        }

        while (!walks.isEmpty ()) {

            Walk current = walks.removeFirst ();

            if (current.finish) {
                return current.path;
            }

            for (Atom c : current.atom.connectedTo) {
                if (command (current.atom, c).equalsIgnoreCase (commands.get (current.commandIndex))) {
                    walks.addFirst (
                            new Walk (
                                    add (current.path, new Edge (current.atom, c)),
                                    current.atom,
                                    c,
                                    current.commandIndex + 1,
                                    current.commandIndex + 1 == commands.size ()
                            )
                    );
                }
            }

        }

        return null;
    }

    @Override
    public boolean appliesTo (Molecule molecule) {

        return firstMatch (molecule) != null;

    }

    public static final class Edge {

        public final Atom a;
        public final Atom b;

        public Edge (Atom a, Atom b) {
            this.a = a;
            this.b = b;
        }

    }

    private static final class Walk {

        public final List<Edge> path;
        public final Atom fromAtom;
        public final Atom atom;
        public final int commandIndex;
        public final boolean finish;


        private Walk (List<Edge> path, Atom fromAtom, Atom atom, int commandIndex, boolean finish) {
            this.path = path;
            this.fromAtom = fromAtom;
            this.atom = atom;
            this.commandIndex = commandIndex;
            this.finish = finish;
        }

    }

}
