using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;

namespace Barotrauma
{
    class MonsterEvent : ScriptedEvent
    {
        private string characterFile;

        private int minAmount, maxAmount;

        private Character[] monsters;

        private bool spawnDeep;

        private Vector2 spawnPos;

        private bool disallowed;
                
        private Level.PositionType spawnPosType;

        private bool spawnPending;

        private string characterFileName;
        
        public override Vector2 DebugDrawPos
        {
            get { return spawnPos; }
        }
        
        public override string ToString()
        {
            if (maxAmount <= 1)
            {
                return "MonsterEvent (" + characterFileName + ")";
            }
            else if (minAmount < maxAmount)
            {
                return "MonsterEvent (" + characterFileName + " x" + minAmount + "-" + maxAmount + ")";
            }
            else
            {
                return "MonsterEvent (" + characterFileName + " x" + maxAmount + ")";
            }
        }

        public MonsterEvent(ScriptedEventPrefab prefab)
            : base (prefab)
        {
            characterFile = prefab.ConfigElement.GetAttributeString("characterfile", "");

            int defaultAmount = prefab.ConfigElement.GetAttributeInt("amount", 1);
            minAmount = prefab.ConfigElement.GetAttributeInt("minamount", defaultAmount);
            maxAmount = Math.Max(prefab.ConfigElement.GetAttributeInt("maxamount", 1), minAmount);

            var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");

            if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
                !Enum.TryParse(spawnPosTypeStr, true, out spawnPosType))
            {
                spawnPosType = Level.PositionType.MainPath;
            }

            spawnDeep = prefab.ConfigElement.GetAttributeBool("spawndeep", false);
            characterFileName = Path.GetFileName(Path.GetDirectoryName(characterFile)).ToLower();

            if (GameMain.NetworkMember != null)
            {
                List<string> monsterNames = GameMain.NetworkMember.ServerSettings.MonsterEnabled.Keys.ToList();
                string tryKey = monsterNames.Find(s => characterFileName == s.ToLower());

                if (!string.IsNullOrWhiteSpace(tryKey))
                {
                    if (!GameMain.NetworkMember.ServerSettings.MonsterEnabled[tryKey]) disallowed = true; //spawn was disallowed by host
                }
            }
        }

        public override bool CanAffectSubImmediately(Level level)
        {
            float maxRange = Items.Components.Sonar.DefaultSonarRange * 0.8f;

            List<Vector2> positions = GetAvailableSpawnPositions();
            foreach (Vector2 position in positions)
            {
                if (Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition) < maxRange * maxRange)
                {
                    return true;
                }
            }

            return false;
        }
        
        public override void Init(bool affectSubImmediately)
        {
            FindSpawnPosition(affectSubImmediately);
            if (GameSettings.VerboseLogging)
            {
                DebugConsole.NewMessage("Initialized MonsterEvent (" + characterFile + ")", Color.White);
            }
        }

        private List<Vector2> GetAvailableSpawnPositions()
        {
            var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => spawnPosType.HasFlag(p.PositionType));

            List<Vector2> positions = new List<Vector2>();
            foreach (var allowedPosition in availablePositions)
            {
                positions.Add(allowedPosition.Position.ToVector2());
            }

            if (spawnDeep)
            {
                for (int i = 0; i < positions.Count; i++)
                {
                    positions[i] = new Vector2(positions[i].X, positions[i].Y - Level.Loaded.Size.Y);
                }
            }

            positions.RemoveAll(pos => pos.Y < Level.Loaded.GetBottomPosition(pos.X).Y);
            
            return positions;
        }

        private void FindSpawnPosition(bool affectSubImmediately)
        {
            if (disallowed) return;

            spawnPos = Vector2.Zero;
            var availablePositions = GetAvailableSpawnPositions();
            if (affectSubImmediately && spawnPosType != Level.PositionType.Ruin)
            {
                if (availablePositions.Count == 0)
                {
                    //no suitable position found, disable the event
                    Finished();
                    return;
                }

                float closestDist = float.PositiveInfinity;
                //find the closest spawnposition that isn't too close to any of the subs
                foreach (Vector2 position in availablePositions)
                {
                    float dist = Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition);
                    foreach (Submarine sub in Submarine.Loaded)
                    {
                        if (sub.IsOutpost) { continue; }
                        float minDistToSub = GetMinDistanceToSub(sub);
                        if (dist > minDistToSub * minDistToSub && dist < closestDist)
                        {
                            closestDist = dist;
                            spawnPos = position;
                        }
                    }
                }

                //only found a spawnpos that's very far from the sub, pick one that's closer
                //and wait for the sub to move further before spawning
                if (closestDist > 15000.0f * 15000.0f)
                {
                    foreach (Vector2 position in availablePositions)
                    {
                        float dist = Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition);
                        if (dist < closestDist)
                        {
                            closestDist = dist;
                            spawnPos = position;
                        }
                    }
                }
            }
            else
            {
                float minDist = spawnPosType == Level.PositionType.Ruin ? 0.0f : 20000.0f;
                availablePositions.RemoveAll(p => Vector2.Distance(Submarine.MainSub.WorldPosition, p) < minDist);
                if (availablePositions.Count == 0)
                {
                    //no suitable position found, disable the event
                    Finished();
                    return;
                }

                spawnPos = availablePositions[Rand.Int(availablePositions.Count, Rand.RandSync.Server)];
            }
            spawnPending = true;
        }

        private float GetMinDistanceToSub(Submarine submarine)
        {
            //9000 units is slightly less than the default range of the sonar
            return Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), 9000.0f);
        }

        public override void Update(float deltaTime)
        {
            if (disallowed)
            {
                Finished();
                return;
            }
            
            if (isFinished) return;

            //isActive = false;

            if (spawnPending)
            {
                //wait until there are no submarines at the spawnpos
                foreach (Submarine submarine in Submarine.Loaded)
                {
                    if (submarine.IsOutpost) { continue; }
                    float minDist = GetMinDistanceToSub(submarine);
                    if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos) < minDist * minDist) return;
                }

                //+1 because Range returns an integer less than the max value
                int amount = Rand.Range(minAmount, maxAmount + 1, Rand.RandSync.Server);
                monsters = new Character[amount];

                for (int i = 0; i < amount; i++)
                {
                    bool isClient = false;
#if CLIENT
                    isClient = GameMain.Client != null;
#endif

                    monsters[i] = Character.Create(
                        characterFile, spawnPos + Rand.Vector(100.0f, Rand.RandSync.Server), 
                        i.ToString(), null, isClient, true, true);
                }

                spawnPending = false;
            }

            Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
#if CLIENT
            if (Character.Controlled != null) targetEntity = (Entity)Character.Controlled;
#endif
            
            bool monstersDead = true;
            foreach (Character monster in monsters)
            {
                if (!monster.IsDead)
                {
                    monstersDead = false;

                    if (targetEntity != null && Vector2.DistanceSquared(monster.WorldPosition, targetEntity.WorldPosition) < 5000.0f * 5000.0f)
                    {
                        break;
                    }
                }
            }

            if (monstersDead) Finished();
        }
    }
}
