using System.Collections.Generic;
using System.Text;

namespace Editor.GON.StageEditor
{
    public enum StageValidationSeverity
    {
        Info,
        Warning,
        Error
    }

    public readonly struct StageValidationMessage
    {
        public readonly StageValidationSeverity severity;
        public readonly string message;

        public StageValidationMessage(StageValidationSeverity severity, string message)
        {
            this.severity = severity;
            this.message = message;
        }

        public override string ToString()
        {
            return severity + ": " + message;
        }
    }

    public static class StageValidator
    {
        public static List<StageValidationMessage> Validate(StageDefinition stage)
        {
            var messages = new List<StageValidationMessage>();

            if (stage is null)
            {
                messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "No StageDefinition selected."));
                return messages;
            }

            stage.EnsureDefaults();
            
            if (stage.floors.Count == 0)
            {
                messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Stage has no floors."));
            }

            for (var i = 0; i < stage.floors.Count; i++)
            {
                var floor = stage.floors[i];
                if (floor is null)
                {
                    messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Floor at index " + i + " is null."));
                    continue;
                }

                if (string.IsNullOrEmpty(floor.floorId))
                {
                    messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Floor at index " + i + " has no floor id."));
                }
            }

            var hasSpawn = false;
            var ids = new HashSet<string>();
            for (var i = 0; i < stage.objects.Count; i++)
            {
                var obj = stage.objects[i];
                if (obj is null)
                {
                    messages.Add(new StageValidationMessage(StageValidationSeverity.Warning, "Object at index " + i + " is null."));
                    continue;
                }

                if (string.IsNullOrEmpty(obj.id))
                {
                    messages.Add(new StageValidationMessage(StageValidationSeverity.Warning, "Object at index " + i + " has no id."));
                }
                else if (!ids.Add(obj.id))
                {
                    messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Duplicate object id: " + obj.id));
                }

                var floor = stage.GetFloor(obj.floorId);
                if (floor is null)
                {
                    messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Object " + obj.id + " references missing floor: " + obj.floorId));
                    continue;
                }

                if (obj.width <= 0 || obj.height <= 0)
                {
                    messages.Add(new StageValidationMessage(StageValidationSeverity.Error, "Object " + obj.id + " has invalid size."));
                }

                if (obj.kind == StageObjectKind.Spawn)
                {
                    hasSpawn = true;
                }
            }

            if (!hasSpawn)
            {
                messages.Add(new StageValidationMessage(StageValidationSeverity.Warning, "Stage has no player spawn marker."));
            }

            if (messages.Count == 0)
            {
                messages.Add(new StageValidationMessage(StageValidationSeverity.Info, "No validation issues found."));
            }

            return messages;
        }

        public static string Format(List<StageValidationMessage> messages)
        {
            var sb = new StringBuilder();
            foreach (var stageValidationMessage in messages)
            {
                sb.AppendLine(stageValidationMessage.ToString());
            }
            return sb.ToString();
        }
    }
}
