package modules;

import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class TabellListe<T> implements Liste<T>
{
    private T[] a;
    private int antall;
    private int endringer;

    public TabellListe(int størrelse)    // konstruktør
    {
        @SuppressWarnings("unchecked")   // pga. konverteringen: Object[] -> T[]
                T[] b = (T[]) new Object[størrelse];  // oppretter tabellen

        a = b;
        antall = 0;                           // foreløpig ingen verdier
        endringer = 0;                        // foreløpig ingen endringer
    }

    public TabellListe()                    // standardkonstruktør
    {
        this(10);                             // startstørrelse på 10
    }

    public TabellListe(T[] b)               // en T-tabell som parameter
    {
        this(b.length);                       // kaller en konstruktør

        for (T verdi : b)
        {
            if (verdi != null) a[antall++] = verdi;  // hopper over null-verdier
        }
    }

    private static void fratilKontroll(int antall, int fra, int til)
    {
        if (fra < 0)                                  // fra er negativ
            throw new IndexOutOfBoundsException
                    ("fra(" + fra + ") er negativ!");

        if (til > antall)                             // til er utenfor tabellen
            throw new IndexOutOfBoundsException
                    ("til(" + til + ") > antall(" + antall + ")");

        if (fra > til)                                // fra er større enn til
            throw new IllegalArgumentException
                    ("fra(" + fra + ") > til(" + til + ") - illegalt intervall!");
    }

    public Liste<T> subliste(int fra, int til)
    {
        fratilKontroll(antall, fra, til);

        TabellListe<T> liste = new TabellListe<>(til - fra);

        for (int i = fra, j = 0; i < til; i++, j++) liste.a[j] = a[i];
        liste.antall = til - fra;

        return liste;
    }

    @Override
    public boolean leggInn(T verdi)  // inn bakerst
    {
        Objects.requireNonNull(verdi, "null er ulovlig!");

        // En full tabell utvides med 50%
        if (antall == a.length)
        {
            a = Arrays.copyOf(a,(3*antall)/2 + 1);
        }

        a[antall++] = verdi;   // setter inn ny verdi

        endringer++;

        return true;
    }

    @Override
    public void leggInn(int indeks, T verdi)
    {
        Objects.requireNonNull(verdi, "null er ulovlig!");

        indeksKontroll(indeks, true);  // true: indeks = antall er lovlig

        // En full tabell utvides med 50%
        if (antall == a.length) a = Arrays.copyOf(a,(3*antall)/2 + 1);

        // rydder plass til den nye verdien
        for (int i = antall; i > indeks; i--) a[i] = a[i-1];

        a[indeks] = verdi;     // setter inn ny verdi

        antall++;              // en ny verdi
        endringer++;           // en endring
    }

    @Override
    public int antall()
    {
        return antall;         // returnerer antallet
    }

    @Override
    public boolean tom()
    {
        return antall == 0;    // listen er tom hvis antall er 0
    }

    @Override
    public T hent(int indeks)
    {
        indeksKontroll(indeks, false);   // false: indeks = antall er ulovlig
        return a[indeks];                // returnerer er tabellelement
    }

    @Override
    public int indeksTil(T verdi)
    {
        for (int i = 0; i < antall; i++)
        {
            if (a[i].equals(verdi)) return i;   // funnet!
        }
        return -1;   // ikke funnet!
    }

    @Override
    public boolean inneholder(T verdi)
    {
        return indeksTil(verdi) != -1;
    }

    @Override
    public T oppdater(int indeks, T verdi)
    {
        Objects.requireNonNull(verdi, "null er ulovlig!");

        indeksKontroll(indeks, false);  // false: indeks = antall er ulovlig

        T gammelverdi = a[indeks];      // tar vare på den gamle verdien
        a[indeks] = verdi;              // oppdaterer

        endringer++;                    // en endring

        return gammelverdi;             // returnerer den gamle verdien
    }

    @Override
    public T fjern(int indeks)
    {
        indeksKontroll(indeks, false);  // false: indeks = antall er ulovlig
        T verdi = a[indeks];

        antall--;  // reduserer antallet

        // sletter ved å flytte verdier mot venstre
        for (int i = indeks; i < antall; i++) a[i] = a[i + 1];

        a[antall] = null;   // tilrettelegger for "søppeltømming"

        endringer++;

        return verdi;
    }

    @Override
    public boolean fjern(T verdi)
    {
        Objects.requireNonNull(verdi, "null er ulovlig!");

        for (int i = 0; i < antall; i++)
        {
            if (a[i].equals(verdi))
            {
                antall--;  // reduserer antallet

                // sletter ved å flytte verdier mot venstre
                for (int j = i; j < antall; j++) a[j] = a[j + 1];

                a[antall] = null;   // tilrettelegger for "søppeltømming"
                endringer++;

                return true;  // vellykket fjerning
            }
        }
        return false;  // ingen fjerning
    }

    @Override
    public boolean fjernHvis(Predicate<? super T> p)    // betingelsesfjerning
    {
        Objects.requireNonNull(p, "predikatet er null");  // kaster unntak

        int nyttAntall = antall;

        for (int i = 0, j = 0; j < antall; j++)
        {
            if (p.test(a[j])) nyttAntall--;   // a[j] skal fjernes
            else a[i++] = a[j];               // forskyver
        }

        for (int i = nyttAntall; i < antall; i++)
        {
            a[i] = null;   // tilrettelegger for "søppeltømming"
        }

        boolean fjernet = nyttAntall < antall;

        if (fjernet) endringer++;

        antall = nyttAntall;

        return fjernet;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void nullstill()
    {
        if (a.length > 10)
            a = (T[])new Object[10];
        else
            for (int i = 0; i < antall; i++)
            {
                a[i] = null;
            }

        antall = 0;
        endringer++;
    }

    public String toString2()
    {
        StringBuilder s = new StringBuilder("[");

        if (!tom())
        {
            s.append(a[0]);
            for (int i = 1; i < antall; i++)
            {
                s.append(',').append(' ').append(a[i]);
            }
        }
        s.append(']');

        return s.toString();
    }

    @Override
    public String toString()
    {
        StringJoiner s = new StringJoiner(", ","[","]");

        for (int i = 0; i < antall; i++) s.add(a[i].toString());

        return s.toString();
    }

    @SuppressWarnings("unchecked")
    public <T> T[] tilTabell(T[] b)
    {
        if (b.length < antall)
            return (T[]) Arrays.copyOf(a, antall, b.getClass());
        System.arraycopy(a, 0, b, 0, antall);
        if (b.length > antall) b[antall] = null;
        return b;
    }

    @Override
    public void forEach(Consumer<? super T> action)
    {
        for (int i = 0; i < antall; i++)
        {
            action.accept(a[i]);
        }
    }

    @Override
    public Iterator<T> iterator()
    {
        return new TabellListeIterator();
    }

    private class TabellListeIterator implements Iterator<T>
    {
        private int denne;               // instansvariabel
        private boolean fjernOK;         // instansvariabel
        private int iteratorendringer;   // instansvariabel

        private TabellListeIterator()
        {
            denne = 0;
            fjernOK = false;
            iteratorendringer = endringer;
        }

        @Override
        public boolean hasNext()     // sjekker om det er flere igjen
        {
            return denne < antall;     // sjekker verdien til denne
        }

        @Override
        public T next()
        {
            if (iteratorendringer != endringer)
            {
                throw new ConcurrentModificationException("Listen er endret!");
            }

            if (!hasNext())
            {
                throw new NoSuchElementException("Tomt eller ingen verdier igjen!");
            }

            T denneverdi = a[denne];   // henter aktuell verdi
            denne++;                   // flytter indeksen
            fjernOK = true;            // nå kan remove() kalles

            return denneverdi;         // returnerer verdien
        }

        @Override
        public void forEachRemaining(Consumer<? super T> action)
        {
            while (denne < antall)
            {
                action.accept(a[denne++]);
            }
        }

        @Override
        public void remove()
        {
            if (iteratorendringer != endringer)
                throw new ConcurrentModificationException("Listen er endret!");

            if (!fjernOK)
                throw new IllegalStateException("Ulovlig tilstand!");

            fjernOK = false;           // remove() kan ikke kalles på nytt

            // verdien i denne - 1 skal fjernes da den ble returnert i siste kall
            // på next(), verdiene fra og med denne flyttes derfor en mot venstre
            antall--;           // en verdi vil bli fjernet
            denne--;            // denne må flyttes til venstre

            for (int i = denne; i < antall; i++) a[i] = a[i + 1];  // tetter igjen

            a[antall] = null;   // verdien som lå lengst til høyre nulles

            endringer++;
            iteratorendringer++;
        }



    } // TabellListeIterator

} // TabellListe

// pga. konverteringen: Object[] -> T[]
package modules;

import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class TabellListe<T> implements Liste<T>
{
    private T[] a;
    private int antall;
    private int endringer;

    public TabellListe(int størrelse)    // konstruktør
    {
        @SuppressWarnings("unchecked")   // pga. konverteringen:<SUF>
                T[] b = (T[]) new Object[størrelse];  // oppretter tabellen

        a = b;
        antall = 0;                           // foreløpig ingen verdier
        endringer = 0;                        // foreløpig ingen endringer
    }

    public TabellListe()                    // standardkonstruktør
    {
        this(10);                             // startstørrelse på 10
    }

    public TabellListe(T[] b)               // en T-tabell som parameter
    {
        this(b.length);                       // kaller en konstruktør

        for (T verdi : b)
        {
            if (verdi != null) a[antall++] = verdi;  // hopper over null-verdier
        }
    }

    private static void fratilKontroll(int antall, int fra, int til)
    {
        if (fra < 0)                                  // fra er negativ
            throw new IndexOutOfBoundsException
                    ("fra(" + fra + ") er negativ!");

        if (til > antall)                             // til er utenfor tabellen
            throw new IndexOutOfBoundsException
                    ("til(" + til + ") > antall(" + antall + ")");

        if (fra > til)                                // fra er større enn til
            throw new IllegalArgumentException
                    ("fra(" + fra + ") > til(" + til + ") - illegalt intervall!");
    }

    public Liste<T> subliste(int fra, int til)
    {
        fratilKontroll(antall, fra, til);

        TabellListe<T> liste = new TabellListe<>(til - fra);

        for (int i = fra, j = 0; i < til; i++, j++) liste.a[j] = a[i];
        liste.antall = til - fra;

        return liste;
    }

    @Override
    public boolean leggInn(T verdi)  // inn bakerst
    {
        Objects.requireNonNull(verdi, "null er ulovlig!");

        // En full tabell utvides med 50%
        if (antall == a.length)
        {
            a = Arrays.copyOf(a,(3*antall)/2 + 1);
        }

        a[antall++] = verdi;   // setter inn ny verdi

        endringer++;

        return true;
    }

    @Override
    public void leggInn(int indeks, T verdi)
    {
        Objects.requireNonNull(verdi, "null er ulovlig!");

        indeksKontroll(indeks, true);  // true: indeks = antall er lovlig

        // En full tabell utvides med 50%
        if (antall == a.length) a = Arrays.copyOf(a,(3*antall)/2 + 1);

        // rydder plass til den nye verdien
        for (int i = antall; i > indeks; i--) a[i] = a[i-1];

        a[indeks] = verdi;     // setter inn ny verdi

        antall++;              // en ny verdi
        endringer++;           // en endring
    }

    @Override
    public int antall()
    {
        return antall;         // returnerer antallet
    }

    @Override
    public boolean tom()
    {
        return antall == 0;    // listen er tom hvis antall er 0
    }

    @Override
    public T hent(int indeks)
    {
        indeksKontroll(indeks, false);   // false: indeks = antall er ulovlig
        return a[indeks];                // returnerer er tabellelement
    }

    @Override
    public int indeksTil(T verdi)
    {
        for (int i = 0; i < antall; i++)
        {
            if (a[i].equals(verdi)) return i;   // funnet!
        }
        return -1;   // ikke funnet!
    }

    @Override
    public boolean inneholder(T verdi)
    {
        return indeksTil(verdi) != -1;
    }

    @Override
    public T oppdater(int indeks, T verdi)
    {
        Objects.requireNonNull(verdi, "null er ulovlig!");

        indeksKontroll(indeks, false);  // false: indeks = antall er ulovlig

        T gammelverdi = a[indeks];      // tar vare på den gamle verdien
        a[indeks] = verdi;              // oppdaterer

        endringer++;                    // en endring

        return gammelverdi;             // returnerer den gamle verdien
    }

    @Override
    public T fjern(int indeks)
    {
        indeksKontroll(indeks, false);  // false: indeks = antall er ulovlig
        T verdi = a[indeks];

        antall--;  // reduserer antallet

        // sletter ved å flytte verdier mot venstre
        for (int i = indeks; i < antall; i++) a[i] = a[i + 1];

        a[antall] = null;   // tilrettelegger for "søppeltømming"

        endringer++;

        return verdi;
    }

    @Override
    public boolean fjern(T verdi)
    {
        Objects.requireNonNull(verdi, "null er ulovlig!");

        for (int i = 0; i < antall; i++)
        {
            if (a[i].equals(verdi))
            {
                antall--;  // reduserer antallet

                // sletter ved å flytte verdier mot venstre
                for (int j = i; j < antall; j++) a[j] = a[j + 1];

                a[antall] = null;   // tilrettelegger for "søppeltømming"
                endringer++;

                return true;  // vellykket fjerning
            }
        }
        return false;  // ingen fjerning
    }

    @Override
    public boolean fjernHvis(Predicate<? super T> p)    // betingelsesfjerning
    {
        Objects.requireNonNull(p, "predikatet er null");  // kaster unntak

        int nyttAntall = antall;

        for (int i = 0, j = 0; j < antall; j++)
        {
            if (p.test(a[j])) nyttAntall--;   // a[j] skal fjernes
            else a[i++] = a[j];               // forskyver
        }

        for (int i = nyttAntall; i < antall; i++)
        {
            a[i] = null;   // tilrettelegger for "søppeltømming"
        }

        boolean fjernet = nyttAntall < antall;

        if (fjernet) endringer++;

        antall = nyttAntall;

        return fjernet;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void nullstill()
    {
        if (a.length > 10)
            a = (T[])new Object[10];
        else
            for (int i = 0; i < antall; i++)
            {
                a[i] = null;
            }

        antall = 0;
        endringer++;
    }

    public String toString2()
    {
        StringBuilder s = new StringBuilder("[");

        if (!tom())
        {
            s.append(a[0]);
            for (int i = 1; i < antall; i++)
            {
                s.append(',').append(' ').append(a[i]);
            }
        }
        s.append(']');

        return s.toString();
    }

    @Override
    public String toString()
    {
        StringJoiner s = new StringJoiner(", ","[","]");

        for (int i = 0; i < antall; i++) s.add(a[i].toString());

        return s.toString();
    }

    @SuppressWarnings("unchecked")
    public <T> T[] tilTabell(T[] b)
    {
        if (b.length < antall)
            return (T[]) Arrays.copyOf(a, antall, b.getClass());
        System.arraycopy(a, 0, b, 0, antall);
        if (b.length > antall) b[antall] = null;
        return b;
    }

    @Override
    public void forEach(Consumer<? super T> action)
    {
        for (int i = 0; i < antall; i++)
        {
            action.accept(a[i]);
        }
    }

    @Override
    public Iterator<T> iterator()
    {
        return new TabellListeIterator();
    }

    private class TabellListeIterator implements Iterator<T>
    {
        private int denne;               // instansvariabel
        private boolean fjernOK;         // instansvariabel
        private int iteratorendringer;   // instansvariabel

        private TabellListeIterator()
        {
            denne = 0;
            fjernOK = false;
            iteratorendringer = endringer;
        }

        @Override
        public boolean hasNext()     // sjekker om det er flere igjen
        {
            return denne < antall;     // sjekker verdien til denne
        }

        @Override
        public T next()
        {
            if (iteratorendringer != endringer)
            {
                throw new ConcurrentModificationException("Listen er endret!");
            }

            if (!hasNext())
            {
                throw new NoSuchElementException("Tomt eller ingen verdier igjen!");
            }

            T denneverdi = a[denne];   // henter aktuell verdi
            denne++;                   // flytter indeksen
            fjernOK = true;            // nå kan remove() kalles

            return denneverdi;         // returnerer verdien
        }

        @Override
        public void forEachRemaining(Consumer<? super T> action)
        {
            while (denne < antall)
            {
                action.accept(a[denne++]);
            }
        }

        @Override
        public void remove()
        {
            if (iteratorendringer != endringer)
                throw new ConcurrentModificationException("Listen er endret!");

            if (!fjernOK)
                throw new IllegalStateException("Ulovlig tilstand!");

            fjernOK = false;           // remove() kan ikke kalles på nytt

            // verdien i denne - 1 skal fjernes da den ble returnert i siste kall
            // på next(), verdiene fra og med denne flyttes derfor en mot venstre
            antall--;           // en verdi vil bli fjernet
            denne--;            // denne må flyttes til venstre

            for (int i = denne; i < antall; i++) a[i] = a[i + 1];  // tetter igjen

            a[antall] = null;   // verdien som lå lengst til høyre nulles

            endringer++;
            iteratorendringer++;
        }



    } // TabellListeIterator

} // TabellListe
