import random
import json
import copy
import threading
import matplotlib.pyplot as plt
import time
from multiprocessing import Process, Manager


def load_banner_data(banner_name):
    try:
        file = open(f'data/banner_data/{banner_name}.txt', 'r', encoding='utf-8')
        content = json.loads(file.read())
        file.close()
        return content
    except FileNotFoundError:
        return None


def single_pull(banner_data, no_six_pull_count=0):
    # Process Pity Pull
    six_star_threshold = (no_six_pull_count - 50) * 2 if no_six_pull_count > 50 else 0
    factor = (100 - (2 + six_star_threshold)) / 98
    prob_list = [2 + six_star_threshold, 8 * factor, 50 * factor, 40 * factor]
    sum_prob_list = [prob_list[0], prob_list[0] + prob_list[1], prob_list[0] + prob_list[1] + prob_list[2],
                     prob_list[0] + prob_list[1] + prob_list[2] + prob_list[3]]

    # get star info
    star_random_int = random.randint(1, 100)
    if 0 < star_random_int <= sum_prob_list[0]:
        result = 6
        target = get_operator_consider_upper(banner_data['Upper_6'], banner_data['operator_6'],
                                             banner_data['Upper_history_6'])
    elif sum_prob_list[0] < star_random_int <= sum_prob_list[1]:
        result = 5
        target = get_operator_consider_upper(banner_data['Upper_5'], banner_data['operator_5'])
    elif sum_prob_list[1] < star_random_int <= sum_prob_list[2]:
        result = 4
        target = random.choice(banner_data['operator_4'])
    else:
        result = 3
        target = random.choice(banner_data['operator_3'])
    if result == 6:
        no_six_pull_count = 0
    else:
        no_six_pull_count += 1
    return (result, target), no_six_pull_count


def get_operator_consider_upper(upper_data, operator_list, multiplex_data=None):
    random_int = random.randint(1, 100)
    upper_operator_list = list(upper_data.keys())
    upper_operator_probability_list = [0]
    new_list = copy.deepcopy(operator_list)
    flag = 0
    for operator in upper_operator_list:
        flag += upper_data[operator] * 100
        upper_operator_probability_list.append(flag)

    for i in range(len(upper_operator_list)):
        if upper_operator_probability_list[i] < random_int <= upper_operator_probability_list[i+1]:
            return upper_operator_list[i]

    if multiplex_data:
        for key, value in multiplex_data.items():
            for i in range(value-1):
                new_list.append(key)
    target = random.choice(new_list)
    return target


def ten_pull(banner_data, no_six_pull_count=0):
    result_list = []
    is_african = True
    for i in range(10):
        res, no_six_pull_count = single_pull(banner_data, no_six_pull_count)
        result_list.append(res)
        if res[0] > 4:
            is_african = False
    return result_list, no_six_pull_count, is_african


def continues_pull(banner_data, total_pull):
    ten_pull_count = int(total_pull / 10)
    single_pull_count = ten_pull_count - ten_pull_count
    no_six_pull_count = 0
    african_count = 0
    full_res_list = []
    for i in range(ten_pull_count):
        ten_pull_res, no_six_pull_count, is_african = ten_pull(banner_data, no_six_pull_count)
        full_res_list += copy.deepcopy(ten_pull_res)
        if is_african:
            african_count += 1

    for i in range(single_pull_count):
        single_res, no_six_pull_count = single_pull(banner_data, no_six_pull_count)
    six_star_list = [i for i in full_res_list if i[0] == 6]

    # pull_result = {'Result': full_res_list, 'african': african_count, 'six star count': len(six_star_list)}
    pull_result = {'african': african_count, '6 star count': len(six_star_list), '6 star list': six_star_list,
                   'full res': full_res_list}
    return pull_result


def banner_upper_pull(banner_data):
    target_dict = copy.deepcopy(banner_data['Upper_6'])
    total_pull = 0
    full_res_list = []
    while target_dict:
        res, total_pull = single_pull(banner_data, total_pull)
        full_res_list.append(res)
        if res[0] == 6 and res[1] in target_dict:
            del target_dict[res[1]]
    six_star_list = [i for i in full_res_list if i[0] == 6]
    pull_result = {'total pull': len(full_res_list), '6 star count': len(six_star_list), '6 star list': six_star_list,
                   'full res': full_res_list}
    return pull_result


def banner_upper_pull_batch_sim(banner_data, sim_count, index, res):
    local_sim_result, local_statics = {}, {'6 star count': 0, 'total pull': 0}
    for i in range(sim_count):
        pull_data = banner_upper_pull(banner_data)
        total_pull = pull_data['total pull']
        if total_pull not in local_sim_result:
            local_sim_result[total_pull] = 0
        local_sim_result[total_pull] += 1
        local_statics['6 star count'] += pull_data['6 star count']
        local_statics['total pull'] += total_pull
    res[index] = [local_sim_result, local_statics]
    return


def given_six_star_num_pull(banner_data):
    six_star_num = 6
    total_pull = 0
    full_res_list = []
    while six_star_num > 0:
        res, total_pull = single_pull(banner_data, total_pull)
        full_res_list.append(res)
        if res[0] == 6:
            six_star_num -= 1
    six_star_list = [i for i in full_res_list if i[0] == 6]
    pull_result = {'total pull': len(full_res_list), '6 star count': len(six_star_list), '6 star list': six_star_list,
                   'full res': full_res_list}
    return pull_result


def given_six_star_num_batch_sim(banner_data, sim_count, index, res):
    local_sim_result, local_statics = {}, {'6 star count': 0, 'total pull': 0}
    for i in range(sim_count):
        pull_data = given_six_star_num_pull(banner_data)
        total_pull = pull_data['total pull']
        if total_pull not in local_sim_result:
            local_sim_result[total_pull] = 0
        local_sim_result[total_pull] += 1
        local_statics['6 star count'] += pull_data['6 star count']
        local_statics['total pull'] += total_pull
    res[index] = [local_sim_result, local_statics]
    return


def banner_upper_pull_batch_sim_vis(res, total_tries):
    sim_res = {}
    sim_statics = {'6 star count': 0, 'total pull': 0}
    for item in res:
        for key, values in item[0].items():
            if key not in sim_res:
                sim_res[key] = values
            else:
                sim_res[key] += values
        for key, values in item[1].items():
            sim_statics[key] += values

    # 重排序
    sim_res = {item[0]: item[1] for item in sorted(sim_res.items(), key=lambda x: x[0])}
    # 分离键和值作为横纵坐标
    x = list(sim_res.keys())
    y = [pull * 10000 / total_tries for pull in sim_res.values()]
    y_sum = []
    sum_y = 0
    for i in y:
        sum_y += i / 10000
        y_sum.append(sum_y)

    print('Total Pull:', sim_statics["total pull"])
    print('Total Six Star Count', sim_statics["6 star count"])
    print('Six Star Percentage', sim_statics["6 star count"] / sim_statics["total pull"] * 100)

    # 创建散点图
    plt.figure(figsize=(8, 4))
    # plt.scatter(x, y)
    # plt.bar(x, y)
    plt.subplot(2, 1, 1)
    plt.plot(x, y)
    plt.title("Pull count for getting both of the upper 6")
    plt.ylabel("Parts per 10000")

    plt.subplot(2, 1, 2)
    plt.plot(x, y_sum)
    plt.ylabel("Percentage")
    plt.xlabel("Total Pull")
    plt.grid(True)
    plt.show()


if __name__ == '__main__':
    banner = load_banner_data('宿愿')

    start = time.time()
    process_count = 4
    process_sim = 50000
    with Manager() as manager:
        results = manager.list([None for i in range(process_count)])  # 创建一个长度为5的列表，用于存储结果
        # 创建一个进程列表
        processes = []
        # 创建和启动进程
        for i in range(process_count):
            # process = Process(target=banner_upper_pull_batch_sim, args=(banner, process_sim, i, results))
            process = Process(target=given_six_star_num_batch_sim, args=(banner, process_sim, i, results))
            process.start()
            processes.append(process)

        # 等待所有进程完成
        for process in processes:
            process.join()

        # 所有线程执行完成后打印执行时间
        print("Done, Time:", time.time() - start)

        banner_upper_pull_batch_sim_vis(results, process_count * process_sim)
    # banner_upper_pull_batch_sim(banner, 1000)
    pass

