package fr.uge.timeseries.q0;

import com.ecorp.TemperatureReporter;

import java.time.Instant;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

public class TimeSeries {


    private final String name;
    private final Map<Instant, Long> values;
    private final Set<Instant> instants;

    private TimeSeries(String name, Map<Instant, Long> values) {
        this.name = Objects.requireNonNull(name);
        this.values = Objects.requireNonNull(values);
        this.instants = Set.copyOf(values.keySet());
    }

    public String name(){
        return name;
    }

    /**
     * Return the set of instants for which the timeSeries contains data
     * @return
     */
    public Set<Instant> instants(){
        return instants;
    }

    /**
     * Create a TimeSeries by collecting nbPoints of values from a fictitious API
     * starting from start at a 1-minute increment between each sample. The API calls
     * are simulated by drawing a random number between 1 and 9001.
     *
     * @param start
     * @param nbPoints
     * @return a new TimeSeries
     */
    public static TimeSeries fromWeb(String name, Instant start, int nbPoints) {
        Objects.requireNonNull(name);
        Objects.requireNonNull(start);
        if (nbPoints < 0){
            throw new IllegalStateException();
        }
        var values = new HashMap<Instant, Long> ();

        for (int i = 0; i < nbPoints; i++) {
            var time = start.plus(Duration.ofMinutes(i));
            var powerLevel = (long) TemperatureReporter.retrievePowerLevelAt(time);
            values.put(time, powerLevel);
        }

        return new TimeSeries(name,values);
    }

    /**
     * A convenient method to find the closest in the map keys to the given instant
     * @param instant
     * @return
     */
    private static Instant findClosestInstant(Instant instant, Set<Instant> instants){
       return instants.stream().min(Comparator.comparing(otherInstant -> Duration.between(otherInstant, instant).abs().toMillis())).orElse(null);
    }

    /**
     * Return the value stored in the TimeSeries that was recorded at
     * the closest moment to the given instant.
     *
     * @param instant
     * @return an OptionalLong containing the value that was recorded at the closest moment to instant,
     *         or an empty OptionalLong if no values exist in the TimeSeries.
     */

    public OptionalLong valueAt(Instant instant) {
        Objects.requireNonNull(instant);
        var closestInstant = findClosestInstant(instant,instants);
        if (closestInstant == null) { return OptionalLong.empty(); }
        return OptionalLong.of(values.get(closestInstant));
    }


}

