using System;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using HISP.Player;
using HISP.Game;
using HISP.Game.Horse;
using HISP.Game.Events;
using HISP.Game.Items;
using HISP.Util;

namespace HISP.Server
{
    public class GameClient
    {
        private static ThreadSafeList<GameClient> connectedClients = new ThreadSafeList<GameClient>();
        public static GameClient[] ConnectedClients // Done to prevent Enumerator Changed errors.
        {
            get
            {
                return connectedClients.ToArray();
            }
        }

        public Socket ClientSocket;
        public string RemoteIp;
        private bool loggedIn = false;
        public bool LoggedIn 
        {
            get
            {
                bool login = loggedIn;
                if (LoggedinUser == null)
                    return false;
                if (LoggedinUser.LoggedinClient == null)
                    return false;
                return login;
            }
            set
            {
                loggedIn = value;
            }
        }
        public User LoggedinUser;

        private Timer keepAliveTimer;
        private Timer timeoutTimer;

        private Timer warnTimer;
        private Timer kickTimer;
        private Timer minuteTimer;

        private bool isDisconnecting = false;
        private int timeoutInterval = 95 * 1000;

        private int totalMinutesElapsed = 0;
        private int oneMinute = 60 * 1000;
        private int warnInterval = GameServer.IdleWarning * 60 * 1000; // Time before showing a idle warning
        private int kickInterval = GameServer.IdleTimeout * 60 * 1000; // Time before kicking for inactivity
        
        private List<byte> currentPacket = new List<byte>();
        private byte[] workBuffer = new byte[0x8000];

        public GameClient(Socket clientSocket)
        {
            clientSocket.SendTimeout = 10 * 1000; // 10sec
            clientSocket.ReceiveTimeout = 10 * 1000; // 10sec

            ClientSocket = clientSocket;
            if(clientSocket.RemoteEndPoint != null)
            {

                RemoteIp = clientSocket.RemoteEndPoint.ToString();

                if (RemoteIp.Contains(":"))
                    RemoteIp = RemoteIp.Substring(0, RemoteIp.IndexOf(":"));

                Logger.DebugPrint("Client connected @ " + RemoteIp);
            }
            else
            {
                Logger.DebugPrint("Client connected @ (IP UNKNOWN) // How is this possible?");
            }
            kickTimer = new Timer(new TimerCallback(kickTimerTick), null, kickInterval, kickInterval);
            warnTimer = new Timer(new TimerCallback(warnTimerTick), null, warnInterval, warnInterval);
            minuteTimer = new Timer(new TimerCallback(minuteTimerTick), null, oneMinute, oneMinute);

            connectedClients.Add(this);

            SocketAsyncEventArgs evt = new SocketAsyncEventArgs();
            evt.Completed += receivePackets;
            evt.SetBuffer(workBuffer, 0, workBuffer.Length);
            if (!clientSocket.ReceiveAsync(evt))
                receivePackets(null, evt);
        }

        public static void OnShutdown()
        {
            try
            {
                foreach (GameClient client in ConnectedClients)
                {

                    if (client.LoggedIn)
                    {
                        for (int i = 0; i < 2; i++)
                        {
                            ItemInstance rubyItem = new ItemInstance(Item.Ruby);
                            client.LoggedinUser.Inventory.AddIgnoringFull(rubyItem);
                        }

                        client.LoggedinUser.TrackedItems.GetTrackedItem(Tracking.TrackableItem.GameUpdates).Count++;
                        Logger.DebugPrint("Kicking: " + client.LoggedinUser.Username);
                    }
                    client.Kick("Server shutdown.");

                }
            }
            catch (Exception) { };
        }
        public static void CreateClient(object sender, SocketAsyncEventArgs e)
        {
            try
            {
                while (!GameServer.ServerSocket.AcceptAsync(e))
                {
                    Socket eSocket = e.AcceptSocket;

                    if (GameServer.ServerSocket == null)
                        return;
                    if (eSocket == null)
                        continue;
                    if (eSocket.RemoteEndPoint == null)
                        continue;
                    new GameClient(eSocket);
                    e.AcceptSocket = null;

                    if (GameServer.ServerSocket == null)
                        return;
                }
            }
            catch (ObjectDisposedException ex) { Logger.ErrorPrint("Server shutdown due to " + ex.Message); } // server shutdown
        }
        private void timeoutTimerTick(object state)
        {
            if (this.LoggedIn)
            {
                Disconnect();
            }
        }

        public void Disconnect()
        {
            if (this.isDisconnecting)
                return;
            this.isDisconnecting = true;
            
            // Close Socket
            if (ClientSocket != null)
            {
                ClientSocket.Disconnect(false);
                ClientSocket.Dispose();
                ClientSocket = null;
            }


            // Stop Timers
            if (timeoutTimer != null)
            {
                timeoutTimer.Dispose();
                timeoutTimer = null;
            }
            if (keepAliveTimer != null)
            {
                keepAliveTimer.Dispose();
                keepAliveTimer = null;
            }
            if (warnTimer != null)
            {
                warnTimer.Dispose();
                warnTimer = null;
            }
            if (kickTimer != null)
            {
                kickTimer.Dispose();
                kickTimer = null;
            }

            // Call OnDisconnect
            
            connectedClients.Remove(this);
            GameServer.OnDisconnect(this);
            LoggedIn = false;

        }


        private void receivePackets(object sender, SocketAsyncEventArgs e)
        {
            do
            {
                // HI1 Packets are terminates by 0x00 so we have to read until we receive that terminator

                if (isDisconnecting ||
                    ClientSocket == null ||
                    e.BytesTransferred <= 0 ||
                    !ClientSocket.Connected || 
                    e.SocketError != SocketError.Success)
                {
                    Disconnect();
                    return;
                }

                int availble = e.BytesTransferred;
                if (availble >= 1) // More than 1 byte transfered..
                {

                    for (int i = 0; i < availble; i++)
                    {
                        currentPacket.Add(e.Buffer[i]);
                        if (e.Buffer[i] == PacketBuilder.PACKET_TERMINATOR) // Read until \0...
                        {
                            parsePackets(currentPacket.ToArray());
                            currentPacket.Clear();
                        }
                    }
                }

                if (availble == 0)
                    Disconnect();

                if (isDisconnecting || ClientSocket == null)
                    return;


            } while (!ClientSocket.ReceiveAsync(e));

        }

        private void keepAliveTick(object state)
        {
            Logger.DebugPrint("Sending keep-alive packet to " + LoggedinUser.Username);
            byte[] updatePacket = PacketBuilder.CreateKeepAlive();
            SendPacket(updatePacket);
            if(!isDisconnecting && keepAliveTimer != null) // wtf how is this still a problem?
               keepAliveTimer.Change(oneMinute, oneMinute);
        }
        private void minuteTimerTick(object state)
        {
            totalMinutesElapsed++;
            if (LoggedIn)
            {

                GameServer.UpdatePlayer(this);

                LoggedinUser.CanUseAdsChat = true;
                LoggedinUser.FreeMinutes -= 1;

                GameServer.DoItemPurchases(this);

                if (totalMinutesElapsed % 2 == 0)
                {
                    LoggedinUser.TotalGlobalChatMessages++;
                }

                if (LoggedinUser.FreeMinutes <= 0)
                {
                    LoggedinUser.FreeMinutes = 0;
                    if (!LoggedinUser.Subscribed && !LoggedinUser.Moderator && !LoggedinUser.Administrator)
                    {
                        Kick(Messages.KickReasonNoTime);
                        return;
                    }

                }

                // Those fun messages when u have been playing for awhile.
                if (totalMinutesElapsed % (2 * 60) == 0)
                {
                    string ptMessage = Messages.RngMessages[GameServer.RandomNumberGenerator.Next(0, Messages.RngMessages.Length)];
                    byte[] playTimeMessage = PacketBuilder.CreateChat(Messages.FormatPlaytimeMessage(totalMinutesElapsed / 60) + ptMessage, PacketBuilder.CHAT_BOTTOM_RIGHT);
                    SendPacket(playTimeMessage);
                }


                if (GameServer.RandomNumberGenerator.Next(0, 100) == 59) // RANDOM EVENT HAS OCCURED!
                {
                    RandomEvent.ExecuteRandomEvent(LoggedinUser);
                }

                bool gotoPrision = false;
                foreach(HorseInstance horse in LoggedinUser.HorseInventory.HorseList)
                {
                    if (totalMinutesElapsed % 2 == 0)
                    {
                        horse.BasicStats.Thirst--;
                        horse.BasicStats.Hunger--;

                        if (horse.BasicStats.Thirst <= 0 && horse.BasicStats.Hunger <= 0)
                        {
                            horse.BasicStats.Health -= 5;
                            if(horse.BasicStats.Hunger <= 0)
                            {
                                gotoPrision = true; // Goto jail, go directly to jail, do not pass go, do not collect 200$

                                horse.BasicStats.Health = 10;
                                horse.BasicStats.Hunger = 500;
                                horse.BasicStats.Thirst = 500;
                            }
                        }
                    }

                  
                    if(horse.Leaser > 0)
                    {
                        horse.LeaseTime--;

                        if (horse.LeaseTime <= 0)
                        {
                            int tpX = 0;
                            int tpY = 0;
                            if(horse.Breed.Type == "unicorn" || horse.Breed.Type == "pegasus")
                            {
                                foreach (World.SpecialTile tile in World.SpecialTiles)
                                {
                                    if (tile.Code == null)
                                        continue;

                                    if (tile.Code.StartsWith("HORSELEASER-"))
                                    {
                                        int id = int.Parse(tile.Code.Split("-")[1]);
                                        if (horse.Leaser == id)
                                        {
                                            string msg = Messages.FormatHorseReturnedToUniter(horse.Breed.Name);
                                            if (horse.Breed.Type == "pegasus")
                                                msg = Messages.HorseLeaserReturnedToUniterPegasus;

                                            byte[] youWereTeleportedToUniter = PacketBuilder.CreateChat(msg, PacketBuilder.CHAT_BOTTOM_RIGHT);
                                            SendPacket(youWereTeleportedToUniter);

                                            tpX = tile.X;
                                            tpY = tile.Y;

                                            if(tile.ExitX != 0 && tile.ExitY != 0)
                                            {
                                                tpX = tile.ExitX;
                                                tpY = tile.ExitY;
                                            }
                                            else
                                            {
                                                tpY++;
                                            }

                                        }
                                    }
                                }
                                
                            }

                            byte[] horseReturned = PacketBuilder.CreateChat(Messages.FormatHorseReturnedToOwner(horse.Name), PacketBuilder.CHAT_BOTTOM_RIGHT);
                            SendPacket(horseReturned);

                            if(tpX != 0 && tpY != 0)
                                LoggedinUser.Teleport(tpX, tpY);


                            if (LoggedinUser.CurrentlyRidingHorse != null)
                            {
                                if(LoggedinUser.CurrentlyRidingHorse.RandomId == horse.RandomId)
                                {
                                    GameServer.StopRidingHorse(this);
                                }
                                
                             }

                            if(LoggedinUser.LastViewedHorse != null)
                            {
                                if(LoggedinUser.LastViewedHorse.RandomId == horse.RandomId)
                                {
                                    LoggedinUser.LastViewedHorse = null;
                                }
                            }    


                             LoggedinUser.HorseInventory.DeleteHorse(horse);
                        }

                        
                    }

                }
                if(gotoPrision)
                {
                    byte[] sendToPrision = PacketBuilder.CreateChat(Messages.YouWereSentToPrisionIsle, PacketBuilder.CHAT_BOTTOM_RIGHT);
                    SendPacket(sendToPrision);
                    LoggedinUser.Teleport(45, 35);
                }


                if (totalMinutesElapsed % 5 == 0)
                    LoggedinUser.Thirst--;
                
                if (totalMinutesElapsed % 15 == 0)
                    LoggedinUser.Hunger--;

                if (totalMinutesElapsed % 15 == 0)
                    LoggedinUser.Tiredness--;
            }
            if (!isDisconnecting)
               minuteTimer.Change(oneMinute, oneMinute);

        }

        private void warnTimerTick(object state)
        {
            Logger.DebugPrint("Sending inactivity warning to: " + RemoteIp);
            byte[] chatPacket = PacketBuilder.CreateChat(Messages.FormatIdleWarningMessage(), PacketBuilder.CHAT_BOTTOM_RIGHT);
            SendPacket(chatPacket);
            if (LoggedIn)
                LoggedinUser.Idle = true;
        }

        private void kickTimerTick(object state)
        {
            Kick(Messages.FormatIdleKickMessage());
        }

        public void Login(int id)
        {
            /*
             *  Check for duplicate user
             *  and disconnect them.
             */
            foreach (GameClient Client in GameClient.ConnectedClients)
            {
                if (Client.LoggedIn)
                {
                    if (Client.LoggedinUser.Id == id)
                        Client.Kick(Messages.KickReasonDuplicateLogin);
                }
            }

            LoggedinUser = new User(this, id);
            LoggedIn = true;

            Database.SetIpAddress(id, RemoteIp);
            Database.SetLoginCount(id, Database.GetLoginCount(id) + 1);

            keepAliveTimer = new Timer(new TimerCallback(keepAliveTick), null, oneMinute, oneMinute);
            timeoutTimer = new Timer(new TimerCallback(timeoutTimerTick), null, timeoutInterval, timeoutInterval);
        }

        private void parsePackets(byte[] Packet)
        {
            if (Packet.Length < 1)
            {
                Logger.ErrorPrint("Received an invalid packet (size: "+Packet.Length+")");
            }
            byte identifier = Packet[0];

            /*
             *  Every time ive tried to fix this properly by just checking if its null or something
             *  it keeps happening, so now im just going to catch the exception
             *  and hope it works.
             */
            try
            {
                if (LoggedIn)
                {
                    if (timeoutTimer != null)
                        timeoutTimer.Change(timeoutInterval, timeoutInterval); // Reset time before timing out
                    else
                        return;

                    if (keepAliveTimer != null && identifier != PacketBuilder.PACKET_KEEP_ALIVE)
                    {
                        if (LoggedIn)
                            LoggedinUser.Idle = false;
                        keepAliveTimer.Change(oneMinute, oneMinute);
                    }
                    else
                    {
                        return;
                    }
                }

                if (kickTimer != null && identifier != PacketBuilder.PACKET_KEEP_ALIVE)
                    kickTimer.Change(kickInterval, kickInterval);
                else
                    return;

                if (warnTimer != null && identifier != PacketBuilder.PACKET_KEEP_ALIVE)
                    warnTimer.Change(warnInterval, warnInterval);
                else
                    return;

            }
            catch (ObjectDisposedException) 
            {
                return;
            }


            /*
             *  Put packet handling in a try/catch
             *  this prevents the entire server from crashing
             *  if theres an error in handling a particular packet.
             */
#if (!DEBUG)
            try
            {
#endif
                if (!LoggedIn) // Must be either login or policy-file-request
                {
                    switch (identifier)
                    {
                        case PacketBuilder.PACKET_FLASH_XML_CROSSDOMAIN:
                            GameServer.OnCrossdomainPolicyRequest(this, Packet);
                            break;
                        case PacketBuilder.PACKET_LOGIN:
                            GameServer.OnLoginRequest(this, Packet);
                            break;
                    }
                }
                else
                {
                    switch (identifier)
                    {
                        case PacketBuilder.PACKET_LOGIN:
                            GameServer.OnUserInfoRequest(this, Packet);
                            break;
                        case PacketBuilder.PACKET_MOVE:
                            GameServer.OnMovementPacket(this, Packet);
                            break;
                        case PacketBuilder.PACKET_PLAYERINFO:
                            GameServer.OnPlayerInfoPacket(this, Packet);
                            break;
                        case PacketBuilder.PACKET_PLAYER:
                            GameServer.OnProfilePacket(this, Packet);
                            break;
                        case PacketBuilder.PACKET_CHAT:
                            GameServer.OnChatPacket(this, Packet);
                            break;
                        case PacketBuilder.PACKET_CLICK:
                            GameServer.OnClickPacket(this, Packet);
                            break;
                        case PacketBuilder.PACKET_KEEP_ALIVE:
                            GameServer.OnKeepAlive(this, Packet);
                            break;
                        case PacketBuilder.PACKET_TRANSPORT:
                            GameServer.OnTransportUsed(this, Packet);
                            break;
                        case PacketBuilder.PACKET_INVENTORY:
                            GameServer.OnInventoryRequested(this, Packet);
                            break;
                        case PacketBuilder.PACKET_DYNAMIC_BUTTON:
                            GameServer.OnDynamicButtonPressed(this, Packet);
                            break;
                        case PacketBuilder.PACKET_DYNAMIC_INPUT:
                            GameServer.OnDynamicInputReceived(this, Packet);
                            break;
                        case PacketBuilder.PACKET_ITEM_INTERACTION:
                            GameServer.OnItemInteraction(this, Packet);
                            break;
                        case PacketBuilder.PACKET_ARENA_SCORE:
                            GameServer.OnArenaScored(this, Packet);
                            break;
                        case PacketBuilder.PACKET_QUIT:
                            GameServer.OnQuitPacket(this, Packet);
                            break;
                        case PacketBuilder.PACKET_NPC:
                            GameServer.OnNpcInteraction(this, Packet);
                            break;
                        case PacketBuilder.PACKET_BIRDMAP:
                            GameServer.OnBirdMapRequested(this, Packet);
                            break;
                        case PacketBuilder.PACKET_SWFMODULE:
                            GameServer.OnSwfModuleCommunication(this, Packet);
                            break;
                        case PacketBuilder.PACKET_HORSE:
                            GameServer.OnHorseInteraction(this, Packet);
                            break;
                        case PacketBuilder.PACKET_WISH:
                            GameServer.OnWish(this, Packet);
                            break;
                        case PacketBuilder.PACKET_RANCH:
                            GameServer.OnRanchPacket(this, Packet);
                            break;
                        case PacketBuilder.PACKET_AUCTION:
                            GameServer.OnAuctionPacket(this, Packet);
                            break;
                        case PacketBuilder.PACKET_PLAYER_INTERACTION:
                            GameServer.OnPlayerInteration(this, Packet);
                            break;
                        case PacketBuilder.PACKET_SOCIALS:
                            GameServer.OnSocialPacket(this, Packet);
                            break;
                        default:
                            Logger.ErrorPrint("Unimplemented Packet: " + BitConverter.ToString(Packet).Replace('-', ' '));
                            break;
                    }
                }
#if (!DEBUG)
            }
            catch(Exception e)
            {
                Logger.ErrorPrint("Unhandled Exception: " + e.ToString() + "\n" + e.Message + "\n" + e.StackTrace);
                Kick("Unhandled Exception: " + e.ToString());
            }
#endif
        }

        public void Kick(string Reason)
        {
            byte[] kickPacket = PacketBuilder.CreateKickMessage(Reason);
            SendPacket(kickPacket);
            Disconnect();

            Logger.InfoPrint("CLIENT: "+RemoteIp+" KICKED for: "+Reason);
        }

       public void SendPacket(byte[] PacketData)
        {
            try
            {
                ClientSocket.Send(PacketData);
            }
            catch (Exception)
            {
                Disconnect();
            }
        }

    }


}