﻿using JumpAndRun.Actors;
using JumpAndRun.API;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using GameLib;

namespace JumpAndRun.ClientBase.GameComponents
{
    public class World : GameComponent
    {
        public override string Name => "JumpAndRun.World";
        private Player _player;
        private CameraBase _camera;

        public float NpcBackgroundThreadTime { get; private set; }

        private bool _running;

        public List<WorldComponent> WorldComponents { get; private set; }

        public World(Game game, List<GameComponent> gameComponents, Player player) : base(game, gameComponents)
        {
            _player = player;
        }

        public override void Update(JumpAndRunTime gameTime)
        {
            foreach (var component in WorldComponents)
            {
                component.Update(gameTime);
            }
        }

        public override void Init()
        {
            _camera = (CameraBase)GameComponents.First(
                x => x.Name == ContentManager.Instance.GameComponentNames["Camera"]);
            //_player = (PlayerComponent)GameComponents.First(
            //    x => x.Name == ContentManager.Instance.GameComponentNames["Player"]);

            WorldComponents = new List<WorldComponent>();
            foreach (var component in ContentManager.Instance.WorldComponents)
            {
                component.Value.Initialize(Game, _player);
                WorldComponents.Add(component.Value);
            }

            _running = true;
            var thread = new Thread(BackgroundActorUpdate) { Name = "Local ActorUpdate" };
            thread.Start();
            base.Init();
        }

        private void BackgroundActorUpdate()
        {
            var elapsed = new Stopwatch();
            var totalThreadTime = new Stopwatch();
            var watch = new Stopwatch();
            var target = TimeSpan.FromSeconds(1 / (float)60);

            

            long counter = 1;

            watch.Start();
            while (_running)
            {
                var gameTime = new JumpAndRunTime(
                watch.Elapsed.Multiply(TimeManager.Instance.EnvironmentTimeFactor),
                TimeManager.Instance.EnvironmentTotalGameTime,
                watch.Elapsed,
                totalThreadTime.Elapsed,
                watch.Elapsed.Multiply(TimeManager.Instance.PlayerTimeFactor[InformationProvider.Instance.Info.Player.Id]),
                TimeManager.Instance.PlayerTotalGameTime);

                elapsed.Restart();

                var chunks = InformationProvider.Instance.Info.GlobalCache.GetChunks();

                foreach (var chunk in chunks)
                {
                    if (chunk == null)
                        break;

                    var actors = chunk.Chunk.Actors.ToArray();

                    foreach (var actor in actors)
                    {
                        if (actor == null)
                            break;
                        if (actor.Initialized)
                            actor.Update(gameTime);
                        else
                            actor.Init(_player, InformationProvider.Instance.Info.GlobalCache, Game, _camera);
                    }
                }

                if (counter % 120 == 0)
                {
                    counter = 0;
                    //Chunk Sorting
                    var removeList = new List<Actor>();

                    foreach (var chunk in chunks)
                    {
                        if (chunk == null)
                            break;

                        var actors = chunk.Chunk.Actors.ToArray();

                        foreach (var actor in actors)
                        {
                            if (actor == null)
                                break;
                            if (chunk.Chunk.ChunkPosition == actor.ChunkPosition) continue;
                            InformationProvider.Instance.Info.GlobalCache.GetChunk(actor.ChunkPosition).Actors.Add(actor);
                            removeList.Add(actor);
                        }
                        lock (chunk.Chunk.Actors)
                        {
                            foreach (var item in removeList)
                            {
                                chunk.Chunk.Actors.Remove(item);
                            }
                        }
                        removeList.Clear();
                    }
                }

                if (counter % 60 == 0)
                {
                    InformationProvider.Instance.Info.GlobalCache.CollectGarbage();
                }

                counter++;
                elapsed.Stop();

                if (target - elapsed.Elapsed > TimeSpan.Zero)
                {
                    Thread.Sleep(target - elapsed.Elapsed);
                }

                NpcBackgroundThreadTime = (float)(elapsed.Elapsed.TotalMilliseconds / target.TotalMilliseconds);
                totalThreadTime.Stop();
            }
        }

        public override void Dispose()
        {
            _running = false;
            Thread.Sleep(100);

            foreach (var chunk in InformationProvider.Instance.Info.GlobalCache.GetChunks())
            {
                if (chunk == null) continue;
                foreach (var actor in chunk.Chunk.Actors)
                {
                    actor.Dispose();
                }
            }

            InformationProvider.Instance.Dispose();
            _player.Dispose();
        }

        public override void Draw(JumpAndRunTime gameTime)
        {
        }
    }
}
