package leigh.astro.scheduler;

import leigh.astro.catalog.CatalogObject;
import leigh.astro.fits.Exposure;
import leigh.astro.fits.ExposureCounter;
import leigh.astro.fits.SubManifest;
import leigh.mecha.lang.IdentifiedObject;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * This class represents a specific tasking for stellar observation.
 *
 * @author Alex Leigh
 */

/* WARNING : CHANGING METHOD NAMES HERE MUST MATCH IN THE .VM TEMPLATE FILES */

public class Tasking implements IdentifiedObject {
    private final HashMap<Exposure, Integer> exposures = new HashMap<>();
    private final CatalogObject catalogObject;
    private final UUID id = UUID.randomUUID();
    private final Instant startTime;

    /**
     * Create a new tasking for the given catalog object, beginning no sooner than the given minimum time. A newly
     * created tasking will be empty (will not have any exposures set).
     */
    public Tasking(CatalogObject catalogObject, Instant minTime) {
        // TODO: Add end-time for taskings which can expire
        this.catalogObject = catalogObject;
        this.startTime = minTime;
    }

    /**
     * Add an {@link Exposure} to this tasking. Note that any exposure configuration can only be present in the tasking
     * once, so setting this replaces any existing value.
     */
    public void putTasking(Exposure e, int cnt) {
        exposures.put(e, cnt);
    }

    /**
     * Return the {@link Exposure} objects, if any, associated with this tasking. If no exposures are associated,
     * an empty map is returned. The value of the map indicates the count of exposures tasked.
     *
     * @return The map of exposures.
     */
    public HashMap<Exposure, Integer> getExposures() {
        return exposures;
    }

    /**
     * Increment the {@link Exposure} of the given type by 1. If the exposure is not already in the tasking, it will be
     * added with a value of 1.
     *
     * @param e The exposure to increment.
     */
    public void increment(Exposure e) {
        Integer cnt = exposures.get(e);
        if (cnt == null) {
            exposures.put(e, 1);
            return;
        }

        cnt++;
        exposures.put(e, cnt);
    }

    @Override
    public String toString() {
        return "Tasking{" +
                "exposures=" + exposures +
                ", catalogObject=" + catalogObject +
                ", id=" + id +
                ", startTime=" + startTime +
                '}';
    }

    /**
     * Return the minimum time for the task. This is the earliest the task could be considered eligible to be
     * performed.
     *
     * @return The minimum time.
     */
    public Instant getMinTime() {
        return startTime;
    }

    /**
     * Given the provided manifest, determine what if anything is required in order to complete the tasking.
     */
    public HashMap<Exposure, Integer> missing(SubManifest manifest) {
        HashMap<Exposure, Integer> remaining = new HashMap<>();
        ExposureCounter fc = manifest.filterCount();

        for (Map.Entry<Exposure, Integer> entry : exposures.entrySet()) {
            int haveCnt = fc.getCount(entry.getKey());
            int need = entry.getValue() - haveCnt;
            if (need > 0) remaining.put(entry.getKey(), need);
        }

        return remaining;
    }

    /**
     * Return the length of time in seconds the entire task will take, if performed.
     *
     * @return The length of time in seconds.
     */
    public double getLength() {
        double length = 0;
        for (Map.Entry<Exposure, Integer> exp : exposures.entrySet()) {
            double len = exp.getValue() * exp.getKey().getLength();
            length += len;
        }
        return length;
    }

    /**
     * Return the {@link CatalogObject} this task refers to.
     *
     * @return The catalog object.
     */
    public CatalogObject getCatalogObject() {
        return catalogObject;
    }

    /**
     * Add all the given exposures, and their counts, to this object. If an exposure already exists which is stated
     * in the provided map, its exposure count will be overwritten.
     *
     * @param exp The map of exposures and counts.
     */
    public void putAll(Map<Exposure, Integer> exp) {
        exposures.putAll(exp);
    }

    /**
     * Return the unique ID for this task.
     */
    public UUID getId() {
        return id;
    }

    /**
     * Pop an exposure off this tasking. This will decrement it from the task list.
     */
    public Exposure pop() {
        for (Map.Entry<Exposure, Integer> entry : exposures.entrySet()) {
            Exposure exp = entry.getKey();
            int cnt = entry.getValue();

            if (cnt > 0) {
                cnt--;
                exposures.put(exp, cnt);
                return exp;
            }
        }
        return null;
    }
}
