using System;
using Intrepid.Gameplay.Journey.Runtime.Messages;

namespace Intrepid.Gameplay.Journey.Runtime.State
{
    public sealed class JourneyRuntimeState
    {
        public JourneyRuntimePhase Phase { get; private set; } = JourneyRuntimePhase.None;

        public StageTag CurrentStageTag { get; private set; }

        public JourneyTravelRequest CurrentTravelRequest { get; private set; }

        public bool IsBooting => Phase == JourneyRuntimePhase.Booting;

        public bool IsReady => Phase == JourneyRuntimePhase.Ready;

        public bool IsTransitionRunning => Phase == JourneyRuntimePhase.Transitioning;

        public bool IsFailed => Phase == JourneyRuntimePhase.Failed;

        public bool IsBooted =>
            Phase == JourneyRuntimePhase.Ready ||
            Phase == JourneyRuntimePhase.Transitioning;

        public bool HasCurrentStage => CurrentStageTag.IsNotNull();

        public void BeginBoot()
        {
            Phase = JourneyRuntimePhase.Booting;
            CurrentStageTag = null;
            CurrentTravelRequest = null;
        }

        public void CompleteBoot(StageTag stageTag)
        {
            CurrentStageTag = stageTag;
            CurrentTravelRequest = null;
            Phase = JourneyRuntimePhase.Ready;
        }

        public void FailBoot()
        {
            CurrentTravelRequest = null;
            CurrentStageTag = null;
            Phase = JourneyRuntimePhase.Failed;
        }

        public bool CanAcceptTravelRequest(out string reason)
        {
            if (IsBooting)
            {
                reason = "runtime is still booting";
                return false;
            }

            if (!IsBooted)
            {
                reason = "runtime is not booted";
                return false;
            }

            if (IsTransitionRunning)
            {
                reason = "another transition is running";
                return false;
            }

            if (CurrentStageTag.IsNull())
            {
                reason = "current stage is null";
                return false;
            }

            reason = null;
            return true;
        }

        public bool TryBeginTransition(
            JourneyTravelRequest request,
            out string reason)
        {
            if (!CanAcceptTravelRequest(out reason))
            {
                return false;
            }

            if (request == null)
            {
                reason = "request is null";
                return false;
            }

            if (request.OriginStageTag.IsNull())
            {
                reason = "origin stage is null";
                return false;
            }

            if (request.DestinyStageTag.IsNull())
            {
                reason = "destiny stage is null";
                return false;
            }

            if (request.OriginStageTag != CurrentStageTag)
            {
                reason =
                    "origin stage is not current. " +
                    $"Current: {CurrentStageTag.StageId}, " +
                    $"Origin: {request.OriginStageTag.StageId}";

                return false;
            }

            if (request.DestinyStageTag == CurrentStageTag)
            {
                reason =
                    "destiny stage is already current. " +
                    $"Stage: {CurrentStageTag.StageId}";

                return false;
            }

            CurrentTravelRequest = request;
            Phase = JourneyRuntimePhase.Transitioning;

            reason = null;
            return true;
        }

        public void CompleteTransition(JourneyTravelRequest request)
        {
            if (request != null)
            {
                CurrentStageTag = request.DestinyStageTag;
            }

            CurrentTravelRequest = null;
            Phase = JourneyRuntimePhase.Ready;
        }

        public void FailTransition()
        {
            CurrentTravelRequest = null;

            if (CurrentStageTag.IsNull())
            {
                Phase = JourneyRuntimePhase.Failed;
                return;
            }

            Phase = JourneyRuntimePhase.Ready;
        }

        public void Reset()
        {
            Phase = JourneyRuntimePhase.None;
            CurrentStageTag = null;
            CurrentTravelRequest = null;
        }

        public override string ToString()
        {
            return
                $"Phase={Phase}, " +
                $"CurrentStage={CurrentStageTag?.StageId}, " +
                $"CurrentTravel={CurrentTravelRequest}";
        }
    }
}