import java.util.*;

public class LaukkuTest {

    public static void main(String[] args) {

        // syötekoko
        int N = 10;
        if (args.length > 0)
            N = Integer.valueOf(args[0]);
        // satunnaislukusiemen

        int siemen = 42;
        if (args.length > 2)
            siemen = Integer.valueOf(args[2]);
        java.util.Random r = new java.util.Random(siemen);

        int print = 2;
        if (args.length > 3)
            print = Integer.valueOf(args[3]);

        Laukku<String> L1 = new omatunnustahan<>();

        List<String> lisatyt = testaaLisaysta(L1, N, r, print);

        testaaPoistoa(L1, lisatyt, r, print);

        testaaSailytaPoista(L1, N, r, print);

        testaaLisaysJaIteraattori(L1, N, r, print);

    }

    /**
     * Tulostaa laukun sisällön halutussa laajuudessa.
     * @param L laukku
     * @param print tulostustarkkuun
     */
    public static void tulosta(Laukku L, int print) {
        if (print > 0) System.out.print(": " + L.koko() + " kpl:");
        if (print > 1) System.out.print(L);
        if (print > 0) System.out.println();
    }

    /**
     * Testaa lisäämistä. Kukin arvottu alkio lisätään ensin kertaalleen,
     * sitten samat uudelleen, ja vielä kertaalleen tuplana. Lopputuloksena
     * alkioiden pitäisi olla laukussa neljään kertaan (tai neljän monikertana
     * jos sama alkio arvottiin useasti).
     * @param L testattava laukku
     * @param n arvottavien alkioiden määrä
     * @param r satunnaislukugeneraattori
     * @param print tulostusten määrä
     * @return arvotut alkiot (vain kertaalleen)
     */
    public static List<String> testaaLisaysta(Laukku<String> L, int n, Random r, int print) {
        System.out.println("\ntestaaLisausta()");

        System.out.println("Lisätään yksittäin");
        List<String> lisatyt = lisaaYksittainNkpl(L, n, r, print);

        if (print > 0) {
            System.out.print("Sisältö lisäysten jälkeen ");
            tulosta(L, print);
        }

        System.out.println("Lisätään samat uudelleen");

        lisaaAlkiot(L, lisatyt, print);

        if (print > 0) {
            System.out.print("Sisältö uusien lisäysten jälkeen ");
            tulosta(L, print);
        }

        System.out.println("Lisätään samat uudelleen tuplana");

        lisaaAlkiot(L, lisatyt, 2, print);

        if (print > 0) {
            System.out.print("Sisältö uusien lisäysten jälkeen ");
            tulosta(L, print);
        }

        // nyt kaikkia alkioita pitäisi olla 4:n monikerta määrä (1+1+2)
        for (Map.Entry<String, Integer> e : L.alkioMaarat()) {
            if ((e.getValue() % 4) != 0)
                System.out.print("Alkiota " + e.getKey() + " on " + e.getValue() +
                        " kappaletta (pitäisi olla 4 tai 4:n monikerta)");
        }

        return lisatyt;
    }


    /**
     * Testaa poistoa alkioittain
     * @param L testattava laukku
     * @param C poistettavia alkioita
     * @param r satunnaislukugeneraattori
     * @param print tulostusten määrä
     */
    public static void testaaPoistoa(Laukku<String> L, Collection<String> C , Random r, int print) {
        System.out.println("\ntestaaPoistoa()");

        System.out.println("Poistetaan kertaalleen");

        poistaAlkiot(L, C, print);

        if (print > 0) {
            System.out.print("Sisältö ekojen poistojen jälkeen");
            tulosta(L, print);
        }

        // JOS alkiot laitettiin neljänä, niin nyt pitäisi olla kolmet jäljellä
        // nyt kaikkia alkioita pitäisi olla 3:n monikerta määrä (1+1+2-1)
        for (Map.Entry<String, Integer> e : L.alkioMaarat()) {
            if ((e.getValue() % 3) != 0)
                System.out.print("Alkiota " + e.getKey() + " on " + e.getValue() +
                        " kappaletta (pitäisi olla 3 tai 3:n monikerta)");
        }

        System.out.println("Poistetaan triplana");
        poistaAlkiot(L, C, 3, print);

        if (print > 0) {
            System.out.print("Sisältö triplapoistojen jälkeen");
            tulosta(L, print);
        }

        // JOS alkiot laitettiin neljänä, niin nyt ei pitäisi olla enää yhtään jälkellä
        // (1+1+2-1-3 = 0)
        for (Map.Entry<String, Integer> e : L.alkioMaarat()) {
            if (e.getValue() > 0)
                System.out.print("Alkiota " + e.getKey() + " on " + e.getValue() +
                        " kappaletta vaikka pitäisi olla 0");
        }
    }


    /**
     * Testaa operaatioita sailytaKaikki ja poistaKaikki.
     * @param L
     * @param n
     * @param r
     * @param print
     */
    public static void testaaSailytaPoista(Laukku<String> L, int n, Random r, int print) {
        System.out.println("\ntestaaSailytaPoista()");

        System.out.println("Lisätään taas");
        List<String> lisatyt = testaaLisaysta(L, n, r, print);

        System.out.println("Otetaan kopio");

        Laukku<String> L2 = L.kopio();

        System.out.println("Poistetaan kertaalleen kopiosta");

        poistaAlkiot(L2, lisatyt, print);

        System.out.println("Säilytetään kaikki kertaalleen");

        int pois = L.sailytaKaikki(L2);

        if (print > 0) {
            System.out.print("Sisältö säilytysten jälkeen");
            tulosta(L, print);
        }

        // JOS alkiot laitettiin neljänä, kopiosta otettiin yhdet pois, niin
        // nyt kaikkia alkioita pitäisi olla 3:n monikerta määrä (1+1+2-1)
        for (Map.Entry<String, Integer> e : L.alkioMaarat()) {
            if ((e.getValue() % 3) != 0)
                System.out.print("Alkiota " + e.getKey() + " on " + e.getValue() +
                        " kappaletta (pitäisi olla 3 tai 3:n monikerta)");
        }

        // listätään yksi kontrollialkio

        String kontrolli = "TRAI";
        L.lisaa(kontrolli);

        // testataan sisaltaaKaikki()
        System.out.println("L <= L2 : " + L2.sisaltaaKaikki(L));
        System.out.println("L2 <= L : " + L.sisaltaaKaikki(L2));

        // kun alkuperäisessä L on säilytetty kaikki mitä oli kopiossa L2
        // niin poistaKaikki pitäisi poistaa ne kaiken (paitsi kontrollia)

        pois += L.poistaKaikki(L2);

        if (print > 0) {
            System.out.print("Sisältö poistojen jälkeen, pitäisi olla vain kontrolli");
            tulosta(L, print);
        }

        if (! L.sisaltaa("TRAI"))
            System.out.print("Kontrollialkio puuttuu");

        if (L.koko() != 1)
            System.out.print("Koko lienee väärin: " + L.koko() +  " piti olla 1");

        if (pois != n*4)
            System.out.print("Poistettujen määrä ehkä väärin: " + pois +  " piti olla " + (n*4));

    }

    public static void testaaLisaysJaIteraattori(Laukku<String> L, int n, Random r, int print) {

        // tyhjennetään laukku
        System.out.println("\ntestaaLisaysJaIteraattori()");

        Laukku<String> L2 = L.kopio();

        L.poistaKaikki(L2);

        System.out.println("L pitäisi olla tyhjä: " + L);

        System.out.println("Tyhjennetään myös L2");
        L2.tyhjenna();

        System.out.println("Lisätään L2:een");
        List<String> lisatyt = lisaaYksittainNkpl(L2, n, r, print);

        System.out.println("Lisätään L2 alkiot L:ään");

        L.lisaaKaikki(L2);

        System.out.println("L == L2 : " + L.samatSisallot(L2));

        System.out.print("Läpikäydään L : ");
        int nn = 0;
        for (String s : L) {
            if (print > 1)
                System.out.print(s + " ");
            nn++;
        }
        System.out.println(" | " + nn + " kpl");
        if (n != nn)
            System.out.println("Läpikäynnin askelmäärä = " + nn +
                    " piti olla " + n);

    }




    /**
     * Lisää laukkuun L yksitellen n kpl 1-merkkisiä merkkijonoja.
     *
     * @param L     laukku
     * @param n     lisättävien määrä
     * @param r     satunnaislukugeneraattori
     * @param print tulostusten määrä
     * @return lisätyt alkiot listana
     */
    public static List<String> lisaaYksittainNkpl(Laukku<String> L, int n, Random r, int print) {
        ArrayList<String> AL = new ArrayList<>();
        if (print > 0) System.out.print("Lisätään yksittäin " + n + " kpl: ");
        for (int i = 0; i < n; i++) {
            String s = randomString(r, n / 3, 1);
            AL.add(s);
            L.lisaa(s);
            if (print > 1) System.out.print(s + " ");
        }
        if (print > 0) System.out.println();
        return AL;
    }


    /**
     * Lisää kokoelman alkiot laukkuun yksitellen.
     *
     * @param L     laukku
     * @param C     kokoelma
     * @param print tulostusten määrä
     * @param <E>   alkiotyyppi
     * @return tosi jos kaikki lisäykset palauttivat toden, muuten epätosi
     */
    public static <E> boolean lisaaAlkiot(Laukku<E> L, Collection<E> C, int print) {
        boolean ok = true;
        if (print > 0) System.out.print("Lisätään kokoelmasta " + C.size() + " kpl: ");
        for (E x : C) {
            ok &= L.lisaa(x);
            if (print > 1) System.out.print(x + " ");
        }
        if (print > 0) System.out.println();
        return ok;
    }


    /**
     * Lisää kokoelman alkiot laukkuun useana kopiona.
     *
     * @param L        laukku
     * @param C        kokoelma
     * @param kopioita montako kappaeltta kutakin lisätään
     * @param print    tulostusten määrä
     * @param <E>      alkiotyyppi
     * @return tosi jos kaikki lisäykset palauttivat toden, muuten epätosi
     */
    public static <E> boolean lisaaAlkiot(Laukku<E> L, Collection<E> C,
                                          int kopioita, int print) {
        boolean ok = true;
        if (print > 0) System.out.print("Lisätään kokoelmasta " + C.size() +
                " * " + kopioita + " kpl, : ");
        for (E x : C) {
            ok &= L.lisaa(x, kopioita);
            if (print > 1) System.out.print(x + " ");
        }
        if (print > 0) System.out.println();
        return ok;
    }


    /**
     * Poistaa kokoelman alkiot laukusta yksitellen.
     *
     * @param L     laukku
     * @param C     kokoelma
     * @param print tulostusten määrä
     * @param <E>   alkiotyyppi
     * @return tosi jos kaikki lisäykset palauttivat toden, muuten epätosi
     */
    public static <E> boolean poistaAlkiot(Laukku<E> L, Collection<E> C, int print) {
        boolean ok = true;
        if (print > 0) System.out.print("Poistetaan kokoelmasta " + C.size() + " kpl: ");
        for (E x : C) {
            ok &= L.poista(x);
            if (print > 1) System.out.print(x + " ");
        }
        if (print > 0) System.out.println();
        return ok;
    }

    /**
     * Poistaa kokoelman alkiot laukusta useana kopiona.
     *
     * @param L        laukku
     * @param C        kokoelma
     * @param kopioita montako kappaeltta kutakin lisätään
     * @param print    tulostusten määrä
     * @param <E>      alkiotyyppi
     * @return tosi jos kaikki lisäykset palauttivat toden, muuten epätosi
     */
    public static <E> int poistaAlkiot(Laukku<E> L, Collection<E> C,
                                          int kopioita, int print) {
        int pois = 0;
        if (print > 0) System.out.print("Poistetaan kokoelmasta " + C.size() +
                " * " + kopioita + " kpl, : ");
        for (E x : C) {
            pois += L.poista(x, kopioita);
            if (print > 1) System.out.print(x + " ");
        }
        if (print > 0) System.out.println();
        return pois;
    }




    /**
     * Palauttaa satunnaisen len mittaisen merkkijonon käyttäen max ensimmäistä kirjainta.
     *
     * @param r   satunnaislukugeneraattori
     * @param max suurin kirjan
     * @param len merkkijonon pituus
     * @return uusi merkkijono
     */
    public static String randomString(Random r, int max, int len) {
        if (max > 26)
            max = 26;
        char[] C = new char[len];
        for (int i = 0; i < len; i++)
            C[i] = (char) (r.nextInt(max) + 'a');
        return new String(C);
    }

    /**
     * Palauttaa satunnaisen len mittaisen merkkijonon.
     *
     * @param r   satunnaislukugeneraattori
     * @param len merkkijonon pituus
     * @return uusi merkkijono
     */
    public static String randomString(Random r, int len) {
        return randomString(r, 26, len);
    }

}
