# ----------------------------------------------------------------------------
# cocos2d
# Copyright (c) 2008 Daniel Moisset, Ricardo Quesada, Rayentray Tappa, Lucio Torre
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright 
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
#   * Neither the name of cocos2d nor the names of its
#     contributors may be used to endorse or promote products
#     derived from this software without specific prior written
#     permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
'''Transitions between Scenes'''

__docformat__ = 'restructuredtext'

import pyglet

from cocos.actions import *
import cocos.scene as scene
from cocos.director import director
from cocos.layer import ColorLayer
from cocos.sprite import Sprite

__all__ = [ 'TransitionScene',
            'RotoZoomTransition','JumpZoomTransition',

            'MoveInLTransition','MoveInRTransition',
            'MoveInBTransition','MoveInTTransition',
            'SlideInLTransition','SlideInRTransition',
            'SlideInBTransition','SlideInTTransition',

            'FlipX3DTransition', 'FlipY3DTransition','FlipAngular3DTransition',

            'ShuffleTransition',
            'TurnOffTilesTransition',
            'FadeTRTransition', 'FadeBLTransition',
            'FadeUpTransition', 'FadeDownTransition',

            'ShrinkGrowTransition',
            'CornerMoveTransition',
            'EnvelopeTransition',

            'SplitRowsTransition', 'SplitColsTransition',

            'FadeTransition',

            'ZoomTransition',
            ]

class TransitionScene(scene.Scene):
    """TransitionScene
    A Scene that takes two scenes and makes a transition between them.
    
    These scenes are children of the transition scene.
    """
    
    def __init__(self, dst, duration=1.25, src=None):
        '''Initializes the transition

        :Parameters:
            `dst` : Scene
                Incoming scene
            `duration` : float
                Duration of the transition in seconds. Default: 1.25
            `src` : Scene
                Outgoing scene. Default: current scene
        '''
        super(TransitionScene, self).__init__()

        self.in_scene = dst                     #: scene that will replace the old one
        if src == None:
            src = director.scene
        self.out_scene = src                    #: scene that will be replaced
        self.duration = duration                #: duration in seconds of the transition
        if not self.duration:
            self.duration = 1.25

        if self.out_scene is None:
            raise Exception("You need to specfy a `src` argument")

        if id(self.out_scene) == id(self.in_scene):
            raise Exception("Incoming scene must be different from outgoing scene")

        self.start()
        
    def start(self):
        '''Adds the incoming scene with z=1 and the outgoing scene with z=0'''
        self.add( self.in_scene, z=1 )
        self.add( self.out_scene, z=0 )

    def finish(self):
        '''Called when the time is over.
        It removes both the incoming and the outgoing scenes from the transition scene,
        and restores the outgoing scene's attributes like: position, visible and scale.
        '''
        self.remove( self.in_scene )
        self.remove( self.out_scene )
        self.restore_out()
        director.replace( self.in_scene )

    def hide_out_show_in( self ):
        '''Hides the outgoing scene and shows the incoming scene'''
        self.in_scene.visible = True
        self.out_scene.visible = False

    def hide_all( self ):
        '''Hides both the incoming and outgoing scenes'''
        self.in_scene.visible = False
        self.out_scene.visible = False

    def restore_out( self ):
        '''Restore the position, visible and scale attributes of the outgoing scene
        to the original values'''
        self.out_scene.visible = True
        self.out_scene.position = (0,0)
        self.out_scene.scale = 1
        
class RotoZoomTransition(TransitionScene):
    '''Rotate and zoom out the outgoing scene, and then rotate and zoom in the incoming 
    '''

    def __init__( self, *args, **kwargs ):
        super(RotoZoomTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()

        self.in_scene.scale = 0.001
        self.out_scene.scale = 1.0

        self.in_scene.transform_anchor = (width // 2, height //2 )
        self.out_scene.transform_anchor = (width // 2, height //2 )

        rotozoom = ( ScaleBy(0.001, duration=self.duration/2.0 ) | \
                     Rotate(360 * 2, duration=self.duration/2.0 ) ) + \
                Delay( self.duration / 2.0 )

        self.out_scene.do( rotozoom ) 
        self.in_scene.do( Reverse(rotozoom) + CallFunc(self.finish) )

class JumpZoomTransition(TransitionScene):
    '''Zoom out and jump the outgoing scene, and then jump and zoom in the incoming 
    '''

    def __init__( self, *args, **kwargs ):
        super(JumpZoomTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()

        self.in_scene.scale = 0.5
        self.in_scene.position = ( width, 0 )
        self.in_scene.transform_anchor = (width // 2, height //2 )
        self.out_scene.transform_anchor = (width // 2, height //2 )

        jump = JumpBy( (-width,0), width//4, 2, duration=self.duration / 4.0 )
        scalein = ScaleTo( 1, duration=self.duration / 4.0 )
        scaleout = ScaleTo( 0.5, duration=self.duration / 4.0 )

        jumpzoomout = scaleout + jump
        jumpzoomin = jump + scalein

        delay = Delay( self.duration / 2.0 )
        self.out_scene.do(  jumpzoomout ) 
        self.in_scene.do( delay + jumpzoomin + CallFunc(self.finish) )


class MoveInLTransition(TransitionScene):
    '''Move in from to the left the incoming scene.
    '''
    def __init__( self, *args, **kwargs ):
        super(MoveInLTransition, self ).__init__( *args, **kwargs)

        self.init()
        a = self.get_action()

        self.in_scene.do( (Accelerate(a,0.5) ) + CallFunc(self.finish) )

    def init(self):
        width, height = director.get_window_size()
        self.in_scene.position=(-width,0)

    def get_action(self):
        return MoveTo( (0,0), duration=self.duration)


class MoveInRTransition(MoveInLTransition):
    '''Move in from to the right the incoming scene.
    '''
    def init(self):
        width, height = director.get_window_size()
        self.in_scene.position=(width,0)

    def get_action(self):
        return MoveTo( (0,0), duration=self.duration)


class MoveInTTransition(MoveInLTransition):
    '''Move in from to the top the incoming scene.
    '''
    def init(self):
        width, height = director.get_window_size()
        self.in_scene.position=(0,height)

    def get_action(self):
        return MoveTo( (0,0), duration=self.duration)


class MoveInBTransition(MoveInLTransition):
    '''Move in from to the bottom the incoming scene.
    '''
    def init(self):
        width, height = director.get_window_size()
        self.in_scene.position=(0,-height)

    def get_action(self):
        return MoveTo( (0,0), duration=self.duration)

class SlideInLTransition(TransitionScene):
    '''Slide in the incoming scene from the left border.
    '''
    def __init__( self, *args, **kwargs ):
        super(SlideInLTransition, self ).__init__( *args, **kwargs)

        self.width, self.height = director.get_window_size()
        self.init()

        move = self.get_action()

        self.in_scene.do( Accelerate(move,0.5) )
        self.out_scene.do( Accelerate(move,0.5) + CallFunc( self.finish) )

    def init(self):
        self.in_scene.position=( -self.width,0)

    def get_action(self):
        return MoveBy( (self.width,0), duration=self.duration)


class SlideInRTransition(SlideInLTransition):
    '''Slide in the incoming scene from the right border.
    '''
    def init(self):
        self.in_scene.position=(self.width,0)

    def get_action(self):
        return MoveBy( (-self.width,0), duration=self.duration)


class SlideInTTransition(SlideInLTransition):
    '''Slide in the incoming scene from the top border.
    '''
    def init(self):
        self.in_scene.position=(0,self.height)

    def get_action(self):
        return MoveBy( (0,-self.height), duration=self.duration)


class SlideInBTransition(SlideInLTransition):
    '''Slide in the incoming scene from the bottom border.
    '''
    def init(self):
        self.in_scene.position=(0,-self.height)

    def get_action(self):
        return MoveBy( (0,self.height), duration=self.duration)


class FlipX3DTransition(TransitionScene):
    '''Flips the screen horizontally.
    The front face is the outgoing scene and the back face is the incoming scene.
    '''
    def __init__( self, *args, **kwargs ):
        super(FlipX3DTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()

        turnongrid = Waves3D( amplitude=0, duration=0, grid=(1,1), waves=2 )
        flip90 = OrbitCamera( angle_x=0,  delta_z=90, duration = self.duration / 2.0 )
        flipback90 = OrbitCamera( angle_x=0, angle_z=90, delta_z=90, duration = self.duration / 2.0 )

        self.in_scene.visible = False
        flip = turnongrid + \
                flip90 + \
                CallFunc(self.hide_all) + \
                FlipX3D(duration=0) + \
                CallFunc( self.hide_out_show_in ) + \
                flipback90
        
        self.do( flip + \
                CallFunc(self.finish) + \
                StopGrid() )


class FlipY3DTransition(TransitionScene):
    '''Flips the screen vertically.
    The front face is the outgoing scene and the back face is the incoming scene.
    '''
    def __init__( self, *args, **kwargs ):
        super(FlipY3DTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()

        turnongrid = Waves3D( amplitude=0, duration=0, grid=(1,1), waves=2 )
        flip90 = OrbitCamera( angle_x=90, delta_z=-90, duration = self.duration / 2.0 )
        flipback90 = OrbitCamera( angle_x=90, angle_z=90, delta_z=90, duration = self.duration / 2.0 )

        self.in_scene.visible = False
        flip = turnongrid + \
                flip90 + \
                CallFunc(self.hide_all) + \
                FlipX3D(duration=0) + \
                CallFunc( self.hide_out_show_in ) + \
                flipback90
        
        self.do( flip + \
                    CallFunc(self.finish) + \
                    StopGrid() )

class FlipAngular3DTransition(TransitionScene):
    '''Flips the screen half horizontally and half vertically.
    The front face is the outgoing scene and the back face is the incoming scene.
    '''
    def __init__( self, *args, **kwargs ):
        super(FlipAngular3DTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()

        turnongrid = Waves3D( amplitude=0, duration=0, grid=(1,1), waves=2 )
        flip90 = OrbitCamera( angle_x=45,  delta_z=90, duration = self.duration / 2.0 )
        flipback90 = OrbitCamera( angle_x=45, angle_z=90, delta_z=90, duration = self.duration / 2.0 )

        self.in_scene.visible = False
        flip = turnongrid + \
                flip90 + \
                CallFunc(self.hide_all) + \
                FlipX3D(duration=0) + \
                CallFunc( self.hide_out_show_in ) + \
                flipback90
        
        self.do( flip + \
                    CallFunc(self.finish) + \
                    StopGrid() )


class ShuffleTransition(TransitionScene):
    '''Shuffle the outgoing scene, and then reorder the tiles with the incoming scene.
    '''
    def __init__( self, *args, **kwargs ):
        super(ShuffleTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()
        aspect = width / float(height)
        x,y = int(12*aspect), 12

        shuffle = ShuffleTiles( grid=(x,y), duration=self.duration/2.0, seed=15 )
        self.in_scene.visible = False

        self.do( shuffle + \
                 CallFunc(self.hide_out_show_in) + \
                 Reverse(shuffle) + \
                 CallFunc(self.finish) + \
                 StopGrid()
                )


class ShrinkGrowTransition(TransitionScene):
    '''Shrink the outgoing scene while grow the incoming scene
    '''
    def __init__( self, *args, **kwargs ):
        super(ShrinkGrowTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()

        self.in_scene.scale = 0.001
        self.out_scene.scale = 1

        self.in_scene.transform_anchor = ( 2*width / 3.0, height / 2.0 )
        self.out_scene.transform_anchor = ( width / 3.0, height / 2.0 )

        scale_out = ScaleTo( 0.01, duration=self.duration )
        scale_in = ScaleTo( 1.0, duration=self.duration )

        self.in_scene.do( Accelerate(scale_in,0.5) )
        self.out_scene.do( Accelerate(scale_out,0.5) + CallFunc( self.finish) )


class CornerMoveTransition(TransitionScene):
    '''Moves the bottom-right corner of the incoming scene to the top-left corner
    '''
    def __init__( self, *args, **kwargs ):
        super(CornerMoveTransition, self ).__init__( *args, **kwargs)

        self.out_scene.do( MoveCornerUp( duration=self.duration ) + \
                            CallFunc(self.finish) + \
                            StopGrid() )

    def start(self):
        # don't call super. overriding order
        self.add( self.in_scene, z=0 )
        self.add( self.out_scene, z=1 )

class EnvelopeTransition(TransitionScene):
    '''From the outgoing scene:
        - moves the top-right corner to the center
        - moves the bottom-left corner to the center

      From the incoming scene:
        - performs the reverse action of the outgoing scene
    '''
    def __init__( self, *args, **kwargs ):
        super(EnvelopeTransition, self ).__init__( *args, **kwargs)

        self.in_scene.visible = False

        move = QuadMoveBy( delta0=(320,240), delta1=(-630,0), delta2=(-320,-240), delta3=(630,0), duration=self.duration / 2.0 )
#        move = Accelerate( move )
        self.do( move + \
                 CallFunc(self.hide_out_show_in) + \
                 Reverse(move) + \
                 CallFunc(self.finish) + \
                 StopGrid()
                 )


class FadeTRTransition(TransitionScene):
    '''Fade the tiles of the outgoing scene from the left-bottom corner the to top-right corner.
    '''
    def __init__( self, *args, **kwargs ):
        super(FadeTRTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()
        aspect = width / float(height)
        x,y = int(12*aspect), 12

        a = self.get_action(x,y)

#        a = Accelerate( a)
        self.out_scene.do( a + \
                            CallFunc(self.finish) + \
                            StopGrid() )

    def start(self):
        # don't call super. overriding order
        self.add( self.in_scene, z=0 )
        self.add( self.out_scene, z=1 )

    def get_action(self,x,y):
        return FadeOutTRTiles( grid=(x,y), duration=self.duration )

class FadeBLTransition(FadeTRTransition):
    '''Fade the tiles of the outgoing scene from the top-right corner to the bottom-left corner.
    '''
    def get_action(self,x,y):
        return FadeOutBLTiles( grid=(x,y), duration=self.duration )

class FadeUpTransition(FadeTRTransition):
    '''Fade the tiles of the outgoing scene from the bottom to the top.
    '''
    def get_action(self,x,y):
        return FadeOutUpTiles( grid=(x,y), duration=self.duration )

class FadeDownTransition(FadeTRTransition):
    '''Fade the tiles of the outgoing scene from the top to the bottom.
    '''
    def get_action(self,x,y):
        return FadeOutDownTiles( grid=(x,y), duration=self.duration )

class TurnOffTilesTransition(TransitionScene):
    '''Turn off the tiles of the outgoing scene in random order
    '''
    def __init__( self, *args, **kwargs ):
        super(TurnOffTilesTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()
        aspect = width / float(height)
        x,y = int(12*aspect), 12

        a = TurnOffTiles( grid=(x,y), duration=self.duration )
#        a = Accelerate( a)
        self.out_scene.do( a + \
                        CallFunc(self.finish) + \
                        StopGrid() )

    def start(self):
        # don't call super. overriding order
        self.add( self.in_scene, z=0 )
        self.add( self.out_scene, z=1 )


class FadeTransition(TransitionScene):
    '''Fade out the outgoing scene and then fade in the incoming scene.'''
    def __init__( self, *args, **kwargs ):
        super(FadeTransition, self ).__init__( *args, **kwargs)

        self.fadelayer = ColorLayer(0,0,0,0)

        self.in_scene.visible = False
        self.add( self.fadelayer, z=2 )


    def on_enter( self ):
        super( FadeTransition, self).on_enter()
        self.fadelayer.do( FadeIn( duration=self.duration/2.0) + \
                           CallFunc( self.hide_out_show_in) + \
                           FadeOut( duration=self.duration /2.0 ) + \
                           CallFunc( self.finish) )
    def on_exit( self ):
        super( FadeTransition, self).on_exit()
        self.remove( self.fadelayer )

class SplitColsTransition(TransitionScene):
    '''Splits the screen in columns.
    The odd columns goes upwards while the even columns goes downwards.
    '''
    def __init__( self, *args, **kwargs ):
        super(SplitColsTransition, self ).__init__( *args, **kwargs)

        width, height = director.get_window_size()

        self.in_scene.visible = False
        flip_a =  self.get_action()
        flip = flip_a + \
                CallFunc( self.hide_out_show_in ) + \
                Reverse(flip_a)
        
        self.do( AccelDeccel(flip) + \
                    CallFunc(self.finish) + \
                    StopGrid() )

    def get_action( self ):
        return SplitCols( cols=3, duration=self.duration/2.0)

class SplitRowsTransition(SplitColsTransition):
    '''Splits the screen in rows.
    The odd rows goes to the left while the even rows goes to the right.
    '''
    def get_action( self ):
        return SplitRows( rows=3, duration=self.duration/2.0)


class ZoomTransition(TransitionScene):
    '''Zoom and FadeOut the outgoing scene.'''

    def __init__(self, *args, **kwargs):
        if 'src' in kwargs or len(args) == 3:
            raise Exception("ZoomTransition does not accept 'src' parameter.")

        super(ZoomTransition, self ).__init__( *args, **kwargs)
        self.out_scene.visit()

    def start(self):
        screensprite = self._create_out_screenshot()

        zoom = ScaleBy(2, self.duration) | FadeOut(self.duration)
        screensprite.do(zoom)

        restore = CallFunc(self.finish)
        self.in_scene.do(Delay(self.duration * 2) + restore)

        self.add(screensprite, z=1)
        self.add(self.in_scene, z=0)

    def finish(self):
        '''Called when the time is over.
        It removes both the incoming and the outgoing scenes from the transition scene,
        and restores the outgoing scene's attributes like: position, visible and scale.
        '''
        self.remove( self.in_scene )
        self.restore_out()
        director.replace( self.in_scene )

    def _create_out_screenshot(self):
        # TODO: try to use `pyglet.image.get_buffer_manager().get_color_buffer()`
        #       instead of create a new BufferManager... note that pyglet uses
        #       a BufferManager singleton that fail when you change the window
        #       size.
        buffer = pyglet.image.BufferManager()
        image = buffer.get_color_buffer()

        width, height = director.window.width, director.window.height 
        actual_width, actual_height = director.get_window_size()

        out = Sprite(image)
        out.position = actual_width / 2, actual_height / 2
        out.scale = max(actual_width / float(width), actual_height / float(height))

        return out
