from os.path import exists
import random, uuid
from datetime import datetime, timedelta
import pandas as pd
from pygdg.common import *
from pygdg.options import *

class PlayerEvent:
    """A player event class. It represents an event generated by a player during a game session."""
    
    def __init__(self, cohort_id, player_id, player_type, session_id, event_type, timestamp):
        self.id = uuid.uuid4()
        self.cohort_id = cohort_id
        self.player_id = player_id
        self.player_type = player_type
        self.session_id = session_id
        self.event_type = event_type
        self.timestamp = timestamp
    
    def to_dict(self):
        return {
            PlayerEventField.id.name : [self.id.hex],
            PlayerEventField.cohort_id.name : [self.cohort_id.hex],
            PlayerEventField.player_id.name : [self.player_id.hex],
            PlayerEventField.player_type.name : [self.player_type],
            PlayerEventField.session_id.name : [self.session_id.hex],
            PlayerEventField.event_type.name : [self.event_type.name],
            PlayerEventField.timestamp.name : [self.timestamp.isoformat()],
        }

    def to_dataframe(self):
        return pd.DataFrame.from_dict(self.to_dict())

class SessionActivity:
 
    def __init__(self, cohort_id, player_id, player_type, session_id, session_start_date, session_options):
        self.cohort_id = cohort_id
        self.player_id = player_id
        self.player_type = player_type
        self.session_id = session_id
        self.session_start_date = session_start_date
        self.session_options = session_options

    def generate_events(self):

        session_datetime = self.session_start_date + self.session_options.time()
        session_duration = self.session_options.duration()

        begin_session = PlayerEvent(
            self.cohort_id, 
            self.player_id, 
            self.player_type, 
            self.session_id, 
            PlayerEventType.BEGIN_SESSION, 
            session_datetime
        )

        # TODO: add events

        end_session = PlayerEvent(
            self.cohort_id, 
            self.player_id, 
            self.player_type, 
            self.session_id, 
            PlayerEventType.END_SESSION, 
            session_datetime + session_duration
        )
        
        events = [
            begin_session, 
            end_session
        ]

        return pd.concat(map(PlayerEvent.to_dataframe, events))        

class PlayerActivity:

    def __init__(self, cohort_id, player_id, player_type, player_options, player_start_date):
        self.cohort_id = cohort_id
        self.player_id = player_id
        self.player_type = player_type
        self.player_options = player_options
        self.player_start_date = player_start_date
        self.current_day = 0

    def generate_events(self):

        lifetime_weight = self.player_options.lifetime[self.current_day]

        if (lifetime_weight <= 0):
            return None

        sessions_dataframes = []
        session_date = datetime.combine(
            self.player_start_date.date() + timedelta(days=self.current_day), 
            datetime.min.time()
        )
        session_weekday = WeekDay(session_date.weekday())

        if session_weekday in self.player_options.sessions_options:

            sessions_options = self.player_options.sessions_options[session_weekday]

            for session_options in sessions_options:

                weight = lifetime_weight * sessions_options[session_options]

                if (random.random() < weight):
                    session_id = uuid.uuid4()
                    session_activity = SessionActivity(
                        self.cohort_id, 
                        self.player_id, 
                        self.player_type, 
                        session_id, 
                        session_date, 
                        session_options
                    )
                    session_dataframe = session_activity.generate_events()
                    sessions_dataframes.append(session_dataframe)

        self.current_day += 1

        if len(sessions_dataframes) == 0:
            return pd.DataFrame()
        else:
            return pd.concat(sessions_dataframes)  

class GameActivity:

    def __init__(self, game_options, start_date):
        self.game_options = game_options
        self.start_date = start_date
        self.current_day = 0

    def generate_events(self):

        # update progress bar
        
        print_progress_bar(self.current_day, self.game_options.simulation_days, prefix = 'generating events:', suffix = '', length = 50)

        player_activities = []
        players_dataframes = []

        while self.current_day < self.game_options.simulation_days:

            # handle old players activities

            old_players_activities = player_activities
            player_activities = []

            for player_activity in old_players_activities:

                player_dataframe = player_activity.generate_events()

                if player_dataframe is not None:
                    players_dataframes.append(player_dataframe)
                    player_activities.append(player_activity)

            # handle new players activities

            current_new_player = int(self.game_options.players_acquisition[self.current_day])

            cohort_id =uuid.uuid4()

            while current_new_player > 0:

                player_options = self.game_options[random.random()]

                player_id = uuid.uuid4()
                player_type = player_options.player_type
                player_start_date = self.start_date + timedelta(days=self.current_day)

                player_activity = PlayerActivity(
                    cohort_id,
                    player_id, 
                    player_type,
                    player_options, 
                    player_start_date
                )
                player_dataframe = player_activity.generate_events()

                if player_dataframe is not None:
                    players_dataframes.append(player_dataframe)
                    player_activities.append(player_activity)

                current_new_player -= 1
        
            self.current_day += 1

            # update progress bar

            print_progress_bar(self.current_day, self.game_options.simulation_days, prefix = 'generating events:', suffix = '', length = 50)

        return pd.concat(players_dataframes)  

def generate(filename, date, players, days, seed, plot, overwrite, debug):

    # set seed

    random.seed(seed)

    # generate events
    
    events_file = f'{filename}.csv'
    
    if not exists(events_file) or overwrite:
        
        game_activity = GameActivity(
            default_game_options(players, days), 
            date
        )
    
        events_dataframe = game_activity.generate_events()
    
        if events_dataframe.size > 0:
            events_dataframe[PlayerEventField.timestamp.name] = pd.to_datetime(events_dataframe[PlayerEventField.timestamp.name])
            events_dataframe = events_dataframe.sort_values(by=[PlayerEventField.timestamp.name])
            
        print('storing events...')
        events_dataframe.to_csv(events_file, index=False)
        print(f'events stored in {events_file}!')
        
    else:
    
        print(f'{events_file} already exists, use --overwrite to replace the current events!')

    # plot events

    if plot:
        
        player_index = 'player_index'
        events_by_players = events_dataframe.groupby([PlayerEventField.player_id.name])
        
        for index, (player_id, group) in enumerate(events_by_players):
            events_dataframe.loc[events_dataframe[PlayerEventField.player_id.name] == player_id, player_index] = int(index)

        first_event_timestamp = events_dataframe[PlayerEventField.timestamp.name].min()
        last_event_timestamp = events_dataframe[PlayerEventField.timestamp.name].max()
        first_event_datetime = datetime(first_event_timestamp.year, first_event_timestamp.month, first_event_timestamp.day)
        last_event_datetime = datetime(last_event_timestamp.year, last_event_timestamp.month, last_event_timestamp.day+1)
        first_player_index = int(events_dataframe[player_index].min())
        last_player_index = int(events_dataframe[player_index].max()) + 2
        
        plot = events_dataframe.plot.scatter(
            x=PlayerEventField.timestamp.name, y=player_index, 
            title='Players events', xlabel="Days", ylabel='Players',
            xticks=pd.date_range(first_event_datetime, last_event_datetime),
            yticks=range(first_player_index, last_player_index, 1 + (last_player_index-first_player_index) // 20),
            rot=20
        )
        
        print('plot events...')
        plot_file = f'{filename}.png'
        plot.get_figure().savefig(plot_file)
        print(f'events plotted in {plot_file}!')