package dev.kkorolyov.pancake.sound;

import dev.kkorolyov.pancake.Config;
import dev.kkorolyov.simplelogs.Logger;
import dev.kkorolyov.simpleprops.Properties;

import javafx.scene.media.AudioClip;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Random;

/**
 * Provides access to sounds by name.
 * Names are mapped to an arbitrary-length collection of sound resources.
 * When retrieving from a name with multiple resource associations, a randomly-selected associated resource is returned.
 */
public class SoundPool {
	private static final int DEFAULT_CACHE_SIZE = 15;
	private static final Random rand = new Random();
	private static final Logger log = Config.getLogger(SoundPool.class);

	private final Map<String, List<String>> resourceLists = new HashMap<>();
	private final Map<String, AudioClip> cache;
	private int cacheSize;

	/**
	 * Constructs a new sound pool using the {@link #DEFAULT_CACHE_SIZE}.
	 */
	public SoundPool() {
		this(DEFAULT_CACHE_SIZE);
	}
	/**
	 * Constructs a new sound pool using some cache size.
	 * @param cacheSize maximum size of audio file cache
	 */
	public SoundPool(int cacheSize) {
		cache = new LinkedHashMap<String, AudioClip>(cacheSize) {
			@Override
			protected boolean removeEldestEntry(Entry eldest) {
				return size() > getCacheSize();
			}
		};
		setCacheSize(cacheSize);

		log.debug("Constructed a new SoundPool with cacheSize={}", getCacheSize());
	}

	/**
	 * Retrieves a sound randomly selected from all resources associated with a name.
	 * @param name sound name
	 * @return randomly-selected sound associated with {@code name}
	 * @throws NoSuchElementException if no sounds associated with {@code name}
	 */
	public AudioClip get(String name) {
		return cache.computeIfAbsent(getResource(name), AudioClip::new);
	}
	private String getResource(String name) {
		List<String> resources = resourceLists.get(name);
		if (resources == null) throw new NoSuchElementException("No resources associated with name: " + name);

		return resources.get(rand.nextInt(resources.size()));
	}

	/**
	 * Parses sounds from a configuration file.
	 * Each entry is a sound name mapped to an arbitrary-length list of resources.
	 * If a resource does not contain {@code //}, it is assumed to be found on the local filesystem.
	 * @param soundConfig sound configuration file
	 */
	public void put(Properties soundConfig) {
		for (String name : soundConfig.keys()) {
			for (String resource : soundConfig.getArray(name)) {
				String url = resource;

				if (!resource.contains("//")) {
					URL systemUrl = ClassLoader.getSystemResource(resource);
					if (systemUrl == null) throw new NoSuchElementException("No such system resource: " + resource);

					url = systemUrl.toExternalForm();
				}
				put(name, url);
			}
			log.info("Parsed sound entry: {}", name);
		}
	}
	/**
	 * Adds a {@code name -> resource} association.
	 * @param name sound name
	 * @param resource resource URL
	 */
	public void put(String name, String resource) {
		List<String> resources = resourceLists.computeIfAbsent(name, k -> {
			log.info("No resource list for name={}, creating new...", name);
			return new ArrayList<>();
		});
		resources.add(resource);
	}

	/** @return maximum size of audio file cache */
	public int getCacheSize() {
		return cacheSize;
	}
	/**
	 * Constrained {@code >= 0}.
	 * This method also clears the current audio clip cache.
	 * @param cacheSize new audio clip cache maximum size
	 */
	public void setCacheSize(int cacheSize) {
		this.cacheSize = Math.abs(cacheSize);
		cache.clear();
	}
}

@@ -68,16 +68,72 @@
 Config;%0A
+import dev.kkorolyov.pancake.math.WeightedDistribution;%0A
 import d
@@ -272,36 +272,8 @@
 RL;%0A
-import java.util.ArrayList;%0A
 impo
@@ -330,31 +330,8 @@
 ap;%0A
-import java.util.List;%0A
 impo
@@ -420,33 +420,8 @@
 ion;
-%0Aimport java.util.Random;
 %0A%0A/*
@@ -739,58 +739,8 @@
 15;%0A
-%09private static final Random rand = new Random();%0A
 %09pri
@@ -833,20 +833,36 @@
 String, 
-List
+WeightedDistribution
 %3CString%3E
@@ -871,20 +871,28 @@
 resource
-L
+D
 ist
+ribution
 s = new 
@@ -1956,28 +1956,44 @@
 g name) %7B%0A%09%09
-List
+WeightedDistribution
 %3CString%3E res
@@ -2009,20 +2009,28 @@
 resource
-L
+D
 ist
+ribution
 s.get(na
@@ -2167,38 +2167,8 @@
 get(
-rand.nextInt(resources.size())
 );%0A%09
@@ -2298,16 +2298,43 @@
 esources
+, all with weight %7B@code 1%7D
 .%0A%09 * If
@@ -2967,24 +2967,25 @@
 me);%0A%09%09%7D%0A%09%7D%0A
+%0A
 %09/**%0A%09 * Add
@@ -3024,16 +3024,38 @@
 ociation
+ with weight %7B@code 1%7D
 .%0A%09 * @p
@@ -3164,20 +3164,340 @@
 ce) %7B%0A%09%09
-List
+put(name, resource, 1);%0A%09%7D%0A%09/**%0A%09 * Adds a %7B@code name -%3E resource%7D association.%0A%09 * @param name sound name%0A%09 * @param resource resource URL%0A%09 * @param weight %7B@code resource%7D weight compared to other resources in the same distribution%0A%09 */%0A%09public void put(String name, String resource, int weight) %7B%0A%09%09WeightedDistribution
 %3CString%3E
@@ -3517,20 +3517,28 @@
 resource
-L
+D
 ist
+ribution
 s.comput
@@ -3646,17 +3646,28 @@
 new 
-ArrayList
+WeightedDistribution
 %3C%3E()
@@ -3690,16 +3690,24 @@
 ces.add(
+weight, 
 resource

Change resource lists to use WeightedDistribution