/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.math;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import net.ranides.assira.collection.ArrayUtils;
import net.ranides.assira.collection.list.NativeArrayList;
import net.ranides.assira.reflection.util.ClassUtils;

/**
 *
 * @author ranides
 */
public final class Randomizer {

    public static final String DIGITS = "1234567890";
    public static final String ALPHA_LOWER = "qwertyuiopasdfghjklzxcvbnm";
    public static final String ALPHA_UPPER = "QWERTYUIOPASDFGHJKLZXCVBNM";
    public static final String ALPHA = ALPHA_LOWER + ALPHA_UPPER;
    public static final String ALPHANUM = ALPHA_LOWER + ALPHA_UPPER + DIGITS;

    private Randomizer() { }

    /**
     * Losuje liczbę z zakresu [0;Long.MAX_VALUE)
     * @return
     */
    public static long number() {
        return number(Long.MAX_VALUE);
    }

    /**
     * Losuje liczbę z zakresu [0;max)
     * @param max
     * @return
     */
    public static long number(long max) {
        return (long) Math.floor( Math.random() * max ) ;
    }

    /**
     * Losuje liczbę z zakresu [0;max)
     * @param max
     * @return
     */
    public static byte number(byte max) {
        return (byte) Math.floor( Math.random() * max ) ;
    }

    /**
     * Losuje liczbę z zakresu [min;max]
     * @param min
     * @param max
     * @return
     */
    public static long number(long min, long max) {
        return min + Math.round( Math.random() * (max-min) );
    }

    /**
     * Losuje liczbę z zakresu [0;max)
     * @param max
     * @return
     */
    public static int number(int max) {
        return (int) Math.floor( Math.random() * max ) ;
    }

    /**
     * Losuje liczbę z zakresu [min;max]
     * @param min
     * @param max
     * @return
     */
    public static int number(int min, int max) {
        return min + (int)Math.round( Math.random() * (max-min) );
    }

    /**
     * Losuje liczbę z zakresu [0;max)
     * @param max
     * @return
     */
    public static short number(short max) {
        return (short) Math.floor( Math.random() * max ) ;
    }

    /**
     * Losuje liczbę z zakresu [min;max]
     * @param min
     * @param max
     * @return
     */
    public static short number(short min, short max) {
        return (short)(min + Math.round( Math.random() * (max-min) ));
    }

    /**
     * Losuje liczbę z zakresu [min;max]
     * @param min
     * @param max
     * @return
     */
    public static byte number(byte min, byte max) {
        return (byte)(min + Math.round( Math.random() * (max-min) ));
    }

    /**
     * Losuje liczbę z zakresu [min;max]
     * @param min
     * @param max
     * @return
     */
    public static float number(float min, float max) {
        return min + (float)(Math.random() * (max-min));
    }

    public static float number(float max) {
        return (float)(Math.random() * max);
    }

    public static double number(double min, double max) {
        return min + (Math.random() * (max-min));
    }

    public static double number(double max) {
        return Math.random() * max;
    }

    /**
     * Losuje liczbę z zakresu [0;max)
     * @param max
     * @return
     */
    public static char number(char max) {
        return (char) Math.floor( Math.random() * max ) ;
    }

    /**
     * Losuje liczbę z zakresu [min;max]
     * @param min
     * @param max
     * @return
     */
    public static char number(char min, char max) {
        return (char)(min + Math.round( Math.random() * (max-min) ));
    }

    public static boolean bool() {
        return Math.random() >= 0.5;
    }

    /**
     * Generuje losowy ciąg znaków, zawierający podane znaki, o długości {@code size}.
     * @param size
     * @param chars
     * @return
     */
    public static String text(int size, String chars) {
        char[] buffer = new char[size];
        for(int i=0; i<size; i++) { buffer[i] = character(chars); }
        return new String(buffer);
    }

    /**
     * Generuje losowy ciąg znaków, zawierający losowe znaki alfanumeryczne oraz spacje, o długości {@code size}.
     * @param size
     * @param chars
     * @return
     */
    public static String text(int size) {
        char[] buffer = new char[size];
        String domain = ALPHA + "      ";
        for(int i=0; i<size; i++) { buffer[i] = character(domain); }
        return new String(buffer);
    }

    /**
     * Losuje kolor o maksymalnym nasyceniu (Saturation), dowolnej barwie (Hue) i
     * jasności z podanego zakresu.
     * @param min
     * @param max
     * @return
     */
    public static Color color(float min, float max) {
        return Color.getHSBColor(
            (float)Math.random(), 1, number(min,max)
        );
    }

    /**
     * Losuje kolor pośredni między dwoma podanymi.
     * @param min
     * @param max
     * @return
     */
    public static Color color(Color min, Color max) {
        return new Color(
            number(min.getRed(), max.getRed()),
            number(min.getGreen(), max.getGreen()),
            number(min.getBlue(), max.getBlue())
        );
    }

    /**
     * Zwraca dowolny losowy znak alfanumeryczny
     * @return
     */
    public static char character() {
        return character(ALPHANUM);
    }

    /**
     * Zwraca losowy znak z podanego zbioru.
     * @param chars
     * @return
     */
    public static char character(String chars) {
        return chars.charAt( number(chars.length()) );
    }

    @SuppressWarnings({"unchecked", "PMD"})
    public static <T> T object(Class<T> type) {
        Class<?> boxed = ClassUtils.box(type);
        if(Character.class.equals(boxed)) {
            return (T)(Object)Randomizer.character();
        }
        if(Byte.class.equals(boxed)) {
            return (T)(Object)Randomizer.number(Byte.MAX_VALUE);
        }
        if(Short.class.equals(boxed)) {
            return (T)(Object)Randomizer.number(Short.MAX_VALUE);
        }
        if(Integer.class.equals(boxed)) {
            return (T)(Object)Randomizer.number(Integer.MAX_VALUE);
        }
        if(Long.class.equals(boxed)) {
            return (T)(Object)Randomizer.number(Long.MAX_VALUE);
        }
        if(Float.class.equals(boxed)) {
            return (T)(Object)Randomizer.number(Float.MAX_VALUE);
        }
        if(Double.class.equals(boxed)) {
            return (T)(Object)Randomizer.number(Double.MAX_VALUE);
        }
        if(Boolean.class.equals(boxed)) {
            return (T)(Object)Randomizer.bool();
        }
        if(Color.class.equals(boxed)) {
            return (T)(Object)Randomizer.color(0.0f, 1.0f);
        }
        throw new UnsupportedOperationException("Unsupported type: " + type);
    }

    public static <T> List<T> list(Class<T> type, int size) {
        List<T> list = new ArrayList<>(size);
        for(int i=0; i<size; i++) {
            list.add(object(type));
        }
        return list;
    }

    @SuppressWarnings("unchecked")
    public static <Auto> Auto array(Class<?> type, int size) {
        Object array = ArrayUtils.make(type, size);
        List<Object> list = new NativeArrayList<>(array,0);
        for(int i=0; i<size; i++) {
            list.add(object(type));
        }
        return (Auto)array;
    }

}
