# Soya 3D
# Copyright (C) 2003-2004 Jean-Baptiste LAMY -- jiba@tuxfamily.org
#
# 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

cdef object LIGHTS, LAST_LIGHTS
LIGHTS      = []
LAST_LIGHTS = []

cdef class _Light(CoordSyst):
  #cdef public float radius, angle, exponent
  #cdef float _w, _constant, _linear, _quadratic
  #cdef float _colors[16] # ambient + diffuse + specular + shadow colors
  #cdef float _data[3] # used by cell-shading and shadow
  #cdef readonly int _id
  #cdef int _gl_id_enabled
  ##cdef int _used
  
  def __new__(self, *args, **kargs):
    self.__raypick_data = -1
    self._id            = -1
    
  def __init__(self, _World parent = None):
    CoordSyst.__init__(self, parent)
    self._w          = 1.0
    self._constant   = 1.0
    self.angle       = 180.0
    self._colors[ 0] = 0.0 # ambient color
    self._colors[ 1] = 0.0 # ambient color
    self._colors[ 2] = 0.0 # ambient color
    self._colors[ 3] = 0.0 # ambient color
    self._colors[ 4] = 1.0 # diffuse color
    self._colors[ 5] = 1.0
    self._colors[ 6] = 1.0
    self._colors[ 7] = 1.0
    self._colors[ 8] = 1.0 # specular color
    self._colors[ 9] = 1.0
    self._colors[10] = 1.0
    self._colors[11] = 1.0
    self._colors[12] = 0.0 # shadow color
    self._colors[13] = 0.0
    self._colors[14] = 0.0
    self._colors[15] = 0.5
    self._option     = self._option | LIGHT_STATIC
    self.radius      = -1.0
    
  cdef __getcstate__(self):
    #return struct.pack("<iffffffffffffffffffffffffffffffffffffffffff", self._option, self._matrix[0], self._matrix[1], self._matrix[2], self._matrix[3], self._matrix[4], self._matrix[5], self._matrix[6], self._matrix[7], self._matrix[8], self._matrix[9], self._matrix[10], self._matrix[11], self._matrix[12], self._matrix[13], self._matrix[14], self._matrix[15], self._matrix[16], self._matrix[17], self._matrix[18], self.radius, self.angle, self.exponent, self.linear, self.constant, self.quadratic, self._w, self._colors[0], self._colors[1], self._colors[2], self._colors[3], self._colors[4], self._colors[5], self._colors[6], self._colors[7], self._colors[8], self._colors[9], self._colors[10], self._colors[11], self._colors[12], self._colors[13], self._colors[14], self._colors[15])
    
    cdef Chunk* chunk
    chunk = get_chunk()
    chunk_add_int_endian_safe   (chunk, self._option)
    chunk_add_floats_endian_safe(chunk, self._matrix, 19)
    chunk_add_float_endian_safe (chunk, self.radius)
    chunk_add_float_endian_safe (chunk, self.angle)
    chunk_add_float_endian_safe (chunk, self.exponent)
    chunk_add_float_endian_safe (chunk, self._linear)
    chunk_add_float_endian_safe (chunk, self._constant)
    chunk_add_float_endian_safe (chunk, self._quadratic)
    chunk_add_float_endian_safe (chunk, self._w)
    chunk_add_floats_endian_safe(chunk, self._colors, 16)
    return drop_chunk_to_string (chunk)
  
  cdef void __setcstate__(self, cstate):
    self._validity = COORDSYS_INVALID
    #self._option, self._matrix[0], self._matrix[1], self._matrix[2], self._matrix[3], self._matrix[4], self._matrix[5], self._matrix[6], self._matrix[7], self._matrix[8], self._matrix[9], self._matrix[10], self._matrix[11], self._matrix[12], self._matrix[13], self._matrix[14], self._matrix[15], self._matrix[16], self._matrix[17], self._matrix[18], self.radius, self.angle, self.exponent, self.linear, self.constant, self.quadratic, self._w, self._colors[0], self._colors[1], self._colors[2], self._colors[3], self._colors[4], self._colors[5], self._colors[6], self._colors[7], self._colors[8], self._colors[9], self._colors[10], self._colors[11], self._colors[12], self._colors[13], self._colors[14], self._colors[15] = struct.unpack("<iffffffffffffffffffffffffffffffffffffffffff", cstate)
    
    cdef Chunk* chunk
    chunk = string_to_chunk(cstate)
    chunk_get_int_endian_safe   (chunk, &self._option)
    chunk_get_floats_endian_safe(chunk,  self._matrix, 19)
    chunk_get_float_endian_safe (chunk, &self.radius)
    chunk_get_float_endian_safe (chunk, &self.angle)
    chunk_get_float_endian_safe (chunk, &self.exponent)
    chunk_get_float_endian_safe (chunk, &self._linear)
    chunk_get_float_endian_safe (chunk, &self._constant)
    chunk_get_float_endian_safe (chunk, &self._quadratic)
    chunk_get_float_endian_safe (chunk, &self._w)
    chunk_get_floats_endian_safe(chunk,  self._colors, 16)
    drop_chunk(chunk)
    
  property directional:
    def __get__(self):
      return self._w == 0.0
    def __set__(self, int x):
      if x: self._w = 0.0
      else: self._w = 1.0
      
  property constant:
    def __get__(self):
      return self._constant
    def __set__(self, float x):
      self._constant = x
      self._compute_radius()
      
  property linear:
    def __get__(self):
      return self._linear
    def __set__(self, float x):
      self._linear = x
      self._compute_radius()
      
  property quadratic:
    def __get__(self):
      return self._quadratic
    def __set__(self, float x):
      self._quadratic = x
      self._compute_radius()
      
  property cast_shadow:
    def __get__(self):
      return self._option & LIGHT_NO_SHADOW == 0
    def __set__(self, int x):
      if x: self._option = self._option & ~LIGHT_NO_SHADOW
      else: self._option = self._option |  LIGHT_NO_SHADOW
      
  property top_level:
    def __get__(self):
      return self._option & LIGHT_TOP_LEVEL != 0
    def __set__(self, int x):
      if x: self._option = self._option |  LIGHT_TOP_LEVEL
      else: self._option = self._option & ~LIGHT_TOP_LEVEL
      
  property static:
    def __get__(self):
      return (self._option & LIGHT_STATIC) != 0
    def __set__(self, int x):
      if x: self._option = self._option |  LIGHT_STATIC
      else: self._option = self._option & ~LIGHT_STATIC
      
  property ambient:
    def __get__(self):
      return self._colors[0], self._colors[1], self._colors[2], self._colors[3]
    def __set__(self, color):
      self._colors[0], self._colors[1], self._colors[2], self._colors[3] = color
      
  property diffuse:
    def __get__(self):
      return self._colors[4], self._colors[5], self._colors[6], self._colors[7]
    def __set__(self, color):
      self._colors[4], self._colors[5], self._colors[6], self._colors[7] = color
      
  property specular:
    def __get__(self):
      return self._colors[8], self._colors[9], self._colors[10], self._colors[11]
    def __set__(self, color):
      self._colors[8], self._colors[9], self._colors[10], self._colors[11] = color
      
  property shadow_color:
    def __get__(self):
      return self._colors[12], self._colors[13], self._colors[14], self._colors[15]
    def __set__(self, color):
      self._colors[12], self._colors[13], self._colors[14], self._colors[15] = color
      
  cdef int _shadow_at(self, float position[3]):
    cdef _World root
    cdef RaypickData data
    cdef float* rdata
    root  = self._get_root()
    if root is None: return 0
    data  = RaypickData()
    rdata = data.root_data
    if self._w == 0.0:
      rdata[3] =  0.0
      rdata[4] =  0.0
      rdata[5] = -1.0
      vector_by_matrix(rdata + 3, self._root_matrix())
      vector_normalize(rdata + 3)
      rdata[6] = 100.0
      point_by_matrix_copy(rdata, position, self._parent._root_matrix())
      rdata[0] = rdata[0] - rdata[3] * rdata[6]
      rdata[1] = rdata[1] - rdata[4] * rdata[6]
      rdata[2] = rdata[2] - rdata[5] * rdata[6]
      rdata[6] = rdata[6] - 1.0
    else:
      vector_from_points(rdata + 3, self._matrix + 12, position)
      if self._parent is None:
        memcpy(rdata, self._matrix + 12, 3 * sizeof(GLfloat))
      else:
        point_by_matrix_copy(rdata, self._matrix + 12, self._parent._root_matrix())
        vector_by_matrix(rdata + 2, self._parent._root_matrix())
      rdata[6] = vector_length(rdata + 3) - 1.0
      vector_normalize(rdata + 3)
    data.option = RAYPICK_HALF_LINE
    return root._raypick_b(data, None)
  
  cdef float _spotlight_at(self, float position[3]):
    cdef float v[3], w[3]
    cdef float m
    if (fabs(self.angle - 180.0) < EPSILON) or (self._w == 0.0):
      return 1.0 # point or directional light
    v[0] = position[0] - self._matrix[12]
    v[1] = position[1] - self._matrix[13]
    v[2] = position[2] - self._matrix[14]
    w[0] = -self._matrix[ 8] # front is -Z
    w[1] = -self._matrix[ 9]
    w[2] = -self._matrix[10]
    m = vector_dot_product(v, w)
    if m < 0.0: m = 0.0
    if m <= cos(self.angle): return 0.0 # position is out of cone
    else: return pow(m, self.exponent)
    
  cdef float _attenuation_at(self, float position[3]):
    cdef float d
    if self._w == 0.0: return 1.0 # directional light
    d = point_distance_to(self._matrix + 12, position)
    return 1.0 / (self._constant + self._linear * d + self._quadratic * d * d)

  cdef void _static_light_at(self, float* position, float* normal, int shadow, float* result):
    cdef float f, g, angle, attenuation, spotlight
    cdef float v[3], n[3]
    attenuation = self._attenuation_at(position)
    spotlight   = self._spotlight_at  (position)
    f           = attenuation * spotlight
    
    if f == 0.0: return
    if shadow and self._shadow_at(position): g = 0.0
    else:
      if (normal == NULL): angle = 1.0
      else:
        memcpy(n, normal, sizeof(n))
        vector_normalize(n)
        if self._w == 0.0: # directional light
          v[0] =  0.0
          v[1] =  0.0
          v[2] =  1.0 # XXX should be -1, but works only with 1...???
          vector_by_matrix(v, self._matrix)
        else:
          vector_from_points(v, self._matrix + 12, position)
          vector_normalize(v)
        angle = vector_dot_product(n, v)
        
        if angle > 0.0: angle = 0.0
        else:           angle = -angle
      g = angle
      
    result[0] = result[0] + f * (self._colors[0] + g * self._colors[4])
    result[1] = result[1] + f * (self._colors[1] + g * self._colors[5])
    result[2] = result[2] + f * (self._colors[2] + g * self._colors[6])
    #result[3] = result[3] + f * (self._colors[3] + g * self._colors[7])
    
  cdef void _cast_into(self, CoordSyst coordsyst):
    if self._w == 0.0: # convert light direction in coordsyst
      self._data[0] = -self._matrix[ 8] # light direction is -Z
      self._data[1] = -self._matrix[ 9]
      self._data[2] = -self._matrix[10]
      if (not self.parent is None) and (not self.parent is coordsyst):
        vector_by_matrix(self._data, self._parent._root_matrix())
        vector_by_matrix(self._data, coordsyst._inverted_root_matrix())
        vector_normalize(self._data)
    else: # convert light position in coordsys
      if (self.parent is None) or (self.parent is coordsyst):
        memcpy (self._data, self._matrix + 12, sizeof(self._data))
      else:
        point_by_matrix_copy(self._data, self._matrix + 12, self._parent._root_matrix())
        point_by_matrix     (self._data, coordsyst._inverted_root_matrix())
        
  cdef void _batch(self, CoordSyst coordsyst):
#     cdef float data[4]
    if self._option & HIDDEN: return
    #multiply_matrix(self._render_matrix, renderer.current_camera._render_matrix, self._root_matrix())
    multiply_matrix(self._render_matrix, coordsyst._render_matrix, self._matrix)
#     if self.radius != -1.0: # frustum test
#       data[0] = self._matrix[12]
#       data[1] = self._matrix[13]
#       data[2] = self._matrix[14]
#       data[3] = self.radius
#       self._frustum_id = -1
#       if sphere_in_frustum(renderer._frustum(coordsyst), data) != 1: return
    if self._option & LIGHT_TOP_LEVEL: renderer.top_lights.append(self)
    else:                              renderer.current_context.lights.append(self)
    
  cdef void _activate(self):
    cdef int     id
    cdef _Light  light
    cdef GLfloat p[4]
    cdef GLfloat q[3]

    p[3] = self._w
    if not(self._option & HIDDEN):
      glLoadMatrixf(self._render_matrix)
      if self._id == -1:
        id = 0
        for light in LIGHTS:
          if light is None:
            self._id = id
            LIGHTS[id] = self
            break
          id = id + 1
        else:
          print "Too many lights!"
          #raise ValueError("Too many lights!")
          return

      id = GL_LIGHT0 + self._id

      if not self is LAST_LIGHTS[self._id]:
        LAST_LIGHTS[self._id] = self
        glLightf (id, GL_SPOT_EXPONENT,         self.exponent)
        glLightf (id, GL_SPOT_CUTOFF,           self.angle)
        glLightfv(id, GL_AMBIENT,               self._colors)
        glLightfv(id, GL_DIFFUSE,               self._colors + 4)
        glLightfv(id, GL_SPECULAR,              self._colors + 8)
        glLightf (id, GL_CONSTANT_ATTENUATION,  self._constant)
        glLightf (id, GL_LINEAR_ATTENUATION,    self._linear)
        glLightf (id, GL_QUADRATIC_ATTENUATION, self._quadratic)

      if self._w == 0.0:
        p[0] = p[1] = 0.0
        p[2] = 1.0 # XXX should be -1, but works only with 1...???
        glLightfv(id, GL_POSITION, p)
      else: # positional light
        p[0] = p[1] = p[2] = 0.0
        q[0] = q[1] = 0.0
        q[2] = -1.0
        glLightfv(id, GL_POSITION, p)
        glLightfv(id, GL_SPOT_DIRECTION, q)
      glEnable(id)
      self._gl_id_enabled = 1
      
      
  cdef void _compute_radius(self):
    if   self._w == 0.0:
      self.radius = -1.0
    elif (self._linear == 0.0) and (self._quadratic == 0.0): # point infinite light
      self.radius = -1.0
    elif (self._linear != 0.0) and (self._quadratic == 0.0):
      self.radius = ((1.0 / LIGHT_NULL_ATTENUATION) - self._constant) / self._linear
    else:
      # XXX sub optimal
      self.radius = ((1.0 / LIGHT_NULL_ATTENUATION) - self._constant) / (self._linear + self._quadratic)
      
      
cdef void disable_all_lights():
  """Disable all lights."""
  cdef _Light light
  for light in LIGHTS:
    if not light is None:
      if light._gl_id_enabled:
        glDisable(GL_LIGHT0 + light._id)
        light._gl_id_enabled = 0
      LIGHTS[light._id] = None
      light._id = -1
        
cdef void disable_static_lights():
  """Disable all static lights. Static lights are disabled when rendering static lit
shape (Soya assume the effect of static lights was already taken into account at the
shape computation time)."""
  cdef _Light light
  for light in LIGHTS:
    if (not light is None) and (light._gl_id_enabled == 1) and (light._option & LIGHT_STATIC):
      glDisable(GL_LIGHT0 + light._id)
      light._gl_id_enabled = 0
      
cdef void enable_static_lights():
  """Enable all static lights. Static lights are disabled when rendering static lit
object (Soya assume the effect of static lights was already taken into account at the
shape computation time)."""
  cdef _Light light
  for light in LIGHTS:
    if (not light is None) and (light._gl_id_enabled == 0) and (light._option & LIGHT_STATIC):
      glEnable(GL_LIGHT0 + light._id)
      light._gl_id_enabled = 1
      
cdef void disable_deep_lights():
  """Disable all non top level lights."""
  cdef _Light light
  for light in LIGHTS:
    if (not light is None) and (light._option & LIGHT_TOP_LEVEL) and (light._gl_id_enabled == 1):
      glDisable(GL_LIGHT0 + i)
      light._gl_id_enabled = 0
      
