# AUTOGENERATED! DO NOT EDIT! File to edit: ../01_anlyzer.ipynb.

# %% auto 0
__all__ = ['Analyzer']

# %% ../01_anlyzer.ipynb 4
from .reader import *
from collections import Counter
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import seaborn as sns

# %% ../01_anlyzer.ipynb 5
pd.options.mode.chained_assignment = None

# %% ../01_anlyzer.ipynb 6
class Analyzer(Read_data):
    "Provides useful functions for analyzing bitcoin abuse data"
    def __init__(self,path): # Path from the parent class
        super().__init__(path)
        self.addr_df = None
        self.abuse_type_df = None
        self.top10_abuser_grp = None
        self.dup_addr = self.get_dup_addr()
        self.ovl_bot = self.get_ovl_bot_acty()
        self.tp_abuse_type = self.abuse_desc()
        self.tp_abuser = self.get_top_abusers()
        self.tp_10_contr = self.get_top_abusers_contr()
        self.summary = self.get_summary()
        self.bot_dst = self.bot_distribution()

    # Give a summary of the data
    def get_summary(self):
        numb_addr = self.dup_addr['address'].count()
        total_dupl = self.dup_addr['count_duplicates'].sum()
        total_reports = self.dup_addr['description_count'].sum()
        sum_data = [['Number of addresses',numb_addr],['Number of duplicates',total_dupl],['Total reports',total_reports],['Overall Bot activity',self.ovl_bot],['Top abuse type',self.tp_abuse_type],['Top Abuser',self.tp_abuser],
           ['Top 10 abuser Contribution', self.tp_10_contr]]
        df = pd.DataFrame(sum_data, columns=['Metric','Details'])
        return df
    
    # Displays the distribution of bot activity as the probability of being a bot increase
    def bot_distribution(self):
        df_data=[]
        for x in np.arange(0,1,0.1):
            data = self.dup_addr[self.dup_addr['prob_bot'] >= x]
            addr_count = data['address'].count()
            bot_pct = f"{int(round(x,2)*100)}%"
            row_n = [bot_pct,addr_count]
            df_data.append(row_n)

        df = pd.DataFrame(data=df_data, columns=['Bot_activity','Number of Addresses'])
        df['prop_per_total_address'] = df['Number of Addresses'] / self.dup_addr['address'].count()
        return df
    
    def get_dup_addr(self):
        # Selects all the description count of each address
        desc_count =self.data.groupby('address')['description'].count().to_frame().reset_index()
        desc_count = desc_count.rename(columns={'description':'description_count'})
        # Selects all the unique description under each address
        unq_desc_count = self.data.groupby('address')['description'].nunique().to_frame().reset_index()
        unq_desc_count = unq_desc_count.rename(columns={'description':'unique_description_count'})
        # Merges both the address count and the unique count into one dataframe
        merged_grp = desc_count.merge(unq_desc_count)
        merged_grp['count_duplicates'] = merged_grp['description_count'] - merged_grp['unique_description_count']
        merged_grp['prob_bot'] = merged_grp['count_duplicates'] / merged_grp['description_count']
        #serves the result as a property of the class
        self.dup_addr = merged_grp
        return merged_grp
    
    # Plot the top 10 addresses with the highest number of duplicates
    def get_top_10(self):
        # References `self.dup_addr` for a dataframe of all duplicated addresses
        dup_addr = self.dup_addr.copy()
        # Shorten the address length for display on graph
        dup_addr['address_short'] = dup_addr['address'].apply(lambda x: x[:8])
        top_10_dup = dup_addr.nlargest(10,'prob_bot')
        fig,ax = plt.subplots()
        # Plot a bar graph
        sns.barplot(x='address_short',y='prob_bot', data=top_10_dup, ax=ax)
        # Change the label titles for proper visualisation
        ax.set_xticklabels(list(top_10_dup['address_short'].values), rotation=45)
        ax.set_xlabel('Addresses')
        ax.set_ylabel('Number of Duplicates')
        ax.set_title('Top 10 Addresses with Highest Bot Activity')
        fig.tight_layout()
     
    #Displays timeline for Bot activities on a specific address
    def get_bot_timeline(self,addr:str):
        # Extracting the dataframe for the address searched
        cp_spamdf=self.data.copy()
        spec_addr_df = cp_spamdf.loc[cp_spamdf['address']== addr]
        spec_addr_df['dup_desc_count'] = spec_addr_df.groupby('description', sort=False).cumcount() + 1
        spec_addr_df['short_desc'] = spec_addr_df['description'].str[:30]+'..'
        #set the dataframe for the specific address
        self.addr_df = spec_addr_df
        # Plot the timeline for this address
        sns.set(rc={'figure.figsize':(10,6)})
        sns.set_theme(style='darkgrid')
        fig,ax = plt.subplots()
        fig.autofmt_xdate()
        sns.scatterplot(x='date',y='dup_desc_count',data=spec_addr_df, ax=ax, hue='short_desc')
        plt.legend(title='Description')
        xfmt = mdates.DateFormatter('%d-%m-%y %H:%M')
        ax.xaxis.set_major_formatter(xfmt)
        ax.set_ylabel('Count of duplicates')
        ax.set_xlabel('Full Date')
        ax.set_title(f"Duplicate count for Address: {srch_addr[:5]}...")
        fig.tight_layout()
    
    #Displays abuse type with the highest number of reported descriptions
    def abuse_desc(self):
        #make a copy of the data
        abuse_type_grp = self.data.copy()
        abuse_type_grp = abuse_type_grp.groupby('abuse_type_id')['description'].count().to_frame().reset_index()
        abuse_type_grp.rename({"abuse_type_id":"abuse_type", "description":"desc_count"},axis=1, inplace=True)
        abuse_type_grp['abuse_type'] = abuse_type_grp['abuse_type'].map({1:'ransom ware',2:'black mail scam',3:'sextortion',4:'dark market', 5:'bitcoin tumblr', 99:'other'})
        self.abuse_type_df = abuse_type_grp
        tp_abuse_type = abuse_type_grp.sort_values(by='desc_count',ascending=False).iloc[0,0]
        return tp_abuse_type
    
    #plot abuse description plot
    def plot_abuse_desc(self):
        abuse_type_grp = self.abuse_type_df.copy()
        sns.barplot(x='abuse_type',y='desc_count', data=abuse_type_grp);
        
    # Creates a dataframe for ml training
    def ml_data(self):
        "Creates a dataframe to be exported for training an NLP model"
        ml_data =self.data.copy()
        ml_data["dup_val"] =ml_data.groupby('description')['description'].transform('count')
        ml_data["l1_dup"] = np.log(ml_data['dup_val'])
        max_val = max(ml_data["l1_dup"])
        ml_data["norm"] = (ml_data["l1_dup"] / max_val)
        ml_data["norm"] = ml_data["norm"].round(2)
        ml_data.drop_duplicates(subset='description', inplace=True)
        cln_ml_dt = ml_data.drop(['abuse_type_other','dup_val','l1_dup'], axis=1)
        cln_ml_dt['abuse_type_id'] = cln_ml_dt['abuse_type_id'].map({1:'ransom ware',2:'black mail scam',3:'sextortion',4:'dark market', 5:'bitcoin tumblr', 99:'other'})
        cln_ml_dt.dropna(inplace=True)
        cln_ml_dt.rename(columns={"abuse_type_id":"abuse_type", "norm":"score"}, inplace=True)
        cln_ml_dt.reset_index(drop=True)
        return cln_ml_dt
    
    # Returns BOT activity on the whole dataset
    def get_ovl_bot_acty(self):
        "Divides the total number of duplicates with the total number of reports"
        total_dup = self.dup_addr['count_duplicates'].sum() 
        total_cnt =self.dup_addr['description_count'].sum()
        total_bot_activity = total_dup/total_cnt
        total_bot_activity = round(total_bot_activity,3)
        #sets the overall bot activity as a property of the class
        self.ovl_bot = total_bot_activity
        return total_bot_activity
    
    # Displays a graph of the top abusers in the dataset
    def get_top_abusers(self):
        abuse_df = self.data.groupby('abuser', as_index=False).size()
        top10_abuser = abuse_df.nlargest(10, 'size')
        self.top10_abuser_grp = top10_abuser
        #sets the top abuser as a property of the class
        tp_abuser = top10_abuser.iloc[0,0]
        return tp_abuser
    
    #plot a graph of the top abusers
    def plot_top_abusers(self):
        top10_abuser = self.top10_abuser_grp
        fig,ax = plt.subplots()
        fig.autofmt_xdate()
        sns.barplot(x='abuser',y='size',data=top10_abuser, ax=ax);
        
    # Get the proportion of the top abusers contribution to the total reported cases
    def get_top_abusers_contr(self):
        abuse_df = self.data.groupby('abuser', as_index=False).size()
        totl_abuser_rept = abuse_df['size'].sum()
        top_10_abuse_df = abuse_df.nlargest(10,'size')
        total_tp10 = top_10_abuse_df['size'].sum()
        contr_abuser = round(total_tp10/totl_abuser_rept,3)
        # sets the contribution of the top 10 abuser to the total reports
        return contr_abuser

