package cz.moneta.test.dsl.util.data.generators;

import cz.moneta.test.dsl.util.task.ods.OdsTasks;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.ods.OdsEndpoint;
import cz.moneta.test.harness.support.data.Generator;
import org.apache.commons.lang3.tuple.Pair;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Formula https://cs.wikipedia.org/wiki/Identifika%C4%8Dn%C3%AD_%C4%8D%C3%ADslo_osoby
 */
public class IcoGenerator implements Generator<String> {

    public String generate(StoreAccessor harness, Object... params) {
        return Stream.<Supplier<Optional<String>>>of(
                () -> Optional.ofNullable(harness)
                        .filter(h -> h.getEndpoint(OdsEndpoint.class).canAccess())
                        .map(IcoGenerator::getUniqueIco),
                () -> Optional.of(generate()))
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .findFirst()
                .orElse(null);
    }

    private static String getUniqueIco(StoreAccessor harness) {
        OdsTasks ods = new OdsTasks(harness);
        return Stream.generate(IcoGenerator::generate)
                .limit(20)
                .filter(ico -> ods.findIco(ico).isEmpty())
                .findFirst()
                .orElseThrow(() -> new IllegalStateException("Failed to generate unique ICO after 20 trials"));
    }

    public static String generate() {
        return Stream.iterate(8, n -> n - 1)
                .limit(7)
                .map(i -> {
                    Integer rnd = ThreadLocalRandom.current().nextInt(9) + 1;
                    return Pair.of(rnd, (i * rnd));
                })
                .collect(Collectors.collectingAndThen(Collectors.toList(), IcoGenerator::composeIco));
    }

    private static String composeIco(List<Pair<Integer, Integer>> pairIcoList) {
        int sum = pairIcoList.stream().mapToInt(Pair::getRight).sum();
        int lastNumber = (11 - sum % 11) % 10;
        return pairIcoList.stream().map(Pair::getLeft)
                .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
                .append(lastNumber)
                .toString();
    }

}
