import numpy as np

from ..utils import arraytize
from .pyglet_utils import * # all the draw_* functions

import pyglet
import time

try :
    import pyglet.gl as gl
except:
    # may fail on bamboo or cluster
    pass

class PygletRenderer:

    """
    Renders world state instances, as returned for example by
    :py:meth:`roboball2d.physics.b2_world.B2World.step`. 
    See: :py:class:`roboball2d.physics.world_state.WorldState`.

    """

    __slots__=["robot_configs","ball_configs",
               "rendering_config","_callbacks","window",
               "_t_last_frame","mode"]
    
    def __init__(self,
                 rendering_config,
                 robot_configs,
                 ball_configs,
                 callbacks=[]):

        """

        Parameters
        ----------
        
        rendering_config:
            instance of :py:class:`roboball2d.rendering.rendering_config.RenderingConfig`

        robot_configs : 
            list of robot configurations, 
            see :py:class:`roboball2d.robot.default_robot_config.DefaultRobotConfig` 
            If only one robot is managed, a configuration instance may be passed.
    
        ball_configs:
            list of ball configurations, 
            see :py:class:`roboball2d.ball.ball_config.BallConfig` 
            If only one ball is managed, a configuration instance may be passed.
    
        callbacks: 
            list of callback functions, which should take a world state as argument 
            `roboball2d.physics.world_state.WorldState` and use pyglet API for 
            supplementary rendering. See: :py:meth:`robotball2d.demos.rendering_callback`.

        """

        
        self.robot_configs = arraytize(robot_configs)
        self.ball_configs = arraytize(ball_configs)
        self.rendering_config = rendering_config

        self._callbacks = callbacks
        
        self.window = None

        self._t_last_frame = None
        
        self.mode = "human"

    # world_state : instance of physics.world_state.WorldState:
    #               state of the world at current iteration as
    #               generated by the (b2_world) physics engine
    # goals : optional of array of tuple (x1 position,x2 position,color),
    #         for each of this tuple the renderer will
    #         draw a corresponding line on the floor
    
    def render(self, world_state,
               goals=[],
               time_step = None,
               wait=True):

        """"
        Renders the world state. Spawn a window if called for the first time.

        Parameters
        ----------

        world_state:
            state of the world as an instance of `roboball2d.physics.world_state.WorldState`
        
        goals:
            list of tuple (x1, x2, (r,g,b)). For each item, a goal will be 
            drawn on the ground, using the specified color

        time_step: 
             allows to force a frame rate by either;
             - having rendering waiting a suitable amount of time (wait=True)
             or
             - skipping some frames (wait=False)

        wait:
             see time_step above

        """

        if time_step is not None:

            # if wait : sleep the right amount of time such that the correct framerate is met
            # TODO: Does it make sense to wait before actually having rendered? This will result in a slower 
            # framerate than desired. Should be right before self.window.flip()
            if wait:
            
                if self._t_last_frame is None:
                    sleep_time = 0.0
                else:
                    sleep_time = max(0.0, time_step - (time.time() - self._t_last_frame))

                time.sleep(sleep_time)

                self._t_last_frame = time.time()

            # if not wait : skipping frame if required
            else :
                t = time.time()

                if self._t_last_frame is None:
                    self._t_last_frame = t

                if t-self._t_last_frame < time_step :
                    return

                self._t_last_frame = t
        
        if self.window is None:
            self.window = pyglet.window.Window(width = self.rendering_config.window.width,
                                               height = self.rendering_config.window.height,
                                               vsync = False,
                                               resizable = True)
            self.window.set_location(self.rendering_config.location[0],
                                     self.rendering_config.location[1])
            gl.glClearColor(*self.rendering_config.background_color)

            @self.window.event
            def on_resize(width, height):
                gl.glViewport(0, 0, width, height)
                gl.glMatrixMode(gl.GL_PROJECTION)
                gl.glLoadIdentity()
                gl.glOrtho(0., 
                           self.rendering_config.visible_area_width, 
                           -0.1*float(height)/width*self.rendering_config.visible_area_width,
                           0.9*float(height)/width*self.rendering_config.visible_area_width, 
                           -1., 
                           1.)
                gl.glMatrixMode(gl.GL_MODELVIEW)
                return pyglet.event.EVENT_HANDLED

        self.window.clear()
        self.window.switch_to()
        self.window.dispatch_events()

        gl.glLoadIdentity()


        # balls
        if self.ball_configs:
            for ball,config in zip(world_state.balls,self.ball_configs):
                draw_ball(ball.position,
                          ball.angle,
                          config.radius,
                          16,
                          config.color,
                          config.line_color)

        # robots
        for robot in world_state.robots:
            robot.render()

        gl.glBegin(gl.GL_QUADS)
        gl.glColor3f(*self.rendering_config.ground_color)
        gl.glVertex2f(0., 0.)
        gl.glVertex2f(0., -10.)
        gl.glVertex2f(self.rendering_config.visible_area_width, -10.)
        gl.glVertex2f(self.rendering_config.visible_area_width, 0.)
        gl.glEnd()

        # goals (if any)
        for goal in goals:
            x1,x2,color = goal
            draw_box([(x1+x2)/2.0,
                      -0.5*self.rendering_config.visual_height],
                     max(x1,x2)-min(x1,x2), 
                     self.rendering_config.visual_height,
                     0.,
                     color)

        for callback in self._callbacks:
            callback(world_state)
            
        self.window.flip()

