package de.duehl.math.stochastic;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Diese Klasse stellt die Basisklasse für eine Methode zur Erzeugung einer Stichprobe dar.
 *
 * @version 1.01     2025-10-31
 * @author Christian Dühl
 */

public abstract class RandomSample {

    private final Random random;

    /** Erste natürliche positive Zahl im Set, von dem die Stichprobe gezogen werden soll. */
    private final int from;

    /**
     * Letzte (einschließlich) natürliche positive Zahl im Set, von dem die Stichprobe gezogen
     * werden soll.
     */
    private final int to;

    /** Größe der zu ziehenden Stichprobe. */
    protected final int sampleSize;

    /** Größe der Menge, aus der die Stichprobe gezogen werden soll. */
    protected final int setSize;

    /** Die Liste der unerwünschten Nummern. Diese werden nicht gezogen. */
    private List<Integer> notWantedNumbers;

    /** Die Stichprobe die erstellt wird. */
    private final List<Integer> sample;

    /**
     * Konstruktor. Die erste natürliche positive Zahl im Set, von dem die Stichprobe gezogen
     * werden soll, wird hier auf 1 gesetzt.
     *
     * @param to
     *            Letzte (einschließlich) natürliche positive Zahl im Set, von dem die Stichprobe
     *            gezogen werden soll.
     * @param sampleSize
     *            Größe der zu ziehenden Stichprobe.
     */
    public RandomSample(int to, int sampleSize) {
        this(1, to, sampleSize);
    }

    /**
     * Konstruktor.
     *
     * @param from
     *            Erste natürliche positive Zahl im Set, von dem die Stichprobe gezogen werden
     *            soll.
     * @param to
     *            Letzte (einschließlich) natürliche positive Zahl im Set, von dem die Stichprobe
     *            gezogen werden soll.
     * @param sampleSize
     *            Größe der zu ziehenden Stichprobe.
     */
    public RandomSample(int from, int to, int sampleSize) {
        if (from > to) {
            throw new IllegalArgumentException("Der Wert von (" + from + ") darf nicht größer "
                    + "sein als der Wert bis (" + to + ").");
        }
        this.from = from;
        this.to = to;
        this.sampleSize = sampleSize;
        setSize = to - from + 1;

        random = new Random();
        sample = new ArrayList<>();
        notWantedNumbers = new ArrayList<>();
    }

    /** Setter für die Liste der unerwünschten Nummern. Diese werden nicht gezogen. */
    public void setIndicesToIgnore(List<Integer> notWantedNumbers) {
        this.notWantedNumbers = notWantedNumbers;
    }

    /** Zieht die Stichprobe. */
    public void drawSample() {
        while (sampleIsNotFull()) {
            int number = getNextRandomNumber();
            if (!notWantedNumbers.contains(number) && isNumberValidToAddToSample(number)) {
                addToSample(number);
            }
        }
    }

    private boolean sampleIsNotFull() {
        return sample.size() < sampleSize;
    }

    private int getNextRandomNumber() {
        int randomNumber = random.nextInt(setSize);
        if (from + randomNumber > to) {
            throw new RuntimeException("Logischer Fehler.\n\tthis = " + this);
        }
        return from + randomNumber;
    }

    /** Gibt an, ob die Nummer zulässig ist, wenn man sie zur Stichprobe hinzufügt. */
    protected abstract boolean isNumberValidToAddToSample(int number);

    protected boolean sampleContains(int number) {
        return sample.contains(number);
    }

    private void addToSample(int number) {
        sample.add(number);
    }

    /** Getter für die erstellte Stichprobe. */
    public List<Integer> getSample() {
        return sample;
    }

    @Override
    public String toString() {
        return "RandomSample [from=" + from + ", to=" + to + ", sampleSize=" + sampleSize
                + ", setSize=" + setSize + ", sample=" + sample + "]";
    }

}
