@RestController
@SpringBootApplication
public class Application {
    private static final int B_TO_KIB = 1024;
    private static final int KIB_TO_MIB = 1024;

    private final String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private final Random r = new Random();
    private final Map<Long, Mono<String>> cached = new ConcurrentHashMap<>();

    private Mono<String> randomString(long size) {
        return Mono.just(chars)
        .map(s -> s.charAt(r.nextInt(chars.length())))
        .repeat(size)
        .reduce(new StringBuilder(), StringBuilder::append)
        .map(StringBuilder::toString)
        .cache();
    }

    private <T> Mono<T> delay(Mono<T> element, Long delayMs) {
        return Mono.justOrEmpty(delayMs).map(Duration::ofMillis).flatMap(Mono::delay).then(element);
    }

    private Mono<String> data(long size, Long delay) {
        return delay(cached.computeIfAbsent(size, this::randomString), delay);
    }

    @GetMapping("/1KiB")
    public Mono<String> get1KiB(@RequestParam(value = "delayMs", required = false) Long delayMs) {
        return data(B_TO_KIB, delayMs);
    }

    @GetMapping("/5KiB")
    public Mono<String> get5KiB(@RequestParam(value = "delayMs", required = false) Long delayMs) {
        return data(5 * B_TO_KIB, delayMs);
    }

    @GetMapping("/10KiB")
    public Mono<String> get10KiB(@RequestParam(value = "delayMs", required = false) Long delayMs) {
        return data(10 * B_TO_KIB, delayMs);
    }

    @GetMapping("/100KiB")
    public Mono<String> get100KiB(@RequestParam(value = "delayMs", required = false) Long delayMs) {
        return data(100 * B_TO_KIB, delayMs);
    }

    @GetMapping("/1MiB")
    public Mono<String> get1MiB(@RequestParam(value = "delayMs", required = false) Long delayMs) {
        return data(B_TO_KIB * KIB_TO_MIB, delayMs);
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}