package net.ranides.assira.collection.lists;

import net.ranides.assira.text.StringTraits;

import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

/**
 * Simple generator which creates list of integer values from string expression.
 * It supports
 *  - list of values separater by colons
 *  - closed ranges separated by dash
 *  - symbolic names
 *
 * For example:
 *  "1,2,4-7,9" will create list of values: [1,2,4,5,6,7,9]
 *
 * Symbolic names:
 *  you can provide list of names and then use it instead of numbers.
 *
 * For example
 *  "a,b,c", "c,a,b" will create list of values: [2,0,1]
 */
public class IntListGenerator {

    private static final Pattern IS_RANGE = Pattern.compile("([0-9]+)-([0-9]+)");

    /**
     * Returns new list of numbers
     * @param selector selector
     * @return list
     */
    public static IntList parse(String selector) {
        return parse(Collections.emptyList(), Integer.MAX_VALUE, selector);
    }

    /**
     * Returns new list of numbers.
     * It allows only numbers lower than "size"
     *
     * @param size size
     * @param selector selector
     * @return list
     */
    public static IntList parse(int size, String selector) {
        return parse(Collections.emptyList(), size, selector);
    }

    /**
     * Returns new list of numbers.
     * It allows only numbers lower than "names.size"
     * It allows optional use names instead of numbers.
     *
     * @param names names
     * @param selector selector
     * @return list
     */
    public static IntList parse(List<String> names, String selector) {
        return parse(names, names.size(), selector);
    }

    private static IntList parse(List<String> names, int size, String selector) {
        IntList indexes = new IntArrayList();

        Matcher hit;

        for(String expr : selector.split(",")) {
            int fieldIndex = names.indexOf(expr);
            if(fieldIndex != -1) {
                indexes.add(fieldIndex);
            }
            else if((hit = IS_RANGE.matcher(expr)).matches()) {
                range(hit).filter(v -> v < size).forEach(indexes::add);
            }
            else if(StringTraits.isNumber(expr)) {
                int i = Integer.parseInt(expr);
                if(i > size) {
                    throw new IndexOutOfBoundsException(i + " out of range: [0-" + size + ")");
                } else {
                    indexes.add(i);
                }
            }
            else {
                throw new IllegalArgumentException("Invalid index name: " + expr);
            }
        }
        return indexes;
    }

    private static IntStream range(Matcher range) {
        int a = Integer.parseInt(range.group(1));
        int b = Integer.parseInt(range.group(2));
        if (a <= b) {
            return IntStream.rangeClosed(a, b);
        } else {
            return IntStream.rangeClosed(b, a).map(v -> b + a - v);
        }
    }
}
