import pandas as pd

# accuracy
# kast
# adr
# kill stats
# flash stats
# econ stats
def player_stats(game_rounds, return_type="json"):
    player_statistics = {}
    for r in game_rounds:
        # Add players
        ct_side = r["ctSide"]
        t_side = r["tSide"]
        for p in ct_side["players"]:
            if p["steamID"] not in player_statistics:
                player_statistics[p["steamID"]] = {
                    "steamID": p["steamID"],
                    "playerName": p["playerName"],
                    "teamName": ct_side["teamName"],
                    "totalRounds": 0,
                    "kills": 0,
                    "deaths": 0,
                    "kdr": 0,
                    "assists": 0,
                    "tradeKills": 0,
                    "teamKills": 0,
                    "suicides": 0,
                    "flashAssists": 0,
                    "totalDamageGiven": 0,
                    "totalDamageTaken": 0,
                    "totalTeamDamageGiven": 0,
                    "adr": 0,
                    "totalShots": 0,
                    "shotsHit": 0,
                    "accuracy": 0,
                    "rating": 0,
                    "kast": 0,
                    "hs": 0,
                    "hsPercent": 0,
                    "firstKills": 0,
                    "firstDeaths": 0,
                    "utilityDamage": 0,
                    "smokesThrown": 0,
                    "flashesThrown": 0,
                    "heThrown": 0,
                    "fireThrown": 0,
                    "enemiesFlashed": 0,
                    "teammatesFlashed": 0,
                    "blindTime": 0,
                    "plants": 0,
                    "defuses": 0,
                }
                player_statistics[p["steamID"]]["totalRounds"] += 1
            else:
                player_statistics[p["steamID"]]["totalRounds"] += 1
        for p in t_side["players"]:
            if p["steamID"] not in player_statistics:
                player_statistics[p["steamID"]] = {
                    "steamID": p["steamID"],
                    "playerName": p["playerName"],
                    "teamName": t_side["teamName"],
                    "totalRounds": 0,
                    "kills": 0,
                    "deaths": 0,
                    "kdr": 0,
                    "assists": 0,
                    "tradeKills": 0,
                    "teamKills": 0,
                    "suicides": 0,
                    "flashAssists": 0,
                    "totalDamageGiven": 0,
                    "totalDamageTaken": 0,
                    "totalTeamDamageGiven": 0,
                    "adr": 0,
                    "totalShots": 0,
                    "shotsHit": 0,
                    "accuracy": 0,
                    "rating": 0,
                    "kast": 0,
                    "hs": 0,
                    "hsPercent": 0,
                    "firstKills": 0,
                    "firstDeaths": 0,
                    "utilityDamage": 0,
                    "smokesThrown": 0,
                    "flashesThrown": 0,
                    "heThrown": 0,
                    "fireThrown": 0,
                    "enemiesFlashed": 0,
                    "teammatesFlashed": 0,
                    "blindTime": 0,
                    "plants": 0,
                    "defuses": 0,
                }
                player_statistics[p["steamID"]]["totalRounds"] += 1
            else:
                player_statistics[p["steamID"]]["totalRounds"] += 1
        kast = {}
        for p in t_side["players"]:
            kast[p["steamID"]] = {}
            kast[p["steamID"]]["k"] = False
            kast[p["steamID"]]["a"] = False
            kast[p["steamID"]]["s"] = True
            kast[p["steamID"]]["t"] = False
        for p in ct_side["players"]:
            kast[p["steamID"]] = {}
            kast[p["steamID"]]["k"] = False
            kast[p["steamID"]]["a"] = False
            kast[p["steamID"]]["s"] = True
            kast[p["steamID"]]["t"] = False
        # Calculate kills
        for i, k in enumerate(r["kills"]):
            if (
                k["attackerSteamID"]
                and k["attackerSteamID"] in player_statistics.keys()
            ):
                player_statistics[k["attackerSteamID"]]["kills"] += 1
                kast[k["attackerSteamID"]]["k"] = True
            if k["victimSteamID"] and k["victimSteamID"] in player_statistics.keys():
                player_statistics[k["victimSteamID"]]["deaths"] += 1
                kast[k["victimSteamID"]]["s"] = False
            if (
                k["assisterSteamID"]
                and k["assisterSteamID"] in player_statistics.keys()
            ):
                player_statistics[k["assisterSteamID"]]["assists"] += 1
                kast[k["assisterSteamID"]]["a"] = True
            if (
                k["flashThrowerSteamID"]
                and k["flashThrowerSteamID"] in player_statistics.keys()
            ):
                player_statistics[k["flashThrowerSteamID"]]["flashAssists"] += 1
                kast[k["flashThrowerSteamID"]]["a"] = True
            if (
                k["isTrade"]
                and k["attackerSteamID"]
                and k["attackerSteamID"] in player_statistics.keys()
            ):
                player_statistics[k["attackerSteamID"]]["tradeKills"] += 1
                kast[r["kills"][i - 1]["victimSteamID"]]["t"] = True
            if (
                k["isFirstKill"]
                and k["attackerSteamID"]
                and k["attackerSteamID"] in player_statistics.keys()
            ):
                player_statistics[k["attackerSteamID"]]["firstKills"] += 1
                player_statistics[k["victimSteamID"]]["firstDeaths"] += 1
            if (
                k["isTeamkill"]
                and k["attackerSteamID"]
                and k["attackerSteamID"] in player_statistics.keys()
            ):
                player_statistics[k["attackerSteamID"]]["teamKills"] += 1
            if (
                k["isHeadshot"]
                and k["attackerSteamID"]
                and k["attackerSteamID"] in player_statistics.keys()
            ):
                player_statistics[k["attackerSteamID"]]["hs"] += 1
            if k["isSuicide"] and k["victimSteamID"] in player_statistics.keys():
                player_statistics[k["victimSteamID"]]["suicides"] += 1
        for d in r["damages"]:
            if (
                d["attackerSteamID"]
                and not d["isFriendlyFire"]
                and d["attackerSteamID"] in player_statistics.keys()
            ):
                player_statistics[d["attackerSteamID"]]["totalDamageGiven"] += d[
                    "hpDamageTaken"
                ]
            if d["victimSteamID"] and d["victimSteamID"] in player_statistics.keys():
                player_statistics[d["victimSteamID"]]["totalDamageTaken"] += d[
                    "hpDamageTaken"
                ]
            if d["isFriendlyFire"] and d["attackerSteamID"] in player_statistics.keys():
                player_statistics[d["attackerSteamID"]]["totalTeamDamageGiven"] += d[
                    "hpDamageTaken"
                ]
            if (
                d["weaponClass"] not in ["Unknown", "Grenade", "Equipment"]
                and d["attackerSteamID"] in player_statistics.keys()
            ):
                player_statistics[d["attackerSteamID"]]["shotsHit"] += 1
            if (
                d["weaponClass"] == "Grenade"
                and d["attackerSteamID"] in player_statistics.keys()
            ):
                player_statistics[d["attackerSteamID"]]["utilityDamage"] += d[
                    "hpDamageTaken"
                ]
        for w in r["weaponFires"]:
            if w["playerSteamID"] in player_statistics.keys():
                player_statistics[w["playerSteamID"]]["totalShots"] += 1
        for f in r["flashes"]:
            if (
                f["attackerSteamID"]
                and f["attackerSteamID"] in player_statistics.keys()
            ):
                if f["attackerSide"] == f["playerSide"]:
                    player_statistics[f["attackerSteamID"]]["teammatesFlashed"] += 1
                else:
                    player_statistics[f["attackerSteamID"]]["enemiesFlashed"] += 1
                    player_statistics[f["attackerSteamID"]]["blindTime"] += f[
                        "flashDuration"
                    ]
        for g in r["grenades"]:
            if g["throwerSteamID"] and g["throwerSteamID"] in player_statistics.keys():
                if g["grenadeType"] == "Smoke Grenade":
                    player_statistics[g["throwerSteamID"]]["smokesThrown"] += 1
                if g["grenadeType"] == "Flashbang":
                    player_statistics[g["throwerSteamID"]]["flashesThrown"] += 1
                if g["grenadeType"] == "HE Grenade":
                    player_statistics[g["throwerSteamID"]]["heThrown"] += 1
                if g["grenadeType"] in ["Incendiary Grenade", "Molotov"]:
                    player_statistics[g["throwerSteamID"]]["fireThrown"] += 1
        for b in r["bombEvents"]:
            if b["playerSteamID"] and b["playerSteamID"] in player_statistics.keys():
                if b["bombAction"] == "plant":
                    player_statistics[b["playerSteamID"]]["plants"] += 1
                if b["bombAction"] == "defuse":
                    player_statistics[b["playerSteamID"]]["defuses"] += 1
        for player in kast:
            all_true = False
            for component in kast[player]:
                if kast[player][component]:
                    all_true = True
            if all_true:
                player_statistics[player]["kast"] += 1
    for player in kast:
        player_statistics[player]["kast"] = round(
            100
            * player_statistics[player]["kast"]
            / player_statistics[player]["totalRounds"],
            1,
        )
    for player in player_statistics:
        player_statistics[player]["blindTime"] = round(
            player_statistics[player]["blindTime"], 2
        )
    for player in player_statistics:
        player_statistics[player]["kdr"] = round(
            player_statistics[player]["kills"] / player_statistics[player]["deaths"]
            if player_statistics[player]["deaths"] != 0
            else player_statistics[player]["kills"],
            2,
        )
    for player in player_statistics:
        player_statistics[player]["adr"] = round(
            player_statistics[player]["totalDamageGiven"]
            / player_statistics[player]["totalRounds"],
            1,
        )
    for player in player_statistics:
        player_statistics[player]["accuracy"] = round(
            player_statistics[player]["shotsHit"]
            / player_statistics[player]["totalShots"],
            2,
        )
    for player in player_statistics:
        player_statistics[player]["hsPercent"] = round(
            player_statistics[player]["hs"] / player_statistics[player]["kills"]
            if player_statistics[player]["kills"] != 0
            else 0,
            2,
        )
    for player in player_statistics:
        impact = (
            2.13
            * (
                player_statistics[player]["kills"]
                / player_statistics[player]["totalRounds"]
            )
            + 0.42
            * (
                player_statistics[player]["assists"]
                / player_statistics[player]["totalRounds"]
            )
            - 0.41
        )
        player_statistics[player]["rating"] = (
            0.0073 * player_statistics[player]["kast"]
            + 0.3591
            * (
                player_statistics[player]["kills"]
                / player_statistics[player]["totalRounds"]
            )
            - 0.5329
            * (
                player_statistics[player]["deaths"]
                / player_statistics[player]["totalRounds"]
            )
            + 0.2372 * (impact)
            + 0.0032 * (player_statistics[player]["adr"])
            + 0.1587
        )
        player_statistics[player]["rating"] = round(
            player_statistics[player]["rating"], 2
        )
    if return_type == "df":
        return (
            pd.DataFrame()
            .from_dict(player_statistics, orient="index")
            .reset_index(drop=True)
        )
    else:
        return player_statistics


# def accuracy(data):
#     """_summary_

#     Args:
#         data (_type_): _description_
#     """
#     accuracy_data = {}
#     for r in data:
#         if r["weaponFires"]:
#             for w in r["weaponFires"]:
#                 if w["playerSteamID"] not in accuracy_data:
#                     accuracy_data[w["playerSteamID"]] = {}
#                     accuracy_data[w["playerSteamID"]]["playerName"] = w["playerName"]
#                     accuracy_data[w["playerSteamID"]]["shotsFired"] = 1
#                     accuracy_data[w["playerSteamID"]]["shotsHit"] = 0
#                 else:
#                     accuracy_data[w["playerSteamID"]]["shotsFired"] += 1
#             for d in r["damages"]:
#                 if (
#                     (d["weaponClass"] != "Grenades")
#                     and (d["weaponClass"] != "Equipment")
#                     and (d["weaponClass"] != "Unknown")
#                 ):
#                     accuracy_data[w["playerSteamID"]]["shotsHit"] += 1
#     return accuracy_data


# def kast(
#     data,
#     flash_assists=True,
#     return_type="json",
# ):
#     """Creates KAST% for a given round/kill dataframe combination.

#     Args:
#         data (dict): Default
#         flash_assists (bool, optional): _description_. Defaults to True.
#         return_type (str, optional): _description_. Defaults to "json".
#     """
#     kast_data = {}
#     for r in data["gameRounds"]:
#         if r["kills"]:
#             for k in r["kills"]:
#                 if k["attackerName"] is not None:
#                     # Count kills
#                     if k["attackerName"] in kast_data:
#                         kast_data[k["attackerName"]]["kills"] += 1
#                     else:
#                         kast_data[k["attackerName"]]["kills"] = 1
#                     # Count assists
#     pass


# def extract_num_filters(
#     filters: Dict[str, Union[List[bool], List[str]]], key: str
# ) -> Tuple[List[str], List[float]]:
#     """Extracts the numeric column filters from a key-value pair in a dictionary.

#     Extracts logical operators and numeric values from the column filters in the
#     the value of a specified key in the dictionary represented by filters.

#     Args:
#         filters: A dictionary where the keys are the columns of the dataframe
#             represented by df to filter the data by and the values are lists
#             that contain the column filters.
#         key: The key in the dictionary represented by filters for which the
#             numeric column filters should be extracted.

#     Returns:
#         A tuple of two lists where the first list contains all of the logical
#         operators in the column filters in the value of a specified key and the
#         second list contains all of the numeric values in the column filters in
#         the value of a specified key. For example:
#         ([">", "<"], [15.0, 25.0])

#     Raises:
#         ValueError: The value of the specified key contains a column filter of
#             type boolean.
#         Exception: The column filters in the value of the specified key contain
#             an invalid logical operator.
#         Exception: The column filters in the value of the specified key contain
#             an invalid numeric value.
#     """
#     sign_list = []
#     val_list = []
#     for index in filters[key]:
#         if not isinstance(index, str):
#             raise ValueError(
#                 f'Filter(s) for column "{key}" must be of type ' f"string."
#             )
#         i = 0
#         sign = ""
#         while i < len(index) and not index[i].isdecimal():
#             sign += index[i]
#             end_index = i
#             i += 1
#         if sign not in ("==", "!=", "<=", ">=", "<", ">"):
#             raise Exception(
#                 f'Invalid logical operator in filters for "{key}"' f" column."
#             )
#         sign_list.append(sign)
#         try:
#             val_list.append(float(index[end_index + 1 :]))
#         except ValueError as ve:
#             raise Exception(
#                 f'Invalid numerical value in filters for "{key}" ' f"column."
#             ) from ve
#     return sign_list, val_list


# def check_filters(df: pd.DataFrame, filters: Dict[str, Union[List[bool], List[str]]]):
#     """Checks if the column filters are valid.

#     Iterates through each key-value pair in the dictionary represented by
#     filters and checks if the column filters for columns of type boolean are
#     of type boolean and the column filters for columns of type string are of
#     type string. Calls the function extract_numeric_filters to check if the
#     column filters for columns of type float and integer are valid.

#     Args:
#         df: A dataframe to be filtered.
#         filters: A dictionary where the keys are the columns of the dataframe
#             represented by df to filter the data by and the values are lists
#             that contain the column filters.

#     Raises:
#         ValueError: The value of a key corresponding to a column of type boolean
#             contains a column filter of a different type.
#         ValueError: The value of a key corresponding to a column of type string
#             contains a column filter of a different type.
#     """
#     for key in filters:
#         if df.dtypes[key] == "bool":
#             for index in filters[key]:
#                 if not isinstance(index, bool):
#                     raise ValueError(
#                         f'Filter(s) for column "{key}" must be ' f"of type boolean"
#                     )
#         elif df.dtypes[key] == "O":
#             for index in filters[key]:
#                 if not isinstance(index, str):
#                     raise ValueError(
#                         f'Filter(s) for column "{key}" must be ' f"of type string"
#                     )
#         else:
#             extract_num_filters(filters, key)


# def num_filter_df(df: pd.DataFrame, col: str, sign: str, val: float) -> pd.DataFrame:
#     """Filters numeric data in a dataframe.

#     Args:
#         df: A dataframe to be filtered.
#         col: A column of the dataframe represented by df to filter by.
#         sign: A logical operator representing the operation to filter the
#             dataframe represented by df by.
#         val: A numeric value to filter the dataframe represented by df by.

#     Returns:
#         A filtered dataframe.
#     """
#     ops = {
#         "==": operator.eq(df[col], val),
#         "!=": operator.ne(df[col], val),
#         "<=": operator.le(df[col], val),
#         ">=": operator.ge(df[col], val),
#         "<": operator.lt(df[col], val),
#         ">": operator.gt(df[col], val),
#     }
#     filtered_df = df.loc[ops[sign]]
#     return filtered_df


# def filter_df(
#     df: pd.DataFrame, filters: Dict[str, Union[List[bool], List[str]]]
# ) -> pd.DataFrame:
#     """Filters data in a dataframe.

#     Filters a dataframe for columns of type boolean and string and calls the
#     function num_filter_df to filter a dataframe for columns of type float
#     and integer.

#     Args:
#         df: A dataframe to be filtered.
#         filters: A dictionary where the keys are the columns of the dataframe
#             represented by df to filter the data by and the values are lists
#             that contain the column filters.

#     Returns:
#         A filtered dataframe.
#     """
#     df_copy = df.copy()
#     check_filters(df_copy, filters)
#     for key in filters:
#         if df_copy.dtypes[key] == "bool" or df_copy.dtypes[key] == "O":
#             df_copy = df_copy.loc[df_copy[key].isin(filters[key])]
#         else:
#             i = 0
#             for _ in extract_num_filters(filters, key)[0]:
#                 val = extract_num_filters(filters, key)[1][i]
#                 df_copy = num_filter_df(
#                     df_copy, key, extract_num_filters(filters, key)[0][i], val
#                 )
#                 i += 1
#     return df_copy


# def calc_stats(
#     df: pd.DataFrame,
#     filters: Dict[str, Union[List[bool], List[str]]],
#     col_to_groupby: List[str],
#     col_to_agg: List[str],
#     agg: List[List[str]],
#     col_names: List[str],
# ) -> pd.DataFrame:
#     """Calculates statistics for a dataframe.

#     Calls the function filter_df to filter a data in dataframe and then performs
#     groupby and aggregation operations on the dataframe and renames the columns.

#     Args:
#         df: A dataframe from which statistics should be calculated.
#         filters: A dictionary where the keys are the columns of the dataframe
#             represented by df to filter the data by and the values are lists
#             that contain the column filters.
#         col_to_groupby: Columns of the dataframe represented by df to perform
#             the groupby operation on.
#         col_to_agg: Columns of the dataframe represented by df to perform
#             aggregation operations on.
#         agg: The aggregation operations to be applied to the columns in the list
#             represented by col_to_agg.
#         col_names: Column names for the returned dataframe.

#     Returns:
#         A dataframe with the specified statistics.
#     """
#     df_copy = filter_df(df, filters)
#     agg_dict = dict(zip(col_to_agg, agg))
#     if col_to_agg:
#         df_copy = df_copy.groupby(col_to_groupby).agg(agg_dict).reset_index()
#     df_copy.columns = col_names
#     return df_copy


# def accuracy(
#     damage_data: pd.DataFrame,
#     weapon_fire_data: pd.DataFrame,
#     team: bool = False,
#     damage_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     weapon_fire_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with accuracy statistics.

#     Args:
#         damage_data: A dataframe with damage data.
#         weapon_fire_data: A dataframe with weapon fire data.
#         team: A boolean specifying wheter to calculate statistics for each team
#             or for each player. The default is to calculate statistics for
#             each player.
#         damage_filters: A dictionary where the keys are the columns of the
#             dataframe represented by damage_data to filter the damage data by
#             and the values are lists that contain the column filters.
#         weapon_fire_filters: A dictionary where the keys are the columns of the
#             dataframe represented by weapon_fires to filter the weapon fire data
#             by and the values are lists that contain the column filters.

#     Returns:
#         A dataframe with the total shots, strafe shots, hits, accuracy (hits/total number of shots), headshot accuracy (head hits/total number of shots)
#     """
#     stats = ["playerName", "attackerName", "Player"]
#     if team:
#         stats = ["playerTeam", "attackerTeam", "Team"]
#     weapon_fires = calc_stats(
#         weapon_fire_data,
#         weapon_fire_filters,
#         [stats[0]],
#         [stats[0]],
#         [["size"]],
#         [stats[2], "Weapon Fires"],
#     )
#     strafe_fires = calc_stats(
#         weapon_fire_data.loc[weapon_fire_data["playerStrafe"] == True],
#         weapon_fire_filters,
#         [stats[0]],
#         [stats[0]],
#         [["size"]],
#         [stats[2], "Strafe Fires"],
#     )
#     hits = calc_stats(
#         damage_data.loc[damage_data["attackerTeam"] != damage_data["victimTeam"]],
#         damage_filters,
#         [stats[1]],
#         [stats[1]],
#         [["size"]],
#         [stats[2], "Hits"],
#     )
#     headshots = calc_stats(
#         damage_data.loc[
#             (damage_data["attackerTeam"] != damage_data["victimTeam"])
#             & (damage_data["hitGroup"] == "Head")
#         ],
#         damage_filters,
#         [stats[1]],
#         [stats[1]],
#         [["size"]],
#         [stats[2], "Headshots"],
#     )
#     acc = weapon_fires.merge(strafe_fires, how="outer")  # .fillna(0)
#     acc = acc.merge(hits, how="outer")  # .fillna(0)
#     acc = acc.merge(headshots, how="outer")  # .fillna(0)
#     acc["Strafe%"] = acc["Strafe Fires"] / acc["Weapon Fires"]
#     acc["ACC%"] = acc["Hits"] / acc["Weapon Fires"]
#     acc["HS ACC%"] = acc["Headshots"] / acc["Weapon Fires"]
#     acc = acc[[stats[2], "Weapon Fires", "Strafe%", "ACC%", "HS ACC%"]]
#     acc.sort_values(by="ACC%", ascending=False, inplace=True)
#     acc.reset_index(drop=True, inplace=True)
#     return acc


# def kast(
#     kill_data: pd.DataFrame,
#     kast_string: str = "KAST",
#     flash_assists: bool = True,
#     kill_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     death_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with KAST statistics.

#     Args:
#         kill_data: A dataframe with kill data.
#         kast_string: A string specifying which combination of KAST statistics
#             to use.
#         flash_assists: A boolean specifying if flash assists are to be
#             counted as assists or not.
#         kill_filters: A dictionary where the keys are the columns of the
#             dataframe represented by kill_data to filter the kill data by and
#             the values are lists that contain the column filters.
#         death_filters: A dictionary where the keys are the columns of the
#             dataframe represented by kill_data to filter the death data by and
#             the values are lists that contain the column filters.

#     Returns:
#         A dataframe containing the percentage of a player's rounds with at least one of (K)ill, (A)ssist, (S)urvival and (T)rades.
#     """
#     columns = ["Player", f"{kast_string.upper()}%"]
#     kast_counts = {}
#     kast_rounds = {}
#     for stat in kast_string.upper():
#         columns.append(stat)
#     kill_data = kill_data[~kill_data["attackerName"].isna()]
#     killers = calc_stats(
#         kill_data.loc[
#             (kill_data["attackerTeam"] != kill_data["victimTeam"])
#             & (kill_data["attackerTeam"].notnull())
#         ],
#         kill_filters,
#         ["roundNum"],
#         ["attackerName"],
#         [["sum"]],
#         ["RoundNum", "Killers"],
#     )
#     victims = calc_stats(
#         kill_data,
#         kill_filters,
#         ["roundNum"],
#         ["victimName"],
#         [["sum"]],
#         ["RoundNum", "Victims"],
#     )
#     assisters = calc_stats(
#         kill_data.loc[
#             (kill_data["assisterTeam"] != kill_data["victimTeam"])
#             & (kill_data["assisterTeam"].notnull())
#         ],  # .fillna(""),
#         kill_filters,
#         ["roundNum"],
#         ["assisterName"],
#         [["sum"]],
#         ["RoundNum", "Assisters"],
#     )
#     traded = calc_stats(
#         kill_data.loc[
#             (kill_data["attackerTeam"] != kill_data["victimTeam"])
#             & (kill_data["isTrade"] == True)
#             & (kill_data["attackerTeam"].notnull())
#         ],  # .fillna(""),
#         kill_filters,
#         ["roundNum"],
#         ["playerTradedName"],
#         [["sum"]],
#         ["RoundNum", "Traded"],
#     )
#     if flash_assists:
#         flash_assisters = calc_stats(
#             kill_data.loc[
#                 (kill_data["flashThrowerTeam"] != kill_data["victimTeam"])
#                 & (kill_data["flashThrowerTeam"].notnull())
#             ],  # .fillna(""),
#             kill_filters,
#             ["roundNum"],
#             ["flashThrowerName"],
#             [["sum"]],
#             ["RoundNum", "Flash Assisters"],
#         )
#         assisters = assisters.merge(flash_assisters, on="RoundNum")
#         assisters["Assisters"] = assisters["Assisters"] + assisters["Flash Assisters"]
#         assisters = assisters[["RoundNum", "Assisters"]]
#     kast_data = killers.merge(assisters, how="outer")  # .fillna("")
#     kast_data = kast_data.merge(victims, how="outer")  # .fillna("")
#     kast_data = kast_data.merge(traded, how="outer")  # .fillna("")
#     killer_names = kill_data["attackerName"].tolist()
#     cleaned_killer_names = []
#     for x in killer_names:
#         if x != None:
#             cleaned_killer_names.append(x)
#     cleaned_killer_names = pd.Series(cleaned_killer_names)
#     for player in cleaned_killer_names:
#         kast_counts[player] = [[0, 0, 0, 0] for i in range(len(kast_data))]
#         kast_rounds[player] = [0, 0, 0, 0, 0]
#     for rd in kast_data.index:
#         for player in kast_counts:
#             if "K" in kast_string.upper():
#                 kast_counts[player][rd][0] = kast_data.iloc[rd]["Killers"].count(player)
#                 kast_rounds[player][1] += kast_data.iloc[rd]["Killers"].count(player)
#             if "A" in kast_string.upper():
#                 kast_counts[player][rd][1] = kast_data.iloc[rd]["Assisters"].count(
#                     player
#                 )
#                 kast_rounds[player][2] += kast_data.iloc[rd]["Assisters"].count(player)
#             if "S" in kast_string.upper():
#                 if player not in kast_data.iloc[rd]["Victims"]:
#                     kast_counts[player][rd][2] = 1
#                     kast_rounds[player][3] += 1
#             if "T" in kast_string.upper():
#                 kast_counts[player][rd][3] = kast_data.iloc[rd]["Traded"].count(player)
#                 kast_rounds[player][4] += kast_data.iloc[rd]["Traded"].count(player)
#     for player in kast_rounds:
#         for rd in kast_counts[player]:
#             if any(rd):
#                 kast_rounds[player][0] += 1
#         kast_rounds[player][0] /= len(kast_data)
#     kast = pd.DataFrame.from_dict(kast_rounds, orient="index").reset_index()
#     kast.columns = ["Player", f"{kast_string.upper()}%", "K", "A", "S", "T"]
#     kast = kast[columns]
#     kast  # .fillna(0, inplace=True)
#     kast.sort_values(by=f"{kast_string.upper()}%", ascending=False, inplace=True)
#     kast.reset_index(drop=True, inplace=True)
#     return kast


# def kill_stats(
#     damage_data: pd.DataFrame,
#     kill_data: pd.DataFrame,
#     round_data: pd.DataFrame,
#     weapon_fire_data: pd.DataFrame,
#     team: bool = False,
#     damage_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     kill_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     death_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     round_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     weapon_fire_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with kill statistics.

#     Args:
#         damage_data: A dataframe with damage data.
#         kill_data: A dataframe with kill data.
#         round_data: A dataframe with round data.
#         weapon_fire_data: A dataframe with weapon fire data.
#             where each round is a dictionary.
#         team: A boolean specifying whether to calculate statistics for each team
#             or for each player. The default is to calculate statistics for
#             each player.
#         damage_filters: A dictionary where the keys are the columns of the
#             dataframe represented by damage_data to filter the damage data by
#             and the values are lists that contain the column filters.
#         kill_filters: A dictionary where the keys are the columns of the
#             dataframe represented by kill_data to filter the kill data by and
#             the values are lists that contain the column filters.
#         death_filters: A dictionary where the keys are the columns of the
#             dataframe represented by kill_data to filter the death data by and
#             the values are lists that contain the column filters.
#         round_filters: A dictionary where the keys are the columns of the
#             dataframe represented by round_data to filter the round data by and
#             the values are lists that contain the column filters.
#         weapon_fire_filters: A dictionary where the keys are the columns of the
#             dataframe represented by weapon_fires to filter the weapon fire data
#             by and the values are lists that contain the column filters.

#     Returns:
#         A dataframe with kills, deaths, assists, flash assists, first kills/deaths, headshots, headshot kills/total kills, KAST and accuracy statistics.
#     """
#     stats = ["attackerName", "victimName", "assisterName", "flashThrowerName", "Player"]
#     if team:
#         stats = [
#             "attackerTeam",
#             "victimTeam",
#             "assisterTeam",
#             "flashThrowerTeam",
#             "Team",
#         ]
#     kills = calc_stats(
#         kill_data.loc[kill_data["attackerTeam"] != kill_data["victimTeam"]],
#         kill_filters,
#         [stats[0]],
#         [stats[0]],
#         [["size"]],
#         [stats[4], "K"],
#     )
#     deaths = calc_stats(
#         kill_data,
#         death_filters,
#         [stats[1]],
#         [stats[1]],
#         [["size"]],
#         [stats[4], "D"],
#     )
#     assists = calc_stats(
#         kill_data.loc[kill_data["assisterTeam"] != kill_data["victimTeam"]],
#         kill_filters,
#         [stats[2]],
#         [stats[2]],
#         [["size"]],
#         [stats[4], "A"],
#     )
#     flash_assists = calc_stats(
#         kill_data.loc[kill_data["flashThrowerTeam"] != kill_data["victimTeam"]],
#         kill_filters,
#         [stats[3]],
#         [stats[3]],
#         [["size"]],
#         [stats[4], "FA"],
#     )
#     first_kills = calc_stats(
#         kill_data.loc[
#             (kill_data["attackerTeam"] != kill_data["victimTeam"])
#             & (kill_data["isFirstKill"] == True)
#         ],
#         kill_filters,
#         [stats[0]],
#         [stats[0]],
#         [["size"]],
#         [stats[4], "FK"],
#     )
#     first_deaths = calc_stats(
#         kill_data.loc[
#             (kill_data["attackerTeam"] != kill_data["victimTeam"])
#             & (kill_data["isFirstKill"] == True)
#         ],
#         kill_filters,
#         [stats[1]],
#         [stats[1]],
#         [["size"]],
#         [stats[4], "FD"],
#     )
#     headshots = calc_stats(
#         kill_data.loc[
#             (kill_data["attackerTeam"] != kill_data["victimTeam"])
#             & (kill_data["isHeadshot"] == True)
#         ],
#         kill_filters,
#         [stats[0]],
#         [stats[0]],
#         [["size"]],
#         [stats[4], "HS"],
#     )
#     headshot_pct = calc_stats(
#         kill_data.loc[kill_data["attackerTeam"] != kill_data["victimTeam"]],
#         kill_filters,
#         [stats[0]],
#         ["isHeadshot"],
#         [["mean"]],
#         [stats[4], "HS%"],
#     )
#     if not team:
#         acc_stats = accuracy(
#             damage_data, weapon_fire_data, False, damage_filters, weapon_fire_filters
#         )
#     else:
#         acc_stats = accuracy(
#             damage_data, weapon_fire_data, True, damage_filters, weapon_fire_filters
#         )
#     kast_stats = kast(kill_data, "KAST", kill_filters, death_filters)
#     kill_stats = kills.merge(deaths, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(assists, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(flash_assists, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(first_kills, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(first_deaths, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(headshots, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(headshot_pct, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(acc_stats, how="outer")  # .fillna(0)
#     if not team:
#         kill_stats = kill_stats.merge(kast_stats, how="outer")  # .fillna(0)
#     kill_stats["+/-"] = kill_stats["K"] - kill_stats["D"]
#     kill_stats["KDR"] = kill_stats["K"] / kill_stats["D"]
#     kill_stats["KPR"] = kill_stats["K"] / len(
#         calc_stats(round_data, round_filters, [], [], [], round_data.columns)
#     )
#     kill_stats["FK +/-"] = kill_stats["FK"] - kill_stats["FD"]
#     int_stats = ["K", "D", "A", "FA", "+/-", "FK", "FK +/-", "HS", "T"]
#     if team:
#         int_stats = int_stats[0:-1]
#     kill_stats[int_stats] = kill_stats[int_stats].astype(int)
#     kill_stats["HS%"] = kill_stats["HS%"].astype(float)
#     order = [
#         stats[4],
#         "K",
#         "D",
#         "A",
#         "FA",
#         "+/-",
#         "FK",
#         "FK +/-",
#         "T",
#         "HS",
#         "HS%",
#         "ACC%",
#         "HS ACC%",
#         "KDR",
#         "KPR",
#         "KAST%",
#     ]
#     if team:
#         order = order[0:8] + order[9:-1]
#     kill_stats = kill_stats[order]
#     kill_stats.sort_values(by="K", ascending=False, inplace=True)
#     kill_stats.reset_index(drop=True, inplace=True)
#     return kill_stats


# def adr(
#     damage_data: pd.DataFrame,
#     round_data: pd.DataFrame,
#     team: bool = False,
#     damage_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     round_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with ADR statistics.

#     Args:
#         damage_data: A dataframe with damage data.
#         round_data: A dataframe with round data.
#         team: A boolean specifying whether to calculate statistics for each team
#             or for each player. The default is to calculate statistics for
#             each player.
#         damage_filters: A dictionary where the keys are the columns of the
#             dataframe represented by damage_data to filter the damage data by
#             and the values are lists that contain the column filters.
#         round_filters: A dictionary where the keys are the columns of the
#             dataframe represented by round_data to filter the round data by and
#             the values are lists that contain the column filters.

#     Returns:
#         A dataframe with the average damage per round (ADR).
#     """
#     stats = ["attackerName", "Player"]
#     if team:
#         stats = ["attackerTeam", "Team"]
#     adr = calc_stats(
#         damage_data.loc[damage_data["attackerTeam"] != damage_data["victimTeam"]],
#         damage_filters,
#         [stats[0]],
#         ["hpDamageTaken", "hpDamage"],
#         [["sum"], ["sum"]],
#         [stats[1], "Norm ADR", "Raw ADR"],
#     )
#     adr["Norm ADR"] = adr["Norm ADR"] / len(
#         calc_stats(round_data, round_filters, [], [], [], round_data.columns)
#     )
#     adr["Raw ADR"] = adr["Raw ADR"] / len(
#         calc_stats(round_data, round_filters, [], [], [], round_data.columns)
#     )
#     adr.sort_values(by="Norm ADR", ascending=False, inplace=True)
#     adr.reset_index(drop=True, inplace=True)
#     return adr


# def rating(
#     damage_data: pd.DataFrame,
#     kill_data: pd.DataFrame,
#     round_data: pd.DataFrame,
#     damage_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     death_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     kill_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     round_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with an HLTV-esque rating, found by doing:

#     Rating = 0.0073*KAST + 0.3591*KPR + -0.5329*DPR + 0.2372*Impact + 0.0032*ADR + 0.1587
#     where Impact = 2.13*KPR + 0.42*Assist per Round -0.41

#     Args:
#         damage_data: A dataframe with damage data.
#         kill_data: A dataframe with damage data.
#         round_data: A dataframe with round data.
#         damage_filters: A dictionary where the keys are the columns of the
#             dataframe represented by damage_data to filter the damage data by
#             and the values are lists that contain the column filters.
#         death_filters: A dictionary where the keys are the columns of the
#             dataframe represented by kill_data to filter the death data by and
#             the values are lists that contain the column filters.
#         kill_filters: A dictionary where the keys are the columns of the
#             dataframe represented by kill_data to filter the kill data by and
#             the values are lists that contain the column filters.
#         round_filters: A dictionary where the keys are the columns of the
#             dataframe represented by round_data to filter the round data by and
#             the values are lists that contain the column filters.

#     Returns:
#         A dataframe with the impact rating and rating 2.0
#     """
#     kast_stats = kast(kill_data, "KAST", kill_filters, death_filters)
#     kast_stats = kast_stats[["Player", "KAST%"]]
#     kast_stats.columns = ["Player", "KAST"]
#     adr_stats = adr(damage_data, round_data, damage_filters, round_filters)
#     adr_stats = adr_stats[["Player", "Norm ADR"]]
#     adr_stats.columns = ["Player", "ADR"]
#     stats = ["attackerName", "victimName", "assisterName", "flashThrowerName", "Player"]
#     kills = calc_stats(
#         kill_data.loc[kill_data["attackerTeam"] != kill_data["victimTeam"]],
#         kill_filters,
#         [stats[0]],
#         [stats[0]],
#         [["size"]],
#         [stats[4], "K"],
#     )
#     deaths = calc_stats(
#         kill_data,
#         death_filters,
#         [stats[1]],
#         [stats[1]],
#         [["size"]],
#         [stats[4], "D"],
#     )
#     assists = calc_stats(
#         kill_data.loc[kill_data["assisterTeam"] != kill_data["victimTeam"]],
#         kill_filters,
#         [stats[2]],
#         [stats[2]],
#         [["size"]],
#         [stats[4], "A"],
#     )
#     kill_stats = kills.merge(deaths, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(assists, how="outer")  # .fillna(0)
#     kill_stats["KPR"] = kill_stats["K"] / len(
#         calc_stats(round_data, round_filters, [], [], [], round_data.columns)
#     )
#     kill_stats["DPR"] = kill_stats["D"] / len(
#         calc_stats(round_data, round_filters, [], [], [], round_data.columns)
#     )
#     kill_stats["APR"] = kill_stats["A"] / len(
#         calc_stats(round_data, round_filters, [], [], [], round_data.columns)
#     )
#     kill_stats = kill_stats[["Player", "KPR", "DPR", "APR"]]
#     kill_stats = kill_stats.merge(adr_stats, how="outer")  # .fillna(0)
#     kill_stats = kill_stats.merge(kast_stats, how="outer")  # .fillna(0)
#     kill_stats["Impact"] = 2.13 * kill_stats["KPR"] + 0.42 * kill_stats["APR"] - 0.41
#     kill_stats["Rating"] = (
#         0.73 * kill_stats["KAST"]
#         + 0.3591 * kill_stats["KPR"]
#         - 0.5329 * kill_stats["DPR"]
#         + 0.2372 * kill_stats["Impact"]
#         + 0.0032 * kill_stats["ADR"]
#         + 0.1587
#     )
#     kill_stats = kill_stats[["Player", "Impact", "Rating"]]
#     kill_stats.sort_values(by="Rating", ascending=False, inplace=True)
#     kill_stats.reset_index(drop=True, inplace=True)
#     return kill_stats


# def util_dmg(
#     damage_data: pd.DataFrame,
#     grenade_data: pd.DataFrame,
#     team: bool = False,
#     damage_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     grenade_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with utility damage statistics.

#     Args:
#         damage_data: A dataframe with damage data.
#         grenade_data: A dataframe with grenade data.
#         team: A boolean specifying whether to calculate statistics for each team
#             or for each player. The default is to calculate statistics for
#             each player.
#         damage_filters: A dictionary where the keys are the columns of the
#             dataframe represented by damage_data to filter the damage data by
#             and the values are lists that contain the column filters.
#         grenade_filters: A dictionary where the keys are the columns of the
#             dataframe represented by grenade_data to filter the grenade data by
#             and the values are lists that contain the column filters.

#     Returns:
#         A dataframe with nades thrown, given utility damage (UD) per nade
#     """
#     stats = ["attackerName", "throwerName", "Player"]
#     if team:
#         stats = ["attackerTeam", "throwerTeam", "Team"]
#     util_dmg = calc_stats(
#         damage_data.loc[
#             (damage_data["attackerTeam"] != damage_data["victimTeam"])
#             & (
#                 damage_data["weapon"].isin(
#                     ["HE Grenade", "Incendiary Grenade", "Molotov"]
#                 )
#             )
#         ],
#         damage_filters,
#         [stats[0]],
#         ["hpDamageTaken", "hpDamage"],
#         [["sum"], ["sum"]],
#         [stats[2], "Given UD", "UD"],
#     )
#     nades_thrown = calc_stats(
#         grenade_data.loc[
#             grenade_data["grenadeType"].isin(
#                 ["HE Grenade", "Incendiary Grenade", "Molotov"]
#             )
#         ],
#         grenade_filters,
#         [stats[1]],
#         [stats[1]],
#         [["size"]],
#         [stats[2], "Nades Thrown"],
#     )
#     util_dmg_stats = util_dmg.merge(nades_thrown, how="outer")  # .fillna(0)
#     util_dmg_stats["Given UD Per Nade"] = (
#         util_dmg_stats["Given UD"] / util_dmg_stats["Nades Thrown"]
#     )
#     util_dmg_stats["UD Per Nade"] = (
#         util_dmg_stats["UD"] / util_dmg_stats["Nades Thrown"]
#     )
#     util_dmg_stats.sort_values(by="Given UD", ascending=False, inplace=True)
#     util_dmg_stats.reset_index(drop=True, inplace=True)
#     return util_dmg_stats


# def flash_stats(
#     flash_data: pd.DataFrame,
#     grenade_data: pd.DataFrame,
#     kill_data: pd.DataFrame,
#     team: bool = False,
#     flash_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     grenade_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     kill_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with flashbang statistics.

#     Args:
#         flash_data: A dataframe with flash data.
#         grenade_data: A dataframe with grenade data.
#         kill_data: A dataframe with kill data.
#         team: A boolean specifying whether to calculate statistics for each team
#             or for each player. The default is to calculate statistics for
#             each player.
#         flash_filters: A dictionary where the keys are the columns of the
#             dataframe represented by flash_data to filter the flash data by
#             and the values are lists that contain the column filters.
#         grenade_filters: A dictionary where the keys are the columns of the
#             dataframe represented by grenade_data to filter the grenade data by
#             and the values are lists that contain the column filters.
#         kill_filters: A dictionary where the keys are the columns of the
#             dataframe represented by kill_data to filter the kill data by and
#             the values are lists that contain the column filters.

#     Returns:
#         A dataframe with the effective flashes (EF), flash assists (FA), effective blind time (EBT), team flashes (TF), total flashes
#     """
#     stats = ["attackerName", "flashThrowerName", "throwerName", "Player"]
#     if team:
#         stats = ["attackerTeam", "flashThrowerTeam", "throwerTeam", "Team"]
#     enemy_flashes = calc_stats(
#         flash_data.loc[flash_data["attackerTeam"] != flash_data["playerTeam"]],
#         flash_filters,
#         [stats[0]],
#         [stats[0]],
#         [["size"]],
#         [stats[3], "EF"],
#     )
#     flash_assists = calc_stats(
#         kill_data.loc[kill_data["flashThrowerTeam"] != kill_data["victimTeam"]],
#         kill_filters,
#         [stats[1]],
#         [stats[1]],
#         [["size"]],
#         [stats[3], "FA"],
#     )
#     blind_time = calc_stats(
#         flash_data.loc[flash_data["attackerTeam"] != flash_data["playerTeam"]],
#         flash_filters,
#         [stats[0]],
#         ["flashDuration"],
#         [["sum"]],
#         [stats[3], "EBT"],
#     )
#     team_flashes = calc_stats(
#         flash_data.loc[flash_data["attackerTeam"] == flash_data["playerTeam"]],
#         flash_filters,
#         [stats[0]],
#         [stats[0]],
#         [["size"]],
#         [stats[3], "TF"],
#     )
#     flashes_thrown = calc_stats(
#         grenade_data.loc[grenade_data["grenadeType"] == "Flashbang"],
#         flash_filters,
#         [stats[2]],
#         [stats[2]],
#         [["size"]],
#         [stats[3], "Flashes Thrown"],
#     )
#     flash_stats = enemy_flashes.merge(flash_assists, how="outer")  # .fillna(0)
#     flash_stats = flash_stats.merge(blind_time, how="outer")  # .fillna(0)
#     flash_stats = flash_stats.merge(team_flashes, how="outer")  # .fillna(0)
#     flash_stats = flash_stats.merge(flashes_thrown, how="outer")  # .fillna(0)
#     flash_stats["EF Per Throw"] = flash_stats["EF"] / flash_stats["Flashes Thrown"]
#     flash_stats["EBT Per Enemy"] = flash_stats["EBT"] / flash_stats["EF"]
#     flash_stats["FA"] = flash_stats["FA"].astype(int)
#     flash_stats.sort_values(by="EF", ascending=False, inplace=True)
#     flash_stats.reset_index(drop=True, inplace=True)
#     return flash_stats


# def bomb_stats(
#     bomb_data: pd.DataFrame,
#     bomb_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with bomb event statistics.

#     Args:
#         bomb_data: A dataframe with bomb event data.
#         bomb_filters: A dictionary where the keys are the columns of the
#             dataframe represented by bomb_data to filter the bomb data by and
#             the values are lists that contain the column filters.

#     Returns:
#         A dataframe with bomb plant summaries.
#     """
#     team_one = bomb_data["playerTeam"].unique()[0]
#     team_two = bomb_data["playerTeam"].unique()[1]
#     team_one_plants = calc_stats(
#         bomb_data.loc[
#             (bomb_data["bombAction"] == "plant") & (bomb_data["playerTeam"] == team_one)
#         ],
#         bomb_filters,
#         ["bombSite"],
#         ["bombSite"],
#         [["size"]],
#         ["Bombsite", f"{team_one} Plants"],
#     )
#     team_two_plants = calc_stats(
#         bomb_data.loc[
#             (bomb_data["bombAction"] == "plant") & (bomb_data["playerTeam"] == team_two)
#         ],
#         bomb_filters,
#         ["bombSite"],
#         ["bombSite"],
#         [["size"]],
#         ["Bombsite", f"{team_two} Plants"],
#     )
#     team_one_defuses = calc_stats(
#         bomb_data.loc[
#             (bomb_data["bombAction"] == "defuse")
#             & (bomb_data["playerTeam"] == team_one)
#         ],
#         bomb_filters,
#         ["bombSite"],
#         ["bombSite"],
#         [["size"]],
#         ["Bombsite", f"{team_one} Defuses"],
#     )
#     team_two_defuses = calc_stats(
#         bomb_data.loc[
#             (bomb_data["bombAction"] == "defuse")
#             & (bomb_data["playerTeam"] == team_two)
#         ],
#         bomb_filters,
#         ["bombSite"],
#         ["bombSite"],
#         [["size"]],
#         ["Bombsite", f"{team_two} Defuses"],
#     )
#     bomb_stats = team_one_plants.merge(team_two_defuses, how="outer")  # .fillna(0)
#     bomb_stats[f"{team_two} Defuse %"] = (
#         bomb_stats[f"{team_two} Defuses"] / bomb_stats[f"{team_one} Plants"]
#     )
#     bomb_stats = bomb_stats.merge(team_two_plants, how="outer")  # .fillna(0)
#     bomb_stats = bomb_stats.merge(team_one_defuses, how="outer")  # .fillna(0)
#     bomb_stats[f"{team_one} Defuse %"] = (
#         bomb_stats[f"{team_one} Defuses"] / bomb_stats[f"{team_two} Plants"]
#     )
#     bomb_stats.loc[2] = [
#         "A and B",
#         bomb_stats[f"{team_one} Plants"].sum(),
#         bomb_stats[f"{team_two} Defuses"].sum(),
#         (
#             bomb_stats[f"{team_two} Defuses"].sum()
#             / bomb_stats[f"{team_one} Plants"].sum()
#         ),
#         bomb_stats[f"{team_two} Plants"].sum(),
#         bomb_stats[f"{team_one} Defuses"].sum(),
#         (
#             bomb_stats[f"{team_one} Defuses"].sum()
#             / bomb_stats[f"{team_two} Plants"].sum()
#         ),
#     ]
#     bomb_stats  # .fillna(0, inplace=True)
#     bomb_stats.iloc[:, [1, 2, 4, 5]] = bomb_stats.iloc[:, [1, 2, 4, 5]].astype(int)
#     return bomb_stats


# def econ_stats(
#     round_data: pd.DataFrame,
#     round_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with economy statistics.

#     Args:
#         round_data: A dataframe with round data.
#         round_filters: A dictionary where the keys are the columns of the
#             dataframe represented by round_data to filter the round data by and
#             the values are lists that contain the column filters.

#     Returns:
#         A dataframe with round economy summaries.
#     """
#     ct_stats = calc_stats(
#         round_data,
#         round_filters,
#         ["ctTeam"],
#         ["ctStartEqVal", "ctRoundStartMoney", "ctSpend"],
#         [["mean"], ["mean"], ["mean"]],
#         ["Side", "Avg EQ Value", "Avg Cash", "Avg Spend"],
#     )
#     ct_stats["Side"] = ct_stats["Side"] + " CT"
#     ct_buys = calc_stats(
#         round_data,
#         round_filters,
#         ["ctTeam", "ctBuyType"],
#         ["ctBuyType"],
#         [["size"]],
#         ["Side", "Buy Type", "Counts"],
#     )
#     ct_buys = ct_buys.pivot(index="Side", columns="Buy Type", values="Counts")
#     ct_buys.reset_index(inplace=True)
#     ct_buys.rename_axis(None, axis=1, inplace=True)
#     ct_buys["Side"] = ct_buys["Side"] + " CT"
#     t_stats = calc_stats(
#         round_data,
#         round_filters,
#         ["tTeam"],
#         ["tStartEqVal", "tRoundStartMoney", "tSpend"],
#         [["mean"], ["mean"], ["mean"]],
#         ["Side", "Avg EQ Value", "Avg Cash", "Avg Spend"],
#     )
#     t_stats["Side"] = t_stats["Side"] + " T"
#     t_buys = calc_stats(
#         round_data,
#         round_filters,
#         ["tTeam", "tBuyType"],
#         ["tBuyType"],
#         [["size"]],
#         ["Side", "Buy Type", "Counts"],
#     )
#     t_buys = t_buys.pivot(index="Side", columns="Buy Type", values="Counts")
#     t_buys.reset_index(inplace=True)
#     t_buys.rename_axis(None, axis=1, inplace=True)
#     t_buys["Side"] = t_buys["Side"] + " T"
#     econ_buys = ct_buys.append(t_buys)
#     econ_stats = ct_stats.append(t_stats)
#     econ_stats = econ_buys.merge(econ_stats, how="outer")
#     econ_stats  # .fillna(0, inplace=True)
#     econ_stats.iloc[:, 1:] = econ_stats.iloc[:, 1:].astype(int)
#     return econ_stats


# def weapon_type(weapon: str) -> str:
#     """Returns the weapon type of a weapon.

#     Args:
#         weapon: Weapon name

#     Returns:
#         A string for the weapon type (Knife, Pistol, Shotgun, SMG, Assault Rifle, Machine Gun, Sniper Rifle, Utility)
#     """
#     if weapon in ["Knife"]:
#         return "Melee Kills"
#     elif weapon in [
#         "CZ-75 Auto",
#         "Desert Eagle",
#         "Dual Berettas",
#         "Five-SeveN",
#         "Glock-18",
#         "P2000",
#         "P250",
#         "R8 Revolver",
#         "Tec-9",
#         "USP-S",
#     ]:
#         return "Pistol Kills"
#     elif weapon in ["MAG-7", "Nova", "Sawed-Off", "XM1014"]:
#         return "Shotgun Kills"
#     elif weapon in ["MAC-10", "MP5-SD", "MP7", "MP9", "P90", "PP-Bizon", "UMP-45"]:
#         return "SMG Kills"
#     elif weapon in ["AK-47", "AUG", "FAMAS", "Galil AR", "M4A1-S", "M4A4", "SG 553"]:
#         return "Assault Rifle Kills"
#     elif weapon in ["M249", "Negev"]:
#         return "Machine Gun Kills"
#     elif weapon in ["AWP", "G3SG1", "SCAR-20", "SSG 08"]:
#         return "Sniper Rifle Kills"
#     else:
#         return "Utility Kills"


# def kill_breakdown(
#     kill_data: pd.DataFrame,
#     team: bool = False,
#     kill_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with kills broken down by weapon type.

#     Args:
#         kill_data: A dataframe with kill data.
#         team: A boolean specifying whether to calculate statistics for each team
#             or for each player. The default is to calculate statistics for
#             each player.
#         kill_filters: A dictionary where the keys are the columns of the
#             dataframe represented by kill_data to filter the kill data by and
#             the values are lists that contain the column filters.

#     Returns:
#         A dataframe with kill type statistics.
#     """
#     stats = ["attackerName", "Player"]
#     if team:
#         stats = ["attackerTeam", "Team"]
#     kill_breakdown = kill_data.loc[
#         kill_data["attackerTeam"] != kill_data["victimTeam"]
#     ].copy()
#     kill_breakdown["Kills Type"] = kill_breakdown.apply(
#         lambda row: weapon_type(row["weapon"]), axis=1
#     )
#     kill_breakdown = calc_stats(
#         kill_breakdown,
#         kill_filters,
#         [stats[0], "Kills Type"],
#         [stats[0]],
#         [["size"]],
#         [stats[1], "Kills Type", "Kills"],
#     )
#     kill_breakdown = kill_breakdown.pivot(
#         index=stats[1], columns="Kills Type", values="Kills"
#     )
#     for col in [
#         "Melee Kills",
#         "Pistol Kills",
#         "Shotgun Kills",
#         "SMG Kills",
#         "Assault Rifle Kills",
#         "Machine Gun Kills",
#         "Sniper Rifle Kills",
#         "Utility Kills",
#     ]:
#         if not col in kill_breakdown.columns:
#             kill_breakdown.insert(0, col, 0)
#         kill_breakdown[col]  # .fillna(0, inplace=True)
#         kill_breakdown[col] = kill_breakdown[col].astype(int)
#     kill_breakdown["Total Kills"] = kill_breakdown.iloc[0:].sum(axis=1)
#     kill_breakdown.reset_index(inplace=True)
#     kill_breakdown.rename_axis(None, axis=1, inplace=True)
#     kill_breakdown = kill_breakdown[
#         [
#             stats[1],
#             "Melee Kills",
#             "Pistol Kills",
#             "Shotgun Kills",
#             "SMG Kills",
#             "Assault Rifle Kills",
#             "Machine Gun Kills",
#             "Sniper Rifle Kills",
#             "Utility Kills",
#             "Total Kills",
#         ]
#     ]
#     kill_breakdown.sort_values(by="Total Kills", ascending=False, inplace=True)
#     kill_breakdown.reset_index(drop=True, inplace=True)
#     return kill_breakdown


# def util_dmg_breakdown(
#     damage_data: pd.DataFrame,
#     grenade_data: pd.DataFrame,
#     team: bool = False,
#     damage_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     grenade_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with utility damage statistics broken down by grenade type.

#     Args:
#         damage_data: A dataframe with damage data.
#         grenade_data: A dataframe with grenade data.
#         team: A boolean specifying whether to calculate statistics for each team
#             or for each player. The default is to calculate statistics for
#             each player.
#         damage_filters: A dictionary where the keys are the columns of the
#             dataframe represented by damage_data to filter the damage data by
#             and the values are lists that contain the column filters.
#         grenade_filters: A dictionary where the keys are the columns of the
#             dataframe represented by grenade_data to filter the grenade data by
#             and the values are lists that contain the column filters.

#     Returns:
#         A dataframe with utility damage summary.
#     """
#     stats = ["attackerName", "throwerName", "Player"]
#     if team:
#         stats = ["attackerTeam", "throwerTeam", "Team"]
#     util_dmg = calc_stats(
#         damage_data.loc[
#             (damage_data["attackerTeam"] != damage_data["victimTeam"])
#             & (
#                 damage_data["weapon"].isin(
#                     ["HE Grenade", "Incendiary Grenade", "Molotov"]
#                 )
#             )
#         ],
#         damage_filters,
#         [stats[0], "weapon"],
#         ["hpDamageTaken", "hpDamage"],
#         [["sum"], ["sum"]],
#         [stats[2], "Nade Type", "Given UD", "UD"],
#     )
#     nades_thrown = calc_stats(
#         grenade_data.loc[
#             grenade_data["grenadeType"].isin(
#                 ["HE Grenade", "Incendiary Grenade", "Molotov"]
#             )
#         ],
#         grenade_filters,
#         [stats[1], "grenadeType"],
#         [stats[1]],
#         [["size"]],
#         [stats[2], "Nade Type", "Nades Thrown"],
#     )
#     util_dmg_breakdown = util_dmg.merge(
#         nades_thrown, how="outer", on=[stats[2], "Nade Type"]
#     )  # .fillna(0)
#     util_dmg_breakdown["Given UD Per Nade"] = (
#         util_dmg_breakdown["Given UD"] / util_dmg_breakdown["Nades Thrown"]
#     )
#     util_dmg_breakdown["UD Per Nade"] = (
#         util_dmg_breakdown["UD"] / util_dmg_breakdown["Nades Thrown"]
#     )
#     util_dmg_breakdown.sort_values(
#         by=[stats[2], "Given UD"], ascending=[True, False], inplace=True
#     )
#     util_dmg_breakdown.reset_index(drop=True, inplace=True)
#     return util_dmg_breakdown


# def win_breakdown(
#     round_data: pd.DataFrame,
#     round_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a dataframe with wins broken down by round end reason.

#     Args:
#         round_data: A dataframe with round data.
#         round_filters: A dictionary where the keys are the columns of the
#             dataframe represented by round_data to filter the round data by and
#             the values are lists that contain the column filters.

#     Returns:
#         A dataframe with round wins described by reason.
#     """
#     round_data_copy = round_data.copy()
#     round_data_copy.replace("BombDefused", "CT Bomb Defusal Wins", inplace=True)
#     round_data_copy.replace("CTWin", "CT T Elim Wins", inplace=True)
#     round_data_copy.replace("TargetBombed", "T Bomb Detonation Wins", inplace=True)
#     round_data_copy.replace("TargetSaved", "CT Time Expired Wins", inplace=True)
#     round_data_copy.replace("TerroristsWin", "T CT Elim Wins", inplace=True)
#     win_breakdown = calc_stats(
#         round_data_copy,
#         round_filters,
#         ["winningTeam", "roundEndReason"],
#         ["roundEndReason"],
#         [["size"]],
#         ["Team", "RoundEndReason", "Count"],
#     )
#     win_breakdown = win_breakdown.pivot(
#         index="Team", columns="RoundEndReason", values="Count"
#     )  # .fillna(0)
#     win_breakdown.reset_index(inplace=True)
#     win_breakdown.rename_axis(None, axis=1, inplace=True)
#     win_breakdown["Total CT Wins"] = win_breakdown.iloc[0:][
#         list(
#             set.intersection(
#                 set(win_breakdown.columns),
#                 set(["CT Bomb Defusal Wins", "CT T Elim Wins", "CT Time Expired Wins"]),
#             )
#         )
#     ].sum(axis=1)
#     win_breakdown["Total T Wins"] = win_breakdown.iloc[0:][
#         list(
#             set.intersection(
#                 set(win_breakdown.columns),
#                 set(["T Bomb Detonation Wins", "T CT Elim Wins"]),
#             )
#         )
#     ].sum(axis=1)
#     win_breakdown["Total Wins"] = win_breakdown.iloc[0:, 0:-2].sum(axis=1)
#     win_breakdown.iloc[:, 1:] = win_breakdown.iloc[:, 1:].astype(int)
#     return win_breakdown


# def player_box_score(
#     damage_data: pd.DataFrame,
#     flash_data: pd.DataFrame,
#     grenade_data: pd.DataFrame,
#     kill_data: pd.DataFrame,
#     round_data: pd.DataFrame,
#     weapon_fire_data: pd.DataFrame,
#     damage_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     flash_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     grenade_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     kill_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     death_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     round_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     weapon_fire_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a player box score dataframe.

#     Args:
#        damage_data: A dataframe with damage data.
#        flash_data: A dataframe with flash data.
#        grenade_data: A dataframe with grenade data.
#        kill_data: A dataframe with kill data.
#        round_data: A dataframe with round data.
#        weapon_fire_data: A dataframe with weapon fire data.
#        damage_filters: A dictionary where the keys are the columns of the
#            dataframe represented by damage_data to filter the damage data by
#            and the values are lists that contain the column filters.
#        flash_filters: A dictionary where the keys are the columns of the
#            dataframe represented by flash_data to filter the flash data by
#            and the values are lists that contain the column filters.
#        grenade_filters: A dictionary where the keys are the columns of the
#            dataframe represented by grenade_data to filter the grenade data by
#            and the values are lists that contain the column filters.
#        kill_filters: A dictionary where the keys are the columns of the
#            dataframe represented by kill_data to filter the kill data by and
#            the values are lists that contain the column filters.
#        death_filters: A dictionary where the keys are the columns of the
#            dataframe represented by kill_data to filter the death data by and
#            the values are lists that contain the column filters.
#        round_filters: A dictionary where the keys are the columns of the
#            dataframe represented by round_data to filter the round data by and
#            the values are lists that contain the column filters.
#        weapon_fire_filters: A dictionary where the keys are the columns of the
#            dataframe to filter the weapon fire data by and the values are lists
#            that contain the column filters.

#     Returns:
#         A dataframe with player summaries.
#     """
#     k_stats = kill_stats(
#         damage_data,
#         kill_data,
#         round_data,
#         weapon_fire_data,
#         damage_filters,
#         kill_filters,
#         death_filters,
#         round_filters,
#         weapon_fire_filters,
#     )
#     k_stats = k_stats[
#         ["Player", "K", "D", "A", "FA", "HS%", "ACC%", "HS ACC%", "KDR", "KAST%"]
#     ]
#     adr_stats = adr(damage_data, round_data, damage_filters, round_filters)
#     adr_stats = adr_stats[["Player", "Norm ADR"]]
#     adr_stats.columns = ["Player", "ADR"]
#     ud_stats = util_dmg(damage_data, grenade_data, damage_filters, grenade_filters)
#     ud_stats = ud_stats[["Player", "UD", "UD Per Nade"]]
#     f_stats = flash_stats(
#         flash_data,
#         grenade_data,
#         kill_data,
#         flash_filters,
#         grenade_filters,
#         kill_filters,
#     )
#     f_stats = f_stats[["Player", "EF", "EF Per Throw"]]
#     rating_stats = rating(
#         damage_data,
#         kill_data,
#         round_data,
#         damage_filters,
#         death_filters,
#         kill_filters,
#         round_filters,
#     )
#     box_score = k_stats.merge(adr_stats, how="outer")  # .fillna(0)
#     box_score = box_score.merge(ud_stats, how="outer")  # .fillna(0)
#     box_score = box_score.merge(f_stats, how="outer")  # .fillna(0)
#     box_score = box_score.merge(rating_stats, how="outer")  # .fillna(0)
#     return box_score


# def team_box_score(
#     damage_data: pd.DataFrame,
#     flash_data: pd.DataFrame,
#     grenade_data: pd.DataFrame,
#     kill_data: pd.DataFrame,
#     round_data: pd.DataFrame,
#     weapon_fire_data: pd.DataFrame,
#     damage_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     flash_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     grenade_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     kill_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     death_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     round_filters: Dict[str, Union[List[bool], List[str]]] = {},
#     weapon_fire_filters: Dict[str, Union[List[bool], List[str]]] = {},
# ) -> pd.DataFrame:
#     """Returns a team box score dataframe.

#     Args:
#        damage_data: A dataframe with damage data.
#        flash_data: A dataframe with flash data.
#        grenade_data: A dataframe with grenade data.
#        kill_data: A dataframe with kill data.
#        round_data: A dataframe with round data.
#        weapon_fire_data: A dataframe with weapon fire data.
#        damage_filters: A dictionary where the keys are the columns of the
#            dataframe represented by damage_data to filter the damage data by
#            and the values are lists that contain the column filters.
#        flash_filters: A dictionary where the keys are the columns of the
#            dataframe represented by flash_data to filter the flash data by
#            and the values are lists that contain the column filters.
#        grenade_filters: A dictionary where the keys are the columns of the
#            dataframe represented by grenade_data to filter the grenade data by
#            and the values are lists that contain the column filters.
#        kill_filters: A dictionary where the keys are the columns of the
#            dataframe represented by kill_data to filter the kill data by and
#            the values are lists that contain the column filters.
#        death_filters: A dictionary where the keys are the columns of the
#            dataframe represented by kill_data to filter the death data by and
#            the values are lists that contain the column filters.
#        round_filters: A dictionary where the keys are the columns of the
#            dataframe represented by round_data to filter the round data by and
#            the values are lists that contain the column filters.
#        weapon_fire_filters: A dictionary where the keys are the columns of the
#            dataframe to filter the weapon fire data by and the values are lists
#            that contain the column filters.

#     Returns:
#         A dataframe with team summaries.
#     """
#     k_stats = kill_stats(
#         damage_data,
#         kill_data,
#         round_data,
#         weapon_fire_data,
#         True,
#         damage_filters,
#         kill_filters,
#         death_filters,
#         round_filters,
#         weapon_fire_filters,
#     )
#     acc_stats = accuracy(
#         damage_data, weapon_fire_data, True, damage_filters, weapon_fire_filters
#     )
#     adr_stats = adr(damage_data, round_data, True, damage_filters, round_filters)
#     ud_stats = util_dmg(
#         damage_data, grenade_data, True, damage_filters, grenade_filters
#     )
#     f_stats = flash_stats(
#         flash_data,
#         grenade_data,
#         kill_data,
#         True,
#         flash_filters,
#         grenade_filters,
#         kill_filters,
#     )
#     e_stats = econ_stats(round_data, round_filters)
#     for index in e_stats.index:
#         e_stats.iloc[index, 0] = e_stats["Side"].str.rsplit(n=1)[index][0]
#         rounds = e_stats.iloc[index, 1:-4].sum()
#         e_stats.iloc[index, -3:] = e_stats.iloc[index, -3:] * rounds
#     e_stats = e_stats.groupby(["Side"]).sum()
#     e_stats.reset_index(inplace=True)
#     e_stats.iloc[:, -3:] = (
#         e_stats.iloc[:, -3:] / len(filter_df(round_data, round_filters))
#     ).astype(int)
#     e_stats.rename(columns={"Side": "Team"}, inplace=True)
#     box_score = k_stats.merge(acc_stats, how="outer")
#     box_score = box_score.merge(adr_stats, how="outer")
#     box_score = box_score.merge(ud_stats, how="outer")
#     box_score = box_score.merge(f_stats, how="outer")
#     box_score = box_score.merge(e_stats, how="outer")
#     box_score = box_score.merge(
#         win_breakdown(round_data, round_filters), how="outer"
#     )  # .fillna(0)
#     box_score.rename(
#         columns={
#             "Norm ADR": "ADR",
#             "Total CT Wins": "CT Wins",
#             "Total T Wins": "T Wins",
#             "Total Wins": "Score",
#         },
#         inplace=True,
#     )
#     score = box_score["Score"]
#     ct_wins = box_score["CT Wins"]
#     t_wins = box_score["T Wins"]
#     box_score.drop(["Score", "CT Wins", "T Wins"], axis=1, inplace=True)
#     box_score.insert(1, "Score", score)
#     box_score.insert(2, "CT Wins", ct_wins)
#     box_score.insert(3, "T Wins", t_wins)
#     box_score = box_score.transpose()
#     box_score.columns = box_score.iloc[0]
#     box_score.drop("Team", inplace=True)
#     box_score.rename_axis(None, axis=1, inplace=True)
#     box_score = box_score.loc[
#         [
#             "Score",
#             "CT Wins",
#             "T Wins",
#             "K",
#             "D",
#             "A",
#             "FA",
#             "+/-",
#             "FK",
#             "HS",
#             "HS%",
#             "Strafe%",
#             "ACC%",
#             "HS ACC%",
#             "ADR",
#             "UD",
#             "Nades Thrown",
#             "UD Per Nade",
#             "EF",
#             "Flashes Thrown",
#             "EF Per Throw",
#             "EBT Per Enemy",
#         ],
#         :,
#     ].append(box_score.iloc[31:, :])
#     return box_score
