@model IEnumerable<long>

<style>
    .progress {
        border-radius: 0 !important;
    }

    .player-stat-icon {
        height: 1.5rem;
        width: 1.5rem;
        background-size: 1.5rem 1.5rem;
    }
</style>
<div class="row p-0 ml-auto mr-auto mb-4">
    <div class="col-12 col-xl-10 p-0 ml-auto mr-auto p-0 pl-lg-3 pr-lg-3 ">
        <ul class="nav nav-tabs border-top border-bottom nav-fill" role="tablist">
            @foreach (SharedLibraryCore.Dtos.ServerInfo server in ViewBag.Servers)
            {
                <li class="nav-item">
                    <a asp-controller="Radar" asp-action="Index" asp-route-serverId="@server.ID" class="nav-link @(server.ID == ViewBag.ActiveServerId ? "active": "")" aria-selected="@(server.ID == ViewBag.ActiveServerId ? "true": "false")"><color-code value="@server.Name" allow="@ViewBag.EnableColorCodes"></color-code></a>
                </li>
            }
        </ul>
    </div>
</div>

<div class="row p-0 ml-auto mr-auto col-12 col-xl-10">
    <div class="p-0 pl-lg-3 pr-lg-3 m-0 col-lg-3 col-12 text-lg-right text-center player-data-left" style="opacity: 0;">
    </div>
    <div class="pl-0 pr-0 pl-lg-3 pr-lg-3 col-lg-6 col-12 pb-4">
        <div id="map_name" class="h4 text-center pb-2 pt-2 mb-0 bg-primary">&mdash;</div>
        <div id="map_list" style="background-size:cover; padding-bottom: 100% !important;">
            <canvas id="map_canvas" style="position:absolute;"></canvas>
        </div>
    </div>

    <div class="p-0 pl-lg-3 pr-lg-3 m-0 col-lg-3 col-12 text-lg-left text-center player-data-right" style="opacity: 0;">
    </div>
</div>

<!-- images used by canvas -->
<img class="hide" id="hud_death" src="~/images/radar/death.png" />


@section scripts {

    <script defer="defer">
        const textOffset = 15;
        let previousRadarData = undefined;
        let newRadarData = undefined;

        /************************
         *          IW4         *
         * **********************/
        const weapons = {};
        weapons["ak47"] = "ak47";
        weapons["ak47classic"] = "icon_ak47_classic";
        weapons["ak74u"] = "akd74u";
        weapons["m16"] = "m16a4";
        weapons["m4"] = "m4carbine";
        weapons["fn2000"] = "fn2000";
        weapons["masada"] = "masada";
        weapons["famas"] = "famas";
        weapons["fal"] = "fnfal";
        weapons["scar"] = "scar_h";
        weapons["tavor"] = "tavor";

        weapons["mp5k"] = "mp5k";
        weapons["uzi"] = "mini_uzi";
        weapons["p90"] = "p90";
        weapons["kriss"] = "kriss";
        weapons["ump45"] = "ump45";

        weapons["rpd"] = "rpd";
        weapons["sa80"] = "sa80_lmg";
        weapons["mg4"] = "mg4";
        weapons["m240"] = "m240";
        weapons["aug"] = "steyr";

        weapons["barrett"] = "barrett50cal";
        weapons["wa2000"] = "wa2000";
        weapons["m21"] = "m14ebr";
        weapons["cheytac"] = "cheytac";
        weapons["dragunov"] = "hud_dragunovsvd";

        weapons["beretta"] = "m9beretta";
        weapons["usp"] = "usp_45";
        weapons["deserteagle"] = "desert_eagle";
        weapons["deserteaglegold"] = "desert_eagle_gold";
        weapons["desert"]
        weapons["coltanaconda"] = "colt_anaconda";

        weapons["tmp"] = "mp9";
        weapons["glock"] = "glock";
        weapons["beretta393"] = "beretta393";
        weapons["pp2000"] = "pp2000";

        weapons["ranger"] = "sawed_off";
        weapons["model1887"] = "model1887";
        weapons["striker"] = "striker";
        weapons["aa12"] = "aa12";
        weapons["m1014"] = "benelli_m4";
        weapons["spas12"] = "spas12";

        weapons["m79"] = "m79";
        weapons["rpg"] = "rpg";
        weapons["at4"] = "at4";
        weapons["stinger"] = "stinger";
        weapons["javelin"] = "javelin";

        weapons["m40a3"] = "m40a3";
        weapons["none"] = "neutral";
        weapons["riotshield"] = "riot_shield";
        weapons["peacekeeper"] = "peacekeeper";

        function drawCircle(context, x, y, color) {
            context.beginPath();
            context.arc(x, y, 6 * stateInfo.imageScaler, 0, 2 * Math.PI, false);
            context.fillStyle = color;
            context.fill();
            context.lineWidth = 0.5;
            context.strokeStyle = 'rgba(255, 255, 255, 0.5)';
            context.closePath();
            context.stroke();
        }

        function drawLine(context, x1, y1, x2, y2, color) {
            context.beginPath();
            context.lineWidth = '3';
            context.moveTo(x1, y1);
            context.lineTo(x2, y2);
            context.closePath();
            context.stroke();
        }

        function drawTriangle(context, v1, v2, v3, color) {
            context.beginPath();
            context.moveTo(v1.x, v1.y);
            context.lineTo(v2.x, v2.y);
            context.lineTo(v3.x, v3.y);
            context.closePath();
            context.fillStyle = color;
            context.fill();
        }

        function drawText(context, x, y, text, size, fillColor, strokeColor, alignment = 'left') {
            context.beginPath();
            context.save();
            context.font = `bold ${Math.max(12, size * stateInfo.imageScaler)}px courier new`;
            context.fillStyle = fillColor;
            context.shadowColor = strokeColor;
            context.shadowBlur = 4;
            context.textAlign = alignment;
            context.fillText(text, x, y);
            context.restore();
            context.closePath();
        }

        function drawImage(context, imgSelector, x, y, alpha = 1) {
            context.save();
            context.globalAlpha = alpha;
            context.drawImage(document.getElementById(imgSelector), x - (15 * stateInfo.imageScaler), y - (15 * stateInfo.imageScaler), 32 * stateInfo.imageScaler, 32 * stateInfo.imageScaler);
            context.globalAlpha = 1;
            context.restore();
        }

        function checkCanvasSize(canvas, context, minimap, map) {

            let width = Math.round(minimap.width());
            if (Math.round(context.canvas.width) != width) {

                canvas.width(width);
                canvas.height(width);

                context.canvas.height = width;
                context.canvas.width = context.canvas.height;
            }

            stateInfo.imageScaler = (stateInfo.canvas.width() / 1024)
            stateInfo.mapScalerX = (((stateInfo.mapInfo.right * stateInfo.imageScaler) - (stateInfo.mapInfo.left * stateInfo.imageScaler)) / stateInfo.mapInfo.width);
            stateInfo.mapScalerY = (((stateInfo.mapInfo.bottom * stateInfo.imageScaler) - (stateInfo.mapInfo.top * stateInfo.imageScaler)) / stateInfo.mapInfo.height);
            stateInfo.mapScaler = (stateInfo.mapScalerX + stateInfo.mapScalerY) / 2

            stateInfo.forwardDistance = 500.0;
            stateInfo.fovWidth = 40;
        }

        function calculateViewPosition(x, y, distance) {
            let nx = Math.cos(x) * Math.cos(y);
            let ny = Math.sin(x) * Math.cos(y);
            let nz = Math.sin(360.0 - y);

            return { x: (nx * distance) * stateInfo.mapScaler, y: (ny * distance) * stateInfo.mapScaler, z: (nz * distance)  * stateInfo.mapScaler };
        }

        function lerp(start, end, complete) {
            return (1 - complete) * start + complete * end;
        }

        function easeLerp(start, end, t) {
            let t2 = (1 - Math.cos(t * Math.PI)) / 2;

            return (start * (1-t2) + end * t2);
        }

        function fixRollAngles(oldAngles, newAngles) {
            let newX = newAngles.x;
            let newY = newAngles.y;

            let angleDifferenceX = (oldAngles.x - newAngles.x);

            if (angleDifferenceX > Math.PI) {
                newX = oldAngles.x + (Math.PI * 2) - angleDifferenceX;
            }

            else if (Math.abs(newAngles.x - oldAngles.x) > Math.PI) {
                newX = newAngles.x - (Math.PI * 2);
            }

            let angleDifferenceY = (oldAngles.y - newAngles.y);

            if (angleDifferenceY > Math.PI) {
                newY = oldAngles.y + (Math.PI * 2) - angleDifferenceY;
            }

            else if (Math.abs(newAngles.y - oldAngles.y) > Math.PI) {
                newY = newAngles.y - (Math.PI * 2);
            }

            return { x: newX, y: newY };
        }

        function toRadians(deg) {
            return deg * Math.PI / 180.0;
        }

        function rotate(cx, cy, x, y, angle) {
            var radians = (Math.PI / 180) * angle,
            cos = Math.cos(radians),
            sin = Math.sin(radians),
            nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
            ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
            return {
                x: nx,
                y: ny
            };
        }

        function weaponImageForWeapon(weapon) {
            let name = weapon.split('_')[0];
            if (weapons[name] == undefined) {
                console.log(name);
                name = "none";
            }

            return `../images/radar/hud_weapons/hud_${weapons[name]}.png`;
        }

        function updatePlayerData() {
            $('.player-data-left').html('');
            $('.player-data-right').html('');

            $.each(newRadarData, function (index, player) {
                if (player == null) {
                    return;
                }

                let column = index % 2 == 0 ? $('.player-data-left') : $('.player-data-right');
                column.append(`<div class="progress" style="height: 1.5rem; background-color: transparent;">
                                 <div style="position: absolute; font-size: 1rem; left: 1.5rem;">${player.name}</div>
                                 <div class="progress-bar bg-success" role="progressbar" style="min-width: 0px; width: ${player.health}%" aria-valuenow="${player.health}" aria-valuemin="0" aria-valuemax="100"></div>
                                <div class="progress-bar bg-danger" role="progressbar" style="min-width: 0px; border-right: 0px; width: ${100 - player.health}%" aria-valuenow="${100 - player.health}" aria-valuemin="0" aria-valuemax="100"></div>
                               </div>
                               <div class="d-flex flex-row flex-wrap p-2 mb-4 bg-dark border-bottom">
                                 <div style="width: 3rem; height: 1.5rem; background-image:url(${weaponImageForWeapon(player.weapon)}); background-size: 3rem 1.5rem;" class="mr-auto text-left">
                               </div>
                                <div class="player-stat-icon" style="background-image:url('/images/radar/kills.png')"></div>
                                <div class="pr-2">${player.kills}</div>
                                <div class="player-stat-icon" style="background-image:url('/images/radar/death.png')"></div>
                                <div class="pr-3">${player.deaths}</div>
                                <span class="align-self-center oi oi-target pr-1"></span>
                                <div class="pr-3 ">${player.deaths == 0 ? player.kills.toFixed(2) : (player.kills / player.deaths).toFixed(2)}</div>
                                <span class="align-self-center oi oi-graph pr-1"></span>
                                <div>${ player.playTime == 0 ? '&mdash;' : Math.round(player.score / (player.playTime / 60))}</div>
                              </div>`);
            });

            $('.player-data-left').delay(1000).animate({opacity: 1}, 500);
            $('.player-data-right').delay(1000).animate({opacity: 1}, 500);
        }

        const stateInfo = {
            canvas: $('#map_canvas'),
            ctx: $('#map_canvas')[0].getContext('2d'),
            updateFrequency: 750,
            updateFrameTimeDeviation: 0,
            forwardDistance: undefined,
            fovWidth: undefined,
            mapInfo: undefined,
            mapScaler: undefined,
            deathIcons: {},
            deathIconTime: 4000
        };

        function updateRadarData() {
            $.getJSON('@Url.Action("Data", "Radar", new { serverId = ViewBag.ActiveServerId })', function (_radarItem) {
                newRadarData = _radarItem;
            });


             $.getJSON('@Url.Action("Map", "Radar", new { serverId = ViewBag.ActiveServerId })', function (_map) {
                 stateInfo.mapInfo = _map
             });

            $.each(newRadarData, function (index, value) {
                if (previousRadarData != undefined && index < previousRadarData.length) {

                    let previous = previousRadarData[index];

                    // this happens when the player has first joined and we haven't gotten two snapshots yet
                    if (value == null) {
                      return;
                    }

                    if (previous == null) {
                        previous = value;
                    }

                    // we don't want to treat a disconnected player snapshot as the previous
                    else if (previous.guid == value.guid) {
                        value.previous = previous;
                    }

                    // we haven't gotten a new item, it's just the old one again
                    if (previous.id === value.id) {
                        value.animationTime = previous.animationTime;
                        value.previous = value;
                    }

                    // they died between this snapshot and last so we wanna setup the death icon
                    if (!value.isAlive && previous.isAlive) {
                        stateInfo.deathIcons[value.guid] = {
                            animationTime: now,
                            location: value.location
                        };
                    }

                    // they respawned between this snapshot and last so we don't want to show wherever the were specating from
                    else if (value.isAlive && !previous.isAlive) {
                        value.previous = value;
                    }
            }});

            // we switch out the items to
            previousRadarData = newRadarData;

            $('#map_name').html(stateInfo.mapInfo.alias);
            $('#map_list').css('background-image', `url(../images/radar/minimaps/compass_map_${stateInfo.mapInfo.name}@('@')2x.jpg)`);
            checkCanvasSize(stateInfo.canvas, stateInfo.ctx, $('#map_list'), stateInfo.mapInfo);
            updatePlayerData();
        }

        function updateMap() {
            let ctx = stateInfo.ctx;

            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
            now = performance.now();

            $.each(previousRadarData, function (index, value) {
                if (value == null) {
                    return;
                }

                if (value.previous == null) {
                    value.previous = value;
                }

                // this indicates we got a new snapshot to work with so we set the time based off the previous
                // frame deviation to have minimal interpolation skipping
                if (value.animationTime === undefined) {
                    value.animationTime = now - stateInfo.updateFrameTimeDeviation;
                }

                if (!value.isAlive) {
                    return;
                }

                const elapsedFrameTime = now - value.animationTime;
                const completionPercent = elapsedFrameTime / stateInfo.updateFrequency;

                // certain maps like estate have an off center axis of origin, so we need to account for that
                let rotatedPreviousLocation = rotate(stateInfo.mapInfo.centerX, stateInfo.mapInfo.centerY, value.previous.location.x, value.previous.location.y, stateInfo.mapInfo.rotation);
                let rotatedCurrentLocation = rotate(stateInfo.mapInfo.centerX, stateInfo.mapInfo.centerY, value.location.x, value.location.y, stateInfo.mapInfo.rotation);

                const startX = ((stateInfo.mapInfo.maxLeft - rotatedPreviousLocation.y) * stateInfo.mapScaler) + (stateInfo.mapInfo.left * stateInfo.imageScaler);
                const startY = ((stateInfo.mapInfo.maxTop - rotatedPreviousLocation.x) * stateInfo.mapScalerY) + (stateInfo.mapInfo.top * stateInfo.imageScaler);

                const endX = ((stateInfo.mapInfo.maxLeft - rotatedCurrentLocation.y) * stateInfo.mapScaler) + (stateInfo.mapInfo.left * stateInfo.imageScaler);
                const endY = ((stateInfo.mapInfo.maxTop - rotatedCurrentLocation.x) * stateInfo.mapScalerY) + (stateInfo.mapInfo.top * stateInfo.imageScaler);

                let teamColor = value.team == 'allies' ? 'rgb(0, 122, 204, 1)' : 'rgb(255, 69, 69)';
                let fovColor = value.team == 'allies' ? 'rgba(0, 122, 204, 0.2)' : 'rgba(255, 69, 69, 0.2)';

                // this takes care of moving past the roll-over point of yaw/pitch (ie 360->0)
                const rollAngleFix = fixRollAngles(value.previous.radianAngles, value.radianAngles);

                const radianLerpX = lerp(value.previous.radianAngles.x, rollAngleFix.x, completionPercent);
                const radianLerpY = lerp(value.previous.radianAngles.y, rollAngleFix.y, completionPercent);

                // this is some jankiness to get the fov to point the right direction
                let firstVertex = calculateViewPosition(toRadians(stateInfo.mapInfo.rotation + stateInfo.mapInfo.viewPositionRotation - 90) - radianLerpX + toRadians(stateInfo.fovWidth), radianLerpY, stateInfo.forwardDistance);
                let secondVertex = calculateViewPosition(toRadians(stateInfo.mapInfo.rotation + stateInfo.mapInfo.viewPositionRotation - 90) - radianLerpX - toRadians(stateInfo.fovWidth), radianLerpY, stateInfo.forwardDistance);

                let currentX = lerp(startX, endX, completionPercent);
                let currentY = lerp(startY, endY, completionPercent);

                // we need to calculate the distance from the center of the map so we can scale if necessary
                let centerX = ((stateInfo.mapInfo.maxLeft - stateInfo.mapInfo.centerY) * stateInfo.mapScaler) + (stateInfo.mapInfo.left * stateInfo.imageScaler);
                let centerY = ((stateInfo.mapInfo.maxTop - stateInfo.mapInfo.centerX) * stateInfo.mapScaler) + (stateInfo.mapInfo.top * stateInfo.imageScaler);

                // reuse lerp to scale the pixel to map ratio
                currentX = lerp(centerX, currentX, stateInfo.mapInfo.scaler);
                currentY = lerp(centerY, currentY, stateInfo.mapInfo.scaler);

                drawCircle(ctx, currentX, currentY, teamColor);
                drawTriangle(ctx,
                    { x: currentX, y: currentY },
                    { x: currentX + firstVertex.x, y: currentY + firstVertex.y },
                    { x: currentX + secondVertex.x, y: currentY + secondVertex.y },
                    fovColor);
                drawText(ctx, currentX, currentY - (textOffset * stateInfo.imageScaler), value.name, 16, 'white', teamColor, 'center')
            });

            const completedIcons = [];

            for (let key in stateInfo.deathIcons) {
                const icon = stateInfo.deathIcons[key];

                const x = ((stateInfo.mapInfo.maxLeft - icon.location.y) * stateInfo.mapScaler) + (stateInfo.mapInfo.left * stateInfo.imageScaler);
                const y = ((stateInfo.mapInfo.maxTop - icon.location.x) * stateInfo.mapScaler) + (stateInfo.mapInfo.top * stateInfo.imageScaler);

                const elapsedFrameTime = now - icon.animationTime;
                const completionPercent = elapsedFrameTime / stateInfo.deathIconTime;
                const opacity = easeLerp(1, 0, completionPercent);

                drawImage(stateInfo.ctx, 'hud_death', x, y, opacity);

                if (completionPercent >= 1) {
                    completedIcons.push(key);
                }
            }

            for (let i = 0; i < completedIcons.length; i++) {
                delete stateInfo.deathIcons[completedIcons[i]];
            }

            window.requestAnimationFrame(updateMap);
        }

        $(document).ready(function () {
             $.getJSON('@Url.Action("Map", "Radar", new { serverId = ViewBag.ActiveServerId })', function (_map) {
                 stateInfo.mapInfo = _map;
                 updateRadarData();
                 setInterval(updateRadarData, stateInfo.updateFrequency);
                 window.requestAnimationFrame(updateMap);
             });
        })

    </script>
}