import arcade 
import os
from data import *
from player import *
from beer import *
from securitas import *
from vomit import *
from SuperVomit import *
from map import *
from menu import *
from menus_items import *
import os
import pickle
import re
import operator

# Function which allow to recover highscores saved
def take_scores():
  
    if os.path.exists(HIGHSCORE_FILE) and os.path.getsize(HIGHSCORE_FILE) > 0: # File exists
        highscore_file = open(HIGHSCORE_FILE, "rb")
        mon_depickler = pickle.Unpickler(highscore_file)
        scores = mon_depickler.load()
        highscore_file.close()
    else: # File does not exist
        scores = {}
    return scores

# Function which allow to save highscores updated
def save_score(scores):
  highscore = open(HIGHSCORE_FILE, "wb") # On écrase les anciens scores
  mon_pickler = pickle.Pickler(highscore)
  mon_pickler.dump(scores)
  highscore.close()

class Game(arcade.Window):

  def __init__(self,screen_width=SCREEN_WIDTH,screen_height=SCREEN_HEIGHT):
    super().__init__(screen_width, screen_height, fullscreen=FULLSCREEN)
    
    self.player = None
    
    # To be responsive
    self.screen_width,self.screen_height = self.get_size()

    # Sprites
    self.username="Coco & BD"
    self.player = None   # player object

    self.beer_list= None # list of beers objects

    self.current_beer_number = None

    self.securitas_list = None # list of securistas objects

    self.vomit_list=None # list of vomits object
    self.super_vomit_list=None # list of super vomits object

    # Windows 
    self.game_over = False
    self.game_over_isHighscore=False
    self.menu = True
    self.highscores = False

    # Menu creation
    self.main_menu = Menu(MAIN_MENU[0], MAIN_MENU[1], self.screen_width, self.screen_height)
    self.high_scores_menu = Menu(HIGHSCORES_MENU[0], HIGHSCORES_MENU[1], self.screen_width, self.screen_height)
    self.high_score_menu = Menu(HIGHSCORE_MENU[0], HIGHSCORE_MENU[1], self.screen_width, self.screen_height)
    self.game_over_menu = Menu(GAME_OVER_MENU[0], GAME_OVER_MENU[1], self.screen_width, self.screen_height)
    
    # Background image will be stored in this variable
    self.background = None

    # Set the working directory (where we expect to find files) to the same
    # directory this .py file is in.
    file_path = os.path.dirname(os.path.abspath(__file__))
    os.chdir(file_path)
  
  def setup(self):
    # Player setup
    self.player = player("img/player.png",self.screen_width/2,self.screen_height/2)

    # Sprite lists SETUP
    self.beer_list=arcade.SpriteList()
    self.vomit_list=arcade.SpriteList()
    self.super_vomit_list = arcade.SpriteList()

    beer_sprite=beer(self.screen_width, self.screen_height, self.screen_width//2, self.screen_height//2)
    self.current_beer_number = 1
    self.beer_list.append(beer_sprite)

    securitas_1 = Securitas("img/securitas.png",self.screen_width // 2 + 250,self.screen_height // 2 - 100,False)
    securitas_2 = Securitas("img/securitas.png",self.screen_width // 2 - 250,self.screen_height // 2 + 250,False)
    self.securitas_list=arcade.SpriteList()
    self.securitas_list.append(securitas_1)
    self.securitas_list.append(securitas_2)

    # Background loaded
    self.background = arcade.load_texture("img/map1_1280.png")

    # Window SETUP
    self.score=0
    self.total_time=0

    self.map = Map("maps/map1_wall.csv", self.screen_width, self.screen_height)

    self.physic_engines_list = list()

    self.physic_engines_list.append(arcade.PhysicsEngineSimple(self.player,self.map.wall_list))

    # Physic engines setup
    self.physic_engines_list.append(arcade.PhysicsEngineSimple(securitas_1,self.map.wall_list))
    self.physic_engines_list.append(arcade.PhysicsEngineSimple(securitas_2,self.map.wall_list))

  def on_draw(self): 
   
    arcade.start_render()

    # GameOver display
    if self.game_over :

      score=self.score*self.total_time

      # Game over with highscore display
      if self.game_over_isHighscore:
        output_position = "0" # TODO Implement position
        self.high_score_menu.addItem(POSITION_ITEM[0], POSITION_ITEM[1],POSITION_ITEM[2], output_position, POSITION_ITEM[4], POSITION_ITEM[5])
        output_score = f"{int(self.score)}"
        self.high_score_menu.addItem(SCORE_ITEM[0], SCORE_ITEM[1],SCORE_ITEM[2], output_score, SCORE_ITEM[4], SCORE_ITEM[5])
        self.high_score_menu.draw()

      # Game over witout highscore display
      else :
        output_score = f"{int(self.score)}"
        self.game_over_menu.addItem(SCORE_GAME_OVER_ITEM[0], SCORE_GAME_OVER_ITEM[1],SCORE_GAME_OVER_ITEM[2],
          output_score, SCORE_GAME_OVER_ITEM[4], SCORE_GAME_OVER_ITEM[5])
        self.game_over_menu.draw()
      

    # Menu display
    elif (self.menu):
      # Draw menu with specific values
      self.main_menu.draw()

    # Highscore display
    elif (self.highscores):
      # TODO: need to be improve because we parse highscore file in every draw() call ... Git blame JP :)
      list_highscore=take_scores()
      rank=1
      sorted_highscore=sorted(list_highscore.items(),key=operator.itemgetter(1),reverse=True)
      for pair in sorted_highscore :
        keys=re.sub('@','',pair[0])
        output=str(rank)+ " : " + keys + "  "+ str(pair[1])# + keys 
        self.high_scores_menu.addItem(INITIAL_HIGHSCORES[0], INITIAL_HIGHSCORES[1] - rank * (INITIAL_HIGHSCORES[2] + 10) / self.screen_height, 
          INITIAL_HIGHSCORES[2], output, INITIAL_HIGHSCORES[4], INITIAL_HIGHSCORES[5])
        rank+=1

      self.high_scores_menu.draw()

    # Game display
    else:
      # Background
      arcade.draw_texture_rectangle(self.screen_width // 2, self.screen_height // 2,
                                      self.screen_width, self.screen_height, self.background)
      
      # Sprites display
      self.beer_list.draw()
      self.vomit_list.draw()
      self.super_vomit_list.draw()
      self.securitas_list.draw()
      self.player.draw()
      self.map.wall_list.draw()
      

      # Current time calculation
      minutes=int(self.total_time)//60
      seconds=int(self.total_time)%60
  
      # Score and current time display
      output_score=f"Score: {int(self.score*self.total_time)}"
      output_time=f"Time: {minutes:02d}:{seconds:02d}"
      arcade.draw_text(output_score,620,100,arcade.color.WHITE,18)
      arcade.draw_text(output_time,620,50,arcade.color.WHITE,18)

  def update(self, delta_time):
    # During the game
    if (self.game_over == False and self.menu==False and self.highscores==False):
      
      # Current time update
      self.total_time+=delta_time

      # Beers update
      self.beer_list.update()

      # vomits update
      for vomit_sprites in self.vomit_list : 
        vomit_sprites.life-=delta_time
        if vomit_sprites.life<0:
          vomit_sprites.kill()

      # super vomit life update
      for super_vomit in self.super_vomit_list : 
        super_vomit.update()
        super_vomit.life-=delta_time
        if super_vomit.life<0:
          super_vomit.kill()

      # Player update
      self.player.update(delta_time)
      

      # Change picture if drunk
      if self.player.BAC >= DRUNK_LEVEL_PLAYER and self.player.isDrunk == False:
        newPlayer = player("img/player_drunk.png",self.player.center_x,self.player.center_y,True)
        newPlayer.change_x = self.player.change_x
        newPlayer.change_y = self.player.change_y
        newPlayer.invincible = self.player.invincible
        newPlayer.invincible_time = self.player.invincible_time
        # self.player.kill()
        self.kill_properly(self.player, newPlayer)
        self.player = newPlayer

        
      # Securitas update img
      for secu in self.securitas_list:
        if secu.BAC >= ANGRY_SECURITAS_BAC and secu.isAngry == False:
          newSecuritas = Securitas("img/securitas_angry.png", secu.center_x, secu.center_y, True)
          self.kill_properly(secu, newSecuritas)
          self.securitas_list.append(newSecuritas)
        elif secu.BAC < ANGRY_SECURITAS_BAC and secu.isAngry:
          newSecuritas = Securitas("img/securitas.png", secu.center_x, secu.center_y)
          self.kill_properly(secu, newSecuritas)
          self.securitas_list.append(newSecuritas) 

      # Securitas update   
      for secu in self.securitas_list:
        secu.update(delta_time)
        secu.check_if_player_around(self.player)
        secu.check_if_charge_player(self.player)

      # PHYSIC ENGINE UPDATE
      for physics_engine in self.physic_engines_list:
        physics_engine.update()

      # CHECK SECURITAS BLOCK IN WALLS AND CHANGE THEIR DIRECTION (MUST BE AFTER PHYSIC UPDATE AND SECURITAS UPDATE)
      for securitas in self.securitas_list:
        securitas.check_for_physic_engine_block()
        

      # Handler of collision securitas sprites with statics sprite
      for securitas_sprite in self.securitas_list:
        secu_vomit_hit_list = arcade.check_for_collision_with_list(securitas_sprite,self.vomit_list)
        secu_beer_hit_list = arcade.check_for_collision_with_list(securitas_sprite,self.beer_list)
        if len(secu_vomit_hit_list)>0:
          securitas_sprite.static_time=TIME_STATIC
          securitas_sprite.can_move=False
          securitas_sprite.change_x=0
          securitas_sprite.change_y=0
          securitas_sprite.BAC = 0
        for vomit_sprites in secu_vomit_hit_list:
          vomit_sprites.kill()
        for beer_sprites in secu_beer_hit_list:
          beer_sprites.kill()
          securitas_sprite.BAC+=1

      # Handle collision between super vomit and securitas
      for secu in self.securitas_list:
        secu_super_vomit_hit_list = arcade.check_for_collision_with_list(secu, self.super_vomit_list)
        if len(secu_super_vomit_hit_list) > 0 : 
          secu.static_time = SUPER_TIME_STATIC
          secu.can_move = False
          secu.change_x = 0
          secu.change_y = 0
          secu.BAC = 0
          for super_vomit_being_hit in secu_super_vomit_hit_list:
            self.score += super_vomit_being_hit.power * SUPER_DRUNK_SCORE_MULTIPLICATOR
            super_vomit_being_hit.kill()
      
      # generate current_beer_number 
      if self.total_time//BEER_GENERATION_COEFF<1:
        if self.total_time > 30 and self.total_time < 60:
          self.current_beer_number = 2
        else:
          self.current_beer_number = 1 
      else :
         self.current_beer_number = self.total_time//BEER_GENERATION_COEFF

      # Beer boost 
      self.current_beer_number += (self.player.BAC//BEER_BOOST_COEFF if self.player.BAC//BEER_BOOST_COEFF <= MAX_BAC_BOOST else SECOND_STEP_MAX_BAC_BOOST if self.total_time > TOTAL_TIME_SECOND_STEP else MAX_BAC_BOOST)

      # Beer generation
      if len(self.beer_list) < self.current_beer_number and random.randint(0, BEER_DELAY) == 2:
        newBeer = beer(self.screen_width, self.screen_height)
        self.beer_list.append(newBeer) 

      # Handle beers collapsed by player
      beer_hit_list = arcade.check_for_collision_with_list(self.player,self.beer_list)
      for beer_sprite_hit in beer_hit_list:
        beer_sprite_hit.kill()
        self.player.BAC+=1
 
      # generate current_beer_number 
      if self.total_time//BEER_GENERATION_COEFF<1:
        if self.total_time > 30 and self.total_time < 60:
          self.current_beer_number = 2
        else:
          self.current_beer_number = 1 
      else :
         self.current_beer_number = self.total_time//BEER_GENERATION_COEFF

      # Beer boost 
      self.current_beer_number += (self.player.BAC//BEER_BOOST_COEFF if self.player.BAC//BEER_BOOST_COEFF <= MAX_BAC_BOOST else SECOND_STEP_MAX_BAC_BOOST if self.total_time > TOTAL_TIME_SECOND_STEP else MAX_BAC_BOOST)

      # Beer generation
      if len(self.beer_list) < self.current_beer_number and random.randint(0, BEER_DELAY) == 2:
        newBeer = beer(self.screen_width, self.screen_height)
        self.beer_list.append(newBeer) 

      # Handle player if he walks on a vomit
      vomit_hit_list = arcade.check_for_collision_with_list(self.player,self.vomit_list) 
      for vommit in vomit_hit_list:
        if self.player.invincible == False:
          vommit.kill()
          self.player.can_move=False
          self.player.static_time=TIME_STATIC

      # Handle player collision with super vomit
      super_vomit_hit_list_with_player = arcade.check_for_collision_with_list(self.player, self.super_vomit_list)
      if len(super_vomit_hit_list_with_player) > 0 and self.player.invincible == False:
        self.player.can_move = False
        self.player.static_time = SUPER_TIME_STATIC
        for super_vomit_being_hit_by_player in super_vomit_hit_list_with_player:
          super_vomit_being_hit_by_player.kill()


      # Result of a collision between a player and a securitas
      # # GameOver menu opened
      # # highscore handler       
      securitas_hit_list = arcade.check_for_collision_with_list(self.player,self.securitas_list)
      if len(securitas_hit_list)>0 :
        self.game_over=True   
        self.score=self.score*self.total_time
        highscore=take_scores()
        if len(highscore)<10:
          self.game_over_isHighscore=True
          while self.username in highscore:
            self.username+="@"
          highscore[self.username]=int(self.score)
          save_score(highscore)
        else:
          if self.score>highscore[min(highscore,key=highscore.get)]:
            self.game_over_isHighscore=True
            del highscore[min(highscore,key=highscore.get)]
            while self.player.username in highscore:
              self.player.username+="@"
            highscore[self.username]=int(self.score)
            save_score(highscore)

  # Result of all key pressed possibility
  def on_key_press(self, key, modifiers):
    if(self.game_over==False, self.menu==False, self.highscores==False):
      if(self.player.can_move):
        if key == arcade.key.UP:
          self.player.change_y = MOVEMENT_SPEED
        if key == arcade.key.DOWN:
          self.player.change_y = -MOVEMENT_SPEED
        if key == arcade.key.LEFT:
          self.player.change_x = -MOVEMENT_SPEED
        if key == arcade.key.RIGHT:
          self.player.change_x = MOVEMENT_SPEED


  # Result of all key released possibility
  def on_key_release(self, key, modifiers):
    if(self.game_over==False, self.menu==False, self.highscores==False):
      if key == arcade.key.UP or key == arcade.key.DOWN:
        self.player.change_y = 0
      if key == arcade.key.LEFT or key == arcade.key.RIGHT:
        self.player.change_x = 0
      if key == arcade.key.TAB and self.player.BAC>0:
        self.score+=self.player.BAC
        if self.player.isSuperDrunk and (self.player.change_x != 0 or self.player.change_y != 0):
          super_vomit = SuperVomit(self.player.center_x, self.player.center_y, self.player.change_x, self.player.change_y, self.player.BAC)
          self.super_vomit_list.append(super_vomit)
        else:
          vomit_sprite=vomit(self.player.center_x,self.player.center_y)
          self.vomit_list.append(vomit_sprite)
        if self.player.isDrunk:
          newPlayer = player("img/player.png",self.player.center_x,self.player.center_y)
          newPlayer.change_x = self.player.change_x
          newPlayer.change_y = self.player.change_y 
          newPlayer.invincible = self.player.invincible
          newPlayer.invincible_time = self.player.invincible_time
          # self.player.kill()
          self.kill_properly(self.player, newPlayer)
          self.player = newPlayer
        else:
          self.player.BAC=0
        self.player.invincible = True
        self.player.invincible_time=TIME_INVINCIBLE
          
  # Mouse handler        
  def on_mouse_press(self,x,y,button,modifiers):
    
    # During GameOver display 
    if self.game_over :
      if self.game_over_isHighscore:
        for item in self.high_score_menu.items:
          if item.clickable and item.handleClick(x,y):
            if item.text == "Rejouer":
              self.setup()
              self.game_over = False
            elif item.text == "Highscores":
              self.game_over = False
              self.highscores = True
            elif item.text == "Menu":
              self.game_over=False
              self.menu=True
      else:
        for item in self.game_over_menu.items:
          if item.clickable and item.handleClick(x,y):
            if item.text == "Rejouer":
              self.setup()
              self.game_over = False
            elif item.text == "Highscores":
              self.game_over = False
              self.highscores = True
            elif item.text == "Menu":
              self.game_over=False
              self.menu=True
    
    # During menu display 
    elif self.menu:
      for item in self.main_menu.items:
        if item.clickable and item.handleClick(x,y):
          if item.text == "Jouer":
            self.setup()
            self.menu=False
          elif item.text == "Highscores":
            self.menu=False
            self.highscores=True
          elif item.text == "Quitter":
            arcade.quick_run(0.5)
    
    # During highscore display
    elif self.highscores:
      for item in self.high_scores_menu.items:
        if item.clickable and item.handleClick(x,y):
          if item.text == "Menu":
            self.highscores=False
            self.menu=True
    
  def kill_properly(self, sprite, newSprite):
    for physics_engine in self.physic_engines_list:
      if (physics_engine.player_sprite == sprite):
        self.physic_engines_list.remove(physics_engine)
    sprite.kill()
    self.physic_engines_list.append(arcade.PhysicsEngineSimple(newSprite, self.map.wall_list))
    

def main():
    game = Game()
    game.setup()
    arcade.run()

if __name__ == "__main__":
  main()
