using System;
using System.Collections.Generic;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.Engine.Curves;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Extensions;
using FModel.Framework;
using SkiaSharp;
using SkiaSharp.HarfBuzz;

namespace FModel.Creator.Bases.FN;

public class BaseIconStats : BaseIcon
{
    private readonly IList<IconStat> _statistics;
    private const int _headerHeight = 128;
    private bool _screenLayer;

    public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style)
    {
        Width = 1024;
        Height = _headerHeight;
        Margin = 0;
        _statistics = new List<IconStat>();
        _screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase);
        DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default");
    }

    public override void ParseForInfo()
    {
        base.ParseForInfo();
        DisplayName = DisplayName.ToUpperInvariant();

        if (Object.TryGetValue(out FName accoladeType, "AccoladeType") &&
            accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase))
        {
            _screenLayer = false;
        }

        if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") &&
            Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) &&
            uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData"))
        {
            foreach (var location in poiLocations)
            {
                var locationName = "Unknown";
                foreach (var poi in challengeMapPoiData)
                {
                    if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") ||
                        tagName != location.TagName || !poi.TryGetValue(out FText text, "Text")) continue;

                    locationName = text.Text;
                    break;
                }

                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper()));
            }
        }

        if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize"))
        {
            if (maxStackSize.TryGetValue(out float v, "Value") && v > 0)
            {
                _statistics.Add(new IconStat("Max Stack", v, 15));
            }
            else if (TryGetCurveTableStat(maxStackSize, out var s))
            {
                _statistics.Add(new IconStat("Max Stack", s, 15));
            }
        }

        if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x))
        {
            _statistics.Add(new IconStat("XP Amount", x));
        }

        if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") &&
            weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") &&
            weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") &&
            dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue))
        {
            weaponRowValue.TryGetValue(out float dmgPb, "DmgPB"); //Damage at point blank
            weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge"); //Max damage a weapon can do in a single hit, usually used for shotguns
            weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"); //Headshot multiplier
            weaponRowValue.TryGetValue(out float envDmgPb, "EnvDmgPB"); //Structure damage at point blank
            weaponRowValue.TryGetValue(out int clipSize, "ClipSize"); //Item magazine size
            weaponRowValue.TryGetValue(out float firingRate, "FiringRate"); //Item firing rate, value is shots per second
            weaponRowValue.TryGetValue(out float swingTime, "SwingTime"); //Item swing rate, value is swing per second
            weaponRowValue.TryGetValue(out float armTime, "ArmTime"); //Time it takes for traps to be able to be set off
            weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime"); //Time it takes for a weapon to reload
            weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge"); //Amount of pellets shot by a weapon at once, usually for shotguns
            weaponRowValue.TryGetValue(out float heatMax, "OverheatingMaxValue"); //Maximum heat overheating weapons can hold before they need to cool off
            weaponRowValue.TryGetValue(out float heatPerShot, "OverheatHeatingValue"); //Heat generated per shot on overheat weapons
            weaponRowValue.TryGetValue(out float overheatCooldown, "OverheatedCooldownDelay"); //Cooldown after a weapon reaches its maximum heat capacity
            weaponRowValue.TryGetValue(out int cartridgePerFire, "CartridgePerFire"); //Amount of bullets shot after pressing the fire button once
            weaponRowValue.TryGetValue(out float burstFiringRate, "BurstFiringRate"); //Item firing rate during a burst, value is shots per second
            {
                var multiplier = bpc != 0f ? bpc : 1;
                if (dmgPb != 0f)
                {
                    _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 160));
                }

                if (mdpc > 0f && dmgPb * dmgCritical * multiplier > mdpc)
                {
                    _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), mdpc, 160));
                }

                else if (dmgCritical != 0f && dmgCritical != 1f && dmgPb != 0f)
                {
                    _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 160));
                }
            }
            {
                var envdmgmultiplier = bpc != 0f ? bpc : 1;
                if (envDmgPb != 0f)

                {
                    _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "11AF67134E0F4E27E5E588806AB475BE", "Structure Damage"), envDmgPb * envdmgmultiplier, 160));
                }
            }

            if (clipSize > 999f || clipSize == 0f)
            {
                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), Utils.GetLocalizedResource("", "0FAE8E5445029F2AA209ADB0FE49B23C", "Infinite"), -1));
            }

            else if (clipSize != 0f)
            {
                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 40));
            }

            var burstEquation = cartridgePerFire != 0f && burstFiringRate != 0f ? (cartridgePerFire / (((cartridgePerFire - 1f) / burstFiringRate) + (1f / firingRate))) : 0f;
            if (burstEquation != 0f)
            {
                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), burstEquation, 11));
            }
            else if (firingRate != 0f)
            {
                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 11));
            }
            else if (swingTime != 0f)
            {
                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), swingTime, 11));
            }

            if (armTime != 0f)
            {
                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 5));
            }

            if (reloadTime != 0f && clipSize < 999f && clipSize != 0f)
            {
                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 10));
            }

            if (overheatCooldown != 0f && clipSize > 999f || overheatCooldown != 0f && clipSize == 0f)
            {
                _statistics.Add(new IconStat("Overheat Cooldown", overheatCooldown, 5));
            }

            if (heatMax != 0f && heatPerShot != 0f && clipSize > 999f || heatMax != 0f && heatPerShot != 0f && clipSize == 0f)
            {
                _statistics.Add(new IconStat("Shots to Overheat", Math.Ceiling(heatMax / heatPerShot), 80));
            }
            if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) ||
                 Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) &&
                weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") &&
                weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") &&
                durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) &&
                durability.TryGetValue(out int duraByRarity, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription()))
            {
                _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20));
            }
        }

        if (!string.IsNullOrEmpty(Description))
            Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count;
        Height += 50 * _statistics.Count;
    }

    public override SKBitmap[] Draw()
    {
        var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque);
        using var c = new SKCanvas(ret);

        DrawHeader(c);
        DrawDisplayName(c);
        DrawStatistics(c);

        return new[] { ret };
    }

    private bool TryGetCurveTableStat(FStructFallback property, out float statValue)
    {
        if (property.TryGetValue(out FStructFallback curve, "Curve") &&
            curve.TryGetValue(out FName rowName, "RowName") &&
            curve.TryGetValue(out UCurveTable curveTable, "CurveTable") &&
            curveTable.TryFindCurve(rowName, out var rowValue) &&
            rowValue is FSimpleCurve s && s.Keys.Length > 0)
        {
            statValue = s.Keys[0].Value;
            return true;
        }

        statValue = 0F;
        return false;
    }

    private readonly SKPaint _informationPaint = new()
    {
        IsAntialias = true, FilterQuality = SKFilterQuality.High,
        Color = SKColor.Parse("#262630"), TextSize = 16,
        Typeface = Utils.Typefaces.Description
    };

    private void DrawHeader(SKCanvas c)
    {
        c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint);

        _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
            new[] { Background[0].WithAlpha(180), Background[1].WithAlpha(220) }, SKShaderTileMode.Clamp);
        c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint);

        _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4,
            new[] { SKColor.Parse("#262630"), SKColor.Parse("#1f1f26") }, SKShaderTileMode.Clamp);
        c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint);

        _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75),
            new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
        c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint);

        _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp);
        using var rect = new SKPath { FillType = SKPathFillType.EvenOdd };
        rect.MoveTo(0, 0);
        rect.LineTo(_headerHeight + _headerHeight / 3, 0);
        rect.LineTo(_headerHeight, _headerHeight);
        rect.LineTo(0, _headerHeight);
        rect.Close();
        c.DrawPath(rect, _informationPaint);

        _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2),
            new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp);
        c.DrawPath(rect, _informationPaint);

        _informationPaint.Shader = null;

        ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver;
        c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint);
    }

    private new void DrawDisplayName(SKCanvas c)
    {
        if (string.IsNullOrEmpty(DisplayName)) return;

        _informationPaint.TextSize = 50;
        _informationPaint.Color = SKColors.White;
        _informationPaint.Typeface = Utils.Typefaces.Bundle;
        _informationPaint.FakeBoldText = true;
        while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2)
        {
            _informationPaint.TextSize -= 1;
        }

        var shaper = new CustomSKShaper(_informationPaint.Typeface);
        c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2f + _informationPaint.TextSize / 3, _informationPaint);
    }

    private void DrawStatistics(SKCanvas c)
    {
        var outY = _headerHeight + 25f;
        if (!string.IsNullOrEmpty(Description))
        {
            _informationPaint.TextSize = 16;
            _informationPaint.Color = SKColors.White.WithAlpha(175);
            _informationPaint.Typeface = Utils.Typefaces.Description;
            Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center,
                new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY);
            outY += 25;
        }

        foreach (var stat in _statistics)
        {
            stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY);
            outY += 50;
        }
    }
}

public class IconStat
{
    private readonly string _statName;
    private readonly object _value;
    private readonly float _maxValue;

    public IconStat(string statName, object value, float maxValue = 0)
    {
        _statName = statName.ToUpperInvariant();
        _value = value;
        _maxValue = maxValue;
    }

    private readonly SKPaint _statPaint = new()
    {
        IsAntialias = true, FilterQuality = SKFilterQuality.High,
        TextSize = 25, Typeface = Utils.Typefaces.DisplayName,
        Color = SKColors.White
    };

    public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y)
    {
        while (_statPaint.MeasureText(_statName) > height * 2 - 40)
        {
            _statPaint.TextSize -= 1;
        }

        var shaper = new CustomSKShaper(_statPaint.Typeface);
        c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint);

        _statPaint.TextAlign = SKTextAlign.Right;
        _statPaint.Typeface = Utils.Typefaces.BundleNumber;
        _statPaint.Color = sliderColor;
        var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString());
        c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);

        _statPaint.Color = SKColors.White;
        c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint);

        if (_maxValue == -1) //fill bar if max value is set to -1, for things that don't return a number here but should still be represented as the maximum value
        {
            c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint);
        }
        if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return;
        if (floatValue < 0)
            floatValue = 0;
        var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue);
        c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint);
    }
}
