import liste.*;
import java.util.*;
import net.sourceforge.sizeof.*;
import java.io.*;
import java.lang.reflect.*;

/**
 * Testet Listenvarianten auf ihre Effizenz
 *
 * @author  Rainer Helfrich
 * @version Oktober 2020
 */
public class Benchmark
{
    private Liste<Integer> dieListe;
    private Random zufallszahlen;

    /**
     * Erzeugt ein neues Benchmark-Objekt. Sollte nicht direkt aufgerufen werden.
     * @param l Die Liste, die getestet werden soll.
     */
    public Benchmark(Liste<Integer> l)
    {
        dieListe = l;
        zufallszahlen = new Random();
    }

    /**
     * Wird benötigt, um in einem neuen Prozess den Speichertest durchzuführen.
     * Sollte nicht direkt aufgerufen werden.
     */
    public static void main(String[] args)
    {
        try
        {
            int anzahl = Integer.parseInt(args[0]);
            Class c = Class.forName(args[1]);
            Liste<Integer> l = (Liste<Integer>)c.newInstance();
            Benchmark b = new Benchmark(l);
            Method m = b.getClass().getMethod(args[2], new Class[]{int.class});
            m.invoke(b, new Object[]{anzahl});
            System.out.println("@"+SizeOf.deepSizeOf((Object)l)+"@");
        }
        catch(Exception x)
        {
            System.out.println(x.getMessage());
        }
    }

    /**
     * Hängt Zufallszahlen an je eine Liste jedes Typs an und gibt den benötigten Speicher in Bytes aus
     */
    public static void speichertestFuellen()
    {
        performMany("listeFuellen", true);
    }

    /**
     * Hängt Zufallszahlen an je eine Liste jedes Typs an, entfernt dann 3/4 der Werte wieder und gibt den benötigten Speicher in Bytes aus
     */
    public static void speichertestFuellenUndEntfernen()
    {
        performMany("listeFuellenUnd34Entfernen", true);
    }

    /**
     * Fügt an je eine Liste jedes Typs vorne Zufallswerte ein und gibt die benötigte Laufzeit in µs aus.
     */
    public static void laufzeittestVorneEinfuegen()
    {
        performMany("listeVorneAnfuegen", false);
    }

    /**
     * Fügt an je eine Liste jedes Typs hinten Zufallswerte ein und gibt die benötigte Laufzeit in µs aus.
     */
    public static void laufzeittestHintenEinfuegen()
    {
        performMany("listeFuellen", false);
    }

    /**
     * Fügt an je eine Liste jedes Typs an zufälligen Stellen Zufallswerte ein und gibt die benötigte Laufzeit in µs aus.
     */
    public static void laufzeittestZufaelligEinfuegen()
    {
        performMany("listeZufaelligEinfuegen", false);
    }

    /**
     * Füllt zuerst je eine Liste jedes Typs mit Zufallszahlen.
     * Addiert dann alle Werte einer Liste auf und gibt die benötigte Laufzeit in µs aus.
     * Es wird nur das Aufaddieren gemessen.
     */
    public static void laufzeittestAufsummieren()
    {
        performMany("listeAufsummieren", false);
    }

    private static void performMany(String strMethod, boolean isMemTest)
    {
        String[] typen = { "ListeMitArray", "ListeRekursiv", "ListeIterativ", "ListeMitEnde" };
        System.out.print("Anzahl");
        for (String s : typen)
        {
            System.out.print(";"+s);
        }
        System.out.println();
        int step = isMemTest ? 10 : 100;
        for (int i = step; i <= 100*step; i += step)
        {
            System.out.print(i);
            for (String s : typen)
            {
                int mem = performTest(s, strMethod, i, isMemTest);
                System.out.print(";"+mem);
            }
            System.out.println();
        }
    }

    private static int performTest(String typ, String methode, int anzahl, boolean isMemTest)
    {
        try
        {
            if (isMemTest)
            {
                Process process = new ProcessBuilder("java","-javaagent:./+libs/SizeOf.jar", Benchmark.class.getName(), ""+anzahl, "liste."+typ, methode).start();
                InputStream is = process.getInputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                String line;

                StringBuilder sb = new StringBuilder();
                while ((line = br.readLine()) != null) {
                    sb.append(line);
                }
                String strOut = sb.toString();
                int idx = strOut.indexOf("@");
                if (idx < 0)
                {
                    throw new Exception("Fehler: Ungültige Ausgabe:\n"+strOut);
                }
                int idx2 = strOut.indexOf("@", idx+1);
                if (idx2 < 0)
                {
                    throw new Exception("Fehler: Ungültige Ausgabe:\n"+strOut);
                }
                int n = Integer.parseInt(strOut.substring(idx+1, idx2));
                return n;
            }
            else
            {
                Class c = Class.forName("liste."+typ);
                long summe = 0;
                final int DURCHFUEHRUNGEN = 10;
                for (int i = 0; i < DURCHFUEHRUNGEN; i++)
                {
                    Liste<Integer> l = (Liste<Integer>)c.newInstance();
                    Benchmark b = new Benchmark(l);
                    Method m = b.getClass().getMethod(methode, new Class[]{int.class});
                    if (methode.equals("listeAufsummieren"))
                    {
                        b.listeFuellen(anzahl);
                    }
                    long vorher = System.nanoTime();
                    m.invoke(b, new Object[]{anzahl});
                    long nachher = System.nanoTime();
                    summe += (nachher - vorher);
                }
                return (int)(summe/DURCHFUEHRUNGEN/1000);
            }
        }
        catch(Exception x)
        {
            System.out.println(x.getMessage());
        }
        return -1;
    }

    /**
     * Hängt Zufallszahlen an eine Liste an
     */
    public void listeFuellen(int anzahl)
    {
        for (int i = 0; i < anzahl; i++)
        {
            dieListe.anhaengen(zufallszahlen.nextInt(1000000));
        }
    }

    /**
     * Hängt Zufallszahlen an eine Liste an und entfernt danach 3/4 der Zahlen wieder
     */
    public void listeFuellenUnd34Entfernen(int anzahl)
    {
        listeFuellen(anzahl);
        for (int i = 0; i < 0.75*anzahl; i++)
        {
            int index = zufallszahlen.nextInt(dieListe.laenge());
            dieListe.entferneBei(index);
        }
    }

    /**
     * Fügt Zufallszahlen vorne an eine Liste an
     */
    public void listeVorneAnfuegen(int anzahl)
    {
        for (int i = 0; i < anzahl; i++)
        {
            dieListe.einfuegenBei(0, zufallszahlen.nextInt(1000000));
        }
    }

    /**
     * Fügt Zufallszahlen an zufälligen Stellen in einer Liste ein
     */
    public void listeZufaelligEinfuegen(int anzahl)
    {
        for (int i = 0; i < anzahl; i++)
        {
            int idx = zufallszahlen.nextInt(i+1);
            dieListe.einfuegenBei(idx, zufallszahlen.nextInt(1000000));
        }
    }

    /**
     * Summiert alle Werte einer Liste auf
     */
    public void listeAufsummieren(int anzahl)
    {
        int sum = 0;
        int laenge = dieListe.laenge();
        for (int i = 0; i < laenge; i++)
        {
            sum += dieListe.getNtenWert(i);
        }
    }

}
