const std = @import("std");
const protocol = @import("protocol");
const Session = @import("../Session.zig");
const Packet = @import("../Packet.zig");
const Data = @import("../data.zig");
const GachaData = @import("../commands/value.zig");

const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CmdID = protocol.CmdID;

// function to check the list if true
fn isInList(id: u32, list: []const u32) bool {
    for (list) |item| {
        if (item == id) {
            return true;
        }
    }
    return false;
}

pub fn onGetGachaInfo(session: *Session, _: *const Packet, allocator: Allocator) !void {
    var info = ArrayList(protocol.GachaCeilingAvatar).init(allocator);
    for (GachaData.StandardBanner) |id| {
        try info.appendSlice(&[_]protocol.GachaCeilingAvatar{
            .{ .RepeatedCnt = 300, .AvatarId = id },
        });
    }
    var gacha_info = protocol.GachaInfo.init(allocator);
    gacha_info.begin_time = 0;
    gacha_info.end_time = 2524608000;
    gacha_info.gacha_ceiling = .{
        .avatar_list = info,
        .is_claimed = false,
        .ceiling_num = 200,
    };
    gacha_info.KMNJNMJFGBG = 1;
    gacha_info.GDIFAAHIFBH = 3;
    gacha_info.gacha_id = 1001; // standard banner

    var rsp = protocol.GetGachaInfoScRsp.init(allocator);

    rsp.retcode = 0;
    rsp.DJNDMNPEBKA = 20;
    rsp.NOPBEBKHIKA = 20;
    rsp.NBELNOIPOEK = 900;
    rsp.gacha_random = 0;
    try rsp.gacha_info_list.append(gacha_info);

    try session.send(CmdID.CmdGetGachaInfoScRsp, rsp);
}
pub fn onBuyGoods(session: *Session, packet: *const Packet, allocator: Allocator) !void {
    const req = try packet.getProto(protocol.BuyGoodsCsReq, allocator);

    var rsp = protocol.BuyGoodsScRsp.init(allocator);
    var item = ArrayList(protocol.Item).init(allocator);

    try item.appendSlice(&[_]protocol.Item{.{
        .ItemId = 101,
        .Num = 100,
    }});

    rsp.retcode = 0;
    rsp.GoodsId = req.goods_id;
    rsp.GoodsBuyTimes = req.goods_num;
    rsp.ShopId = 0;
    rsp.ReturnItemList = .{ .ItemList_ = item };

    try session.send(CmdID.CmdBuyGoodsScRsp, rsp);
}
pub fn onGetShopList(session: *Session, _: *const Packet, allocator: Allocator) !void {
    var rsp = protocol.GetShopListScRsp.init(allocator);
    var shop = ArrayList(protocol.Shop).init(allocator);
    var goods = ArrayList(protocol.Goods).init(allocator);

    try shop.appendSlice(&[_]protocol.Shop{.{
        .ShopId = 1000,
        .GoodsList = goods,
    }});
    try goods.appendSlice(&[_]protocol.Goods{.{
        .GoodsId = 101001,
        .ItemId = 101,
        .BuyTimes = 0,
    }});

    rsp.retcode = 0;
    rsp.ShopType = 101;
    rsp.ShopList = shop;

    try session.send(CmdID.CmdGetShopListScRsp, rsp);
}
pub fn onExchangeHcoin(session: *Session, packet: *const Packet, allocator: Allocator) !void {
    const req = try packet.getProto(protocol.ExchangeHcoinCsReq, allocator);

    var rsp = protocol.ExchangeHcoinScRsp.init(allocator);
    rsp.Num = req.Num;
    rsp.retcode = 0;

    try session.send(CmdID.CmdExchangeHcoinScRsp, rsp);
}

var five_star_pity: u32 = 0;
var four_star_pity: u32 = 0;
var guaranteed_five_star_rate_up: bool = false;
var guaranteed_four_star_rate_up: bool = false;
var avatar_list_cached: ?std.ArrayList(u32) = null;
var lightcone_list_3_cached: ?std.ArrayList(u32) = null;
var lightcone_list_4_cached: ?std.ArrayList(u32) = null;
fn pow(base: f64, exp: f64) f64 {
    return @exp(exp * @log(base));
}
fn getFiveStarRate(gacha_count: u32) f64 {
    if (gacha_count < 21) {
        return 0.02;
    }
    if (gacha_count < 72) {
        return 0.008;
    }
    const excess_pulls = @as(f64, @floatFromInt(gacha_count - 71));
    return 0.008 + (1.0 - 0.008) * pow(excess_pulls / 18.0, 2.8);
}
fn getFourStarRate(gacha_count: u32) f64 {
    if (gacha_count < 6) {
        return 0.055;
    }
    const excess_pulls = @as(f64, @floatFromInt(gacha_count - 5));
    return 0.055 + (1.0 - 0.055) * pow(excess_pulls / 3.5, 2.2);
}
fn pickRandomId(rnd: *std.rand.Random, banner: []const u32) u32 {
    return banner[rnd.int(usize) % banner.len];
}

pub fn onDoGacha(session: *Session, packet: *const Packet, allocator: std.mem.Allocator) !void {
    const req = try packet.getProto(protocol.DoGachaCsReq, allocator);
    var rsp = protocol.DoGachaScRsp.init(allocator);
    var seed: u64 = undefined;
    try std.posix.getrandom(std.mem.asBytes(&seed));
    var prng = std.rand.DefaultPrng.init(seed);
    var rnd = prng.random();
    var selected_ids = std.ArrayList(u32).init(allocator);
    defer selected_ids.deinit();
    var got_four_star = false;
    for (0..req.gacha_num) |_| {
        const five_star_rate = getFiveStarRate(five_star_pity);
        const four_star_rate = getFourStarRate(four_star_pity);
        const random_value = rnd.float(f64);
        var selected_banner: []const u32 = &Data.LightconeList_3;
        var is_five_star = false;
        var is_four_star = false;
        if (random_value < five_star_rate or five_star_pity == 89) {
            is_five_star = true;
            five_star_pity = 0;
            if (guaranteed_five_star_rate_up) {
                selected_banner = &GachaData.RateUp;
                guaranteed_five_star_rate_up = false;
            } else {
                if (rnd.boolean()) {
                    selected_banner = &GachaData.RateUp;
                } else {
                    selected_banner = &GachaData.StandardBanner;
                    guaranteed_five_star_rate_up = true;
                }
            }
        } else if (four_star_pity == 9 or random_value < (five_star_rate + four_star_rate)) {
            is_four_star = true;
            four_star_pity = 0;
            got_four_star = true;

            if (guaranteed_four_star_rate_up or rnd.float(f64) < 0.70) {
                selected_banner = &GachaData.RateUpFourStars;
                guaranteed_four_star_rate_up = false;
            } else {
                if (rnd.boolean()) {
                    selected_banner = &Data.AvatarList;
                } else {
                    selected_banner = &Data.LightconeList_4;
                }
                guaranteed_four_star_rate_up = true;
            }
        } else {
            four_star_pity += 1;
        }
        five_star_pity += 1;
        try selected_ids.append(pickRandomId(&rnd, selected_banner));
    }
    if (req.gacha_num > 1 and !got_four_star) {
        selected_ids.items[rnd.int(usize) % selected_ids.items.len] = pickRandomId(&rnd, &GachaData.RateUpFourStars);
    }
    for (selected_ids.items) |id| {
        var gacha_item = protocol.GachaItem.init(allocator);
        gacha_item.gacha_item = .{ .ItemId = id };
        gacha_item.is_new = false;
        var back_item = std.ArrayList(protocol.Item).init(allocator);
        var transfer_item = std.ArrayList(protocol.Item).init(allocator);
        if (id < 10000) {
            if (isInList(id, &GachaData.RateUp) or isInList(id, &GachaData.StandardBanner)) {
                try transfer_item.appendSlice(&[_]protocol.Item{
                    .{ .ItemId = id + 10000, .Num = 1 },
                    .{ .ItemId = 252, .Num = 20 },
                });
            } else {
                try transfer_item.append(.{ .ItemId = 252, .Num = 20 });
            }
        }
        try back_item.append(.{ .ItemId = 252, .Num = 20 });
        gacha_item.transfer_item_list = .{ .ItemList_ = transfer_item };
        gacha_item.token_item = .{ .ItemList_ = back_item };
        try rsp.gacha_item_list.append(gacha_item);
    }
    rsp.gacha_num = req.gacha_num;
    rsp.gacha_id = req.gacha_id;
    rsp.ceiling_num = 200;
    rsp.KMNJNMJFGBG = 1;
    rsp.NOPBEBKHIKA = 20;
    rsp.GDIFAAHIFBH = 3;
    rsp.retcode = 0;

    std.debug.print("FIVE STAR PITY: {}, (RATE: {d:.4}%)\n", .{ five_star_pity, getFiveStarRate(five_star_pity) * 100.0 });
    std.debug.print("FOUR STAR PITY: {}, (RATE: {d:.4}%)\n", .{ four_star_pity, getFourStarRate(four_star_pity) * 100.0 });

    try session.send(protocol.CmdID.CmdDoGachaScRsp, rsp);
}
