package resource_exchange_arena;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

class SimulationRun {
    /**
     * Each Simulation run with the same parameters runs as an isolated instance although data is recorded in a single
     * location.
     *
     * @param demandCurves Double arrays of demand used by the agents, when multiple curves are used the agents
     *                    are split equally between the curves.
     * @param totalDemandValues Double values represeneting the sum of all values in their associated demand curves.
     * @param availabilityCurve Integer array representing the amount of energy available at each timeslot.
     * @param totalAvailability Integer value representing the total energy available throughout the day.
     * @param days Integer value representing the number of days to be simulated.
     * @param maxExchanges Stores the highest number of exchange rounds reached each simulation.
     * @param populationSize Integer value representing the size of the initial agent population.
     * @param uniqueTimeSlots Integer value representing the number of unique time slots available in the simulation.
     * @param slotsPerAgent Integer value representing the number of time slots each agent requires.
     * @param numberOfAgentsToEvolve Integer value representing the number of Agents who's strategy will change at the
     *                               end of each day.
     * @param agentTypes Integer array containing the agent types that the simulation will begin with. The same type
     *                   can exist multiple times in the array where more agents of one type are required.
     * @param uniqueAgentTypes Integer ArrayList containing each unique agent type that exists when the simulation
     *                         begins.
     * @param singleAgentType Boolean value specifying whether only a single agent type should exist, used for
     *                        establishing baseline results.
     * @param selectedSingleAgentType Integer value representing the single agent type to be modelled when
     *                                singleAgentType is true.
     * @param socialCapital Boolean value that determines whether or not social agents will utilise social capital.
     * @param keyDaysData Stores the state of the simulation when a population takes over and when the simulation ends.
     * @param allDailyDataCSVWriter Used to store data ragarding the state of the system at the end of each day.
     * @param perAgentDataCSVWriter Used to store data ragarding the state of the agent at the end of each day.
     * @param eachRoundDataCSVWriter Used to store data ragarding the state of the system at the end of each round.
     * @exception IOException On input error.
     * @see IOException
     */
    SimulationRun(
            double[][] demandCurves,
            double[] totalDemandValues,
            int [] availabilityCurve,
            int totalAvailability,
            int days,
            ArrayList<Integer> maxExchanges,
            int populationSize,
            int uniqueTimeSlots,
            int slotsPerAgent,
            int numberOfAgentsToEvolve,
            int[] agentTypes,
            ArrayList<Integer> uniqueAgentTypes,
            boolean singleAgentType,
            int selectedSingleAgentType,
            boolean socialCapital,
            ArrayList<ArrayList<Double>> keyDaysData,
            FileWriter dailyDataWriter,
            FileWriter perAgentDataCSVWriter,
            FileWriter eachRoundDataCSVWriter,
            int run
    ) throws IOException {

        // List of all the Agents that are part of the current simulation.
        ArrayList<Agent> agents = new ArrayList<>();

        // Create the Agents for the simulation.
        for (int agentNumber = 1; agentNumber <= populationSize; agentNumber++) {
                /*
                 * This is the constructor for Agent objects.
                 *
                 * @param agentID This is an integer value that is unique to the individual agent and used to identify
                 *                it to others in the ExchangeArena.
                 * @param agentType Integer value denoting the agent type, and thus how it will behave.
                 * @param slotsPerAgent Integer value representing the number of time slots each agent requires.
                 * @param agents Array List of all the agents that exist in the current simulation.
                 * @param socialCapital determines whether the agent uses socialCapital.
                 */
                new Agent(
                        agentNumber,
                        agentTypes[agentNumber % agentTypes.length],
                        slotsPerAgent,
                        agents,
                        socialCapital
                );
        }
        Collections.shuffle(agents, ResourceExchangeArena.random);

        // Set all agents to a single type, used for establishing baseline performance.
        if (singleAgentType && selectedSingleAgentType != 0) {
            for (Agent a: agents) {
                a.setType(selectedSingleAgentType);
            }
        }   
        
        // Increment the simulations seed each run.
        ResourceExchangeArena.seed++;
        ResourceExchangeArena.random.setSeed(ResourceExchangeArena.seed);

        // Initialise each Agents relations with each other Agent.
        for (Agent a : agents) {
            a.initializeFavoursStore(agents);
        }

        boolean complete = false;
        boolean takeover = false;
        int extention = 1;
        int day = 1;
        while (!complete) {
            /*
            * Each Simulation run with the same parameters runs as an isolated instance although data is recorded in a
            * single location.
            *
            * @param demandCurves Double arrays of demand used by the agents, when multiple curves are used the agents
            *                    are split equally between the curves.
            * @param totalDemandValues Double values represeneting the sum of all values in their associated demand curves.
            * @param availabilityCurve Integer array representing the amount of energy available at each timeslot.
            * @param totalAvailability Integer value representing the total energy available throughout the day.
            * @param day Integer value representing the current day being simulated.
            * @param maxExchanges Stores the highest number of exchange rounds reached each simulation.
            * @param populationSize Integer value representing the size of the initial agent population.
            * @param uniqueTimeSlots Integer value representing the number of unique time slots available in the
            *                        simulation.
            * @param slotsPerAgent Integer value representing the number of time slots each agent requires.
            * @param numberOfAgentsToEvolve Integer value representing the number of Agents who's strategy will change
            *                               at the end of each day.
            * @param uniqueAgentTypes Integer ArrayList containing each unique agent type that exists when the
            *                         simulation begins.
            * @param agents Array List of all the agents that exist in the current simulation.
            * @param allDailyDataCSVWriter Used to store data ragarding the state of the system at the end of each day.
            * @param perAgentDataCSVWriter Used to store data ragarding the state of the agent at the end of each day.
            * @param eachRoundDataCSVWriter Used to store data ragarding the state of the system at the end of each round.
            * @param run Integer value identifying the current simulation run.
            * @exception IOException On input error.
            * @see IOException
            */
            Day current = new Day(
                demandCurves,
                totalDemandValues,
                availabilityCurve,
                totalAvailability,
                day,
                maxExchanges,
                populationSize,
                uniqueTimeSlots,
                slotsPerAgent,
                numberOfAgentsToEvolve,
                uniqueAgentTypes,
                agents,
                dailyDataWriter,
                perAgentDataCSVWriter,
                eachRoundDataCSVWriter,
                run
            );

            if (((current.selPop == 0 || current.socPop == 0) || numberOfAgentsToEvolve == 0) && !takeover) {
                takeover = true;
                ArrayList<Double> takeoverData = new ArrayList<>();
                takeoverData.add((double) run);
                takeoverData.add((double) day);
                takeoverData.add((double) current.socPop);
                takeoverData.add((double) current.selPop);
                takeoverData.add(current.socSat);
                takeoverData.add(current.selSat);
                takeoverData.add(current.socSD);
                takeoverData.add(current.selSD);
                takeoverData.add(current.socialStatValues[0]);
                takeoverData.add(current.selfishStatValues[0]);
                takeoverData.add(current.socialStatValues[1]);
                takeoverData.add(current.selfishStatValues[1]);
                takeoverData.add(current.socialStatValues[2]);
                takeoverData.add(current.selfishStatValues[2]);
                takeoverData.add(current.socialStatValues[3]);
                takeoverData.add(current.selfishStatValues[3]);
                takeoverData.add(current.socialStatValues[4]);
                takeoverData.add(current.selfishStatValues[4]);
                takeoverData.add(current.socialStatValues[5]);
                takeoverData.add(current.selfishStatValues[5]);
                takeoverData.add(current.randomAllocations);
                takeoverData.add(current.optimumAllocations);
                takeoverData.add(0.0);
                keyDaysData.add(takeoverData);
            }
            if (takeover) {
                extention++;
            }

            if (extention == days) {
                complete = true;
                ArrayList<Double> finalData = new ArrayList<>();
                finalData.add((double) run);
                finalData.add((double) day);
                finalData.add((double) current.socPop);
                finalData.add((double) current.selPop);
                finalData.add(current.socSat);
                finalData.add(current.selSat);
                finalData.add(current.socSD);
                finalData.add(current.selSD);
                finalData.add(current.socialStatValues[0]);
                finalData.add(current.selfishStatValues[0]);
                finalData.add(current.socialStatValues[1]);
                finalData.add(current.selfishStatValues[1]);
                finalData.add(current.socialStatValues[2]);
                finalData.add(current.selfishStatValues[2]);
                finalData.add(current.socialStatValues[3]);
                finalData.add(current.selfishStatValues[3]);
                finalData.add(current.socialStatValues[4]);
                finalData.add(current.selfishStatValues[4]);
                finalData.add(current.socialStatValues[5]);
                finalData.add(current.selfishStatValues[5]);
                finalData.add(current.randomAllocations);
                finalData.add(current.optimumAllocations);
                finalData.add(1.0);
                keyDaysData.add(finalData);
            }
            day++;   
        }
    }
}
