// File: Startup.cs
// Created: 28.02.2017
// 
// See <summary> tags for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace ArBackend
{
    public class Startup
    {
        private readonly bool[,] map = new bool[30, 21];
        private readonly List<Player> players = new List<Player>();
        private readonly object playersLock = new object();

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();
            app.UseDeveloperExceptionPage();

            // Setup map
            var rand = new Random();
            for (var i = 0; i < this.map.GetLength(0); i++)
            {
                for (var j = 0; j < this.map.GetLength(1); j++)
                {
                    this.map[i, j] = rand.Next(0, 4) == 0;
                }
            }

            this.map[0, 0] = false;

            app.UseWebSockets();
            app.Use(async (http, next) =>
            {
                if (http.WebSockets.IsWebSocketRequest)
                {
                    var webSocket = await http.WebSockets.AcceptWebSocketAsync();
                    if (webSocket.State == WebSocketState.Open)
                    {
                        var player = new Player(Guid.NewGuid().ToString(), 0, 0, webSocket);
                        lock (this.playersLock)
                        {
                            this.players.Add(player);
                        }
                        await this.HandlePlayer(player);
                    }
                }
                else
                {
                    await next();
                }
            });

            app.UseDefaultFiles();
            app.UseStaticFiles();
        }

        private async Task HandlePlayer(Player player)
        {
            while (player.Socket.State == WebSocketState.Open)
            {
                var token = CancellationToken.None;
                var buffer = new ArraySegment<byte>(new byte[8192]);
                var received = await player.Socket.ReceiveAsync(buffer, token);

                if (received.MessageType == WebSocketMessageType.Text)
                {
                    var request = Encoding.UTF8.GetString(buffer.Array,
                        0, received.Count);

                    var split = request.Split(';');
                    var cmd = split[0];

                    switch (cmd)
                    {
                        case "req":
                            await player.Socket.SendAsync(
                                new ArraySegment<byte>(Encoding.UTF8.GetBytes($"uid;{player.UUID}")),
                                WebSocketMessageType.Text, true, token);
                            await player.Socket.SendAsync(
                                new ArraySegment<byte>(Encoding.UTF8.GetBytes(this.SerializeMap())),
                                WebSocketMessageType.Text, true, token);
                            await this.SendPositions(token);
                            break;
                        case "mov":
                            int x, y;
                            if (int.TryParse(split[1], out x) && int.TryParse(split[2], out y))
                            {
                                if (x >= 0 && y >= 0 && x < this.map.GetLength(0) &&
                                    y < this.map.GetLength(1) && !this.map[x, y])
                                {
                                    player.X = x;
                                    player.Y = y;

                                    await this.SendPositions(token);
                                }
                            }

                            break;
                    }
                }
            }

            lock (this.playersLock)
            {
                this.players.Remove(player);
            }

            await this.SendPositions(CancellationToken.None);
        }

        private async Task SendPositions(CancellationToken token)
        {
            List<Player> tmpList;

            lock (this.playersLock)
            {
                tmpList = this.players.ToList();
            }

            var msg = this.SerializePositions();

            foreach (var p in tmpList)
            {
                try
                {
                    await p.Socket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg)), WebSocketMessageType.Text,
                        true, token);
                }
                catch
                {
                }
            }
        }

        private string SerializeMap()
        {
            var retval = new StringBuilder();
            retval.Append("map;");
            retval.Append(this.map.GetLength(0));
            retval.Append(";");
            retval.Append(this.map.GetLength(1));
            retval.Append(";");

            for (var i = 0; i < this.map.GetLength(1); i++)
            {
                for (var j = 0; j < this.map.GetLength(0); j++)
                {
                    retval.Append(this.map[j, i] ? "1" : "0");
                }
            }

            return retval.ToString();
        }

        private string SerializePositions()
        {
            var retval = new StringBuilder();
            retval.Append("pos");

            List<Player> tmpList;

            lock (this.playersLock)
            {
                tmpList = this.players.ToList();
            }

            foreach (var player in tmpList)
            {
                retval.Append($";{player.X},{player.Y},{player.UUID}");
            }

            return retval.ToString();
        }
    }
}

/*

Client:

req; <- Server antwortet mit Karte und UUID
mov;x;y <- Client hat sich bewegt

Server:

map;<karte>
uid;uuid
pos;x,y,uuid;x,y,uuid

Karte:

map;30;21;010010111000101000
map;width;height;0=empty/1=block

 */