using Verse;
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;

namespace rjw;

public static class PartSizeCalculator
{
	public static bool TryGetLength(Hediff hediff, out float size)
	{
		return TryGetMeasurementFromCurve(hediff, GetSizeConfig(hediff)?.lengths, true, out size);
	}

	public static bool TryGetLength(HediffDef def, float baseSize, out float size)
	{
		return TryGetMeasurementFromCurve(def, baseSize, 1f, GetSizeConfig(def)?.lengths, out size);
	}

	public static bool TryGetGirth(Hediff hediff, out float size)
	{
		return TryGetMeasurementFromCurve(hediff, GetSizeConfig(hediff)?.girths, true, out size);
	}

	public static bool TryGetGirth(HediffDef def, float baseSize, out float size)
	{
		return TryGetMeasurementFromCurve(def, baseSize, 1f, GetSizeConfig(def)?.girths, out size);
	}

	public static bool TryGetCupSize(Hediff hediff, out float size)
	{
		// Cup size is already "scaled" because the same breast volume has a smaller cup size on a larger band size.
		return TryGetMeasurementFromCurve(hediff, GetSizeConfig(hediff)?.cupSizes, false, out size);
	}

	public static bool TryGetCupSize(HediffDef def, float baseSize, out float size)
	{
		return TryGetMeasurementFromCurve(def, baseSize, 1f, GetSizeConfig(def)?.cupSizes, out size);
	}

	public static float GetBandSize(Hediff hediff)
	{
		var size = GetUnderbustSize(hediff);
		size /= BraSizeConfigDef.Instance.bandSizeInterval;
		size = (float)Math.Round(size, MidpointRounding.AwayFromZero);
		size *= BraSizeConfigDef.Instance.bandSizeInterval;
		return size;
	}

	public static float GetUnderbustSize(Hediff hediff)
	{
		return BraSizeConfigDef.Instance.bandSizeBase * GetScale(hediff);
	}

	static float GetScale(Hediff hediff)
	{
		if (!hediff.TryGetComp<HediffComp_SexPart>(out var comp))
		{
			// Added as a Option for Modders to change Size above max-severity.
			if (comp.bodySizeOverride >= 0.001)
				return comp.bodySizeOverride;
		}
		return hediff.pawn.BodySize;
	}

	public static bool TryGetOverbustSize(Hediff hediff, out float size)
	{
		if (!TryGetCupSize(hediff, out var cupSize))
		{
			size = 0f;
			return false;
		}

		// Cup size is rounded up, so to do the math backwards subtract .9
		size = GetUnderbustSize(hediff) + ((cupSize - .9f) * BraSizeConfigDef.Instance.cupSizeInterval);
		return true;
	}

	static bool TryGetMeasurementFromCurve(
		Hediff hediff,
		List<float> stageSizes,
		bool shouldScale,
		out float measurement)
	{
		var scaleFactor = shouldScale ? GetScale(hediff) : 1.0f;
		if (!hediff.TryGetComp<HediffComp_SexPart>(out var comp))
		{
			measurement = 0;
			return false;
		}
		return TryGetMeasurementFromCurve(hediff.def, comp.Size, scaleFactor, stageSizes, out measurement);
	}

	static bool TryGetMeasurementFromCurve(HediffDef hediffDef, float baseSize, float scale, List<float> stageSizes, out float measurement)
	{
		if (hediffDef is not HediffDef_SexPart || stageSizes.NullOrEmpty())
		{
			measurement = 0f;
			return false;
		}
		var curve = new SimpleCurve(hediffDef.stages.Zip(stageSizes, (stage, stageSize) => new CurvePoint(stage.minSeverity, stageSize)));
		measurement = curve.Evaluate(baseSize) * scale;
		return true;
	}

	public static bool TryGetPenisWeight(Hediff hediff, out float weight)
	{
		if (!TryGetLength(hediff, out float length) ||
			!TryGetGirth(hediff, out float girth))
		{
			weight = 0f;
			return false;
		}

		var density = GetSizeConfig(hediff).density;
		if (density == null)
		{
			weight = 0f;
			return false;
		}

		var r = girth / (2.0 * Math.PI);
		var volume = r * r * Math.PI * length;

		weight = (float)(volume * density.Value / 1000f);
		return true;
	}

	public static bool TryGetBreastWeight(Hediff hediff, out float weight)
	{
		if (!TryGetCupSize(hediff, out float rawSize))
		{
			weight = 0f;
			return false;
		}

		var density = GetSizeConfig(hediff).density;
		if (density == null)
		{
			weight = 0f;
			return false;
		}

		// Up a band size and down a cup size is about the same volume.
		var extraBandSize =  BraSizeConfigDef.Instance.bandSizeBase * (1.0f - GetScale(hediff));
		var extraCupSizes = extraBandSize / BraSizeConfigDef.Instance.bandSizeInterval;
		var size = rawSize + extraCupSizes;

		var pounds = 0.765f
			+ 0.415f * size
			+ -0.0168f * size * size
			+ 2.47E-03f * size * size * size;
		var kg = Math.Max(0, pounds * 0.45359237f);
		weight = kg * density.Value;
		return true;
	}

	private static PartSizeConfigDef GetSizeConfig(Hediff hediff)
	{
		return GetSizeConfig(hediff.def);
	}

	private static PartSizeConfigDef GetSizeConfig(HediffDef def)
	{
		return (def as HediffDef_SexPart)?.sizeProfile;
	}
}