#####################################################################
# -*- coding: iso-8859-1 -*-                                        #
#                                                                   #
# Frets on Fire                                                     #
# Copyright (C) 2006 Sami Kyöstilä                                  #
#                                                                   #
# 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., 51 Franklin Street, Fifth Floor, Boston,        #
# MA  02110-1301, USA.                                              #
#####################################################################

from Player import Player
from View import BackgroundLayer
from Session import MessageHandler, Message
from Input import KeyListener
from Camera import Camera
import Network
import Player
import Config

from OpenGL.GL import *
from OpenGL.GLU import *
#from OpenGL.GLUT import *
import math
import colorsys
import pygame
from Numeric import array, Float, transpose, reshape, matrixmultiply

Config.define("network", "updateinterval", int, 72)

# Messages from client to server
class CreateActor(Message): pass
class ControlEvent(Message): pass

# Messages from server to client
class ActorCreated(Message): pass
class ActorDeleted(Message): pass
class ActorData(Message): pass
class ControlData(Message): pass

class Actor:
  def __init__(self, scene):
    self.scene = scene
    self.body = ode.Body(scene.world)
    self.geom = None
    self.mass = ode.Mass()
    self.svgDrawing = None
    self.scale = 1.0
    self.rotation = 0

  def getState(self):
    return (self.body.getPosition(),
            self.body.getQuaternion(),
            self.body.getLinearVel(),
            self.body.getAngularVel())

  def setSvgDrawing(self, svgDrawing):
    svgDrawing.convertToTexture(256, 256)
    self.svgDrawing = svgDrawing

  def setState(self, pos, quat, linearVel, angularVel):
    self.body.setPosition(pos)
    self.body.setQuaternion(quat)
    self.body.setLinearVel(linearVel)
    self.body.setAngularVel(angularVel)

  def render(self):
    s = self.svgDrawing
    if not s:
      return

    x, y, z    = self.body.getPosition()
    modelview  = glGetDoublev(GL_MODELVIEW_MATRIX)
    projection = glGetDoublev(GL_PROJECTION_MATRIX)
    viewport   = glGetIntegerv(GL_VIEWPORT)

    # calculate the z coordinate
    m = transpose(reshape(modelview, (4, 4)))
    wz = -matrixmultiply(m, reshape((x, y, z, 1), (4, 1)))[2][0]

    # don't draw anything if we're behind the viewer
    if wz < 0.1:
      return

    # calculate the screen-space x and y coordinates
    x, y, z = gluProject(x, y, z, modelview, projection, viewport)
    scale = self.scale / wz

    s.transform.reset()
    s.transform.translate(x, y)
    s.transform.scale(scale, -scale)
    s.transform.rotate(self.rotation)
    s.draw()

class BoxActor(Actor):
  def __init__(self, scene, owner, size = [1.0, 1.0, 1.0], density = 1.0):
    Actor.__init__(self, scene)
    self.owner = owner
    self.size = size
    self.mass.setBox(density, size[0], size[1], size[2])
    self.geom = ode.GeomBox(self.scene.space, lengths = size)
    self.geom.setBody(self.body)
    import random
    self.body.setPosition((random.random(), random.random(), random.random() * 3))
    
  def render(self):
    Actor.render(self)

    x, y, z = self.body.getPosition()
    R = self.body.getRotation()
    T = array((R[0], R[3], R[6], 0,
               R[1], R[4], R[7], 0,
               R[2], R[5], R[8], 0,
                  x,    y,    z, 1), Float)
    glPushMatrix()
    glMultMatrixd(T.tolist())
    sx, sy, sz = self.size
    glScale(sx, sy, sz)

    glDisable(GL_LIGHTING)
    glEnable(GL_BLEND)
    glBlendFunc(GL_ONE, GL_ONE)

    glFrontFace(GL_CW)
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
    glColor3f(1, 1, 1)
    #glutSolidCube(1)
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    glEnable(GL_LIGHTING)
    glFrontFace(GL_CCW)
    glColor4f(*[abs(x) + .3 for x in self.body.getLinearVel() + (.5,)])
    glScale(.97, .97, .97)
    #glutSolidCube(1)
    glDisable(GL_LIGHTING)
    glPopMatrix()
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

#glutInit([]) # FIXME: do we need glut?

class Scene(MessageHandler, BackgroundLayer):
  def __init__(self, engine, owner, **args):
    self.objects = Network.ObjectCollection()
    self.args    = args
    self.owner   = owner
    self.engine  = engine
    self.actors  = []
    self.camera  = Camera()
    self.world   = None
    self.space   = None
    self.time    = 0.0
    self.actors  = []
    self.players = []
    self.createCommon(**args)

  def initPhysics(self):
    import ode

    # Create a default world and a space
    self.world = ode.World()
    self.world.setGravity((0, -9.81 , 0))
    self.world.setERP(0.8)
    self.world.setCFM(1e-5)
    self.space = ode.Space()
    self.contactGroup = ode.JointGroup()

  def addPlayer(self, player):
    self.players.append(player)

  def removePlayer(self, player):
    self.players.remove(player)

  def createCommon(self, **args):
    pass

  def runCommon(self, ticks, world):
    pass
    
  def run(self, ticks):
    self.time += ticks / 50.0

    if self.world:
      self.contactGroup.empty()
      self.space.collide(None, self.nearCallback)
      self.world.step(ticks / 512.0)

  def nearCallback(self, args, geom1, geom2):
    contacts = ode.collide(geom1, geom2)
    
    for c in contacts:
      c.setBounce(0.2)
      c.setMu(5000)
      j = ode.ContactJoint(self.world, self.contactGroup, c)
      j.attach(geom1.getBody(), geom2.getBody())

  def handleActorCreated(self, sender, id, owner, name):
    actor = globals()[name](self, owner)
    self.objects[id] = actor
    self.actors.append(actor)

  def handleActorDeleted(self, sender, id):
    actor = self.objects[id]
    self.actors.remove(actor)
    del self.objects[id]

class SceneClient(Scene, KeyListener):
  def __init__(self, engine, owner, session, **args):
    Scene.__init__(self, engine, owner, **args)
    self.session = session
    self.player = self.session.world.getLocalPlayer()
    self.controls = Player.Controls()
    self.createClient(**args)

  def createClient(self, **args):
    pass

  def createActor(self, name):
    self.session.sendMessage(CreateActor(name = name))

  def shown(self):
    self.engine.input.addKeyListener(self)

  def hidden(self):
    self.engine.input.removeKeyListener(self)

  def keyPressed(self, key, unicode):
    c = self.controls.keyPressed(key)
    if c:
      self.session.sendMessage(ControlEvent(flags = self.controls.flags))
      return True
    return False

  def keyReleased(self, key):
    c = self.controls.keyReleased(key)
    if c:
      self.session.sendMessage(ControlEvent(flags = self.controls.flags))
      return True
    return False

  def handleControlData(self, sender, owner, flags):
    # TODO: player mapping
    for player in self.session.world.players:
      if player.owner == owner:
        player.controls.flags = flags
        break

  def handleActorData(self, sender, id, data):
    actor = self.objects[id]
    actor.setState(*data)

  def run(self, ticks):
    self.runCommon(ticks, self.session.world)
    Scene.run(self, ticks)
    
  def render3D(self):
    for actor in self.actors:
      actor.render()

  def render(self, visibility, topMost):
    font = self.engine.data.font

    # render the scene
    try:
      glMatrixMode(GL_PROJECTION)
      glPushMatrix()
      glLoadIdentity()
      gluPerspective(60, self.engine.view.aspectRatio, 0.1, 1000)
      glMatrixMode(GL_MODELVIEW)
      glLoadIdentity()
      
      glPushMatrix()
      self.camera.apply()
  
      self.render3D()
    finally:
      glPopMatrix()
      glMatrixMode(GL_PROJECTION)
      glPopMatrix()
      glMatrixMode(GL_MODELVIEW)

class SceneServer(Scene):
  def __init__(self, engine, owner, server, **args):
    Scene.__init__(self, engine, owner, **args)
    self.server = server
    self.updateInterval = self.engine.config.get("network", "updateinterval")
    self.updateCounter = 0
    self.changedControlData = {}

  def handleControlEvent(self, sender, flags):
    self.changedControlData[sender] = flags

  def handleControlData(self, sender, owner, flags):
    # TODO: player mapping
    for player in self.server.world.players:
      if player.owner == owner:
        player.controls.flags = flags
        break

  def handleCreateActor(self, sender, name):
    id = self.objects.generateId()
    self.server.broadcastMessage(ActorCreated(owner = sender, name = name, id = id))

  def handleSessionClosed(self, session):
    for actor in self.actors:
      if actor.owner == session.id:
        id = self.objects.id(actor)
        self.server.broadcastMessage(ActorDeleted(id = id))

  def handleSessionOpened(self, session):
    for actor in self.actors:
      id = self.objects.id(actor)
      session.sendMessage(ActorCreated(owner = actor.owner, name = actor.__name__, id = id))

  def broadcastState(self):
    for actor in self.actors:
      id = self.objects.id(actor)
      self.server.broadcastMessage(ActorData(id = id, data = actor.getState()), meToo = False)

    for sender, flags in self.changedControlData.items():
      self.server.broadcastMessage(ControlData(owner = sender, flags = flags))
    self.changedControlData = {}

  def run(self, ticks):
    self.runCommon(ticks, self.server.world)
    Scene.run(self, ticks)

    self.updateCounter += ticks
    if self.updateCounter > self.updateInterval:
      self.updateCounter %= self.updateInterval
      self.broadcastState()
