using Microsoft.Xna.Framework;
using System;

namespace Barotrauma.Networking
{
    partial class OrderChatMessage : ChatMessage
    {
        public readonly Order Order;

        //who was this order given to
        public readonly Character TargetCharacter;

        //which entity is this order referring to (hull, reactor, railgun controller, etc)
        public ISpatialEntity TargetEntity => Order.TargetSpatialEntity;

        //additional instructions (power up, fire at will, etc)
        public Identifier OrderOption => Order.Option;

        public int OrderPriority => Order.ManualPriority;

        /// <summary>
        /// Used when the order targets a wall
        /// </summary>
        public int? WallSectionIndex => Order.WallSectionIndex;

        public bool IsNewOrder { get; }

        /// <summary>
        /// Same as calling <see cref="OrderChatMessage.OrderChatMessage(Order, string, int, string, ISpatialEntity, Character, Character)"/>,
        /// but the text parameter is set using <see cref="Order.GetChatMessage(string, string, bool, string)"/>
        /// </summary>
        public OrderChatMessage(Order order, Character targetCharacter, Character sender, bool isNewOrder = true)
            : this(order,
                   order?.GetChatMessage(targetCharacter?.DisplayName, 
                       (order.TargetEntity as Hull ?? sender?.CurrentHull)?.DisplayName?.Value, 
                       givingOrderToSelf: targetCharacter == sender, orderOption: order.Option, isNewOrder: isNewOrder),
                   targetCharacter, sender, isNewOrder)
        {
            
        }

        public OrderChatMessage(Order order, string text, Character targetCharacter, Entity sender, bool isNewOrder = true)
            : base(NameFromEntityOrNull(sender), text, ChatMessageType.Order, sender, GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == sender))
        {
            Order = order;
            TargetCharacter = targetCharacter;
            IsNewOrder = isNewOrder;
        }

        public static string NameFromEntityOrNull(Entity entity)
            => entity switch
            {
                null => null,
                Character character => character.DisplayName,
                Item it => it.Name,
                _ => throw new ArgumentException("Entity is not a character or item", nameof(entity))
            };

        public static void WriteOrder(IWriteMessage msg, Order order, Entity targetCharacter, bool isNewOrder)
        {
            msg.WriteIdentifier(order.Prefab.Identifier);
            msg.WriteUInt16(targetCharacter == null ? (UInt16)0 : targetCharacter.ID);
            msg.WriteUInt16(order.TargetSpatialEntity is Entity ? (order.TargetEntity as Entity).ID : (UInt16)0);

            // The option of a Dismiss order is written differently so we know what order we target
            // now that the game supports multiple current orders simultaneously
            if (!order.IsDismissal)
            {
                msg.WriteByte((byte)order.Options.IndexOf(order.Option));
            }
            else
            {
                if (order.Option != Identifier.Empty)
                {
                    msg.WriteBoolean(true);
                    string[] dismissedOrder = order.Option.Value.Split('.');
                    msg.WriteByte((byte)dismissedOrder.Length);
                    if (dismissedOrder.Length > 0)
                    {
                        Identifier dismissedOrderIdentifier = dismissedOrder[0].ToIdentifier();
                        var orderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier];
                        msg.WriteIdentifier(dismissedOrderIdentifier);
                        if (dismissedOrder.Length > 1)
                        {
                            Identifier dismissedOrderOption = dismissedOrder[1].ToIdentifier();
                            msg.WriteByte((byte)orderPrefab.Options.IndexOf(dismissedOrderOption));
                        }
                    }
                }
                else
                {
                    // If the order option is not specified for a Dismiss order,
                    // we dismiss all current orders for the character
                    msg.WriteBoolean(false);
                }
            }

            msg.WriteByte((byte)order.ManualPriority);
            msg.WriteByte((byte)order.TargetType);
            if (order.TargetType == Order.OrderTargetType.Position && order.TargetSpatialEntity is OrderTarget orderTarget)
            {
                msg.WriteBoolean(true);
                msg.WriteSingle(orderTarget.Position.X);
                msg.WriteSingle(orderTarget.Position.Y);
                msg.WriteUInt16(orderTarget.Hull == null ? (UInt16)0 : orderTarget.Hull.ID);
            }
            else
            {
                msg.WriteBoolean(false);
                if (order.TargetType == Order.OrderTargetType.WallSection)
                {
                    msg.WriteByte((byte)(order.WallSectionIndex ?? 0));
                }
            }

            msg.WriteBoolean(isNewOrder);
        }

        private void WriteOrder(IWriteMessage msg)
        {
            WriteOrder(msg, Order, TargetCharacter, IsNewOrder);
        }

        public readonly struct OrderMessageInfo
        {
            public Identifier OrderIdentifier { get; }
            public OrderPrefab OrderPrefab => OrderPrefab.Prefabs[OrderIdentifier];
            public Identifier OrderOption { get; }
            public int? OrderOptionIndex { get; }
            public Character TargetCharacter { get; }
            public Order.OrderTargetType TargetType { get; }
            public Entity TargetEntity { get; }
            public OrderTarget TargetPosition { get; }
            public int? WallSectionIndex { get; }
            public int Priority { get; }
            public bool IsNewOrder { get; }

            public OrderMessageInfo(Identifier orderIdentifier, Identifier orderOption, int? orderOptionIndex, Character targetCharacter,
                Order.OrderTargetType targetType, Entity targetEntity, OrderTarget targetPosition, int? wallSectionIndex, int orderPriority, bool isNewOrder)
            {
                OrderIdentifier = orderIdentifier;
                OrderOption = orderOption;
                OrderOptionIndex = orderOptionIndex;
                TargetCharacter = targetCharacter;
                TargetType = targetType;
                TargetEntity = targetEntity;
                TargetPosition = targetPosition;
                WallSectionIndex = wallSectionIndex;
                Priority = orderPriority;
                IsNewOrder = isNewOrder;
            }
        }

        public static OrderMessageInfo ReadOrder(IReadMessage msg)
        {
            Identifier orderIdentifier = msg.ReadIdentifier();
            ushort targetCharacterId = msg.ReadUInt16();
            Character targetCharacter = targetCharacterId != Entity.NullEntityID ? Entity.FindEntityByID(targetCharacterId) as Character : null;
            ushort targetEntityId = msg.ReadUInt16();
            Entity targetEntity = targetEntityId != Entity.NullEntityID ? Entity.FindEntityByID(targetEntityId) : null;

            int? optionIndex = null;
            Identifier orderOption = Identifier.Empty;
            // The option of a Dismiss order is written differently so we know what order we target
            // now that the game supports multiple current orders simultaneously
            if (orderIdentifier != Identifier.Empty)
            {
                var orderPrefab = OrderPrefab.Prefabs[orderIdentifier];
                if (!orderPrefab.IsDismissal)
                {
                    optionIndex = msg.ReadByte();
                }
                // Does the dismiss order have a specified target?
                else if (msg.ReadBoolean())
                {
                    int identifierCount = msg.ReadByte();
                    if (identifierCount > 0)
                    {
                        Identifier dismissedOrderIdentifier = msg.ReadIdentifier();
                        OrderPrefab dismissedOrderPrefab = null;
                        if (dismissedOrderIdentifier != Identifier.Empty)
                        {
                            dismissedOrderPrefab = OrderPrefab.Prefabs[dismissedOrderIdentifier];
                            orderOption = dismissedOrderPrefab.Identifier;
                        }
                        if (identifierCount > 1)
                        {
                            int dismissedOrderOptionIndex = msg.ReadByte();
                            if (dismissedOrderPrefab != null)
                            {
                                var options = dismissedOrderPrefab.Options;
                                if (options != null && dismissedOrderOptionIndex >= 0 && dismissedOrderOptionIndex < options.Length)
                                {
                                    orderOption = $"{orderOption.Value}.{options[dismissedOrderOptionIndex]}".ToIdentifier();
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                optionIndex = msg.ReadByte();
            }

            int orderPriority = msg.ReadByte();
            OrderTarget orderTargetPosition = null;
            Order.OrderTargetType orderTargetType = (Order.OrderTargetType)msg.ReadByte();
            int? wallSectionIndex = null;
            if (msg.ReadBoolean())
            {
                float x = msg.ReadSingle();
                float y = msg.ReadSingle();
                ushort hullId = msg.ReadUInt16();
                var hull = hullId != Entity.NullEntityID ? Entity.FindEntityByID(hullId) as Hull : null;
                orderTargetPosition = new OrderTarget(new Vector2(x, y), hull, creatingFromExistingData: true);
            }
            else if (orderTargetType == Order.OrderTargetType.WallSection)
            {
                wallSectionIndex = msg.ReadByte();
            }

            bool isNewOrder = msg.ReadBoolean();
            return new OrderMessageInfo(orderIdentifier, orderOption, optionIndex, targetCharacter,
                    orderTargetType, targetEntity, orderTargetPosition, wallSectionIndex, orderPriority, isNewOrder);
        }
    }
}
