package de.duehl.basics.datetime.time;

/*
 * Copyright 2017 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.datetime.time.watch.StopWatch;
import de.duehl.basics.debug.Assure;
import de.duehl.basics.io.Charset;
import de.duehl.basics.io.FileHelper;

/**
 * Diese Klasse misst den Zeitverbrauch in verschiedenen Phasen eines Programms. Daher muss nach
 * der Erzeugung einer Instanz dieser Klasse erstmal jede Phase (in Form eines Strings mit der
 * Beschreibung dieser Phase) bekannt gemacht werden.
 *
 * Man kann dann für all diese Phasen und für "idle" Die Zeitmessung anhalten und wieder laufen
 * lassen.
 *
 * @version 1.01     2017-09-06
 * @author Christian Dühl
 */

public class TimeMeasurement {

    static final String IDLE = "___idle___";

    /** Liste mit den Namen der Phasen. */
    private final List<String> phases;

    /** Name der aktuell laufenden Phase (kann auch IDLE sein). */
    private String nameOfRunningPhase;

    /** Verzeichnis der Stoppuhren zu jeder Phase. */
    private final Map<String, StopWatch> watches;

    /** Konstruktor. */
    public TimeMeasurement() {
        phases = new ArrayList<>();
        phases.add(IDLE);

        watches = new HashMap<>();

        nameOfRunningPhase = "! --- not any phase starte yet --- !";
    }

    /** Fügt die Namen der Phasen hinzu. */
    public void addPhases(String ... phaseNames) {
        List<String> list = CollectionsHelper.stringArrayToList(phaseNames);
        addPhases(list);
    }

    /** Fügt die Namen der Phasen hinzu. Die IDLE-Phase wird automatisch gestartet. */
    public void addPhases(List<String> phaseNames) {
        for (String phaseName : phaseNames) {
            Assure.isFalse("Die Phase '" + phaseName + "' ist bereits bekannt. Dies",
                    phases.contains(phaseName));
            phases.add(phaseName);
        }
        createWatchesForAllPhases();
        initialStartIdle();
    }

    private void createWatchesForAllPhases() {
        for (String phase : phases) {
            StopWatch watch = new StopWatch();
            watch.stop();
            watches.put(phase, watch);
        }
    }

    private void initialStartIdle() {
        nameOfRunningPhase = IDLE;
        startWatchForPhase(nameOfRunningPhase);
    }

    /** Startet die angegebene Phase. Die laufende Phase wird automatisch beendet. */
    public void start(String phase) {
        stopWatchForPhase(nameOfRunningPhase);
        nameOfRunningPhase = phase;
        startWatchForPhase(nameOfRunningPhase);
    }

    /** Beendet die aktuell laufende Phase. Die IDLE-Phase wird automatisch gestartet. */
    public void stop(String phase) {
        Assure.isEqual("Die Phase '" + phase + "' scheint nicht zu laufen, sondern die Phase '"
                + nameOfRunningPhase + "'. Diese", nameOfRunningPhase, phase);
        start(IDLE);
    }

    private void startWatchForPhase(String phase) {
        Assure.isTrue("Die Phase '" + phase + "' scheint nicht bekannt zu sein. Dies",
                phases.contains(phase));
        Assure.isTrue("addPhases() vergessen? Es gibt keine Stoppuhr zur Phase '" + phase
                + "'. Dies", watches.containsKey(phase));

        StopWatch watch = watches.get(phase);
        watch.restart();
    }

    private void stopWatchForPhase(String phase) {
        Assure.isTrue("Die Phase '" + phase + "' scheint nicht bekannt zu sein. Dies",
                phases.contains(phase));
        Assure.isTrue("addPhases() vergessen? Es gibt keine Stoppuhr zur Phase '" + phase
                + "'. Dies", watches.containsKey(phase));

        StopWatch watch = watches.get(phase);
        watch.stop();
    }

    /** Erzeugt den Report als ein Verzeichnis der Laufzeiten über die Namen der Phasen. */
    public Map<String, String> createReport() {
        Map<String, String> report = new HashMap<>();

        for (String phase : phases) {
            Assure.isTrue("addPhases() vergessen? Es gibt keine Stoppuhr zur Phase '" + phase
                    + "'. Dies", watches.containsKey(phase));
            StopWatch watch = watches.get(phase);
            String runtime = watch.getTime();
            report.put(phase, runtime);
        }

        return report;
    }

    /**
     * Erzeugt den Report als eine sortierte Liste von Zeilen: Name der Phase und Laufzeit
     * Tabgetrennt.
     */
    public List<String> createReportAsLines() {
        List<String> lines = new ArrayList<>();;

        Map<String, String> runtimeByPhase = createReport();
        for (String phase : phases) {
            String line = phase + "\t" + runtimeByPhase.get(phase);
            lines.add(line);
        }

        return lines;
    }

    /**
     * Schreibt den Report als eine sortierte Liste von Zeilen (Name der Phase und Laufzeit
     * Tabgetrennt) in die angegebene Datei.
     */
    public void writeReportToFile(String filename) {
        FileHelper.writeLinesToFile(createReportAsLines(), filename, Charset.UTF_8);
    }

}
