package pl.edu.pw.mini.sg.gamegen;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.apache.commons.math3.random.RandomGenerator;
import org.apache.commons.math3.random.Well19937c;

import com.google.common.base.Preconditions;
import com.lexicalscope.jewel.cli.CliFactory;

import pl.edu.pw.mini.jk.sg.game.configuration.DiscreteProbabilityFunction;
import pl.edu.pw.mini.jk.sg.game.configuration.Edge;
import pl.edu.pw.mini.jk.sg.game.configuration.GraphDescription;
import pl.edu.pw.mini.jk.sg.game.configuration.StaticGameConfiguration;
import pl.edu.pw.mini.jk.sg.game.configuration.StaticGameConfigurationReader;
import pl.edu.pw.mini.jk.sg.game.graph.advanceable.AdvanceableGraphGameConfiguration;

public class App {

    public static void main(String[] args) throws IOException {
        final GamegenOptions arguments = CliFactory.createCli(GamegenOptions.class).parseArguments(args);
        final AdvanceableGraphGameConfiguration outGraph;
        final Random random = new Random();
        final RandomGenerator rng = new Well19937c();

        switch (arguments.getGraphType()) {
        case SIMPLE:
            outGraph = simpleGameGraph(arguments.getTargetCount(), random);
            break;

        case ER:
            outGraph = erRandomGame(arguments.getVertexCount(), arguments.getTargetCount(),
                    arguments.getEdgeProbability(), random);
            break;
        case SSMD:
            outGraph = ssmdRandomGame(arguments.getTargetCount(), arguments.getDefenderCount(), 20, random);
            break;
        case ERLIM:
            outGraph = erlimRandomGame(arguments.getVertexCount(), arguments.getTargetCount(),
                    arguments.getDefenderCount(), arguments.getEdgeProbability(), random);
            break;
        case BUILDINGLIKE:
            outGraph = new BuildingLikeGameGenerator(rng,
                    BuildingLikeGameGeneratorConfig.parse(arguments.getMethodConfig()))
                            .generateRandomGame(arguments.isOutputFilesPrefix()
                                    ? Optional.of(arguments.getOutputFilesPrefix()) : Optional.empty());
            break;
        default:
            throw new IllegalArgumentException("Bad graph type value: " + arguments.getGraphType());
        }

	StaticGameConfigurationReader.writeToFile(outGraph, arguments.getOutFile());
    }

    public static AdvanceableGraphGameConfiguration simpleGameGraph(int targets, Random random) {
        Preconditions.checkArgument(targets > 0);

        List<Edge> edges = IntStream.range(2, targets + 2).boxed()
                .flatMap(to -> Stream.of(new Edge(0, to), new Edge(1, to))).collect(Collectors.toList());

        int[][] attackerRewards = Stream.generate(() -> new int[] { random.nextInt(20) }).limit(targets)
                .collect(Collectors.toList()).toArray(new int[targets][]);

        int[][] defenderPenalties = Arrays.stream(attackerRewards).map(x -> new int[] { x[0] })
                .collect(Collectors.toList()).toArray(new int[targets][]);

        int[][] vertexDefenderRewards = Stream.generate(() -> new int[] { random.nextInt(20) }).limit(targets + 2)
                .collect(Collectors.toList()).toArray(new int[targets + 2][]);

        int[][] vertexAttackerPenalties = Arrays.stream(vertexDefenderRewards).map(x -> new int[] { x[0] })
                .collect(Collectors.toList()).toArray(new int[targets + 2][]);

        return new AdvanceableGraphGameConfiguration(
                new StaticGameConfiguration(new GraphDescription(targets + 2, edges),
                        IntStream.range(2, targets + 2).boxed().collect(Collectors.toList()), Arrays.asList(1),
                        defenderPenalties, attackerRewards, vertexDefenderRewards, vertexAttackerPenalties, 1, 1,
					    DiscreteProbabilityFunction.instance(), 0, 0),
                1);
    }

    // Czy chemy spradzać spójność?? Może nie.
    public static AdvanceableGraphGameConfiguration erRandomGame(int vertices, int targets, double edgeProb,
            Random random) {
        Preconditions.checkArgument(targets > 0);
        Preconditions.checkArgument(targets <= vertices + 2);
        Preconditions.checkArgument(edgeProb > 0 && edgeProb <= 1.);

        int[][] attackerRewards = Stream.generate(() -> new int[] { random.nextInt(20) }).limit(targets)
                .collect(Collectors.toList()).toArray(new int[targets][]);

        int[][] defenderPenalties = Arrays.stream(attackerRewards).map(x -> new int[] { random.nextInt(20) })
                .collect(Collectors.toList()).toArray(new int[targets][]);

        int[][] vertexDefenderRewards = Stream.generate(() -> new int[] { random.nextInt(20) }).limit(vertices)
                .collect(Collectors.toList()).toArray(new int[vertices][]);

        int[][] vertexAttackerPenalties = Arrays.stream(vertexDefenderRewards)
                .map(x -> new int[] { random.nextInt(20) }).collect(Collectors.toList()).toArray(new int[vertices][]);

        List<Edge> edges = IntStream
                .range(0, vertices).boxed().flatMap(i -> IntStream.range(0, vertices)
                        .filter(j -> j != i && edgeProb > random.nextDouble()).mapToObj(j -> new Edge(i, j)))
                .collect(Collectors.toList());

        return new AdvanceableGraphGameConfiguration(new StaticGameConfiguration(new GraphDescription(vertices, edges),
                IntStream.range(2, targets + 2).boxed().collect(Collectors.toList()), Arrays.asList(1),
                defenderPenalties, attackerRewards, vertexDefenderRewards, vertexAttackerPenalties, 1, 1,
										 DiscreteProbabilityFunction.instance(), 0, 0), 1);
    }

    public static AdvanceableGraphGameConfiguration erlimRandomGame(int vertices, int targets, int defenders,
            double edgeProb, Random random) {
        Preconditions.checkArgument(targets > 0);
        Preconditions.checkArgument(targets <= vertices + 2);
        Preconditions.checkArgument(edgeProb > 0 && edgeProb <= 1.);

        int[][] attackerRewards = Stream.generate(() -> new int[] { random.nextInt(21) }).limit(targets)
                .collect(Collectors.toList()).toArray(new int[targets][]);

        int[][] defenderPenalties = Arrays.stream(attackerRewards).map(x -> new int[] { random.nextInt(21) })
                .collect(Collectors.toList()).toArray(new int[targets][]);

        int[][] vertexDefenderRewards = Stream.generate(() -> new int[] { 0 }).limit(vertices)
                .collect(Collectors.toList()).toArray(new int[vertices][]);

        int[][] vertexAttackerPenalties = Arrays.stream(vertexDefenderRewards).map(x -> new int[] { 0 })
                .collect(Collectors.toList()).toArray(new int[vertices][]);

        List<Edge> edges = IntStream
                .range(0, vertices).boxed().flatMap(i -> IntStream.range(0, vertices)
                        .filter(j -> j != i && edgeProb > random.nextDouble()).mapToObj(j -> new Edge(i, j)))
                .collect(Collectors.toList());

        return new AdvanceableGraphGameConfiguration(new StaticGameConfiguration(new GraphDescription(vertices, edges),
                IntStream.range(2, targets + 2).boxed().collect(Collectors.toList()), Arrays.asList(1),
                defenderPenalties, attackerRewards, vertexDefenderRewards, vertexAttackerPenalties, 1, defenders,
                DiscreteProbabilityFunction.instance(), 0, 0), 1);
    }

    public static AdvanceableGraphGameConfiguration ssmdRandomGame(final int targets, final int defenders,
            final int payoffLimit, Random random) {
        Preconditions.checkArgument(targets > 0);
        Preconditions.checkArgument(defenders > 0);

        List<Edge> edges = IntStream.range(2, targets + 2).boxed()
                .flatMap(to -> Stream.of(new Edge(0, to), new Edge(1, to))).collect(Collectors.toList());

        int[][] attackerRewards = Stream.generate(() -> new int[] { random.nextInt(payoffLimit + 1) }).limit(targets)
                .collect(Collectors.toList()).toArray(new int[targets][]);

        int[][] defenderPenalties = Arrays.stream(attackerRewards)
                .map(x -> new int[] { random.nextInt(payoffLimit + 1) }).collect(Collectors.toList())
                .toArray(new int[targets][]);

        int[][] vertexDefenderRewards = Stream.generate(() -> new int[] { random.nextInt(payoffLimit + 1) })
                .limit(targets + 2).collect(Collectors.toList()).toArray(new int[targets + 2][]);

        int[][] vertexAttackerPenalties = Arrays.stream(vertexDefenderRewards)
                .map(x -> new int[] { random.nextInt(payoffLimit + 1) }).collect(Collectors.toList())
                .toArray(new int[targets + 2][]);

        return new AdvanceableGraphGameConfiguration(
                new StaticGameConfiguration(new GraphDescription(targets + 2, edges),
                        IntStream.range(2, targets + 2).boxed().collect(Collectors.toList()), Arrays.asList(1),
                        defenderPenalties, attackerRewards, vertexDefenderRewards, vertexAttackerPenalties, 1,
					    defenders, DiscreteProbabilityFunction.instance(), 0, 0),
                1);
    }

}
