package de.siphalor.was.content.product.dynamic;

import de.siphalor.was.content.product.Product;
import de.siphalor.was.content.product.ProductType;
import de.siphalor.was.util.Util;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.IntPredicate;
import java.util.stream.Collectors;

public class DynamicProductType implements ProductType<DynamicProduct> {
	private final String id;
	private final Map<String, DynamicProduct> variations;
	private final String[] properties;

	public DynamicProductType(String id, List<DynamicProduct> variations, String[] properties) {
		this.id = id;
		this.variations = new HashMap<>();
		variations.forEach(p -> {
			p.setType(this);
			this.variations.put(p.getPropertySpecifier(), p);
		});
		this.properties = properties;
	}

	@NotNull
	public static DynamicProductType from(@NotNull String id, @NotNull InputStream inputStream) {
		Properties propertiesFile = new Properties();

		try {
			propertiesFile.load(inputStream);

			String[] propNames = ((String) propertiesFile.get("properties")).split(",");

			if (propNames.length > 0) {
				// propNameList = propNames without blank elements & all elements trimmed
				List<String> propNameList = new ArrayList<>(propNames.length);
				List<List<ProductPrototype>> properties = new ArrayList<>(propNames.length);
				for (String propName : propNames) {
					propName = propName.trim();
					if (propName.isEmpty()) {
						continue;
					}
					propNameList.add(propName);
					if (propertiesFile.containsKey(propName + ".variants")) {
						String[] variants = ((String) propertiesFile.get(propName + ".variants")).split(",");
						List<ProductPrototype> propertyVariants = new ArrayList<>(variants.length);
						for (String variant : variants) {
							variant = variant.trim();
							propertyVariants.add(makePrototype(propertiesFile, propName, variant));
						}
						properties.add(propertyVariants);
					} else {
						// if there are no variants assume true/false flag
						properties.add(List.of(
								makePrototype(propertiesFile, propName, "true"),
								makePrototype(propertiesFile, propName, "false")
						));
					}
				}

				List<ProductPrototype> prototypes = properties.stream().reduce((pts1, pts2) -> {
					return pts1.stream().flatMap(pt -> {
						return pts2.stream().map(pt::combine);
					}).collect(Collectors.toList());
				}).get();

				List<DynamicProduct> products = prototypes.stream().map(pt -> {
					DynamicProduct product = new DynamicProduct(pt.value.split(";"));
					pt.apply(product);
					return product;
				}).collect(Collectors.toList());

				return new DynamicProductType(id, products, propNameList.toArray(String[]::new));
			}
		} catch (IOException | NullPointerException e) {
			e.printStackTrace();
		}

		return new DynamicProductType(id, List.of(new DynamicProduct(Util.emptyArray())), Util.emptyArray());
	}

	private static ProductPrototype makePrototype(Properties propertiesFile, String property, String variant) {
		ProductPrototype prototype = new ProductPrototype(variant);
		String base = property + "." + variant + ".";
		if (propertiesFile.containsKey(base + "depth")) {
			prototype.depth = Integer.parseInt((String) propertiesFile.get(base + "depth"));
		}
		if (propertiesFile.containsKey(base + "y")) {
			String val = (String) propertiesFile.get(base + "y");
			switch (val.charAt(0)) {
				case '>': {
					int i = Integer.parseInt(val.substring(1));
					prototype.yPredicate = value -> value > i;
					break;
				}
				case '<': {
					int i = Integer.parseInt(val.substring(1));
					prototype.yPredicate = value -> value < i;
					break;
				}
				default:
					int i = Integer.parseInt(val);
					prototype.yPredicate = value -> value == i;
					break;
			}
		}
		return prototype;
	}

	@Override
	public DynamicProduct getProduct(@NotNull String[] values) {
		return variations.get(String.join("_", values));
	}

	@Override
	public @NotNull String getId() {
		return id;
	}

	@Override
	public @NotNull String[] getProperties() {
		return properties;
	}

	@Override
	public @NotNull Product randomProduct(Random random) {
		List<DynamicProduct> products = new ArrayList<>(variations.values());
		return products.get(random.nextInt(products.size()));
	}

	public Map<String, DynamicProduct> getVariations() {
		return variations;
	}

	private static class ProductPrototype {
		String value;

		Integer depth = null;
		IntPredicate yPredicate = null;

		public ProductPrototype(String value) {
			this.value = value;
		}

		public void apply(DynamicProduct product) {
			if (depth != null)
				product.setDepth(depth);
			if (yPredicate != null)
				product.setYPredicate(yPredicate);
		}

		public ProductPrototype combine(ProductPrototype prototype) {
			ProductPrototype result = new ProductPrototype(value + ";" + prototype.value);
			if (prototype.depth != null)
				result.depth = prototype.depth;
			else
				result.depth = depth;
			if (prototype.yPredicate != null)
				result.yPredicate = prototype.yPredicate;
			else
				result.yPredicate = yPredicate;
			return result;
		}
	}

	@Override
	public String toString() {
		return "DynamicProductType{" +
				"properties=" + String.join(" & ", properties) +
				", variations=\n" + variations.values().stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator())) +
				'}';
	}
}
