#####################################################################
# -*- 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 __future__ import division
from OpenGL.GL import *
from OpenGL.GLU import *

import Log

from Task import Task

class Layer(Task):
  def render(self, visibility, topMost):
    pass
    
  def shown(self):
    pass
  
  def hidden(self):
    pass

  def run(self, ticks):
    pass

  def isBackgroundLayer(self):
    return False

class BackgroundLayer(Layer):
  def isBackgroundLayer(self):
    return True

class View(Task):
  def __init__(self, engine, geometry = None):
    Task.__init__(self)
    self.layers = []
    self.incoming = []
    self.outgoing = []
    self.visibility = {}
    self.transitionTime = 512.0
    self.geometry = geometry or glGetIntegerv(GL_VIEWPORT)
    self.savedGeometry = None
    self.engine = engine
    w = self.geometry[2] - self.geometry[0]
    h = self.geometry[3] - self.geometry[1]
    self.aspectRatio = float(w) / float(h)

  def pushLayer(self, layer):
    Log.debug("View: Push: %s" % layer.__class__.__name__)
    
    if not layer in self.layers:
      self.layers.append(layer)
      self.incoming.append(layer)
      self.visibility[layer] = 0.0
      layer.shown()
    elif layer in self.outgoing:
      layer.hidden()
      layer.shown()
      self.outgoing.remove(layer)
    self.engine.addTask(layer)

  def topLayer(self):
    layers = list(self.layers)
    layers.reverse()
    for layer in layers:
      if layer not in self.outgoing:
        return layer

  def popLayer(self, layer):
    Log.debug("View: Pop: %s" % layer.__class__.__name__)
    
    if layer in self.incoming:
      self.incoming.remove(layer)
    if layer in self.layers and not layer in self.outgoing:
      self.outgoing.append(layer)

  def popAllLayers(self):
    Log.debug("View: Pop all")
    [self.popLayer(l) for l in list(self.layers)]

  def run(self, ticks):
    if not self.layers:
      return

    # don't jump when loading stuff
    ticks = min(32, ticks)

    topLayer = self.topLayer()
    t = ticks / self.transitionTime
    for layer in list(self.layers):
      if not layer in self.visibility:
        continue
      if layer in self.outgoing or (layer is not topLayer and not layer.isBackgroundLayer()):
        if self.visibility[layer] > 0.0:
          self.visibility[layer] = max(0.0, self.visibility[layer] - t)
        else:
          self.visibility[layer] = 0.0
          if layer in self.outgoing:
            self.outgoing.remove(layer)
            self.layers.remove(layer)
            del self.visibility[layer]
            self.engine.removeTask(layer)
            layer.hidden()
          if layer in self.incoming:
            self.incoming.remove(layer)
      elif layer in self.incoming or layer is topLayer:
        if self.visibility[layer] < 1.0:
          self.visibility[layer] = min(1.0, self.visibility[layer] + t)
        else:
          self.visibility[layer] = 1.0
          if layer in self.incoming:
            self.incoming.remove(layer)

  def setOrthogonalProjection(self, normalize = True, yIsDown = True):
    glMatrixMode(GL_PROJECTION)
    glPushMatrix()
    glLoadIdentity()

    viewport = glGetIntegerv(GL_VIEWPORT)
    if normalize:
      w = viewport[2] - viewport[0]
      h = viewport[3] - viewport[1]
      # aspect ratio correction
      h *= (float(w) / float(h)) / (4.0 / 3.0)
      viewport = [0, 0, 1, h / w]
  
    if yIsDown:
      glOrtho(viewport[0], viewport[2] - viewport[0],
              viewport[3] - viewport[1], viewport[1], -100, 100);
    else:
      glOrtho(viewport[0], viewport[2] - viewport[0],
              viewport[1], viewport[3] - viewport[1], -100, 100);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
  
  def resetProjection(self):
    glMatrixMode(GL_PROJECTION)
    glPopMatrix()
    glMatrixMode(GL_MODELVIEW)
    glPopMatrix()

  def setGeometry(self, geometry):
    viewport = glGetIntegerv(GL_VIEWPORT)
    w = viewport[2] - viewport[0]
    h = viewport[3] - viewport[1]
    s = (w, h, w, h)

    geometry = tuple([(type(coord) == float) and int(s[i] * coord) or int(coord) for i, coord in enumerate(geometry)])
    self.savedGeometry, self.geometry = viewport, geometry
    glViewport(*geometry)
    glScissor(*geometry)

  def resetGeometry(self):
    assert self.savedGeometry
    
    self.savedGeometry, geometry = None, self.savedGeometry
    self.geometry = geometry
    glViewport(*geometry)
    glScissor(*geometry)

  def render(self):
    #print [(str(m.__class__), v) for m, v in self.visibility.items()]
    for layer in self.layers:
      layer.render(self.visibility[layer], layer == self.layers[-1])
