# Slune
# Copyright (C) 2002-2003 Jean-Baptiste LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

from __future__ import generators

import time, random, math

import soya, soya.opengl as soyaopengl, soya.widget as widget
import py2play.level as py2p_level, py2play.action as action, py2play.character as character
import slune.character as slune_character
import slune.controler
import slune.globdef as globdef, slune.sound as sound, slune.level as slune_level

def init_race_game(level):
  """Hides all flags except the first."""
  
  # Copy them in level, since the different player may have different config.
  level.race_nb_laps      = globdef.RACE_NB_LAPS
  level.race_nb_opponents = globdef.RACE_NB_OPPONENTS
  
  level.ranks = []
  
  level.flags = [] # Indexes all flags
  
  for item in level:
    if isinstance(item, Flag):
      item.visible = (item.name == "flag_0")
      level.flags.append(item)
      
  level.flags.sort(lambda f1, f2: cmp(f1.flag_id, f2.flag_id))
  
  
def start_race_game(level, add_opponent1 = 1):
  """Inits time property and shows the race time label."""
  for i in range(level.race_nb_opponents - len(level.characters)):
    Opponent().set_level(level, 1)
    
  Checker(level)
  
  for character in level.characters:
    if isinstance(character, slune_character.Competitor): init_race_opponent(character)
    
def init_race_opponent(character, laps_label = 1, minimap = 1):
  character.next_flag_id = 0
  character.nb_laps   = 0
  character.lap_start = time.time()
  character.best_lap  = 9999.0
  if character.played and character.player.active:
    from py2play.idler import IDLER
    if laps_label: IDLER.no_blackbands_group.insert(0, LapsLabel(character))
    if minimap:    IDLER.no_blackbands_group.insert(0, MiniMap(character.level))

def end_race_game(level):
  """Hides the race time label."""
  for widget in soya.root_widget.children:
    if isinstance(widget, LapsLabel):
      soya.root_widget.remove(widget)
      break
    
class Checker(soya.Volume):
  def begin_round(self):
    from py2play.idler import IDLER
    
    level = self.parent
    
    for character in level.characters:
      if isinstance(character, slune_character.Competitor):
        flag = level.flags[character.next_flag_id]
        if character.distance_to(flag) < (flag.radius + character.radius):
          
          #character.life = min(1.0, character.life + 0.1)
          
          if character.next_flag_id + 1 < len(self.parent.flags):
            next_flag_id = character.next_flag_id + 1
            next_flag = self.parent.flags[next_flag_id]
          else:
            next_flag_id = 0
            next_flag = self.parent.flags[0]
            character.nb_laps += 1
            now = time.time()
            this_lap = now - character.lap_start
            if this_lap < character.best_lap: character.best_lap = this_lap
            character.lap_start = now
            
            if character.nb_laps >= character.level.race_nb_laps:
              firework = soya.FlagFirework(level, nb_particles = 6)
              firework.move(flag)
              
              character.next_flag_id = 0
              
              level.ranks.append(character)

              if len(level.ranks) == 1: # Winner
                IDLER.level_completed(character, 1, _("__race_winner__"))
              else:
                IDLER.level_completed(character, 0, _("__race_looser__") % len(level.ranks))
                
              from py2play.player import CURRENT_PLAYER
              if not character is CURRENT_PLAYER.character:
                IDLER.message(_("__race_arrival__") % (character.displayname(), len(level.ranks)))
                
              if not character.played:
                character.level.remove_character(character)
              return
            
          character.next_flag_id = next_flag_id
          
          sound.play("flag-1.wav", character)
          
          if character.played and character.player.active:
            flag.visible = 0
            next_flag.visible = 1
            
            firework = soya.Smoke(level, nb_particles = 10, removable = 1)
            firework.move(flag)
            firework.regenerate()
            
    # check if every one is killer, except the current player.
    from py2play.player import CURRENT_PLAYER
    
    if (len(self.parent.characters) == 1) and (self.parent.characters[0] is CURRENT_PLAYER.character):
      self.parent.ranks.append(CURRENT_PLAYER.character)
      if len(self.parent.ranks) == 1:
        IDLER.level_completed(CURRENT_PLAYER.character, 1, message = _("__race_winner__"))
      else:
        IDLER.level_completed(CURRENT_PLAYER.character, 1, message = _("__race_looser__") % len(self.parent.ranks))
        
      self.parent.remove(self)
  
  
class Flag(soya.Bonus):
  radius = 3.5
  
  def __init__(self, parent = None):
    if parent:
      parent.max_laps = 2
      
      nb = 0
      for item in parent:
        if isinstance(item, Flag): nb += 1
        
      soya.Bonus.__init__(self, parent, soya.Material.get("flag1"), soya.Material.get("halo"))
      
      self.flag_id = nb
      
    else:
      soya.Bonus.__init__(self, parent, soya.Material.get("flag1"), soya.Material.get("halo"))
      
    self.color = (1.0, 1.0, 0.20000000298023224, 1.0)
    
  def get_flag_id(self):
    try: return int(self.name[5:])
    except ValueError: return 0
  def set_flag_id(self, id): self.name = "flag_%s" % id
  flag_id = property(get_flag_id, set_flag_id)


class MiniMap(widget.Widget):
  def __init__(self, level):
    self.level = level
    
    self.top = 10
    self.width  = 200
    self.height = 200
    self.left = soya.root_widget.width - self.width - 40
    
    flags = [x for x in level if isinstance(x, Flag)]
    flags.sort(lambda f1, f2: cmp(f1.flag_id, f2.flag_id))
    min_x = min_y =  10e1000
    max_x = max_y = -10e1000
    for flag in flags:
      if flag.x < min_x: min_x = flag.x
      if flag.x > max_x: max_x = flag.x
      if flag.z < min_y: min_y = flag.z
      if flag.z > max_y: max_y = flag.z
    f_x = self.width  / (max_x - min_x)
    f_y = self.height / (max_y - min_y)
    if f_x < f_y:
      f = f_x
      left = self.left
    else:
      f = f_y
      left = self.left + ((max_y - min_y) - (max_x - min_x)) * f
    top = self.top
    
    self.f     = f
    self.cleft = left
    self.ctop  = top
    self.min_x = min_x
    self.min_y = min_y
    
    self.flags_pos = []
    self._calllist = soyaopengl.glGenList()
    soyaopengl.glNewList(self._calllist)
    soyaopengl.glColor4f(1.0, 1.0, 1.0, 1.0)
    soyaopengl.glBegin(soyaopengl.GL_LINE_LOOP)
    for flag in flags:
      self.flags_pos.append((
        left + f * (flag.x - min_x),
        top  + f * (flag.z - min_y),
        ))
      soyaopengl.glVertex2f(*self.flags_pos[-1])
    soyaopengl.glEnd()
    x, y = self.flags_pos[-1]
    soyaopengl.glBegin(soyaopengl.GL_QUADS)
    soyaopengl.glVertex2f(x - 4.0, y - 4.0)
    soyaopengl.glVertex2f(x - 4.0, y + 4.0)
    soyaopengl.glVertex2f(x + 4.0, y + 4.0)
    soyaopengl.glVertex2f(x + 4.0, y - 4.0)
    soyaopengl.glEnd()
    
    soyaopengl.glEndList()
    
  def resize(self, parent_left, parent_top, parent_width, parent_height):
    self.left = parent_width - self.width - 40
    self.screen_width  = parent_width
    self.screen_height = parent_height
    
  def render(self):
    from py2play.player import CURRENT_PLAYER
    from py2play.idler import IDLER
    ca = IDLER.camera
    x, y, z = ca.transform_point(0.0, 0.0, 0.0, self.level.flags[CURRENT_PLAYER.character.next_flag_id])
    angle = math.atan(x / z)
    if z < 0.0:
      vx = -math.sin(angle)
      vy = -math.cos(angle)
    else:
      vx = math.sin(angle)
      vy = math.cos(angle)
    
    r = self.screen_width / self.screen_height
    if r > abs(vx / vy):
      if vy < 0.0:
        my = -self.screen_height / 2.0
        mx = vx * (my / vy) * r
      else:
        my = self.screen_height / 2.0
        mx = vx * (my / vy) * r
    else:
      if vx < 0.0:
        mx = -self.screen_width / 2.0
        my = vy * (mx / vx) / r
      else:
        mx = self.screen_width / 2.0
        my = vy * (mx / vx) / r
        
    x = mx + self.screen_width  / 2.0
    y = my + self.screen_height / 2.0
    
    x1 = x + (+ my - mx) * 0.06
    x2 = x + (- my - mx) * 0.06
    y1 = y + (- mx - my) * 0.06
    y2 = y + (+ mx - my) * 0.06

    if globdef.QUALITY > 1:
      soyaopengl.glEnable(soyaopengl.GL_BLEND)
      soyaopengl.glColor4f(0.0, 0.0, 0.0, 0.5)
      soyaopengl.glBegin(soyaopengl.GL_TRIANGLES)
      soyaopengl.glVertex2f(x  - 9.0, y  + 9.0)
      soyaopengl.glVertex2f(x1 - 9.0, y1 + 9.0)
      soyaopengl.glVertex2f(x2 - 9.0, y2 + 9.0)
      soyaopengl.glEnd()
      soyaopengl.glDisable(soyaopengl.GL_BLEND)
    
    soyaopengl.glColor4f(1.0, 1.0, 1.0, 1.0)
    soyaopengl.glBegin(soyaopengl.GL_TRIANGLES)
    soyaopengl.glVertex2f(x, y)
    soyaopengl.glVertex2f(x1, y1)
    soyaopengl.glVertex2f(x2, y2)
    soyaopengl.glEnd()
    
    soyaopengl.glCallList(self._calllist)
    for character in self.level.characters:
      if isinstance(character, slune_character.Competitor):
        x = self.cleft + self.f * (character.x - self.min_x)
        y = self.ctop  + self.f * (character.z - self.min_y)
        
        material = soya.Material.get("head_" + character.perso_name + "_1")
        material.activate()
        soyaopengl.glEnable(soyaopengl.GL_BLEND)
        soyaopengl.glBegin(soyaopengl.GL_QUADS)
        soyaopengl.glTexCoord2f(0.0, 0.0); soyaopengl.glVertex2f(x - 20.0, y - 20.0)
        soyaopengl.glTexCoord2f(0.0, 1.0); soyaopengl.glVertex2f(x - 20.0, y + 20.0)
        soyaopengl.glTexCoord2f(1.0, 1.0); soyaopengl.glVertex2f(x + 20.0, y + 20.0)
        soyaopengl.glTexCoord2f(1.0, 0.0); soyaopengl.glVertex2f(x + 20.0, y - 20.0)
        soyaopengl.glEnd()
        soyaopengl.glDisable(soyaopengl.GL_BLEND)
        soya.DEFAULT_MATERIAL.activate()

#         if   character.perso_name == "tux":
#           material = soya.Material.get("tux1"); material.activate()
#           soyaopengl.glBegin(soyaopengl.GL_QUADS)
#           soyaopengl.glTexCoord2f(0.6, 0.0); soyaopengl.glVertex2f(x - 10.0, y - 10.0)
#           soyaopengl.glTexCoord2f(0.6, 1.0); soyaopengl.glVertex2f(x - 10.0, y + 10.0)
#           soyaopengl.glTexCoord2f(1.0, 1.0); soyaopengl.glVertex2f(x + 10.0, y + 10.0)
#           soyaopengl.glTexCoord2f(1.0, 0.0); soyaopengl.glVertex2f(x + 10.0, y - 10.0)
#           soyaopengl.glEnd()
#         elif character.perso_name == "gnu":
#           material = soya.Material.get("gnu"); material.activate()
#           soyaopengl.glBegin(soyaopengl.GL_QUADS)
#           soyaopengl.glTexCoord2f(0.52, 0.4); soyaopengl.glVertex2f(x - 10.0, y - 10.0)
#           soyaopengl.glTexCoord2f(0.52, 1.0); soyaopengl.glVertex2f(x - 10.0, y + 10.0)
#           soyaopengl.glTexCoord2f(1.0 , 1.0); soyaopengl.glVertex2f(x + 10.0, y + 10.0)
#           soyaopengl.glTexCoord2f(1.0 , 0.4); soyaopengl.glVertex2f(x + 10.0, y - 10.0)
#           soyaopengl.glEnd()
#         elif character.perso_name == "shark":
#           material = soya.Material.get("shark1"); material.activate()
#           soyaopengl.glBegin(soyaopengl.GL_QUADS)
#           soyaopengl.glTexCoord2f(0.46, 0.48); soyaopengl.glVertex2f(x - 10.0, y - 10.0)
#           soyaopengl.glTexCoord2f(0.46, 1.0 ); soyaopengl.glVertex2f(x - 10.0, y + 10.0)
#           soyaopengl.glTexCoord2f(1.0 , 1.0 ); soyaopengl.glVertex2f(x + 10.0, y + 10.0)
#           soyaopengl.glTexCoord2f(1.0 , 0.48); soyaopengl.glVertex2f(x + 10.0, y - 10.0)
#           soyaopengl.glEnd()
#         elif character.perso_name == "python":
#           material = soya.Material.get("python1"); material.activate()
#           soyaopengl.glBegin(soyaopengl.GL_QUADS)
#           soyaopengl.glTexCoord2f(1.0 , 0.36); soyaopengl.glVertex2f(x - 10.0, y - 10.0)
#           soyaopengl.glTexCoord2f(0.56, 0.36); soyaopengl.glVertex2f(x - 10.0, y + 10.0)
#           soyaopengl.glTexCoord2f(0.56, 1.0 ); soyaopengl.glVertex2f(x + 10.0, y + 10.0)
#           soyaopengl.glTexCoord2f(1.0 , 1.0 ); soyaopengl.glVertex2f(x + 10.0, y - 10.0)
#           soyaopengl.glEnd()
        
        
class LapsLabel(widget.Label):
  def __init__(self, character):
    self.character = character
    self.format = str(_("__racelapsformat__"))
    widget.Label.__init__(self)
    
    self.left = 10
    self.width  = 400
    self.height = 100
    self.old_nb_laps = -1
    
  def resize(self, parent_left, parent_top, parent_width, parent_height):
    self.top = parent_top + soya.root_widget.height - 43
    
  def widget_begin_round(self):
    if self.old_nb_laps != self.character.nb_laps:
      self.text = self.format % (self.character.nb_laps)
      self.old_nb_laps = self.character.nb_laps
    

class RaceChromosom:
  def __init__(self, max_speed, innitiative, will, agressivity, turbosity):
    self.max_speed   = max_speed
    self.innitiative = innitiative # Lower is better
    self.agressivity = agressivity
    self.will        = will
    self.turbosity   = turbosity
    
  def mutate(self, random):
    for attr, value in self.__dict__.iteritems():
      if attr != "max_speed":
        if random.random() < 0.4: 
          setattr(self, attr, value * (0.5 + random.random()))
      
    
  
# CHROMOSOMS = {
#   ("tux", 0): [0.23161940934461928, 0.54152636319441949, 0.057433463934462713, 1.5669823329989161, 15.877046130951882, 0.18908021610553249, 0.066441050148349098, 3.0216818334258322, 0.2 , 80.0, 30.0, 0.5],
#   ("tux", 1): [0.23161940934461928, 0.54152636319441949, 0.057433463934462713, 1.5669823329989161, 15.877046130951882, 0.18908021610553249, 0.066441050148349098, 3.0216818334258322, 0.55, 30.0, 50.0, 0.5],
#   ("tux", 2): [0.23161940934461928, 0.54152636319441949, 0.057433463934462713, 1.5669823329989161, 15.877046130951882, 0.18908021610553249, 0.066441050148349098, 3.0216818334258322, 1.0 , 20.0, 50.0, 0.6],
  
#   ("gnu", 0): [0.27955060118474129, 0.94713263528551006, 0.0076398049940541654, 0.46242007806615826, 22.758284696585228, 0.07628649333395944, 0.0051247277708940846, 5.6391784712330182, 0.1, 50.0, 50.0, 0.7],
#   ("gnu", 1): [0.27955060118474129, 0.94713263528551006, 0.0076398049940541654, 0.46242007806615826, 22.758284696585228, 0.07628649333395944, 0.0051247277708940846, 5.6391784712330182, 0.5 , 15.0, 80.0, 0.8],
#   ("gnu", 2): [0.27955060118474129, 0.94713263528551006, 0.0076398049940541654, 0.46242007806615826, 22.758284696585228, 0.07628649333395944, 0.0051247277708940846, 5.6391784712330182, 1.0 , 10.0, 99.0, 0.8],
  
#   ("shark", 0): [0.27955060118474129, 0.94713263528551006, 0.0076398049940541654, 0.46242007806615826, 22.758284696585228, 0.07628649333395944, 0.0051247277708940846, 5.6391784712330182, 0.1 , 50.0, 50.0, 0.4],
#   ("shark", 1): [0.27955060118474129, 0.94713263528551006, 0.0076398049940541654, 0.46242007806615826, 22.758284696585228, 0.07628649333395944, 0.0051247277708940846, 5.6391784712330182, 0.5 , 15.0, 50.0, 0.3],
#   ("shark", 2): [0.27955060118474129, 0.94713263528551006, 0.0076398049940541654, 0.46242007806615826, 22.758284696585228, 0.07628649333395944, 0.0051247277708940846, 5.6391784712330182, 1.0 , 10.0, 60.0, 0.2],
#   }
CHROMOSOMS = {
  ("tux",    0): RaceChromosom(0.2,  80.0,  30.0, 0.5, -7.0),
  ("tux",    1): RaceChromosom(0.6,  30.0,  50.0, 0.5, -1.0),
  ("tux",    2): RaceChromosom(1.1,  20.0,  50.0, 0.6,  0.5),
  
  ("gnu",    0): RaceChromosom(0.1,  50.0,  60.0, 0.7, -2.0),
  ("gnu",    1): RaceChromosom(0.5,  15.0,  90.0, 0.8,  0.7),
  ("gnu",    2): RaceChromosom(1.0,  10.0, 150.0, 0.9,  1.0),
  
  ("shark",  0): RaceChromosom(0.1,  40.0,  50.0, 0.3, -9.0),
  ("shark",  1): RaceChromosom(0.5,  15.0,  50.0, 0.3, -4.0),
  ("shark",  2): RaceChromosom(1.0,  10.0,  60.0, 0.2,  0.0),
  
  ("python", 0): RaceChromosom(0.2,  70.0,  30.0, 0.4, -1.0),
  ("python", 1): RaceChromosom(0.55, 25.0,  40.0, 0.4,  1.3),
  ("python", 2): RaceChromosom(1.0,  15.0,  50.0, 0.4,  2.0),
  }


class Opponent(slune_character.NonPlayerCharacter, slune_character.Competitor):
  def __init__(self, parent = None, chromosom = None):
    slune_character.NonPlayerCharacter.__init__(self, parent)
    self.chromosom = chromosom
    
  def init(self):
    if not self.perso_name:
      persos = list(globdef.CHARACTERS)
      self.random.shuffle(persos)
      for perso in persos:
        perso = perso.lower()
        for c in self.level:
          if (hasattr(c, "perso_name")) and (c.perso_name == perso): break
        else:
          self.perso_name = perso
          break
      else: self.perso_name = persos[0].lower()
    self.set_perso(self.perso_name)
    
    if self.vehicle == 1: self.set_vehicle(self.random.choice(globdef.VEHICLES.values()))
    
  def IAControler(self):
    c = self.chromosom = self.chromosom or CHROMOSOMS[self.perso_name, self.level.difficulty]
    self.max_speed = c.max_speed
    
    self.speed.y = 100.0 # Avoid null speed (null speed => jumping). The speed will be overrided later.
    
    while 1:
      for i in range(int(c.innitiative)):
        yield slune.controler.goto(self, self.level.flags[self.next_flag_id], turbosity = c.turbosity + 2.0 * self.life)
        
      # Check boosting
      p = [p for p in self.level.characters if p.played]
      if p:
        if len(p) == 1: p = p[0]
        else:           p = self.random.choice(p)
        delta = (self.nb_laps * len(self.level.flags) + self.next_flag_id) - (p.nb_laps * len(p.level.flags) + p.next_flag_id)
        if   (delta >  1.0) and p.level.difficulty < 2: self.max_speed = self.max_speed * 0.9
        elif (delta < -1.0) and p.level.difficulty > 0: self.max_speed = 1.1
        elif delta > 0.0: self.max_speed = c.max_speed
      else: self.max_speed = c.max_speed
      
      yield slune.controler.CheckBonus(self, self.chromosom.agressivity)
      
      if self.random.random() < c.agressivity: # Attack
        for competitor in self.level.characters:
          if not competitor is self:
            px, py, pz = self.transform_point(competitor.x, competitor.y, competitor.z, competitor.parent)
            if (-5.0 < pz < 0.0) and (-2.0 < py < 2.0) and (-5.0 < px < 5.0):
              yield slune.controler.AttackRun(self, competitor, c.will)
              break
            
            elif competitor.nb_laps >= self.nb_laps + 2: # Too late to win the race, try to kill everybody
              while 1:
                dist = 1000000.0
                best = None
                for competitor in self.level.characters:
                  d = self.distance_to(competitor) - 20.0 * competitor.nb_laps
                  if d < dist:
                    dist = d
                    best = competitor
                yield slune.controler.AttackRun(self, best, c.will)
                
          
      else: # Dodge
        for competitor in self.level.characters:
          if not competitor is self:
            px, py, pz = competitor.transform_point(self.x, self.y, self.z, self.parent)
            if (-5.0 < pz < 0.0) and (-2.0 < py < 2.0) and (-5.0 < px < 5.0):
              h = self.random.random()
              if h < 0.3:
                for i in range(10): yield slune_character.Action(slune_character.ACTION_WAIT, -1.0, self.max_speed)
              elif h < 0.6:
                for i in range(10): yield slune_character.Action(slune_character.ACTION_WAIT,  1.0, self.max_speed)
              else:
                jumping = 1
                
                yield slune_character.Action(slune_character.ACTION_JUMP, 0.0, self.max_speed)
                for i in range(2): yield slune_character.Action(slune_character.ACTION_ROTDOWN, 0.0, self.max_speed)
              break
