using System;
using System.Collections.Generic;
using System.Linq;

namespace FishDice.LuckyDice
{
    public interface ILayerAvailabilityPolicy
    {
        bool IsLayerAvailable(RandomLayerDefinition layer, PoolFunnelContext context);
    }

    public interface IResultWeightCalculator
    {
        int CalculateWeight(RandomResultDefinition result, RandomPoolState state, PoolFunnelContext context);
    }

    public interface IPoolSettlementStrategy
    {
        RandomPoolState Settle(
            RandomPoolState stateBefore,
            RandomResultDefinition selectedResult,
            PoolFunnelContext context);
    }

    public interface IFunnelDecisionStrategy
    {
        PoolFunnelDecision Decide(
            RandomLayerDefinition layer,
            RandomResultDefinition selectedResult,
            RandomPoolState stateAfter,
            PoolFunnelContext context);
    }

    public interface IPoolFunnelActionDispatcher
    {
        IReadOnlyList<RollActionResult> Dispatch(
            RandomLayerDefinition layer,
            RandomResultDefinition selectedResult,
            PoolFunnelContext context);
    }

    public sealed class AlwaysAvailableLayerPolicy : ILayerAvailabilityPolicy
    {
        public bool IsLayerAvailable(RandomLayerDefinition layer, PoolFunnelContext context)
        {
            return true;
        }
    }

    public sealed class LinearLuckValueWeightCalculator : IResultWeightCalculator
    {
        public int CalculateWeight(RandomResultDefinition result, RandomPoolState state, PoolFunnelContext context)
        {
            if (result == null)
            {
                throw new ArgumentNullException(nameof(result));
            }

            if (state == null)
            {
                throw new ArgumentNullException(nameof(state));
            }

            return Math.Max(0, result.BaseWeight + result.LuckValueWeightFactor * state.LuckValue);
        }
    }

    public sealed class DeltaPoolSettlementStrategy : IPoolSettlementStrategy
    {
        public RandomPoolState Settle(
            RandomPoolState stateBefore,
            RandomResultDefinition selectedResult,
            PoolFunnelContext context)
        {
            if (stateBefore == null)
            {
                throw new ArgumentNullException(nameof(stateBefore));
            }

            if (selectedResult == null)
            {
                throw new ArgumentNullException(nameof(selectedResult));
            }

            return stateBefore.With(
                stateBefore.LuckValue + selectedResult.LuckValueDelta,
                stateBefore.RollCount + 1);
        }
    }

    public sealed class ConfigFunnelDecisionStrategy : IFunnelDecisionStrategy
    {
        public PoolFunnelDecision Decide(
            RandomLayerDefinition layer,
            RandomResultDefinition selectedResult,
            RandomPoolState stateAfter,
            PoolFunnelContext context)
        {
            if (selectedResult == null)
            {
                return PoolFunnelDecision.Stop();
            }

            return selectedResult.Decision ?? PoolFunnelDecision.Stop();
        }
    }

    public sealed class NoOpPoolFunnelActionDispatcher : IPoolFunnelActionDispatcher
    {
        public IReadOnlyList<RollActionResult> Dispatch(
            RandomLayerDefinition layer,
            RandomResultDefinition selectedResult,
            PoolFunnelContext context)
        {
            if (selectedResult == null || selectedResult.Actions == null)
            {
                return Array.Empty<RollActionResult>();
            }

            return selectedResult.Actions
                .Select(action => RollActionResult.Succeeded(action.ActionType, "PoolFunnelActionQueued"))
                .ToArray();
        }
    }
}
