# Balazar
# Copyright (C) 2002-2005 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

import bisect, random
import tofu, soya, soya.opengl as soyaopengl, soya.widget as widget
import soya.spc_material
import balazar, balazar.base as base

from balazar.level import LAND_DIM

class SunSprite(soya.Sprite, base.Photographiable):
  def __init__(self, parent = None):
    soya.Sprite.__init__(self, parent, soya.Material.get("sun"))
    self.color = (1.0, 0.7, 0.5, 1.0)
    self.width = self.height = 20.0
    
  def display_name(self): return _("sun")
  
class StarSprite(soya.Sprite, base.Photographiable):
  def __init__(self, parent = None):
    soya.Sprite.__init__(self, parent, soya.Material.get("star"))
    
  def display_name(self): return _("star")
  

WEATHER_SUN  = 101
WEATHER_RAIN = 102
WEATHER_SNOW = 103

class WeatherState(tofu.State):
  def __init__(self, weather):
    self.weather = weather
    
    
class WeatherAgent(tofu.Mobile):
  def __init__(self):
    tofu.Mobile.__init__(self)
    
    if soya.IDLER:
      self.time    = soya.IDLER.scenes[0].atmosphere.time
      self.weather = soya.IDLER.scenes[0].atmosphere.weather
    else:
      self.time    = 7.001
      self.weather = WEATHER_SUN
    self.bot     = 1
    
  def begin_round(self):
    tofu.Mobile.begin_round(self)
    
    self.time = soya.IDLER.scenes[0].atmosphere.time
    
  def big_round(self):
    if not self.doer.remote:
      if random.random() < self.level.random_monster_proba:
        if self.level.nb_monster < self.level.nb_max_monster:
          monster = random.choice(self.level.random_monsters)()
          monster.set_xyz(
            random.uniform(10.0, LAND_DIM - 10.0),
            60.0,
            random.uniform(10.0, LAND_DIM - 10.0),
            )
          self.level.add_mobile(monster)
          
  def set_state(self, state):
    self.weather = state.weather
    soya.IDLER.scenes[0].atmosphere.set_state(state)
    
  def received(self):
    tofu.Mobile.received(self)
    
    soya.IDLER.scenes[0].atmosphere.reset(self)
    
    
class WeatherAtmosphere(soya.SkyAtmosphere):
  def __init__(self, parent = None):
    soya.SkyAtmosphere.__init__(self)
    self.fog      = 1
    self.fog_type = 0
    self.skyplane = 1
    self.cloud    = soya.Material.get("cloud2")
    #self.cloud = soya.spc_material.MovingMaterial(soya.Image.get("cloud2.png"), 0.0001, 0.0002)
    #self.cloud.filename = "cloud2"
    #self.cloud.save()
    self.cloud_scale = 0.2
    
    self.time             = 7.001 # In hour
    self.day              = 1
    self.weather          = WEATHER_SUN
    self.weather_duration = 0.1
    self.old_data         = DAY_NIGHT_DATA[WEATHER_SUN][0]
    self.old_right        = 1
    self.old_weather      = WEATHER_SUN
    self.sun_pos          = 1.0
    
    self.delta   = 0.03
    
    if parent:
      self.sun = soya.Light(parent)
      self.sun.directional = 1
      self.sun.rotate_vertical(7.001 / 24.0 * 360.0 + 90.0)
      
      self.astral_world = soya.World(parent)
      self.astral_world.atmosphere = soya.SkyAtmosphere()
      self.astral_world.atmosphere.ambient = (1.0, 1.0, 1.0, 1.0)
      self.astral_world.rotate_vertical(7.001 / 24.0 * 360.0 + 90.0)
      
      self.sun_sprite = SunSprite(self.astral_world)
      self.sun_sprite.z = 90.0
      
      for i in range(40):
        star = StarSprite(self.astral_world)
        star.color = (random.uniform(0.0, 1.0), random.uniform(0.0, 1.0), random.uniform(0.0, 1.0), 1.0)
        star.size = star.width = star.height = random.uniform(0.5, 2.0)
        
        star.x = random.uniform(-90.0,  90.0)
        star.y = random.uniform(-90.0,  90.0)
        star.z = random.uniform(-90.0, -20.0)
        l = self.astral_world.distance_to(star) / 99.0
        star.x /= l
        star.y /= l
        star.z /= l
        
  def reset(self, agent):
    self.set_state(WeatherState(agent.weather))
    self.time     = agent.time
    data          = DAY_NIGHT_DATA[self.weather]
    self.old_data = data[bisect.bisect(data, (self.time,)) - 1]
    
  def send_state(self, state):
    for level in soya.IDLER.scenes[0]:
      if isinstance(level, tofu.Level):
        level.weather_agent.doer.action_done(state)
        
  def big_round(self, delta = 0.05):
    self.delta = delta
    
    self.time += delta
    if self.time >= 24.0:
      self.time -= 24.0
      self.day  += 1
      
    data = DAY_NIGHT_DATA[self.weather]
    right = bisect.bisect(data, (self.time,))
    
    self.weather_duration -= delta
    
    if right != self.old_right: # Can change weather here
      if tofu.GAME_INTERFACE and (self.weather == WEATHER_RAIN) and not RAIN.master:
        self.astral_world.visible = 0
        soya.root_widget.insert(1, RAIN) # 0 is camera
        
      data2 = DAY_NIGHT_DATA[self.old_weather]
      self.old_right = bisect.bisect(data2, (self.time,))
      self.old_data  = data2[self.old_right - 1]

      if self.weather_duration < 0.0:
        for level in soya.IDLER.scenes[0]:
          if isinstance(level, tofu.Level) and level.active and not level.weather_agent.doer.remote:
            state = WeatherState(random.choice(WEATHERS))
            self.send_state(state)
            self.weather_duration = random.uniform(4.0, WEATHER_DURATION[state.weather])
            break
        else:
          self.weather_duration = 1.0
          
      self.old_weather = self.weather
      
        
    d1 = self.old_data
    d2 = data[right]
    
    if d1[0] < d2[0]: f2 = (self.time - d1[0]) / (d2[0] - d1[0])
    else:             f2 = (24.0 + self.time - d1[0]) / (24.0 + d2[0] - d1[0])
    f1 = 1.0 - f2
    
    self.fog_start   = f1 * d1[1] + f2 * d2[1]
    self.fog_end     = (f1 * d1[2] + f2 * d2[2])
    self.fog_color   = self.bg_color = (f1 * d1[ 3] + f2 * d2[ 3], f1 * d1[ 4] + f2 * d2[ 4], f1 * d1[ 5] + f2 * d2[ 5], 1.0)
    self.sky_color   =                 (f1 * d1[ 6] + f2 * d2[ 6], f1 * d1[ 7] + f2 * d2[ 7], f1 * d1[ 8] + f2 * d2[ 8], 1.0)
    self.ambient     =                 (f1 * d1[ 9] + f2 * d2[ 9], f1 * d1[10] + f2 * d2[10], f1 * d1[11] + f2 * d2[11], 1.0)
    self.sun.diffuse =                 (f1 * d1[12] + f2 * d2[12], f1 * d1[13] + f2 * d2[13], f1 * d1[14] + f2 * d2[14], 1.0)
    
    if   (self.sun_pos == 1.0) and (self.time > 18.0): # the sun becomes the moon and travel backward
      self.sun_pos = -1.0
    elif (self.sun_pos == -1.0) and (6.0 < self.time < 18.0):
      self.sun_pos = 1.0
      
  def set_state(self, state):
    if self.weather != state.weather:
      if tofu.GAME_INTERFACE:
        if   self.weather == WEATHER_RAIN:
          self.astral_world.visible = 1
          if RAIN.master: RAIN.master.remove(RAIN)
        elif self.weather == WEATHER_SNOW:
          if SNOW.master: SNOW.master.remove(SNOW)
          
        if   state.weather == WEATHER_SNOW:
          soya.root_widget.insert(1, SNOW) # 0 is camera
          
      self.old_weather = self.weather
      self.weather = state.weather
      
  def begin_round(self):
    pass
  
  def advance_time(self, proportion):
    a = self.delta * proportion * 0.9375 #360.0 / 16.0 / 24.0 
    self.sun.rotate_vertical(self.sun_pos * a)
    self.astral_world.rotate_vertical(      a)
    
    if tofu.GAME_INTERFACE:
      self.astral_world.move(tofu.GAME_INTERFACE.camera)
      
  def destroy(self):
    if tofu.GAME_INTERFACE:
      if RAIN.master: RAIN.master.remove(RAIN)
      if SNOW.master: SNOW.master.remove(SNOW)
      
      
class Rain(widget.Widget):
  def __init__(self, parent = None):
    self.w = 640
    self.h = 480
    self.next_thunder = 10
    self.particles = [[random.uniform(0.0, self.w), random.uniform(-20.0, self.h), random.uniform(15.0, 50.0), random.uniform(0.5, 1.0)] for i in range(30)]
    
    widget.Widget.__init__(self, parent)
    
  def __getstate__(self): return None
  def __setstate__(self, state): pass
    
  def resize(self, parent_left, parent_top, parent_width, parent_height):
    w = soya.get_screen_width()
    h = soya.get_screen_height()
    fw = float(w) / self.w
    fh = float(h) / self.h
    self.w = w
    self.h = h
    
    for p in self.particles:
      p[0] *= fw
      p[1] *= fh
      p[2] *= fh
      
  def render(self):
    f = soya.IDLER.scenes[0].transform_vector(1.0, 0.0, 0.0, tofu.GAME_INTERFACE.camera)[0] / 2.0
    
    soya.DEFAULT_MATERIAL.activate()
    soyaopengl.glLineWidth(2.0)
    soyaopengl.glBegin(soyaopengl.GL_TRIANGLES)
    for p in self.particles:
      if p[1] > 0.6 * self.h + p[2] * 2.5: p[1] = 0.0
      if   p[0] > self.w: p[0] -= self.w
      elif p[0] < 0.0:    p[0] += self.w
      
      soyaopengl.glColor4f(p[3], 0.8, 1.0, 1.0)
      soyaopengl.glVertex2f(p[0], p[1])
      p[0] += p[2] *  f
      p[1] += p[2]
      soyaopengl.glVertex2f(p[0], p[1])
      soyaopengl.glVertex2f(p[0] + 3, p[1])
      p[0] -= p[2] * 0.5 * f
      p[1] -= p[2] * 0.5
      
    soyaopengl.glEnd()
    
    self.next_thunder -= 1
    if   self.next_thunder == 0:
      self.materials = soya.Material._alls.values()
      
      weather_atmosphere = soya.IDLER.scenes[0].atmosphere
      weather_atmosphere.fog_color = (1.0, 1.0, 1.0, 1.0)
      weather_atmosphere.bg_color  = weather_atmosphere.sky_color  = (1.0, 1.0, 1.0, 1.0)
      weather_atmosphere.fog_start = -20.0
      
      self.next_thunder = random.randint(50, 200)
      
    
    
class Snow(widget.Widget):
  def __init__(self, parent = None):
    widget.Widget.__init__(self, parent)
    self.w = 640
    self.h = 480
    
    self.particles = [[random.uniform(0.0, self.w), 0.0, random.uniform(20.0, 50.0), random.uniform(0.5, 1.0)] for i in range(15)]
    self.old_cam_x = 0.0
    self.old_cam_y = 0.0
    
    self.material = soya.Material.get("star")
    
  def __getstate__(self): return None
  def __setstate__(self, state): pass
  
  def resize(self, parent_left, parent_top, parent_width, parent_height):
    w = soya.get_screen_width()
    h = soya.get_screen_height()
    fw = float(w) / self.w
    fh = float(h) / self.h
    self.w = w
    self.h = h
    
    for p in self.particles:
      p[0] *= fw
      p[1] *= fh
      
    self.v1 = soya.Vector(soya.IDLER.scenes[0], 0.0, 0.0, -1.0)
    self.v2 = soya.Vector(tofu.GAME_INTERFACE.camera, 0.0, 0.0, -1.0)
    
  def render(self):
    if not hasattr(self, "v1"): return
    
    a = self.v1.angle_to(self.v2)
    if self.v1.cross_product(self.v2).y < 0.0: delta_x = (self.old_cam_x - a) * self.w * 0.5
    else:                                      delta_x = (a - self.old_cam_x) * self.w * 0.5
    self.old_cam_x = a
    
    delta_y = (tofu.GAME_INTERFACE.camera.y - self.old_cam_y) * self.h * 0.1
    self.old_cam_y = tofu.GAME_INTERFACE.camera.y
    
    self.material.activate()
    soyaopengl.glEnable(soyaopengl.GL_BLEND)
    soyaopengl.glPointSize(20.0)
    soyaopengl.glBegin(soyaopengl.GL_QUADS)
    for p in self.particles:
      if   p[1] > self.h - 35: p[1] -= self.h
      elif p[1] < - 35: p[1] += self.h
      if   p[0] < 0.0: p[0] += self.w
      elif p[0] > self.w: p[0] -= self.w
      
      soyaopengl.glColor4f(0.5, 0.3, 1.0, 1.0)
      soyaopengl.glTexCoord2f(0.0, 0.0); soyaopengl.glVertex2f(p[0], p[1])
      soyaopengl.glTexCoord2f(1.0, 0.0); soyaopengl.glVertex2f(p[0], p[1] + p[2])
      soyaopengl.glTexCoord2f(1.0, 1.0); soyaopengl.glVertex2f(p[0] + p[2], p[1] + p[2])
      soyaopengl.glTexCoord2f(0.0, 1.0); soyaopengl.glVertex2f(p[0] + p[2], p[1])
      p[0] += delta_x / p[2]
      p[1] += p[2] * (0.1 + 0.02 * delta_y)
      
    soyaopengl.glEnd()
    soya.DEFAULT_MATERIAL.activate()
    
      
SNOW = Snow()
RAIN = Rain()


WEATHERS = [WEATHER_SUN, WEATHER_RAIN, WEATHER_SNOW]

DAY_NIGHT_DATA = {
  WEATHER_SUN : [
  (4.0, # Time
   0.0, 70.0, # Fog distance
   0.0, 0.0, 0.0, # Fog color
   0.0, 0.3, 1.0, # Sky color
   0.15, 0.15, 0.3, # Ambient
   0.3, 0.3, 0.5, # Sun diffuse color
   ),
  (8.0, # Time
   20.0, 80.0, # Fog distance
   0.4, 0.45, 0.7, # Fog color
   1.5, 1.0, 0.8, # Sky color
   0.3, 0.3, 0.4, # Ambient
   1.0, 0.8, 0.4, # Sun diffuse color
   ),
  (12.0, # Time
   50.0, 100.0, # Fog distance
   0.2, 0.5, 0.7, # Fog color
   2.0, 2.0, 2.0, # Sky color
   0.4, 0.4, 0.4, # Ambient
   0.9, 0.9, 0.7, # Sun diffuse color
   ),
  (16.0, # Time
   40.0, 95.0, # Fog distance
   0.45, 0.6, 1.0, # Fog color
   0.0, 0.0, 1.0, # Sky color
   0.3, 0.3, 0.4, # Ambient
   0.7, 0.7, 0.5, # Sun diffuse color
   ),
   (19.0, # Time
   30.0, 80.0, # Fog distance
   0.3, 0.0, 0.6, # Fog color
   1.5, 1.2, 0.2, # Sky color
   0.2, 0.2, 0.6, # Ambient
   0.7, 0.7, 0.5, # Sun diffuse color
   ),
   (24.0, # Time -- This one is MANDATORY !!!
   5.0, 70.0, # Fog distance
   0.0, 0.0, 0.0, # Fog color
   0.2, 0.0, 1.0, # Sky color
   0.15, 0.15, 0.4, # Ambient
   0.3, 0.3, 0.5, # Sun diffuse color
   ),
 ],
  WEATHER_RAIN : [
  (4.0, # Time
   0.0, 65.0, # Fog distance
   0.0, 0.0, 0.0, # Fog color
   0.2, 0.3, 0.5, # Sky color
   0.15, 0.15, 0.2, # Ambient
   0.2, 0.2, 0.3, # Sun diffuse color
   ),
  (8.0, # Time
   5.0, 70.0, # Fog distance
   0.4, 0.45, 0.5, # Fog color
   0.2, 0.2, 0.3, # Sky color
   0.3, 0.3, 0.3, # Ambient
   0.6, 0.4, 0.3, # Sun diffuse color
   ),
  (12.0, # Time
   25.0, 95.0, # Fog distance
   0.4, 0.5, 0.6, # Fog color
   0.3, 0.3, 0.3, # Sky color
   0.3, 0.3, 0.3, # Ambient
   0.5, 0.5, 0.5, # Sun diffuse color
   ),
  (16.0, # Time
   25.0, 90.0, # Fog distance
   0.4, 0.4, 0.6, # Fog color
   0.2, 0.2, 0.4, # Sky color
   0.3, 0.3, 0.3, # Ambient
   0.4, 0.4, 0.4, # Sun diffuse color
   ),
   (19.0, # Time
   15.0, 80.0, # Fog distance
   0.4, 0.3, 0.5, # Fog color
   0.4, 0.3, 0.2, # Sky color
   0.2, 0.2, 0.3, # Ambient
   0.3, 0.3, 0.2, # Sun diffuse color
   ),
   (24.0, # Time -- This one is MANDATORY !!!
   0.0, 65.0, # Fog distance
   0.0, 0.0, 0.0, # Fog color
   0.2, 0.2, 0.8, # Sky color
   0.15, 0.15, 0.3, # Ambient
   0.2, 0.2, 0.3, # Sun diffuse color
   ),
  ],
  WEATHER_SNOW : [
  (4.0, # Time
   0.0, 70.0, # Fog distance
   0.0, 0.0, 0.0, # Fog color
   0.6, 0.0, 0.4, # Sky color
   0.15, 0.15, 0.3, # Ambient
   0.3, 0.2, 0.3, # Sun diffuse color
   ),
  (8.0, # Time
   0.0, 80.0, # Fog distance
   0.6, 0.5, 0.7, # Fog color
   1.5, 1.0, 0.8, # Sky color
   0.3, 0.3, 0.4, # Ambient
   0.5, 0.4, 0.3, # Sun diffuse color
   ),
  (12.0, # Time
   5.0, 100.0, # Fog distance
   0.5, 0.7, 0.7, # Fog color
   2.0, 2.0, 2.0, # Sky color
   0.4, 0.4, 0.4, # Ambient
   0.6, 0.6, 0.4, # Sun diffuse color
   ),
  (16.0, # Time
   3.0, 95.0, # Fog distance
   0.5, 0.8, 0.8, # Fog color
   0.0, 0.0, 1.0, # Sky color
   0.3, 0.3, 0.4, # Ambient
   0.4, 0.4, 0.2, # Sun diffuse color
   ),
   (19.0, # Time
   0.0, 80.0, # Fog distance
   0.3, 0.2, 0.6, # Fog color
   1.5, 1.2, 0.7, # Sky color
   0.2, 0.2, 0.6, # Ambient
   0.4, 0.4, 0.2, # Sun diffuse color
   ),
   (24.0, # Time -- This one is MANDATORY !!!
   0.0, 70.0, # Fog distance
   0.0, 0.0, 0.0, # Fog color
   0.4, 0.0, 0.3, # Sky color
   0.15, 0.15, 0.4, # Ambient
   0.3, 0.2, 0.3, # Sun diffuse color
   ),
  ],
  }
WEATHER_DURATION = {
  WEATHER_SUN  : 20.0,
  WEATHER_RAIN : 10.0,
  WEATHER_SNOW : 10.0,
  }



