Spending/Bank.cs
using System;
using System.Text.Json.Nodes;

namespace PlanetMeat;

public sealed class Bank : GameObjectSystem<Bank>
{
	const double MAX_CAPACITY = 1E250;

	public class Account : ISaveTarget
	{
		private double Amount
		{
			get;
			set
			{
				field = value;
				_roundedAmount = Math.Round( value );
			}
		} = 0.0;
		private double _roundedAmount = 0.0;
		public double Funds => HasCap ? double.Min( _roundedAmount, Capacity ) : _roundedAmount;

		public string BountyImprovementIdent { get; set; } = "";
		public ValueAggregator<float> AggregateBounty => field ??= ValueAggregator<float>.FromImprovement( BountyImprovementIdent );

		private double _capacity = 1000;
		public double CapacityModifier { get; set; } = 0;
		public double Capacity => double.Min( _capacity + CapacityModifier, MAX_CAPACITY );
		public Currency Currency { get; set; } = null;

		public bool HasCap => Capacity >= 0;
		public bool AtCap => Amount >= Capacity;

		public string DisplayText
		{
			get
			{
				var amt = LargeNumberString.ToCompact( Funds );
				return HasCap ? (amt + "/" + LargeNumberString.ToCompact( Capacity )) : amt;
			}
		}

		public void Clear() => Amount = 0.0;

		public int Hash => HashCode.Combine( Amount, Capacity );

		public JsonNode NextSave => Amount;

		public Account( Currency c, double cap = -1 )
		{
			Currency = c;
			_capacity = cap;
		}

		public Account( Currency c, double startAmount, double cap )
		{
			Currency = c;
			_capacity = cap;
			Amount = startAmount;
		}

		public double AfterSpending( double cost ) => Amount - cost;
		public void Spend( double cost ) => Amount = AfterSpending( cost );
		public bool CanAfford( double cost ) => Funds >= cost;

		public bool TrySpend( double cost )
		{
			if ( CanAfford( cost ) )
			{
				Spend( cost );
				return true;
			}
			return false;
		}

		public double AfterProfiting( double profit, bool exceedCap = false )
		{
			if ( HasCap && Amount > Capacity )
				return Amount;
			return Amount.AddCapped( profit, HasCap && !exceedCap ? Capacity : double.MaxValue );
		}
		public void Profit( double profit, bool exceedCap = false ) => Amount = AfterProfiting( profit, exceedCap );

		public void Bounty( double bounty )
		{
			if ( AggregateBounty is not null )
				Profit( bounty.MultiplyCapped( AggregateBounty.Total ) );
			else
				Profit( bounty );
		}

		public void IncreaseCap( double amt )
		{
			if ( HasCap )
				_capacity = _capacity.AddCapped( amt );
		}

		public void DecreaseCap( double amt )
		{
			if ( HasCap )
				_capacity -= amt;
		}

		public void LoadFromSave( JsonNode save )
		{
			Amount = save.GetValue<double>();
		}

		public void ClearSave()
		{
			Amount = 0.0;
		}
	}

	private Dictionary<string, Account> Accounts { get; set; } = null;
	private Dictionary<string, Dictionary<string, double>> CapacityProviders { get; set; } = [];

	public Bank( Scene scene ) : base( scene )
	{
		Accounts = new()
		{
			["goop"] = new( ResourceLibrary.Get<Currency>( "bank/goop.bux" ), 0 ) { BountyImprovementIdent = "goop_bounty" },
			["meat"] = new( ResourceLibrary.Get<Currency>( "bank/meat.bux" ) ),
		};
	}

	public Account GetAccount( string id ) => Accounts.GetValueOrDefault( id, null );

	public bool CanAffordBatch( IDictionary<string, double> costs, double multiplier = 1.0 ) => costs.All( c => GetAccount( c.Key )?.CanAfford( c.Value.MultiplyCapped( multiplier ) ) ?? false );

	public void SpendBatch( IDictionary<string, double> costs, double multiplier = 1.0 )
	{
		foreach ( var (currency, cost) in costs )
			GetAccount( currency )?.Spend( cost.MultiplyCapped( multiplier ) );
	}

	public bool TrySpendBatch( IDictionary<string, double> costs, double multiplier = 1.0 )
	{
		if ( CanAffordBatch( costs, multiplier ) )
		{
			SpendBatch( costs, multiplier );
			return true;
		}
		return false;
	}

	public void ProfitBatch( IDictionary<string, double> payments, double multiplier = 1.0 )
	{
		foreach ( var (currency, profit) in payments )
			GetAccount( currency )?.Profit( profit.MultiplyCapped( multiplier ) );
	}

	private bool TryGetAccountProvidedAmount( string id, string account, out double provided )
	{
		provided = 0.0;
		return CapacityProviders.TryGetValue( id, out var extantCaps ) && extantCaps.TryGetValue( account, out provided );
	}

	public double GetCapacityWithout( string id, string account )
	{
		TryGetAccountProvidedAmount( id, account, out var provided );
		return GetAccount( account ).Capacity - provided;
	}

	public void SetProvidedCapacity( string id, string account, double cap, double multiplier = 1.0 )
	{
		var bank = GetAccount( account );
		if ( TryGetAccountProvidedAmount( id, account, out var extandProvided ) )
			bank.CapacityModifier -= extandProvided;

		var multed = cap.MultiplyCapped( multiplier );
		bank.CapacityModifier += multed;
		CapacityProviders.GetOrCreate( id )[account] = multed;
	}

	private void ClearProvidedCapacity( string id )
	{
		if ( CapacityProviders.TryGetValue( id, out var extantCaps ) )
		{
			foreach ( var (currency, provided) in extantCaps )
				GetAccount( currency ).CapacityModifier -= provided;
		}
		CapacityProviders.Remove( id );
	}

	public void ReplaceProvidedCapacity( string id, Dictionary<string, double> caps, double multiplier = 1.0 )
	{
		ClearProvidedCapacity( id );

		var finalCaps = new Dictionary<string, double>( caps );
		foreach ( var (currency, provided) in caps )
		{
			var multed = provided.MultiplyCapped( multiplier );
			finalCaps[currency] = multed;
			GetAccount( currency ).CapacityModifier += multed;
		}
		CapacityProviders[id] = finalCaps;
	}

	public void RemoveProvidedCapacity( string id )
	{
		ClearProvidedCapacity( id );
		CapacityProviders.Remove( id );
	}
}