/* $Id: PictureGLView.mm,v 1.18 2005/08/01 15:10:44 titer Exp $

   This file is part of the HandBrake source code.
   Homepage: <http://handbrake.m0k.org/>.
   It may be used under the terms of the GNU General Public License. */

#include <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#include <math.h>

#include "PictureGLView.h"

static int GetAlignedSize( int size )
{
    int result = 1;
    while( result < size )
    {
        result *= 2;
    }
    return result;
}

@implementation HBPictureGLView

- (id) initWithFrame: (NSRect) frame
{
    fHasQE  = CGDisplayUsesOpenGLAcceleration( kCGDirectMainDisplay );
    fTarget = fHasQE ? GL_TEXTURE_RECTANGLE_EXT : GL_TEXTURE_2D;

    fBuffers[0] = NULL;
    fBuffers[1] = NULL;
    fWidth      = 0;
    fHeight     = 0;

    fLastEffect = -1;
    
    GLuint attribs[] =
    {
        NSOpenGLPFANoRecovery,
        NSOpenGLPFAWindow,
        NSOpenGLPFAAccelerated,
        NSOpenGLPFADoubleBuffer,
        NSOpenGLPFAColorSize, 24,
        NSOpenGLPFAAlphaSize, 8,
        NSOpenGLPFADepthSize, 24,
        NSOpenGLPFAStencilSize, 8,
        NSOpenGLPFAAccumSize, 0,
        0
    };

    NSOpenGLPixelFormat * fmt = [[NSOpenGLPixelFormat alloc]
        initWithAttributes: (NSOpenGLPixelFormatAttribute*) attribs];

    self = [super initWithFrame:frame pixelFormat: [fmt autorelease]];

    if( !self )
    {
        return NULL;
    }

    [[self openGLContext] makeCurrentContext];
    [self reshape];

    glGenTextures( 2, fTextures );

    return self;
}

- (void) reshape
{
    NSRect bounds;
    [[self openGLContext] update];
    [[self openGLContext] makeCurrentContext];
    bounds = [self bounds];
    glViewport( 0, 0, (int) bounds.size.width,
                (int) bounds.size.height );
}

- (void) drawRect: (NSRect) rect
{
    [[self openGLContext] makeCurrentContext];

    glDisable( GL_DEPTH_TEST );
    glDisable( GL_CULL_FACE );
    glDisable( GL_BLEND );

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    if( fBuffers[0] )
    {
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();

        glEnable( fTarget );
        glBindTexture( fTarget, fTextures[0] );
        glTexImage2D( fTarget, 0, GL_RGBA, fTexWidth, fTexHeight, 0,
                      GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, fBuffers[0] );
        glTexParameteri( fTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
        glTexParameteri( fTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

        glBegin( GL_QUADS );
        glTexCoord2f( 0.0    , 0.0     ); glVertex2f( -1.0,  1.0 );
        glTexCoord2f( 0.0    , fCoordY ); glVertex2f( -1.0, -1.0 );
        glTexCoord2f( fCoordX, fCoordY ); glVertex2f(  1.0, -1.0 );
        glTexCoord2f( fCoordX, 0.0     ); glVertex2f(  1.0,  1.0 );
        glEnd();
    }
    [[self openGLContext] flushBuffer];
}

#define FRUSTUM_NEAR   2.5
#define FRUSTUM_FAR    20.0

- (void) drawCube: (int) anim
{
    uint64_t date;
    float w, rotation, translation;
    
    w = ( anim & HB_ANIMATE_BACKWARD ) ? 1.0 : -1.0;

    glEnable( GL_DEPTH_TEST );
    glEnable( GL_CULL_FACE );
    glDisable( GL_BLEND );

    for( rotation = 0.0; w * rotation < 90.0;
         rotation += w * 90 * 1000 / fAnimDuration / fFrameRate )
    {
        date = hb_get_date();
        translation = - FRUSTUM_NEAR - cos( rotation * M_PI / 180 ) *
            ( 1 + w * tan( rotation * M_PI / 180 ) );

        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        glFrustum( -1.0, 1.0, -1.0, 1.0, FRUSTUM_NEAR, FRUSTUM_FAR );
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        glTranslatef( 0.0, 0.0, translation );
        glRotatef( rotation, 0.0, 1.0, 0.0 );
     
        glBindTexture( fTarget, fTextures[0] );
        glBegin( GL_QUADS );
        glTexCoord2f( 0.0    , 0.0 );     glVertex3f( -1.0,  1.0, 1.0 );
        glTexCoord2f( 0.0    , fCoordY ); glVertex3f( -1.0, -1.0, 1.0 );
        glTexCoord2f( fCoordX, fCoordY ); glVertex3f(  1.0, -1.0, 1.0 );
        glTexCoord2f( fCoordX, 0.0 );     glVertex3f(  1.0,  1.0, 1.0 );
        glEnd();
     
        glBindTexture( fTarget, fTextures[1] );
        glBegin( GL_QUADS );
        if( anim & HB_ANIMATE_FORWARD )
        {
            glTexCoord2f( 0.0,    0.0 );     glVertex3f( 1.0,  1.0,  1.0 );
            glTexCoord2f( 0.0,    fCoordY ); glVertex3f( 1.0, -1.0,  1.0 );
            glTexCoord2f( fCoordX, fCoordY ); glVertex3f( 1.0, -1.0, -1.0 );
            glTexCoord2f( fCoordX, 0.0 );     glVertex3f( 1.0,  1.0, -1.0 );
        }
        else
        {
            glTexCoord2f( 0.0,    0.0 );     glVertex3f( -1.0,  1.0, -1.0 );
            glTexCoord2f( 0.0,    fCoordY ); glVertex3f( -1.0, -1.0, -1.0 );
            glTexCoord2f( fCoordX, fCoordY ); glVertex3f( -1.0, -1.0,  1.0 );
            glTexCoord2f( fCoordX, 0.0 );     glVertex3f( -1.0,  1.0,  1.0 );
        }
        glEnd();
     
        [[self openGLContext] flushBuffer];

        hb_snooze( 1000 / fFrameRate - ( hb_get_date() - date ) );
    }

}

- (void) drawSwap: (int) anim
{
    uint64_t date;
    float w, rotation, x, z;
    
    w = ( anim & HB_ANIMATE_BACKWARD ) ? 1.0 : -1.0;

    glEnable( GL_DEPTH_TEST );
    glEnable( GL_CULL_FACE );
    glDisable( GL_BLEND );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glFrustum( -1.0, 1.0, -1.0, 1.0, FRUSTUM_NEAR, FRUSTUM_FAR );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glTranslatef( 0.0, 0.0, - FRUSTUM_NEAR - 1.0 );

    for( rotation = 0.0; w * rotation < 180.0;
         rotation += w * 180 * 1000 / fAnimDuration / fFrameRate )
    {
        date = hb_get_date();

        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        x = 1.1 * sin( rotation * M_PI / 180 );
        z = cos( rotation * M_PI / 180 );

        glBindTexture( fTarget, fTextures[0] );
        glBegin( GL_QUADS );
        glTexCoord2f( 0.0,    0.0 );     glVertex3f( -1.0 + x,  1.0, z );
        glTexCoord2f( 0.0,    fCoordY ); glVertex3f( -1.0 + x, -1.0, z );
        glTexCoord2f( fCoordX, fCoordY ); glVertex3f(  1.0 + x, -1.0, z );
        glTexCoord2f( fCoordX, 0.0 );     glVertex3f(  1.0 + x,  1.0, z );
        glEnd();
     
        glBindTexture( fTarget, fTextures[1] );
        glBegin( GL_QUADS );
        glTexCoord2f( 0.0,    0.0 );     glVertex3f( -1.0 - x,  1.0, - z );
        glTexCoord2f( 0.0,    fCoordY ); glVertex3f( -1.0 - x, -1.0, - z );
        glTexCoord2f( fCoordX, fCoordY ); glVertex3f(  1.0 - x, -1.0, - z );
        glTexCoord2f( fCoordX, 0.0 );     glVertex3f(  1.0 - x,  1.0, - z );
        glEnd();
     
        [[self openGLContext] flushBuffer];

        hb_snooze( 1000 / fFrameRate - ( hb_get_date() - date ) );
    }
}

- (void) drawFade
{
    uint64_t date;
    float alpha;

    glDisable( GL_DEPTH_TEST );
    glDisable( GL_CULL_FACE );
    glEnable( GL_BLEND );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    for( alpha = 0.0; alpha < 1.0;
         alpha += 1000.0 / fAnimDuration / fFrameRate )
    {
        date = hb_get_date();

        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        glColor4f( 1.0, 1.0, 1.0, 1.0 - alpha );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE );

        glBindTexture( fTarget, fTextures[0] );
        glBegin( GL_QUADS );
        glTexCoord2f( 0.0,    0.0 );     glVertex2f( -1.0,  1.0 );
        glTexCoord2f( 0.0,    fCoordY ); glVertex2f( -1.0, -1.0 );
        glTexCoord2f( fCoordX, fCoordY ); glVertex2f(  1.0, -1.0 );
        glTexCoord2f( fCoordX, 0.0 );     glVertex2f(  1.0,  1.0 );
        glEnd();

        glColor4f( 1.0, 1.0, 1.0, alpha );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE );

        glBindTexture( fTarget, fTextures[1] );
        glBegin( GL_QUADS );
        glTexCoord2f( 0.0,    0.0 );     glVertex2f( -1.0,  1.0 );
        glTexCoord2f( 0.0,    fCoordY ); glVertex2f( -1.0, -1.0 );
        glTexCoord2f( fCoordX, fCoordY ); glVertex2f(  1.0, -1.0 );
        glTexCoord2f( fCoordX, 0.0 );     glVertex2f(  1.0,  1.0 );
        glEnd();

        [[self openGLContext] flushBuffer];

        hb_snooze( 1000 / fFrameRate - ( hb_get_date() - date ) );
    }
}

- (void) drawSlide: (int) anim
{
    uint64_t date;
    float foo, w;
    int left, right;
    if( anim & HB_ANIMATE_FORWARD )
    {
        left  = 0;
        right = 1;
        w     = 1.0;
    }
    else
    {
        left  = 1;
        right = 0;
        w     = -1.0;
    }

    glDisable( GL_DEPTH_TEST );
    glDisable( GL_CULL_FACE );
    glDisable( GL_BLEND );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    for( foo = w; foo >= -1.0 && foo <= 1.0;
         foo -= w * 2000.0 / fAnimDuration / fFrameRate )
    {
        date = hb_get_date();

        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        glBindTexture( fTarget, fTextures[left] );
        glBegin( GL_QUADS );
        glTexCoord2f( 0.0,    0.0 );     glVertex2f( foo - 2.0,  1.0 );
        glTexCoord2f( 0.0,    fCoordY ); glVertex2f( foo - 2.0, -1.0 );
        glTexCoord2f( fCoordX, fCoordY ); glVertex2f( foo,       -1.0 );
        glTexCoord2f( fCoordX, 0.0 );     glVertex2f( foo,        1.0 );
        glEnd();

        glBindTexture( fTarget, fTextures[right] );
        glBegin( GL_QUADS );
        glTexCoord2f( 0.0,    0.0 );     glVertex2f( foo,        1.0 );
        glTexCoord2f( 0.0,    fCoordY ); glVertex2f( foo,       -1.0 );
        glTexCoord2f( fCoordX, fCoordY ); glVertex2f( foo + 2.0, -1.0 );
        glTexCoord2f( fCoordX, 0.0 );     glVertex2f( foo + 2.0,  1.0 );
        glEnd();

        [[self openGLContext] flushBuffer];

        hb_snooze( 1000 / fFrameRate - ( hb_get_date() - date ) );
    }
}

#undef FRUSTUM_NEAR
#undef FRUSTUM_FAR

- (void) drawAnimation: (int) anim
{
    glEnable( fTarget );

    glBindTexture( fTarget, fTextures[0] );
    glTexImage2D( fTarget, 0, GL_RGBA, fTexWidth, fTexHeight, 0,
                  GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, fBuffers[1] );
    glTexParameteri( fTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( fTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    glBindTexture( fTarget, fTextures[1] );
    glTexImage2D( fTarget, 0, GL_RGBA, fTexWidth, fTexHeight, 0,
                  GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, fBuffers[0] );
    glTexParameteri( fTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( fTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    /* Draw a random animation, just making sure we don't use the same
       effect two times in a row */
    int effect;
    do
    {
        effect = hb_get_date() % 4;
    }
    while( effect == fLastEffect );

    fAnimDuration = ( anim & HB_ANIMATE_SLOW ) ? 3000 : 600;
    fFrameRate    = 60.0;
    
    switch( effect )
    {
        case 0:
            [self drawCube: anim];
            break;
        case 1:
            [self drawSwap: anim];
            break;
        case 2:
            [self drawFade];
            break;
        case 3:
            [self drawSlide: anim];
            break;
    }

    fLastEffect = effect;
}

- (void) Display: (int) anim buffer1: (uint8_t *) buffer1
    buffer2: (uint8_t *) buffer2 width: (int) width height: (int) height
{
    [[self openGLContext] makeCurrentContext];

    if( width != fWidth || height != fHeight )
    {
        fWidth  = width;
        fHeight = height;
        if( fHasQE )
        {
            fTexWidth  = fWidth;
            fTexHeight = fHeight;
            fCoordX    = (float) fWidth;
            fCoordY    = (float) fHeight;
        }
        else
        {
            fTexWidth  = GetAlignedSize( fWidth );
            fTexHeight = GetAlignedSize( fHeight );
            fCoordX    = (float) fWidth  / (float) fTexWidth;
            fCoordY    = (float) fHeight / (float) fTexHeight;
        }
        [self clearGLContext];
        [self openGLContext];
        [self reshape];
    }

    fBuffers[0] = buffer1;
    fBuffers[1] = buffer2;

    /* Swap buffers only during the vertical retrace of the monitor.
       http://developer.apple.com/documentation/GraphicsImaging/
       Conceptual/OpenGL/chap5/chapter_5_section_44.html */
    long params[] = { 1 };
    CGLSetParameter( CGLGetCurrentContext(), kCGLCPSwapInterval,
                     params );

    if( !( anim & HB_ANIMATE_NONE ) )
    {
        [self drawAnimation: anim];
    }
    
    [self drawRect: [self bounds]];
}

@end
