# These parse various file formats describing steps.
# Please read docs/dance-spec.txt

import colors
import games
import stepfilters

from lyrics import Lyrics
from util import toRealTime
from constants import *

from pygame.mixer import music

# FIXME: This can probably be replaced by something smaller, like a tuple.
class SongEvent(object):
  def __init__ (self, bpm, when=0.0, beat = 0, feet=None, next=None,
                extra=None, color=None):
    self.bpm  = bpm
    self.when = when
    self.feet = feet
    self.extra = extra
    self.color = color
    self.beat = beat

  def __repr__(self):
    rest = []
    if self.feet: rest.append('feet=%r'%self.feet)
    if self.extra: rest.append('extra=%r'%self.extra)
    if self.extra: rest.append('color=%r'%self.color)
    return '<SongEvent when=%r bpm=%r %s>' % (self.when,self.bpm,
                                                ' '.join(rest))

# Step objects, made from SongItem objects

class Steps(object):
  def __init__(self, song, difficulty, player, pid, lyrics, playmode):
    self.playmode = playmode
    self.difficulty = difficulty
    self.feet = song.difficulty[playmode][difficulty]
    self.length = 0.0
    self.offset = -(song.info["gap"] + mainconfig['masteroffset']) / 1000.0
    self.soffset = self.offset * 1000
    self.bpm = song.info["bpm"]

    if mainconfig['onboardaudio']:
      self.offset = int(self.offset * 48000.0/44128.0)
      self.bpm = self.bpm * 48000.0/44128.0

    self.lastbpmchangetime = []
    self.totalarrows = 0
    self.ready = None

    offbeat_color_mod = 3

    holdlist = []
    holdtimes = []
    holdbeats = []
    releaselist = []
    releasetimes = []
    releasebeats = []
    self.numholds = 1
    holding = [0] * len(games.GAMES[playmode].dirs)
    coloring_mod = 0
    cur_time = float(self.offset)
    last_event_was_freeze = False
    cur_beat = 0
    cur_bpm = self.bpm
    self.speed = player.speed

    # If this is too small, arrows don't appear fast enough. If it's
    # too large, many arrows queue up and pydance slows down.
    # 104 == (480 (screen height) - 64 (space on top)) / 4 (pixels per beat)
    self.nbeat_offset = 104.0 / player.speed
    self.lastbpmchangetime = []
    self.events = [SongEvent(when = cur_time, bpm = cur_bpm, beat = cur_beat,
                             extra = song.difficulty[playmode][difficulty])]

    self.event_idx = 0
    self.nevent_idx = 0

    if playmode in song.steps:
      song_steps = song.steps[playmode][difficulty]
      if playmode in games.COUPLE: song_steps = song_steps[pid]
      # Copy the steps so transformations don't affect both players.
      song_steps = [list(s) for s in song_steps]
    else:
      song_steps = stepfilters.generate_mode(song, difficulty, playmode, pid)

    song_steps = stepfilters.compress(song_steps)

    if player.transform:
      T = stepfilters.rotate[player.transform]
      song_steps = T(playmode).transform(song_steps)

    if not player.holds:
      song_steps = stepfilters.RemoveHoldTransform().transform(song_steps)
    if player.size: stepfilters.size(song_steps, player.size)
    if player.jumps != 1:
      song_steps = stepfilters.jumps[player.jumps]().transform(song_steps)

    if not player.secret_kind:
      song_steps = stepfilters.RemoveSecret().transform(song_steps)

    for words in song_steps:

      if words[0] == 'W':
        cur_time += float(words[1])
        cur_beat += cur_bpm * float(words[1]) / 60
        last_event_was_freeze = False
      elif words[0] == 'R':
        self.ready = cur_time
        last_event_was_freeze = False
        coloring_mod = 0
      elif isinstance(words[0], float):
        # Don't create arrow events with no arrows
        arrowcount = 0
        for i in words[1:]: arrowcount += int(i)

        if arrowcount != 0:
          feetstep = words[1:]

          if last_event_was_freeze:
            time_to_add = last_event_was_freeze
            last_event_was_freeze = False
          else: time_to_add = cur_time

          # Check for holds
          for hold in range(len(feetstep)):
            if feetstep[hold] & 2 and holding[hold] == 0:
              holdtimes.insert(self.numholds, time_to_add)
              holdbeats.insert(self.numholds, cur_beat)
              holdlist.insert(self.numholds, hold)
              releasetimes.append(None)
              releasebeats.append(None)
              releaselist.append(None)
              holding[hold] = self.numholds
              self.numholds += 1

            elif (feetstep[hold] and holding[hold]):
              releasetimes[holding[hold] - 1] = time_to_add
              releasebeats[holding[hold] - 1] = cur_beat
              releaselist[holding[hold] - 1] = hold
              feetstep[hold] = 0
              holding[hold] = 0

          if coloring_mod == int(coloring_mod):
            color = coloring_mod % 4
            if color == 3: color = 1
            offbeat_color_mod = 3
          else:
            color = offbeat_color_mod
            offbeat_color_mod ^= 2
          self.events.append(SongEvent(when = time_to_add, bpm = cur_bpm,
                                       feet = feetstep, extra = words[0],
                                       beat = cur_beat,
                                       color = color))

          for arrowadder in feetstep:
            if arrowadder & 1 and not arrowadder & 4:
              self.totalarrows += 1

        # Even if there are no steps in the event, we don't want to
        # propogate the freeze.
        else: last_event_was_freeze = False

        beat = words[0]

        cur_time += toRealTime(cur_bpm, beat)
        cur_beat += beat
        coloring_mod += beat

        if int(coloring_mod + 0.0001) > int(coloring_mod):
          coloring_mod = float(int(coloring_mod + 0.0001))
        if int(cur_beat + 0.0001) > int(cur_beat):
          cur_beat = float(int(cur_beat + 0.0001))

      elif words[0] == "D":
        last_event_was_freeze = False
        cur_time += toRealTime(cur_bpm, 4.0 * words[1])
        cur_beat += 4.0 * words[1]
        coloring_mod += 4 * words[1]

      elif words[0] == "B":
        cur_bpm = words[1]
        last_event_was_freeze = False
        self.lastbpmchangetime.append([cur_time, cur_bpm])

      elif words[0] == "S":
        # We can treat stops as a BPM change to zero with a wait.
        last_event_was_freeze = cur_time
        self.lastbpmchangetime.append([cur_time, 1e-127]) # This is zero
        cur_time += float(words[1])
        self.lastbpmchangetime.append([cur_time, cur_bpm])

      elif words[0] == "L" and lyrics:
        lyrics.addlyric(cur_time - 0.4, words[1], words[2])

    self.length = cur_time + toRealTime(cur_bpm, 8.0)

    self.holdinfo = zip(holdlist, holdtimes, releasetimes)
    self.holdref = zip(holdlist, holdtimes)
    self.holdbeats = zip(holdbeats, releasebeats)

    if self.ready == None:
      if len(self.events) > 1:
        self.ready = self.events[1].when - toRealTime(self.events[1].bpm, 16)
      else: self.ready = 0.0

  def play(self):
    self.curtime = 0.0
    self.event_idx = self.nevent_idx = 0
    self.playingbpm = self.bpm

  def get_events(self):
    events, nevents = [], []
    idx = self.event_idx
    nidx = self.nevent_idx
    time = self.curtime = float(music.get_pos())/1000.0
    while (idx < len(self.events) and
           self.events[idx].when <= time + 2 * toRealTime(self.events[idx].bpm, 1)):
      events.append(self.events[idx])
      idx += 1
    bpm = self.playingbpm
    self.event_idx = idx

    if idx < len(self.events) and nidx < len(self.events):
      nbeat = self.events[idx].beat + self.nbeat_offset
      while (nidx < len(self.events) and self.events[nidx].beat <= nbeat):
        self.playingbpm = self.events[nidx].bpm
        nevents.append(self.events[nidx])
        nidx += 1
      self.nevent_idx = nidx
    return events, nevents, time, bpm

# Player-indep data generated from SongItem.

class SongData(object):
  def __init__(self, song, config):
    if song.info["background"]: self.background = song.info["background"]
    else: self.background = os.path.join(image_path, "bg.png")

    for key in ["movie", "filename", "title", "artist", "startat", "endat",
                "banner"]:
      self.__dict__[key] = song.info[key]

    self.soffset = song.info["gap"] * 1000

    self.crapout = 0

    self.__dict__.update(config)

    clrs = [colors.color[c] for c in mainconfig["lyriccolor"].split("/")]
    clrs.reverse()
    self.lyricdisplay = Lyrics(clrs)

    atsec = 0
    for lyr in song.lyrics:
      self.lyricdisplay.addlyric(*lyr)

  def init(self):
    try: music.load(self.filename)
    except:
      print "Not a supported file type:", self.filename
      self.crapout = 1

  def play(self):
    music.play(0, self.startat)

  def kill(self):
    music.stop()

  def is_over(self):
    if not music.get_busy(): return True
    elif self.endat and music.get_pos() > self.endat * 1000:
      music.stop()
      return True
    else: return False
