package games.colonier.ld44.chemistry.rule;

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

import java.util.*;

public class SimpleAtomicMolecularRule implements MoleculeRule {

    public SimpleAtomicMolecularRule (
            Counter counter,
            Decider decider,
            int parameter
    ) {
        this.counter = counter;
        this.decider = decider;
        this.parameter = parameter;
    }

    @FunctionalInterface
    public interface Counter {

        int count (Molecule molecule);

    }

    @FunctionalInterface
    public interface Decider {

        boolean decide (SimpleAtomicMolecularRule rule, int input);

    }


    public final Counter counter;
    public final Decider decider;
    public final int parameter;


    @Override
    public boolean appliesTo (Molecule molecule) {

        return decider.decide (this, counter.count (molecule));

    }

    public static class ConnectionCounter implements Counter {

        public final Atom.Type t1;
        public final Atom.Type t2;

        private ConnectionCounter (Atom.Type t1, Atom.Type t2) {
            this.t1 = t1;
            this.t2 = t2;
        }

        @Override
        public int count (Molecule molecule) {
            Set <Connection> s = new HashSet<> ();
            for (Atom a1 : molecule.atoms) {
                if (a1.type != t1) continue;
                for (Atom a2 : a1.connectedTo) {
                    if (a2.type != t2) continue;
                    s.add (new Connection (a1.id, a2.id));
                }
            }
            return s.size ();
        }

    }

    public static class DifferenceCounter implements Counter {

        public final Counter c1;
        public final Counter c2;

        public DifferenceCounter (Counter c1, Counter c2) {
            this.c1 = c1;
            this.c2 = c2;
        }

        @Override
        public int count (Molecule molecule) {
            return Math.abs (c1.count (molecule) - c2.count (molecule));
        }

    }

    public static class SumCounter implements Counter {

        public final Counter c1;
        public final Counter c2;

        public SumCounter (Counter c1, Counter c2) {
            this.c1 = c1;
            this.c2 = c2;
        }

        @Override
        public int count (Molecule molecule) {
            return c1.count (molecule) + c2.count (molecule);
        }

    }

    public static Counter RedAtomCounter = molecule -> (int) molecule.atoms.stream ().filter (Atom::isRed).count ();
    public static Counter GreenAtomCounter = molecule -> (int) molecule.atoms.stream ().filter (Atom::isGreen).count ();
    public static Counter BlueAtomCounter = molecule -> (int) molecule.atoms.stream ().filter (Atom::isBlue).count ();
    public static Counter RedRedConnectionCounter = new ConnectionCounter (Atom.Type.Red, Atom.Type.Red);
    public static Counter RedGreenConnectionCounter = new ConnectionCounter (Atom.Type.Red, Atom.Type.Green);
    public static Counter RedBlueConnectionCounter = new ConnectionCounter (Atom.Type.Red, Atom.Type.Blue);
    public static Counter GreenGreenConnectionCounter = new ConnectionCounter (Atom.Type.Green, Atom.Type.Green);
    public static Counter GreenBlueConnectionCounter = new ConnectionCounter (Atom.Type.Green, Atom.Type.Blue);
    public static Counter BlueBlueConnectionCounter = new ConnectionCounter (Atom.Type.Blue, Atom.Type.Blue);

    public static List <Counter> AllCounters = Arrays.asList (
            RedAtomCounter, GreenAtomCounter, BlueAtomCounter,
            RedRedConnectionCounter, RedGreenConnectionCounter, RedBlueConnectionCounter,
            GreenGreenConnectionCounter, GreenBlueConnectionCounter,
            BlueBlueConnectionCounter
    );

    public static Decider AtLeastDecider = (rule, input) -> input >= rule.parameter;
    public static Decider AtMostDecider = (rule, input) -> input <= rule.parameter;
    public static Decider MoreThanDecider = (rule, input) -> input > rule.parameter;
    public static Decider LessThanDecider = (rule, input) -> input < rule.parameter;
    public static Decider EvenDecider = (rule, input) -> input % 2 == 0;
    public static Decider OddDecider = (rule, input) -> input % 2 != 0;

    public static List <Decider> AllDeciders = Arrays.asList (
            AtLeastDecider,
            AtMostDecider,
            MoreThanDecider,
            LessThanDecider,
            EvenDecider,
            OddDecider
    );

    public static Counter randomCounter (Random random) {

        if (random.nextBoolean ()) {
            Counter c1 = null;
            Counter c2 = null;

            do {
                c1 = AllCounters.get (random.nextInt (AllCounters.size ()));
                c2 = AllCounters.get (random.nextInt (AllCounters.size ()));
            } while (c1 == c2);

            if (random.nextBoolean ()) {
                return new SumCounter (c1, c2);
            } else {
                return new DifferenceCounter (c1, c2);
            }
        }

        return AllCounters.get (random.nextInt (AllCounters.size ()));

    }

    public static Decider randomDecider (Random random) {
        return AllDeciders.get (random.nextInt (AllDeciders.size ()));
    }

}
