import random, math, roman
from gametext import appearance_outcomes, base_string

class Weather:
    name = "Sunny"
    emoji = "🌞"
    duration_range = [3,5]

    def __init__(self, game):
        pass    

    def __str__(self):
        return f"{self.emoji} {self.name}"

    def set_duration(self):
        pass

    def modify_atbat_stats(self, player_rolls):
        # Activates before batting
        pass

    def modify_steal_stats(self, roll):
        pass

    def modify_atbat_roll(self, outcome, roll, defender):
        # activates after batter roll
        pass

    def activate(self, game, result):
        # activates after the batter calculation. modify result, or just return another thing
        pass

    def on_choose_next_batter(self, game):
        pass

    def on_flip_inning(self, game):
        pass

    def modify_top_of_inning_message(self, game, state):
        pass

    def modify_atbat_message(self, game, state):
        pass

    def modify_gamestate(self, game, state):
        pass

    def modify_game_end_message(self, game, state):
        pass


class Supernova(Weather):
    name = "Supernova"
    emoji = "🌟"
    duration_range = [1,2]

    def modify_atbat_stats(self, roll):
        roll["pitch_stat"] *= 0.8

class Midnight(Weather):
    name = "Midnight"
    emoji = "🕶"
    duration_range = [1,1]

    def modify_steal_stats(self, roll):
        roll["run_stars"] *= 2

class SlightTailwind(Weather):
    name = "Slight Tailwind"
    emoji = "🏌️‍♀️"
    duration_range = [1,2]

    def activate(self, game, result):

        if "mulligan" not in game.last_update[0].keys() and not result["ishit"] and result["text"] != appearance_outcomes.walk: 
            mulligan_roll_target = -((((game.get_batter().stlats["batting_stars"])-5)/6)**2)+1
            if random.random() > mulligan_roll_target and game.get_batter().stlats["batting_stars"] <= 5:
                result.clear()
                result.update({
                    "text": f"{game.get_batter()} would have gone out, but they took a mulligan!",
                    "mulligan": True,
                    "text_only": True,
                    "weather_message": True,
                })

class Starlight(Weather):
    name = "Starlight"
    emoji = "🌃"
    duration_range = [2,2]

    def activate(self, game, result):

        if (result["text"] == appearance_outcomes.homerun or result["text"] == appearance_outcomes.grandslam):
            result["weather_message"] = True
            dinger_roll = random.random()
            if "dragon" in game.get_batter().name.lower():
                result["dragin_the_park"] = True

            elif dinger_roll < 0.941:
                result.clear()
                result.update({
                    "text": f"{game.get_batter()} hits a dinger, but the stars do not approve! The ball pulls foul.",
                    "text_only": True,
                    "weather_message": True
                })
            else:
                result["in_the_park"] = True


    def modify_atbat_message(self, game, state):
        result = game.last_update[0]
        if "in_the_park" in result.keys():
            state["update_text"] = f"The stars are pleased with {result['batter']}, and allow a dinger! {game.last_update[1]} runs scored!"
        elif "dragin_the_park" in result.keys():
            state["update_text"] = f"The stars enjoy watching dragons play baseball, and allow {result['batter']} to hit a dinger! {game.last_update[1]} runs scored!"
               

class Blizzard(Weather):
    name = "Blizzard"
    emoji = "❄"
    duration_range = [2,3]

    def __init__(self, game):
        self.counter_away = random.randint(0,len(game.teams['away'].lineup)-1)
        self.counter_home = random.randint(0,len(game.teams['home'].lineup)-1)

        self.swapped_batter_data = None

    def activate(self, game, result):        
        if self.swapped_batter_data:
            original, sub = self.swapped_batter_data
            self.swapped_batter_data = None
            result.clear()
            result.update({
                "snow_atbat": True,
                "text": f"{original.name}'s hands are too cold! {sub.name} is forced to bat!",
                "text_only": True,
                "weather_message": True,
            })

    def on_flip_inning(self, game):
        if game.top_of_inning and self.counter_away < game.teams["away"].lineup_position:
            self.counter_away = self.pitcher_insert_index(game.teams["away"])

        if not game.top_of_inning and self.counter_home < game.teams["home"].lineup_position:
            self.counter_home = self.pitcher_insert_index(game.teams["home"])

    def pitcher_insert_index(self, this_team):
        rounds = math.ceil(this_team.lineup_position / len(this_team.lineup))
        position = random.randint(0, len(this_team.lineup)-1)
        return rounds * len(this_team.lineup) + position

    def on_choose_next_batter(self, game):
        if game.top_of_inning:
            bat_team = game.teams["away"]
            counter = self.counter_away
        else:
            bat_team = game.teams["home"]
            counter = self.counter_home

        if bat_team.lineup_position == counter:
            self.swapped_batter_data = (game.current_batter, bat_team.pitcher) # store this to generate the message during activate()
            game.current_batter = bat_team.pitcher

class Twilight(Weather):
    name = "Twilight"
    emoji = "👻"
    duration_range = [2,3]

    def modify_atbat_roll(self, outcome, roll, defender):
        error_line = - (math.log(defender.stlats["defense_stars"] + 1)/50) + 1
        error_roll = random.random()
        if error_roll > error_line:
            outcome["error"] = True
            outcome["weather_message"] = True
            outcome["defender"] = defender
            roll["pb_system_stat"] = 0.1

    def modify_atbat_message(self, this_game, state):
        result = this_game.last_update[0]
        if "error" in result.keys():
            state["update_text"] = f"{result['batter']}'s hit goes ethereal, and {result['defender']} can't catch it! {result['batter']} reaches base safely."
            if this_game.last_update[1] > 0:
                state["update_text"] += f" {this_game.last_update[1]} runs scored!"

class ThinnedVeil(Weather):
    name = "Thinned Veil"
    emoji = "🌌"
    duration_range = [1,3]

    def activate(self, game, result):
        if result["ishit"]:
           if result["text"] == appearance_outcomes.homerun or result["text"] == appearance_outcomes.grandslam:
                result["veil"] = True

    def modify_atbat_message(self, game, state):
        if "veil" in game.last_update[0].keys():
            state["update_emoji"] = self.emoji    
            state["update_text"] += f" {game.last_update[0]['batter']}'s will manifests on {base_string(game.last_update[1])} base."

class HeatWave(Weather):
    name = "Heat Wave"
    emoji = "🌄"
    duration_range = [2,3]

    def __init__(self,game):
        self.counter_away = random.randint(2,4)
        self.counter_home = random.randint(2,4)

        self.swapped_pitcher_data = None

    def on_flip_inning(self, game):
        original_pitcher = game.get_pitcher()
        if game.top_of_inning:
            bat_team = game.teams["home"]
            counter = self.counter_home
        else:
            bat_team = game.teams["away"]
            counter = self.counter_away

        if game.inning == counter:
            if game.top_of_inning:
                self.counter_home = self.counter_home - (self.counter_home % 5) + 5 + random.randint(1,4) #rounds down to last 5, adds up to next 5. then adds a random number 2<=x<=5 to determine next pitcher                       
            else:
                self.counter_away = self.counter_away - (self.counter_away % 5) + 5 + random.randint(1,4)      

            #swap, accounting for teams where where someone's both batter and pitcher
            tries = 0
            while game.get_pitcher() == original_pitcher and tries < 3:
                bat_team.set_pitcher(use_lineup = True)
                tries += 1
            if game.get_pitcher() != original_pitcher:
                self.swapped_pitcher_data = (original_pitcher, game.get_pitcher())

    def modify_top_of_inning_message(self, game, state):
        if self.swapped_pitcher_data:
            original, sub = self.swapped_pitcher_data
            self.swapped_pitcher_data = None
            state["update_emoji"] = self.emoji
            state["update_text"] += f' {original} is exhausted from the heat. {sub} is forced to pitch!'
             
                

class Drizzle(Weather):
    name = "Drizzle"
    emoji = "🌧"
    duration_range = [2,3]

    def on_flip_inning(self, game):
        if game.top_of_inning:
            next_team = "away"
        else:
            next_team = "home"

        lineup = game.teams[next_team].lineup
        game.bases[2] = lineup[(game.teams[next_team].lineup_position-1) % len(lineup)]

    def modify_top_of_inning_message(self, game, state):
        if game.top_of_inning:
            next_team = "away"
        else:
            next_team = "home"

        placed_player = game.teams[next_team].lineup[(game.teams[next_team].lineup_position-1) % len(game.teams[next_team].lineup)]

        state["update_emoji"] = self.emoji
        state["update_text"] += f' Due to inclement weather, {placed_player.name} is placed on second base.'

class Breezy(Weather):
    name = "Breezy"
    emoji = "🎐"
    duration_range = [1,3]

    def __init__(self, game):       
        self.activation_chance = 0.08

    def activate(self, game, result):
        if random.random() < self.activation_chance:
            teamtype = random.choice(["away","home"])
            team = game.teams[teamtype]
            player = random.choice(team.lineup)
            player.stlats["batting_stars"] = player.stlats["pitching_stars"]
            player.stlats["pitching_stars"] = player.stlats["baserunning_stars"]
            old_player_name = player.name

            if not hasattr(player, "stat_name"):
                player.stat_name = old_player_name

            if ' ' in player.name:
                names = player.name.split(" ")
                try:
                    first_first_letter = names[0][0]
                    last_first_letter = names[-1][0]
                    names[0] = last_first_letter + names[0][1:]
                    names[-1] = first_first_letter + names[-1][1:]
                    player.name = ' '.join(names)
                except:
                    first_letter = player.name[0]
                    last_letter = player.name[-1]
                    player.name = last_letter + player.name[1:-1] + first_letter
            else:
                #name is one word, so turn 'bartholemew' into 'martholemeb'
                first_letter = player.name[0]
                last_letter = player.name[-1]
                player.name = last_letter + player.name[1:-1] + first_letter

            book_adjectives = ["action-packed", "historical", "mystery", "thriller", "horror", "sci-fi", "fantasy", "spooky","romantic"]
            book_types = ["novel", "novella", "poem", "anthology", "fan fiction", "autobiography"]
            book = "{} {}".format(random.choice(book_adjectives),random.choice(book_types))

            result.clear()
            result.update({
                "text": "{} stopped to enjoy a {} in the nice breeze! {} is now {}!".format(old_player_name, book, old_player_name, player.name),
                "text_only": True,
                "weather_message": True
            })

class MeteorShower(Weather):
    name = "Meteor Shower"
    emoji = "🌠"
    duration_range = [1,3]

    def __init__(self, game):
        self.activation_chance = 0.13

    def activate(self, game, result):
        if random.random() < self.activation_chance and game.occupied_bases() != {}:
            base, runner = random.choice(list(game.occupied_bases().items()))
            runner = game.bases[base]
            game.bases[base] = None

            if game.top_of_inning:
                bat_team = game.teams["away"]
            else:
                bat_team = game.teams["home"]

            bat_team.score += 1
            result.clear()
            result.update({
                    "text": f"{runner.name} wished upon one of the shooting stars, and was warped to None base!! 1 runs scored!",
                    "text_only": True,
                    "weather_message": True
                })

class Hurricane(Weather):
    name = "Hurricane"
    emoji = "🌀"
    duration_range = [1,1]

    def __init__(self, game):
        self.swaplength = random.randint(2,4)
        self.swapped = False

    def on_flip_inning(self, game):
        if game.top_of_inning and (game.inning % self.swaplength) == 0:
            self.swaplength = random.randint(2,4)
            self.swapped = True

    def modify_top_of_inning_message(self, game, state):
        if self.swapped:
            game.teams["home"].score, game.teams["away"].score = (game.teams["away"].score, game.teams["home"].score) #swap scores
            state["away_score"], state["home_score"] = (game.teams["away"].score, game.teams["home"].score)
            state["update_emoji"] = self.emoji
            state["update_text"] += " The hurricane rages on, flipping the scoreboard!"
            self.swapped = False

class Tornado(Weather):
    name = "Tornado"
    emoji = "🌪"
    duration_range = [1,2]

    def __init__(self, game):
        self.activation_chance = 0.33
        self.counter = 0

    def activate(self, game, result):
        if self.counter == 0 and random.random() < self.activation_chance and game.occupied_bases() != {}:
            runners = list(game.bases.values())
            current_runners = runners.copy()
            self.counter = 5
            while runners == current_runners and self.counter > 0:
                random.shuffle(runners)
                self.counter -= 1
            for index in range(1,4):
                game.bases[index] = runners[index-1]

            result.clear()
            result.update({
                    "text": f"The tornado sweeps across the field and pushes {'the runners' if len(game.occupied_bases().values())>1 else list(game.occupied_bases().values())[0].name} to a different base!",
                    "text_only": True,
                    "weather_message": True
                })
            self.counter = 2

        elif self.counter > 0:
            self.counter -= 1
            


class Downpour(Weather):
    name = "Torrential Downpour"
    emoji = '⛈'
    duration_range = [1,1]

    def __init__(self, game):
        self.target = game.max_innings
        self.name = f"Torrential Downpour: {roman.roman_convert(str(self.target))}"
        self.emoji = '⛈'
        

    def on_flip_inning(self, game):
        high_score = game.teams["home"].score if game.teams["home"].score > game.teams["away"].score else game.teams["away"].score
        if high_score >= self.target and game.teams["home"].score != game.teams["away"].score:
            game.max_innings = game.inning
        else:
            game.max_innings = game.inning + 1

    def modify_gamestate(self, game, state):
        state["max_innings"] = "∞"

    def modify_top_of_inning_message(self, game, state):
        state["update_emoji"] = self.emoji
        state["update_text"] = "The gods are not yet pleased. Play continues through the storm."

    def modify_game_end_message(self, game, state):
        state["update_emoji"] = self.emoji
        state["update_text"] = f"{self.target} runs are reached, pleasing the gods. The storm clears."
            

def all_weathers():
    weathers_dic = {
            "Supernova" : Supernova,
            "Midnight": Midnight,
            "Slight Tailwind": SlightTailwind,
            "Blizzard": Blizzard,
            "Twilight" : Twilight, 
            "Thinned Veil" : ThinnedVeil,
            "Heat Wave" : HeatWave,
            "Drizzle" : Drizzle,
            "Breezy": Breezy,
            "Starlight" : Starlight,
            "Meteor Shower" : MeteorShower,
            "Hurricane" : Hurricane,
            "Tornado" : Tornado,
            "Torrential Downpour" : Downpour
        }
    return weathers_dic

class WeatherChains():
    light = [SlightTailwind, Twilight, Breezy, Drizzle] #basic starting points for weather, good comfortable spots to return to
    magic = [Twilight, ThinnedVeil, MeteorShower, Starlight] #weathers involving breaking the fabric of spacetime
    sudden = [Tornado, Hurricane, Twilight, Starlight, Midnight, Downpour] #weathers that always happen and leave over 1-3 games
    disaster = [Hurricane, Tornado, Downpour, Blizzard] #storms
    aftermath = [Midnight, Starlight, MeteorShower] #calm epilogues

    dictionary = {
            #Supernova : (magic + sudden + disaster, None), supernova happens leaguewide and shouldn't need a chain, but here just in case
            Midnight : ([SlightTailwind, Breezy, Drizzle, Starlight, MeteorShower, HeatWave],[2,2,2,4,4,1]),
            SlightTailwind : ([Breezy, Drizzle, Tornado], [3,3,1]),
            Blizzard : ([Midnight, Starlight, MeteorShower, Twilight, Downpour], [2,2,2,2,4]),
            Twilight : ([ThinnedVeil, Midnight, MeteorShower, SlightTailwind], [2,4,2,1]),
            ThinnedVeil : (light, None),
            HeatWave : ([Tornado, Hurricane, SlightTailwind, Breezy],[4,4,1,1]),
            Drizzle : ([Hurricane, Downpour, Blizzard],[2,2,1]),
            Breezy : ([Drizzle, HeatWave, Blizzard, Tornado], [3,3,1,1]),
            Starlight : ([SlightTailwind, Twilight, Breezy, Drizzle, ThinnedVeil, HeatWave], None),
            MeteorShower : ([Starlight, ThinnedVeil, HeatWave], None),
            Hurricane : ([Midnight, Starlight, MeteorShower, Twilight, Downpour], [2,2,2,2,4]),
            Tornado : ([Midnight, Starlight, MeteorShower, Twilight, Downpour],[2,2,2,2,4]),
            Downpour : (aftermath, None)
        }

    chains = [
            [Hurricane, Drizzle, Hurricane]
        ]

    def chain_weather(weather_instance):
        #weather_type = type(weather_instance)
        weather_type = weather_instance
        options, weight = WeatherChains.dictionary[weather_type]
        return random.choices(options, weights = weight)[0]

    def parent_weathers(weather_type):
        parents = []
        for this_weather, (children, _) in WeatherChains.dictionary.items():
            if weather_type in children:
                parents.append(this_weather)
        return parents

    def starting_weather():
        return random.choice(WeatherChains.light + WeatherChains.magic)

    def debug_weathers():
        names = ["a.txt", "b.txt", "c.txt"]
        for name in names:
            current = random.choice(list(all_weathers().values()))
            out = ""
            for i in range(0,50):
                out += f"{current.name} {current.emoji}\n"
                current = WeatherChains.chain_weather(current)
            
            with open("data/"+name, "w", encoding='utf-8') as file:
                file.write(out)