# This is the main loop, which passes events to the Player objects.

import pygame
from constants import *

from pad import pad

from util import toRealTime
from player import Player
from announcer import Announcer

from pygame.sprite import RenderUpdates
from interface import *

from pygame.mixer import music
import fontfx
import gradescreen
import steps
import fileparsers
import games
import error
import colors
import records
import ui
import options

import os
import time

# A simple movie-playing sprite. It can only do MPEG1 though.
class BGMovie(pygame.sprite.Sprite):
  def __init__ (self, filename):
    pygame.sprite.Sprite.__init__(self)
    self.filename = filename
    self.image = pygame.surface.Surface([640, 480])
    
    self.movie = pygame.movie.Movie(filename)
    self.movie.set_display(self.image,[[0, 0], [640, 480]])
    self.rect = self.image.get_rect()
    self.rect.top = 0
    self.rect.left = 0
    self.oldframe = -1
    self.changed = False
    
  def resetchange(self):
    self.changed = False

  def update(self,curtime):
    curframe = int(curtime * 29.97)
    if self.oldframe != curframe:
      self.changed = True
      self.movie.render_frame(curframe)
      self.oldframe = curframe

# Display the current FPS and store the average FPS for the song.
class FPSDisp(pygame.sprite.Sprite):
  def __init__(self):
    pygame.sprite.Sprite.__init__(self)
    self.image = pygame.surface.Surface([1, 1])
    self._oldtime = -10000000
    self._clock = pygame.time.Clock()
    self._cycles = 1
    self._totalcount = 0
    self._font = FONTS[16]

  # Return the average of the average FPS rather than just the average
  # FPS. This avoids the average FPS shooting up incredibly fast at
  # the end of the song when no arrows are left.
  def fps(self):
    return self._totalcount / self._cycles

  def update(self, time):
    self._clock.tick()

    loops = int(self._clock.get_fps())
    self._totalcount += loops
    self._cycles += 1

    if (time - self._oldtime) > 1:
      text = "%d fps" % loops
      self.image = self._font.render(text, True, [160, 160, 160])
      self.rect = self.image.get_rect()
      self.rect.bottom = 480
      self.rect.right = 640
      self._oldtime = time

# Puts a little blinking square in the bottom corner, for use with
# a custom lighting setup. This doesn't deal with BPM changes correctly.
class Blinky(pygame.sprite.Sprite):
  def __init__ (self, bpm):
    pygame.sprite.Sprite.__init__(self)
    self.tick = toRealTime(bpm, 0.5)
    self.frame = 0
    self.oldframe = -100
    self.topimg = []
    
    im = pygame.surface.Surface([48, 40])
    im.fill([1, 1, 1])
    self.topimg.append(im.convert())
    self.topimg.append(im.convert())

    im.fill([255, 255, 255])
    for i in range(2):          
      self.topimg.append(im.convert())

    self.image = self.topimg[3]
    self.rect = self.image.get_rect()
    self.rect.top = 440
    self.rect.left = 592

  def update(self, time):
    self.frame = int(time / self.tick) % 8
    if self.frame > 3: self.frame = 3

    if self.frame != self.oldframe:
      self.image = self.topimg[self.frame]
      self.oldframe = self.frame

# Display the time at the top of the screen, in seconds.
class TimeDisp(pygame.sprite.Sprite):
  def __init__(self):
    pygame.sprite.Sprite.__init__(self)
    self._oldtime = -1
    self.image = pygame.surface.Surface([1, 1])
    self.rect = self.image.get_rect()
    self.rect.top = 0
    self.rect.centerx = 320
    self._font = FONTS[32]

  def update(self, time):
    if (time - self._oldtime) > 0.1: # Update 10 times a second at most
      time_str = "%0.1f" % time
      self.image = self._font.render(time_str, True, [224, 224, 224])
      self._oldtime = time
      self.rect = self.image.get_rect()
      self.rect.top = 0
      self.rect.centerx = 320

# The "Ready? Go!" that appears before most songs.
class ReadyGoSprite(pygame.sprite.Sprite):
  def __init__(self, time):
    pygame.sprite.Sprite.__init__(self)
    ready = os.path.join(pydance_path, "images", "ready.png")
    go = os.path.join(pydance_path, "images", "go.png")
    self._time = time
    self._ready = pygame.image.load(ready).convert()
    self._ready.set_colorkey(self._ready.get_at([0, 0]), RLEACCEL)
    self._go = pygame.image.load(go).convert()
    self._go.set_colorkey(self._go.get_at([0, 0]), RLEACCEL)
    self._pick_image(min(0, time))

  def update(self, cur_time):
    if cur_time > self._time: self.kill()
    elif self.alive(): self._pick_image(cur_time)

  def _pick_image(self, cur_time):
    ttl = self._time - cur_time # time to live
    if ttl < 0.25:
      self.image = self._go
      alpha = ttl / 0.25
    elif ttl < 0.750:
      self.image = self._go
      alpha = 1
    elif ttl < 1.00:
      self.image = self._go
      alpha = (1 - ttl) / 0.25
    elif ttl < 1.5:
      self.image = self._ready
      alpha = (ttl - 1.0) / 0.5
    elif cur_time < 0.5:
      self.image = self._ready
      alpha = cur_time / 0.5
    else:
      self.image = self._ready
      alpha = 1

    self.image.set_alpha(int(256 * alpha))
    self.rect = self.image.get_rect()
    self.rect.center = (320, 240)


class SongInfoScreen(InterfaceWindow):
  def __init__(self, song, diffs, playmode, songconf, configs, screen):
    self.song = song
    self.diffs = diffs
    self.playmode = playmode
    self.configs = configs
    self.songconf = songconf
    InterfaceWindow.__init__(self, screen, "songinfoscreen-bg.png")
    self._banner = BannerDisplay([190, 240])
    self._banner.set_song(SongItemDisplay(self.song))
    self.end = pygame.time.get_ticks() + 6000
    self._countdown = WrapTextDisplay(50, 68, [285,429], centered=True)
    
    self._player_widgets = []
    self._player_opts = []
    
    for pid in range(len(self.diffs)):
      self._player_widgets += self.player_widgets(pid)
      self._player_opts += self.player_opts(pid)

    self._other_widgets=[TextDisplay(50, [400, 50], [15, 35], "Next Song"),
                         TextDisplay(30, [263, 37], [15, 450], "Confirm: Begin   Cancel: Stop"),
                         TextDisplay(30, [263, 37], [380, 450], "Start: Change Options")]
    
    self._sprites.add([self._banner, self._countdown] +
                      self._player_widgets + self._player_opts +
                      self._other_widgets)
    
    pygame.display.update()
    ui.ui.clear()
    self.loop()
    
  def loop(self):
    ev = (0, ui.PASS)
    while ev not in [ui.CONFIRM, ui.CANCEL] and pygame.time.get_ticks() < self.end:
      self._countdown.set_text("%02d" % ((self.end - pygame.time.get_ticks()) / 1000,))
      self.update()
      pygame.time.wait(20)

      pid, ev = ui.ui.poll()
      if ev == ui.START:
        options.OptionScreen(self.configs, self.songconf, self._screen,
                             whitelist = [x[0] for x in changeable_between])
        
        for pid in range(len(self.diffs)):
          self._player_opts[pid].set_text(self.opt_summary(pid))
          
        self.end = pygame.time.get_ticks() + 11000
        self._screen.blit(self._bg, [0, 0])
        pygame.display.update()
        self.update()
          
      elif ev == ui.SCREENSHOT:
        self.update(screenshot=True)
        
    self.proceed_to_song = (ev != ui.CANCEL)

  def player_widgets(self, pid):
    v_top = 11 + pid * 210
    pname = WrapTextDisplay(50, 248, [380, v_top], "Player %d" % (pid+1,), centered=True)
    d=DifficultyBox([504, v_top+65])
    d.set(self.diffs[pid], DIFF_COLORS.get(self.diffs[pid], [127, 127, 127]),
          self.song.difficulty[self.playmode][self.diffs[pid]], "")
    return [pname, d]
  
  def player_opts(self,pid):
    v_top = 11 + pid * 210
    return [WrapTextDisplay(30, 248, [380, v_top+95],
                            self.opt_summary(pid), centered=True)]

  def opt_summary(self, pid):
    summary = []
    for opt, special_display in changeable_between:
      if self.configs[pid][opt] == default_conf[opt]: continue
      
      i_choice = [x[0] for x in options.OPTIONS[opt][3]].index(self.configs[pid][opt])
      if self.configs[pid][opt] in special_display:
        summary.append(special_display[self.configs[pid][opt]])
      else:
        summary.append(options.OPTIONS[opt][3][i_choice][1])

    if len(summary) == 0:
      summary = "Default Settings"
    else:
      summary = ", ".join(summary)
      
    return summary
      
      
# Run through a playlist of songs and play each one until people quit.
def play(screen, playlist, configs, songconf, playmode):
  numplayers = len(configs)

  game = games.GAMES[playmode]

  songs_played = 0
  # Decides whether or not the Ready?/Go! should be displayed before
  # the song.
  first = True

  display_songinfo = (mainconfig['songinfoscreen'] == 2 or
                      (mainconfig['songinfoscreen'] == 1 and len(playlist) != 1 ))

  songdata = None

  players = []
  for playerID in range(numplayers):
    plr = Player(playerID, configs[playerID], songconf, game)
    players.append(plr)

  for songfn, diff in playlist:
    try: current_song = fileparsers.SongItem(songfn)
    except None:
      error.ErrorMessage(screen, "There was an error loading " +
                         os.path.split(songfn)[1])
      first = True
      continue

    songs_played += 1
    prevscr = pygame.transform.scale(screen, (640,480))
    songdata = steps.SongData(current_song, songconf)

    if display_songinfo:
      if not SongInfoScreen(current_song, diff, playmode, songconf,
                            configs, screen).proceed_to_song: break
      for playerID in range(numplayers):
        for opt, dummy in changeable_between:
          players[playerID].__dict__[opt] = configs[playerID][opt]

    for pid, player in enumerate(players):
      player.set_song(current_song, diff[pid], songdata.lyricdisplay)

    print "Playing", songfn
    print songdata.title.encode("ascii", "replace"), "by",
    print songdata.artist.encode("ascii", "replace")

    if dance(screen, songdata, players, prevscr, first, game):
      first = False
      break # Failed
    first = False
    if True in [p.escaped for p in players]:
      break

  if mainconfig['grading'] and not first and songdata:
    grade = gradescreen.GradingScreen(screen, players, songdata.banner)

  # If we only play one song (all the way through), then it's safe to enter
  # a grade. This means course grades are going to get kind of messy,
  # and have to be handled by the course stuff rather than here.
  if songs_played == 1 and not players[0].escaped:
    for p in players:
      if not p.failed:
        records.add(current_song.info["recordkey"], diff[p.pid],
                    playmode, p.grade.rank(), " ")
      else:
        records.add(current_song.info["recordkey"], diff[p.pid],
                    playmode, -2, " ")

def dance(screen, song, players, prevscr, ready_go, game):
  songFailed = False

  # text group, e.g. judgings and combos
  tgroup =  RenderUpdates()
  # lyric display group
  lgroup = RenderUpdates()

  background = pygame.Surface([640, 480])

  if song.movie != None:
    backmovie = BGMovie(song.movie)
  else:
    backmovie = None
    
  background.fill(colors.BLACK)
  screen.fill(colors.BLACK)

  if ready_go:
    ready_go_time = min(100, *[plr.ready for plr in players])
    tgroup.add(ReadyGoSprite(ready_go_time))

  if mainconfig['showbackground'] > 0:
    if backmovie is None:
      bgkludge = pygame.image.load(song.background).convert()
      bgkrect = bgkludge.get_rect()
      if (bgkrect.size[0] == 320) and (bgkrect.size[1] == 240):
        bgkludge = pygame.transform.scale2x(bgkludge)
      else:
        bgkludge = pygame.transform.scale(bgkludge, [640, 480])
      bgkludge.set_alpha(mainconfig['bgbrightness'], RLEACCEL)
      
      q = mainconfig['bgbrightness'] / 256.0
      # FIXME
      for i in range(0, 101, 5):
        p = i / 100.0
        prevscr.set_alpha(256 * (1 - p) * q, RLEACCEL)
        screen.fill(colors.BLACK)
        screen.blit(prevscr, [0, 0])
        screen.blit(bgkludge, [0, 0])
        pygame.display.update()
        pygame.time.delay(1)

      background.blit(bgkludge, [0, 0])
    else:
      pygame.display.update()
  else:
    pygame.display.update()

  if mainconfig["strobe"]: tgroup.add(Blinky(song.bpm))

  if mainconfig["fpsdisplay"]:
    fpstext = FPSDisp()
    timewatch = TimeDisp()
    tgroup.add([fpstext, timewatch])
  else: fpstext = None

  if mainconfig['showlyrics']:
    lgroup.add(song.lyricdisplay.channels())

  songtext = fontfx.zztext(song.title, 480, 12)
  grptext = fontfx.zztext(song.artist, 160, 12)

  songtext.zin()
  grptext.zin()

  tgroup.add([songtext, grptext])

  song.init()

  if song.crapout != 0:
    error.ErrorMessage(screen, "The audio file for this song " +
                       song.filename + " could not be found.")
    return False # The player didn't fail.

  if mainconfig['assist']: music.set_volume(0.6)
  else: music.set_volume(1.0)

  song.play()
  for plr in players: plr.start_song()

  autofail = mainconfig['autofail']

  screenshot = False
  pad.empty()

  while True:
    if autofail:
      songFailed = True
      for plr in players:
        if not plr.lifebar.gameover:
          songFailed = False
          break
      if songFailed:
        song.kill()

    time.sleep(0.0001)
    # needed for FreeBSD, otherwise music hangs (it does in non-sleeping loops)
    # and yes, I do know this is an ugly hack

    for plr in players: plr.get_next_events(song)

    if song.is_over(): break
    else: curtime = music.get_pos()/1000.0

    key = []

    ev = pad.poll()

    for i in range(len(players)):
      if (pad.states[(i, pad.START)] and pad.states[(i, pad.SELECT)]):
        ev = (0, pad.QUIT)
        break
      else:
        pass

    while ev[1] != pad.PASS:
      if ev[1] == pad.QUIT:
        for p in players: p.escaped = True
        break
      elif ev[1] == pad.SCREENSHOT:
        screenshot = True
      elif ev[1] == pad.LEFT: key.append((ev[0], 'l'))
      elif ev[1] == pad.DOWNLEFT: key.append((ev[0], 'w'))
      elif ev[1] == pad.UPLEFT: key.append((ev[0], 'k'))
      elif ev[1] == pad.RIGHT: key.append((ev[0], 'r'))
      elif ev[1] == pad.UPRIGHT: key.append((ev[0], 'z'))
      elif ev[1] == pad.DOWNRIGHT: key.append((ev[0], 'g'))
      elif ev[1] == pad.UP: key.append((ev[0], 'u'))
      elif ev[1] == pad.DOWN: key.append((ev[0], 'd'))
      elif ev[1] == pad.CENTER: key.append((ev[0], 'c'))

      ev = pad.poll()

    if ev[1] == pad.QUIT: return False
  
    for ev in key:
      if game.double: pid = ev[0] / 2
      else: pid = ev[0]
      
      if pid < len(players): players[pid].handle_key(ev, curtime)

    rectlist = []

    if backmovie:
      backmovie.update(curtime)
      if backmovie.changed or (fpstext.fps() > 30):
        backmovie.resetchange()
        screen.blit(backmovie.image, [0, 0])

    for plr in players: rectlist.extend(plr.game_loop(curtime, screen))

    lgroup.update(curtime)
    tgroup.update(curtime)
    rectlist.extend(tgroup.draw(screen))
    rectlist.extend(lgroup.draw(screen))

    if backmovie is None: pygame.display.update(rectlist)
    else: pygame.display.update()

    if screenshot:
      fn = os.path.join(rc_path, "screenshot.bmp")
      print "Saving a screenshot to", fn
      pygame.image.save(screen, fn)
      screenshot = False

    if backmovie is None:
      lgroup.clear(screen, background)
      tgroup.clear(screen, background)
      for plr in players: plr.clear_sprites(screen, background)

    if ((curtime > players[0].length - 1) and
        (songtext.zdir == 0) and (songtext.zoom > 0)):
      songtext.zout()
      grptext.zout()

  if fpstext: print "Average FPS for this song was %d." % fpstext.fps()
  return songFailed
