using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;

namespace Barotrauma.Networking
{
    [NetworkSerialize]
    struct TempClient : INetSerializableStruct
    {
        public string Name;
        public Identifier PreferredJob;
        public CharacterTeamType TeamID;
        public CharacterTeamType PreferredTeam;
        public UInt16 NameId;
        public AccountInfo AccountInfo;
        public byte SessionId;
        public UInt16 CharacterId;
        public float Karma;
        public bool Muted;
        public bool InGame;
        public bool HasPermissions;
        public bool IsOwner;
        public bool IsDownloading;
    }
    
    partial class Client : IDisposable
    {
        public const int MaxNameLength = 32;

        public string Name; public UInt16 NameId;
        
        /// <summary>
        /// An ID for this client for the current session.
        /// THIS IS NOT A PERSISTENT VALUE. DO NOT STORE THIS LONG-TERM.
        /// IT CANNOT BE USED TO IDENTIFY PLAYERS ACROSS SESSIONS.
        /// </summary>
        public readonly byte SessionId;

        public AccountInfo AccountInfo;
        
        /// <summary>
        /// The ID of the account used to authenticate this session.
        /// This value can be used as a persistent value to identify
        /// players in the banlist and campaign saves.
        /// </summary>
        public Option<AccountId> AccountId => AccountInfo.AccountId;

        public LanguageIdentifier Language;

        public UInt16 Ping;

        public Identifier PreferredJob;

        private CharacterTeamType teamID;
        public CharacterTeamType TeamID
        {
            get { return teamID; }
            set
            {
                if (value != teamID)
                {
                    DebugConsole.Log($"Changed client {Name}'s team to {teamID}.");
                    if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
                    {
                        GameMain.NetworkMember.LastClientListUpdateID++;
                    }
                    teamID = value;
                }
            }
        }

        public CharacterTeamType PreferredTeam;

        private Character character;
        public Character Character
        {
            get
            {
                if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && (character?.ID ?? 0) != CharacterID)
                {
                    Character = Entity.FindEntityByID(CharacterID) as Character;
                }
                return character;
            }
            set
            {
                if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
                {
                    GameMain.NetworkMember.LastClientListUpdateID++;
                    if (value != null)
                    {
                        CharacterID = value.ID;
                    }
                }
                character = value;
                if (character != null)
                {
                    HasSpawned = true;
                    UsingFreeCam = false;
#if CLIENT
                    GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
#endif
                }
            }
        }

        /// <summary>
        /// Is the client using the 'freecam' console command?
        /// </summary>
        public bool UsingFreeCam;

        public UInt16 CharacterID;

        private Vector2 spectatePos;
        public Vector2? SpectatePos
        {
            get
            {
                if (character == null || character.IsDead)
                {
                    return spectatePos;
                }
                else
                {
                    return null;
                }
            }

            set
            {
                spectatePos = value.Value;
            }
        }

        public bool Spectating
        {
            get
            {
                return inGame && character == null;
            }
        }

        private bool muted;
        public bool Muted
        {
            get { return muted; }
            set
            {
                if (muted == value) { return; }
                muted = value;
#if CLIENT
                GameMain.NetLobbyScreen.SetPlayerVoiceIconState(this, muted, mutedLocally);
                GameMain.GameSession?.CrewManager?.SetPlayerVoiceIconState(this, muted, mutedLocally);
#endif
                if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
                {
                    GameMain.NetworkMember.LastClientListUpdateID++;
                }
            }
        }

        public bool HasPermissions => Permissions != ClientPermissions.None;

        public VoipQueue VoipQueue
        {
            get;
            private set;
        }

        private bool inGame;
        public bool InGame
        {
            get
            {
                return inGame;
            }
            set
            {
                if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
                {
                    GameMain.NetworkMember.LastClientListUpdateID++;
                }
                inGame = value;
            }
        }
        public bool HasSpawned; //has the client spawned as a character during the current round
        
        public HashSet<Identifier> GivenAchievements = new HashSet<Identifier>();

        public ClientPermissions Permissions = ClientPermissions.None;
        public readonly HashSet<DebugConsole.Command> PermittedConsoleCommands = new HashSet<DebugConsole.Command>();

        private readonly object[] votes;

        partial void InitProjSpecific();
        partial void DisposeProjSpecific();
        public Client(string name, byte sessionId)
        {
            this.Name = name;
            this.SessionId = sessionId;

            votes = new object[Enum.GetNames(typeof(VoteType)).Length];

            InitProjSpecific();
        }

        public T GetVote<T>(VoteType voteType)
        {
            return (votes[(int)voteType] is T t) ? t : default;
        }

        public void SetVote(VoteType voteType, object value)
        {
            votes[(int)voteType] = value;
        }
                       
        public bool SessionOrAccountIdMatches(string userId)
            => (AccountId.IsSome() && Networking.AccountId.Parse(userId) == AccountId)
               || (byte.TryParse(userId, out byte sessionId) && SessionId == sessionId);

        public void WritePermissions(IWriteMessage msg)
        {
            msg.WriteByte(SessionId);
            msg.WriteRangedInteger((int)Permissions, 0, (int)ClientPermissions.All);
            if (HasPermission(ClientPermissions.ConsoleCommands))
            {
                msg.WriteUInt16((UInt16)PermittedConsoleCommands.Count);
                foreach (DebugConsole.Command command in PermittedConsoleCommands)
                {
                    msg.WriteIdentifier(command.Names[0]);
                }
            }
        }
        public static void ReadPermissions(IReadMessage inc, out ClientPermissions permissions, out List<DebugConsole.Command> permittedCommands)
        {
            int permissionsInt = inc.ReadRangedInteger(0, (int)ClientPermissions.All);
            permissions = ClientPermissions.None;
            permittedCommands = new List<DebugConsole.Command>();
            try
            {
                permissions = (ClientPermissions)permissionsInt;
            }
            catch (InvalidCastException)
            {
                return;
            }
            if (permissions.HasFlag(ClientPermissions.ConsoleCommands))
            {
                UInt16 commandCount = inc.ReadUInt16();
                for (int i = 0; i < commandCount; i++)
                {
                    Identifier commandName = inc.ReadIdentifier();
                    var consoleCommand = DebugConsole.Commands.Find(c => c.Names.Contains(commandName));
                    if (consoleCommand != null)
                    {
                        permittedCommands.Add(consoleCommand);
                    }
                }
            }
        }

        public void ReadPermissions(IReadMessage inc)
        {
            ReadPermissions(inc, out ClientPermissions permissions, out List<DebugConsole.Command> permittedCommands);
            SetPermissions(permissions, permittedCommands);
        }

        /// <summary>
        /// Strips out newlines and some common non-renderable symbols (ASCII codes below 32) out of the name, and optionally limits the maximum size.
        /// </summary>
        public static string SanitizeName(string name, int maxLength = MaxNameLength)
        {
            name = name.Trim().Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' ');
            if (name.Length > maxLength)
            {
                name = name.Substring(0, maxLength);
            }
            string rName = "";
            for (int i = 0; i < name.Length; i++)
            {
                rName += name[i] < 32 ? '?' : name[i];
            }
            return rName;
        }

        public void Dispose()
        {
            DisposeProjSpecific();
        }
    }
}
