package de.hems.trafficsim;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.concurrent.Semaphore;

/**
 * Main model class of TrafficSim. Represents a round course containing vehicles.
 */
public class Track extends Observable {
    /**
     * list a vehicles on the track
     */
    protected List<Vehicle> vehicles;

    /**
     * list of resulting time records of the simulation
     */
    protected List<List<VehicleTimeRecord>> vtrList;

    /**
     * length of the track
     */
    protected float trackLength;

    /**
     * sum of all vehicle speeds during the simulation, used for average calculation
     */
    protected float sumAvgMemory;

    /**
     * sum of all vehicle speeds during the simulation ignoring the first ten steps,
     * used for average calculation
     */
    protected float sumDelAvgMemory;

    /**
     * length of the history kept
     */
    protected int historyLength;

    /**
     * average over all velocities in the simulation
     */
    protected float overallAvg;

    /**
     * average over all velocities in the simulation, ignoring the first ten steps
     */
    protected float delayedAvg;

    /**
     * average over all velocities in the last step
     */
    protected float lastAvg;

    /**
     * current configured wait time between two simulation steps
     */
    protected int waitTime;

    /**
     * currently configured max velocity for all vehicles
     */
    protected float maxVelocity;

    /**
     * currently configured brake probability for all vehicles
     */
    protected float brakeProb;

    /**
     * counter for executed simulation steps
     */
    protected long steps;

    /**
     * semaphore protecting the vtrlist
     */
    protected Semaphore listSemaphore;

    /**
     * Constructor for a new Track.
     *
     * @param numberVehicles number of vehicles on the Track
     * @param trackLength    length of the new Track
     * @param brakeProb      probability of a vehicle to suddenly brake without reason
     * @param maxVelocity    maxmimum velocity of the vehicles
     * @param waitTime       time between two simulation steps to slow down the simulation artificially
     * @param historyLength  length of the history to keep
     */
    public Track(int numberVehicles, float trackLength, float brakeProb, float maxVelocity, int waitTime, int historyLength) {
        this.trackLength = trackLength;
        this.brakeProb = brakeProb;
        this.maxVelocity = maxVelocity;
        this.vehicles = createVehiclesList(numberVehicles);
        this.vtrList = new LinkedList<>();
        this.sumAvgMemory = 0;
        this.sumDelAvgMemory = 0;
        this.overallAvg = 0;
        this.delayedAvg = 0;
        this.lastAvg = 0;
        this.historyLength = historyLength;
        this.waitTime = waitTime;
        this.steps = 0;
        this.listSemaphore = new Semaphore(1);

    }

    /**
     * Getter for vtrList
     *
     * @return vtrList
     */
    public List<List<VehicleTimeRecord>> getVtrList() {
        return vtrList;
    }

    /**
     * Getter for overallAvg
     *
     * @return overallAvg
     */
    public float getOverallAvg() {
        return overallAvg;
    }

    /**
     * Getter for lastAvg
     *
     * @return lastAvg
     */
    public float getLastAvg() {
        return lastAvg;
    }

    /**
     * Getter for delayedAvg
     *
     * @return delayedAvg
     */
    public float getDelayedAvg() {
        return delayedAvg;
    }

    /**
     * Getter for vehicles
     *
     * @return vehicles
     */
    public List<Vehicle> getVehicles() {
        return vehicles;
    }

    /**
     * Getter for trackLength
     *
     * @return trackLength
     */
    public float getTrackLength() {
        return trackLength;
    }

    /**
     * Getter for steps
     *
     * @return steps
     */
    public long getSteps() {
        return steps;
    }

    /**
     * Getter for listSemaphore
     *
     * @return listSemaphore
     */
    public Semaphore getListSemaphore() {
        return listSemaphore;
    }

    /**
     * Getter for historyLength
     *
     * @return historyLength
     */
    public int getHistoryLength() {
        return historyLength;
    }

    /**
     * Utility function to add vehicles to the Track.
     *
     * @param numberVehicles number of vehicles to add
     * @return filled list with vehicles
     */
    protected List<Vehicle> createVehiclesList(int numberVehicles) {
        List<Vehicle> result = new ArrayList<>();
        for (int i = 0; i < numberVehicles; i++) {
            Vehicle vehicle = new Vehicle(i, i, this.maxVelocity, this.brakeProb, this.trackLength);
            result.add(vehicle);
        }

        return result;
    }

    /**
     * Update the wait time of the simulation.
     *
     * @param waitTime new wait time in ms
     */
    public void setWaitTime(int waitTime) {
        this.waitTime = waitTime;
    }

    /**
     * Update the brake probability of all vehicles.
     *
     * @param brakeProb new brake probability
     */
    public void setBrakeProb(float brakeProb) {
        for (Vehicle v : this.vehicles) {
            v.setBrakeProb(brakeProb);
        }
    }

    /**
     * Update the maximum velocity of all vehicles.
     *
     * @param maxVelocity new maximum velocity
     */
    public void setMaxVelocity(float maxVelocity) {
        for (Vehicle v : this.vehicles) {
            v.setMaxVelocity(maxVelocity);
        }
    }

    /**
     * Calculates on simulation step ahead and then waits for the configured wait time.
     */
    public void timeElapse() {
        for (int i = 0; i < vehicles.size(); i++) {
            Vehicle v = vehicles.get(i);
            int forerunnerIndex = i + 1;
            if (forerunnerIndex >= vehicles.size()) {
                forerunnerIndex -= vehicles.size();
            }
            Vehicle forerunner = vehicles.get(forerunnerIndex);
            float distanceForerunner = forerunner.getPosition() - v.getPosition() - 1;
            if (distanceForerunner < 0.0) {
                distanceForerunner += this.trackLength;
            }
            v.updateVelocity(distanceForerunner);
        }

        try {
            this.listSemaphore.acquire();
        } catch (InterruptedException ex) {return;}
        List <VehicleTimeRecord> records = new ArrayList<>(vehicles.size());
        this.vtrList.add(records);
        if (this.vtrList.size() > this.historyLength) {
            this.vtrList.remove(0);
        }
        for(Vehicle v: vehicles){
            v.timeElapse();
            VehicleTimeRecord vtr = new VehicleTimeRecord(v.id, v.position, v.curVelocity, v.maxVelocity);
            records.add(vtr);
        }
        steps++;
        this.listSemaphore.release();

        update_avg();
        try {
            Thread.sleep(waitTime);
        } catch (InterruptedException ex) {
        }
    }

    /**
     * Returns the average velocity of the given simulation step.
     *
     * @param step index of the step in the history list
     * @return average velocity
     */
    public float avg_step(int step) {
        float sum_step = 0;
        for (VehicleTimeRecord r : vtrList.get(step)) {
            sum_step += r.velocity;
        }
        return sum_step / vehicles.size();
    }

    /**
     * Utility function which updates the averages values with the results from the last simulation
     * step.
     */
    protected void update_avg() {
        lastAvg = avg_step(vtrList.size() - 1);
        if (this.steps > 10) {
            sumDelAvgMemory += lastAvg;
            delayedAvg = sumDelAvgMemory / (this.steps - 10);
        }
        sumAvgMemory += lastAvg;
        overallAvg = sumAvgMemory / this.steps;

    }
}
