/******************************************************************************
 * = NAME
 * videoout_corevideo.cpp
 *
 * = DESCRIPTION
 * Present video frames on screen using CoreVideo and CoreImage
 *
 * = REVISION
 * $Id$
 *
 * = AUTHORS
 * Andrew Kimpton
 *****************************************************************************/


// Typical call sequence
// VideoOutputCoreVideo::VideoOutputCoreVideo
// VideoOutputCoreVideo::Init
// VideoOutputCoreVideo::VideoAspectRatioChanged

// ****************************************************************************
// Configuration:

// Default numbers of buffers from some of the other videoout modules:
const int kNumBuffers      = 31;
const int kNeedFreeFrames  = 1;
const int kPrebufferFramesNormal = 12;
const int kPrebufferFramesSmall = 4;
const int kKeepPrebuffer   = 2;

#define USE_DISPLAY_VIDEO_RECT 1

// ****************************************************************************

#include "videoout_corevideo.h"
#include "mythcontext.h"
#include "filtermanager.h"
#include "util-osx.h"
#include "yuv2rgb.h"

#include <math.h>
#include <Carbon/Carbon.h>
#include <QuickTime/QuickTime.h>
#include <AGL/agl.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/glu.h>

class VideoOutputCoreVideoRep
{
    private:
        friend class VideoOutputCoreVideo;
        
        VideoOutputCoreVideoRep();
        ~VideoOutputCoreVideoRep();
        
        bool Init(int width, int height, float aspect, WId winid,
                int winx, int winy, int winw, int winh, WId embedid = 0, int srcMode = kLetterbox_Off);
        void PrepareFrame(VideoFrame *buffer, FrameScanType t);
        void Show(FrameScanType, const QRect &display_video_rect);

        void InputChanged(int width, int height, float aspect, MythCodecID av_codec_id, int srcMode);
        void VideoAspectRatioChanged(float aspect, int srcMode);
        void Zoom(int direction);

        void EmbedInWidget(WId wid, int x, int y, int w, int h);
        void StopEmbedding(void);

        int GetRefreshRate(void);

        void DrawUnusedRects(bool sync = true);

        void ProcessFrame(VideoFrame *frame, OSD *osd,
                        FilterChain *filterList,
                        NuppelVideoPlayer *pipPlayer);

        bool SetupOpenGL();
        void InitializeGLView();
        bool CreateCoreVideoBuffers();
        void DeleteCoreVideoBuffers();
        void UpdateTransformMatrix();

        // Global preferences:
        bool               mScaleUpVideo;      // Enlarge video as needed?
        yuv2vuy_fun mYUVConverter;  // 420 -> 2vuy conversion function

        // CoreVideo, OpenGL & QuickDraw data
        WindowRef           mMainWindow;
        AGLContext          mAGLContext;
        CVDisplayLinkRef    mDisplayLink;
        CGLPixelFormatObj   mCGLPixelFormat;
        CGLContextObj       mCGLContext;
    
        CVPixelBufferRef        mCurrentFrameBuffer;
        CVOpenGLTextureCacheRef mTextureCache;
        CVOpenGLTextureRef      mTexture;

    	CGRect mTextureFrame;

        GLfloat	mLowerLeft[2]; 
        GLfloat mLowerRight[2]; 
        GLfloat mUpperRight[2];
        GLfloat mUpperLeft[2];

        int mWidth;
        int mHeight;
        float mAspect;
        float mSrcAspect;
        int mSrcMode;
        int mSrcWidth;
        int mSrcHeight;
    
        int                mDesiredWidth,
                           mDesiredHeight,
                           mDesiredXoff,
                           mDesiredYoff;   // output size characteristics
        
        // Zoom preferences:
        int                mZoomedIn;          // These mirror the videooutbase
        int                mZoomedUp;          // variables, for the benefit of
        int                mZoomedRight;       // the views

        int mRefreshRate;
        
        char *mBitmapData;
        size_t mBitmapDataSize;
};

/*
 * VideoOutputCoreVideo implementation
 */
VideoOutputCoreVideo::VideoOutputCoreVideo(void)
                 : VideoOutput()
{
    init(&mPauseFrame, FMT_YV12, NULL, 0, 0, 0, 0);

    mCoreVideoRep = new VideoOutputCoreVideoRep;
}

VideoOutputCoreVideo::~VideoOutputCoreVideo()
{
    if (mPauseFrame.buf)
        delete [] mPauseFrame.buf;

    vbuffers.DeleteBuffers();

    delete mCoreVideoRep;
}

void VideoOutputCoreVideo::VideoAspectRatioChanged(float aspect)
{
    VideoOutput::VideoAspectRatioChanged(aspect);

    mCoreVideoRep->VideoAspectRatioChanged(aspect, db_letterbox);
}

void VideoOutputCoreVideo::Zoom(int direction)
{
    VERBOSE(VB_PLAYBACK,
            QString("VideoOutputCoreVideo::Zoom(direction=%1)").arg(direction));

    VideoOutput::Zoom(direction);
    MoveResize();
    mCoreVideoRep->Zoom(direction);
}

void VideoOutputCoreVideo::InputChanged(int width, int height, float aspect,
                                     MythCodecID av_codec_id)
{
    VERBOSE(VB_PLAYBACK,
            QString("VideoOutputCoreVideo::InputChanged(width=%1, height=%2, aspect=%3")
                   .arg(width).arg(height).arg(aspect));
    VideoOutput::InputChanged(width, height, aspect, av_codec_id);
    
    vbuffers.DeleteBuffers();
    vbuffers.CreateBuffers(video_dim.width(), video_dim.height());
    // Set up pause frame
    if (mPauseFrame.buf)
      delete [] mPauseFrame.buf;

    VideoFrame *scratch = vbuffers.GetScratchFrame();

    init(&mPauseFrame, FMT_YV12, new unsigned char[scratch->size], 
       scratch->width, scratch->height, scratch->bpp, scratch->size);

    mPauseFrame.frameNumber = scratch->frameNumber;
    
    mCoreVideoRep->InputChanged(width, height, aspect, av_codec_id, db_letterbox);
    MoveResize();
}

int VideoOutputCoreVideo::GetRefreshRate(void)
{
    return mCoreVideoRep->GetRefreshRate();
}

bool VideoOutputCoreVideo::Init(int width, int height, float aspect,
                             WId winid, int winx, int winy,
                             int winw, int winh, WId embedid)
{
    VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideo::Init(width=%1, height=%2, aspect=%3, winid=%4\n winx=%5, winy=%6, winw=%7, winh=%8, WId embedid=%9)")
                   .arg(width)
                   .arg(height)
                   .arg(aspect)
                   .arg(winid)
                   .arg(winx)
                   .arg(winy)
                   .arg(winw)
                   .arg(winh)
                   .arg(embedid));

    vbuffers.Init(kNumBuffers, true, kNeedFreeFrames, 
                  kPrebufferFramesNormal, kPrebufferFramesSmall, 
                  kKeepPrebuffer);
    VideoOutput::Init(width, height, aspect, winid,
                      winx, winy, winw, winh, embedid);

    vbuffers.CreateBuffers(video_dim.width(), video_dim.height());

    // Set up pause frame
    if (mPauseFrame.buf)
      delete [] mPauseFrame.buf;

    VideoFrame *scratch = vbuffers.GetScratchFrame();

    init(&mPauseFrame, FMT_YV12, new unsigned char[scratch->size], 
       scratch->width, scratch->height, scratch->bpp, scratch->size);

    mPauseFrame.frameNumber = scratch->frameNumber;

    bool repInited =  mCoreVideoRep->Init(width, height, aspect, winid, winx, winy, winw, winh, embedid, db_letterbox);
    if (repInited)
    {
        MoveResize();
    }
    return repInited;
}

void VideoOutputCoreVideo::EmbedInWidget(WId wid, int x, int y, int w, int h)
{
    VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideo::EmbedInWidget(wid=%1, x=%2, y=%3, w=%4, h=%5)")
                   .arg(wid)
                   .arg(x)
                   .arg(y)
                   .arg(w)
                   .arg(h));

    if (embedding)
        return;

    VideoOutput::EmbedInWidget(wid, x, y, w, h);

    mCoreVideoRep->EmbedInWidget(wid, x, y, w, h);
}

void VideoOutputCoreVideo::StopEmbedding(void)
{
    VERBOSE(VB_PLAYBACK,
        QString("VideoOutputCoreVideo::StopEmbedding()"));

    if (!embedding)
        return;

    VideoOutput::StopEmbedding();

    mCoreVideoRep->StopEmbedding();
}

void VideoOutputCoreVideo::PrepareFrame(VideoFrame *buffer, FrameScanType t)
{
    (void)t;

    if (!buffer)
        buffer = vbuffers.GetScratchFrame();

    framesPlayed = buffer->frameNumber + 1;
}

void VideoOutputCoreVideo::Show(FrameScanType t)
{
    mCoreVideoRep->Show(t, display_video_rect);
}

void VideoOutputCoreVideo::DrawUnusedRects(bool)
{
}

void VideoOutputCoreVideo::UpdatePauseFrame(void)
{
    if (!mPauseFrame.buf)
    {
        puts("VideoOutputQuartz::UpdatePauseFrame() - no buffers?");
        return;
    }

    VideoFrame *pauseb = vbuffers.GetScratchFrame();
    VideoFrame *pauseu = vbuffers.head(kVideoBuffer_used);
    if (pauseu)
        memcpy(mPauseFrame.buf, pauseu->buf, pauseu->size);
    else
        memcpy(mPauseFrame.buf, pauseb->buf, pauseb->size);
}

void VideoOutputCoreVideo::ProcessFrame(VideoFrame *frame, OSD *osd,
                                     FilterChain *filterList,
                                     NuppelVideoPlayer *pipPlayer)
{
    if (!frame)
    {
        frame = vbuffers.GetScratchFrame();
        CopyFrame(vbuffers.GetScratchFrame(), &mPauseFrame);
    }

    if (filterList)
        filterList->ProcessFrame(frame);

    if (m_deinterlacing &&
        m_deintFilter != NULL &&
        m_deinterlaceBeforeOSD)
    {
        m_deintFilter->ProcessFrame(frame);
    }

    ShowPip(frame, pipPlayer);
    DisplayOSD(frame, osd);

    if (m_deinterlacing &&
        m_deintFilter != NULL &&
        !m_deinterlaceBeforeOSD)
    {
        m_deintFilter->ProcessFrame(frame);
    }

    mCoreVideoRep->ProcessFrame(frame, osd, filterList, pipPlayer);
}

bool VideoOutputCoreVideo::IsCoreVideoSupported()
{
    bool supported = false;
    CGDirectDisplayID displayID = NULL;
    Rect windowBounds;
    
    if (GetWindowBounds( FrontNonFloatingWindow(), kWindowStructureRgn, &windowBounds) == noErr)
    {
        CGPoint pt;
        pt.x = windowBounds.left;
        pt.y = windowBounds.top;
        CGDisplayCount ct;
        CGGetDisplaysWithPoint(pt, 1, &displayID, &ct);
    }
    if (!displayID)
        displayID = CGMainDisplayID();
    
    if (displayID && CGDisplayUsesOpenGLAcceleration( displayID ))
        supported = true;
        
    return supported;
}
VideoOutputCoreVideoRep::VideoOutputCoreVideoRep() : mAGLContext(NULL), mRefreshRate(0)
{
    mDesiredXoff = mDesiredYoff = mDesiredWidth = mDesiredHeight = 0;
}

VideoOutputCoreVideoRep::~VideoOutputCoreVideoRep()
{
    aglDestroyContext(mAGLContext);
    DeleteCoreVideoBuffers();
}

bool VideoOutputCoreVideoRep::Init(int width, int height, float aspect, WId winid, int winx, int winy, int winw, int winh, WId embedid, int srcMode)
{
    (void)winid; // unused
    (void)winx; // unused
    (void)winy; // unused
    (void)winw; // unused
    (void)winh; // unused
    (void)embedid; // unused
    
    mYUVConverter = get_yuv2vuy_conv();

    mWidth  = width;
    mHeight = height;
    mAspect = aspect;

    mSrcWidth  = width;
    mSrcHeight = height;
    mSrcAspect = aspect;
    mSrcMode   = srcMode;
    
    mZoomedIn = 0;
    mZoomedUp = 0;
    mZoomedRight = 0;
    
    // Global configuration options
    mScaleUpVideo = gContext->GetNumSetting("MacScaleUp", 1);


    mTextureFrame = CGRectMake(0, 0, mWidth, mHeight);
    
    mMainWindow = FrontNonFloatingWindow();
 
    if (!SetupOpenGL())
    {
        VERBOSE(VB_IMPORTANT, "VideoOutCoreVideo::Init - SetupOpenGL failed");
        return false;
    }
    
    InitializeGLView();
    
    if (!CreateCoreVideoBuffers())
    {
        VERBOSE(VB_IMPORTANT, "VideoOutCoreVideo::Init - CreateCoreVideoBuffers failed");
        return false;
    }

    Rect windowBounds;
    CGDirectDisplayID screen = NULL;
    if (GetWindowBounds( mMainWindow, kWindowStructureRgn, &windowBounds) == noErr)
    {
        CGPoint pt;
        pt.x = windowBounds.left;
        pt.y = windowBounds.top;
        CGDisplayCount ct;
        CGGetDisplaysWithPoint(pt, 1, &screen, &ct);
    }

    if (screen == NULL)
        screen = CGMainDisplayID();

    // Find the refresh rate of our screen
    CFDictionaryRef m;
    m = CGDisplayCurrentMode(screen);
    mRefreshRate = get_float_CF(m, kCGDisplayRefreshRate);
    if (mRefreshRate == 0.0)    // LCD display?
        mRefreshRate = 60.0;  

    UpdateTransformMatrix();

    return true;
}

void VideoOutputCoreVideoRep::PrepareFrame(VideoFrame *buffer, FrameScanType t)
{
    VERBOSE(VB_IMPORTANT, "Implement VideoOutputCoreVideoRep::PrepareFrame");
}
void VideoOutputCoreVideoRep::Show(FrameScanType, const QRect &display_video_rect)
{
    if (!mAGLContext)
        return;
        
    aglSetCurrentContext(mAGLContext);
    
    glClear(GL_COLOR_BUFFER_BIT);	

    glEnable(CVOpenGLTextureGetTarget(mTexture));
    glBindTexture(CVOpenGLTextureGetTarget(mTexture), CVOpenGLTextureGetName(mTexture));

    glColor3f(1,1,1);
    glBegin(GL_QUADS);
#if USE_DISPLAY_VIDEO_RECT
    glTexCoord2f(mLowerLeft[0], mLowerLeft[1]); glVertex2i(	display_video_rect.left(), mDesiredHeight - display_video_rect.top() - display_video_rect.height());
    glTexCoord2f(mUpperLeft[0], mUpperLeft[1]); glVertex2i(	display_video_rect.left(), mDesiredHeight - display_video_rect.top());
    glTexCoord2f(mUpperRight[0], mUpperRight[1]); glVertex2i(	display_video_rect.right(), mDesiredHeight - display_video_rect.top());
    glTexCoord2f(mLowerRight[0], mLowerRight[1]); glVertex2i(	display_video_rect.right(), mDesiredHeight - display_video_rect.top() - display_video_rect.height());
#else
    glTexCoord2f(mLowerLeft[0], mLowerLeft[1]); glVertex2i(	mTextureFrame.origin.x - (mTextureFrame.size.width/2), mTextureFrame.origin.y - (mTextureFrame.size.height/2));
    glTexCoord2f(mUpperLeft[0], mUpperLeft[1]); glVertex2i(	mTextureFrame.origin.x - (mTextureFrame.size.width/2), mTextureFrame.size.height/2);
    glTexCoord2f(mUpperRight[0], mUpperRight[1]); glVertex2i(	mTextureFrame.size.width/2, mTextureFrame.size.height/2);
    glTexCoord2f(mLowerRight[0], mLowerRight[1]); glVertex2i(	mTextureFrame.size.width/2, mTextureFrame.origin.y - (mTextureFrame.size.height/2));
#endif
    glEnd();
    glDisable(CVOpenGLTextureGetTarget(mTexture));
    glFlush();
}

void VideoOutputCoreVideoRep::InputChanged(int width, int height, float aspect, MythCodecID av_codec_id, int srcMode)
{
    DeleteCoreVideoBuffers();

    mWidth  = width;
    mHeight = height;
    mAspect = aspect;

    mSrcWidth  = width;
    mSrcHeight = height;
    mSrcAspect = aspect;
    mSrcMode   = srcMode;
    
    mZoomedIn = 0;
    mZoomedUp = 0;
    mZoomedRight = 0;
    
    mTextureFrame = CGRectMake(0, 0, mWidth, mHeight);

    CreateCoreVideoBuffers();

    UpdateTransformMatrix();
}

void VideoOutputCoreVideoRep::VideoAspectRatioChanged(float aspect, int srcMode)
{
    VERBOSE(VB_PLAYBACK,
            QString("VideoOutputCoreVideoRep::VideoAspectRatioChanged"
                    "(aspect=%1) [was %2]")
            .arg(aspect).arg(mSrcAspect));

    mSrcAspect = aspect;
    mSrcMode   = srcMode;
    UpdateTransformMatrix();
}

void VideoOutputCoreVideoRep::Zoom(int direction)
{
    VERBOSE(VB_IMPORTANT, "Implement VideoOutputCoreVideoRep::Zoom");
}

void VideoOutputCoreVideoRep::EmbedInWidget(WId wid, int x, int y, int w, int h)
{
    VERBOSE(VB_IMPORTANT, "Implement VideoOutputCoreVideoRep::EmbedInWidget");
}

void VideoOutputCoreVideoRep::StopEmbedding(void)
{
    VERBOSE(VB_IMPORTANT, "Implement VideoOutputCoreVideoRep::StopEmbedding");
}

int VideoOutputCoreVideoRep::GetRefreshRate(void)
{
    return (int) 1000000 / mRefreshRate;      // Rate is in microseconds per frame
}

void VideoOutputCoreVideoRep::DrawUnusedRects(bool sync)
{
    VERBOSE(VB_IMPORTANT, "Implement VideoOutputCoreVideoRep::DrawUnusedRects");
}

void VideoOutputCoreVideoRep::ProcessFrame(VideoFrame *frame, OSD *osd, FilterChain *filterList, NuppelVideoPlayer *pipPlayer)
{
    if (mYUVConverter)
    {
        mYUVConverter((uint8_t *)mBitmapData,
                           frame->buf + frame->offsets[0], // Y
                           frame->buf + frame->offsets[1], // U
                           frame->buf + frame->offsets[2], // V
                           frame->width, frame->height,
                           (frame->width % 2), (frame->width % 2), 0);
        // FIXME - These values (stride) should be calculated
        //         from frame->pitches and frame->width ?
    }
    else
        memcpy(mBitmapData, frame->buf, frame->size);

    CVReturn error = CVOpenGLTextureCacheCreateTextureFromImage (NULL, mTextureCache,  mCurrentFrameBuffer,  0, &mTexture);
    if(error != kCVReturnSuccess)
        VERBOSE(VB_IMPORTANT,QString("VideoOutputCoreVideRep::ProcessFrame - Failed to create OpenGL texture error = %1").arg(error));

    CVOpenGLTextureGetCleanTexCoords(mTexture, mLowerLeft, mLowerRight, mUpperRight, mUpperLeft);
}

// Build the transformation matrix to scale the video appropriately.
void VideoOutputCoreVideoRep::UpdateTransformMatrix()
{
#if USE_DISPLAY_VIDEO_RECT
return;
#endif // USE_DISPLAY_VIDEO_RECT
    if (!mAGLContext)
        return;
        
    aglSetCurrentContext(mAGLContext);

    InitializeGLView();
    
    int x, y, w, h, sw, sh;
    x = mDesiredXoff;
    y = mDesiredYoff;
    w = mDesiredWidth;
    h = mDesiredHeight;
    sw = mSrcWidth;
    sh = mSrcHeight;
    float aspect = mSrcAspect;

    VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Window is %1 x %2")
                                .arg(w).arg(h));
    VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Image is %1 x %2")
                                .arg(sw).arg(sh));

    gluOrtho2D(0, 0, w, h);
    
    // Translate so all drawing is about the center of the window
    glTranslatef(w/2.0f, h/2.0f, 0.0f);
    
    // scale for non-square pixels
    if (fabsf(aspect - (sw * 1.0f / sh)) > 0.01f)
    {
        if (mScaleUpVideo)
        {
            // scale width up, leave height alone
            double aspectScale = aspect * sh / sw;
            VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Scaling to %1 of width")
                                        .arg(aspectScale));
            glScalef(aspectScale, 1.0f, 1.0f);
            
            // reset sw to be apparent width
            sw = (int)lroundf(sh * aspect);
        }
        else
        {
            // scale height down
            double aspectScale = sw / (aspect * sh);
            VERBOSE(VB_PLAYBACK,
                    QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Scaling to %1 of height")
                           .arg(aspectScale));
            glScalef(1.0f, aspectScale, 1.0f);

            // reset sw to be apparent width
            sh = (int)lroundf(sw / aspect);
        }
    }

    // figure out how much zooming we want
    double hscale, vscale;
    switch (mSrcMode)
    {
        case kLetterbox_4_3_Zoom:
            // height only fills 3/4 of image, zoom up
            hscale = vscale = h * 1.0 / (sh * 0.75);
            break;
        case kLetterbox_16_9_Zoom:
            // width only fills 3/4 of image, zoom up
            hscale = vscale = w * 1.0 / (sw * 0.75);
            break;
        case kLetterbox_16_9_Stretch:
            // like 16 x 9 standard, but with a horizontal stretch applied
            hscale = vscale = fmin(h * 1.0 / sh, w * 1.0 / sw);
            hscale *= 4.0 / 3.0;
            break;
        case kLetterbox_4_3:
        case kLetterbox_16_9:
        default:
            // standard, fill viewport with scaled image
            hscale = vscale = fmin(h * 1.0 / sh, w * 1.0 / sw);
            break;
    }
    if (mZoomedIn)
    {
        hscale *= 1 + (mZoomedIn * .01);
        vscale *= 1 + (mZoomedIn * .01);
    }

    // cap zooming if we requested it
    if (!mScaleUpVideo)
    {
        double maxScale = fmax(hscale, vscale);
        hscale /= maxScale;
        vscale /= maxScale;
    }

    if ((hscale < 0.99) || (hscale > 1.01) ||
        (vscale < 0.99) || (vscale > 1.01))
    {
        VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Scaling to %1 x %2 of original")
                                    .arg(hscale).arg(vscale));
        glScalef(hscale, vscale, 1.0f);

        // reset sw, sh for new apparent width/height
        sw = (int)(sw * hscale);
        sh = (int)(sh * vscale);
    }

    // center image in viewport
    // if ((h != sh) || (w != sw))
    // {
    //     VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Centering with %1, %2")
    //                                 .arg((w - sw)/2.0).arg((h - sh)/2.0));
    //     glTranslatef((w-sw) / 2.0f, (h-sh) / 2.0f, 0.0f);
    // }

// apply the basic sizing to AccelUtils
#ifdef CONFIG_MAC_ACCEL
    AccelUtils *accel = AccelUtils::singleton();
    if (accel)
      accel->MoveResize(0, 0, mSrcWidth, mSrcHeight,
                        (int)((w - sw) / 2.0), (int)((h - sh) / 2.0),
                        sw, sh);
#endif

#if 0
    // apply over/underscan
    int hscan = gContext->GetNumSetting("HorizScanPercentage", 5);
    int vscan = gContext->GetNumSetting("VertScanPercentage", 5);
    if (hscan || vscan)
    {
        if (vscan > 0)
        {
            vscan *= 2;   // Confusing, but matches X behavior
        }
        if (hscan > 0)
        {
            hscan *= 2;
        }
        
        VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Overscanning to %1, %2")
                                    .arg(hscan).arg(vscan));
        // Translate to new origin, then scale
        glTranslatef(sw / 2.0f, sh / 2.0f, 0.0f);
        glScalef(1.0 + (hscan / 50.0), 1.0 + (vscan / 50.0), 1.0f);
        // ScaleMatrix(&matrix,
        //             X2Fix((double)(1.0 + (hscan / 50.0))),
        //             X2Fix((double)(1.0 + (vscan / 50.0))),
        //             X2Fix(sw / 2.0),
        //             X2Fix(sh / 2.0));
    }

    // apply TV mode offset
    if (1)
    {
        int tv_xoff = gContext->GetNumSetting("xScanDisplacement", 0);
        int tv_yoff = gContext->GetNumSetting("yScanDisplacement", 0);
        if (tv_xoff || tv_yoff)
        {
            VERBOSE(VB_PLAYBACK,
                    QString("VideoOutputCoreVideoRep::UpdateTransformMatrix TV offset by %1, %2").arg(tv_xoff).arg(tv_yoff));
            glTranslatef(tv_xoff, tv_yoff, 0.0f);
        }
    }
#endif
    
    // apply zoomed offsets
    if (mZoomedIn)
    { 
        // calculate original vs. zoomed dimensions
        int zw = (int)(sw / (1.0 + (mZoomedIn * .01)));
        int zh = (int)(sh / (1.0 + (mZoomedIn * .01)));
                
        int zoomx = (int)((sw - zw) * mZoomedRight * .005);
        int zoomy = (int)((sh - zh) * mZoomedUp    * .005);
        
        VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Zoom translating to %1, %2")
                                    .arg(zoomx).arg(zoomy));
        //glTranslatef(zoomx, zoomy, 0.0f);
    }

    // apply graphics port or embedding offset
    if (x || y)
    {
        VERBOSE(VB_PLAYBACK, QString("VideoOutputCoreVideoRep::UpdateTransformMatrix Translating to %1, %2")
                                    .arg(x).arg(y));
        //glTranslatef(x, y, 0.0f);
    }
}

bool VideoOutputCoreVideoRep::SetupOpenGL()
{
      if (!mMainWindow)
      {
          VERBOSE(VB_IMPORTANT, "VideoOutputCoreVideo::SetupOpenGL - failed to find Front Non-floating Window");
          return false;
      }

    // ***** Set up OpenGL *****
    GLint swapInterval = 1;
    GLint surfaceOpacity = 1;

    GLint attributes[] = {
        AGL_RGBA,
        AGL_PIXEL_SIZE, 32,
        AGL_ACCELERATED,
        AGL_NONE
    };

    AGLPixelFormat aglPixelFormat;

    // get a pixel format that is appropriate for the attributes specified above
    aglPixelFormat = aglChoosePixelFormat(NULL, 0, attributes);
    if (NULL == aglPixelFormat) return false;
    
    // create an AGL rendering context
    mAGLContext = aglCreateContext(aglPixelFormat, NULL);
    if (NULL == mAGLContext) return false;
    
    // now that we have a valid context, we can attach it to the window
    if (!aglSetDrawable(mAGLContext, GetWindowPort(mMainWindow)))
        return false;
    
    // make sure to set the current context here
 	if (!aglSetCurrentContext(mAGLContext))
        return false;

    // opaque surface
    if (!aglSetInteger(mAGLContext, AGL_SURFACE_OPACITY, &surfaceOpacity))
        return false;
    
    // sync to the vertical retrace
    if (!aglSetInteger(mAGLContext, AGL_SWAP_INTERVAL, &swapInterval))
        return false;
    
    // get the CGL context from the AGL context which we need for QT & Core Image
    if (!aglGetCGLContext(mAGLContext, (void **)&(mCGLContext)))
        return false;
    
    // get the CGL pixel format from the AGL pixel format which we also need for QT & Core Image
    if (!aglGetCGLPixelFormat(aglPixelFormat, (void **)&(mCGLPixelFormat)))
        return false;

    return true;
}
// adjust the viewport and projection matrix
void VideoOutputCoreVideoRep::InitializeGLView()
{ 
    GLfloat minX, minY, maxX, maxY;
    Rect contentRect;

    GetWindowBounds(mMainWindow, kWindowContentRgn, &contentRect);

    minX = (float)0;
    minY = (float)0;
    maxX = (float)(contentRect.right - contentRect.left);
    maxY = (float)(contentRect.bottom - contentRect.top);
    
    mDesiredXoff = mDesiredYoff = 0;
    mDesiredWidth = (contentRect.right - contentRect.left);
    mDesiredHeight = (contentRect.bottom - contentRect.top);
    
    // for best results when using Core Image to render into an OpenGL context follow these guidelines:
    // * ensure that the a single unit in the coordinate space of the OpenGL context represents a single pixel in the output device
    // * the Core Image coordinate space has the origin in the bottom left corner of the screen -- you should configure the OpenGL
    //   context in the same way
    // * the OpenGL context blending state is respected by Core Image -- if the image you want to render contains translucent pixels,
    //   it's best to enable blending using a blend function with the parameters GL_ONE, GL_ONE_MINUS_SRC_ALPHA

    glViewport(0, 0, (GLsizei)(contentRect.right - contentRect.left), (GLsizei)(contentRect.bottom - contentRect.top));  // set the viewport

VERBOSE(VB_IMPORTANT, QString("VideoOutputCoreVideoRep::InitializeGLView - width = %1 height = %2").arg(contentRect.right-contentRect.left).arg(contentRect.bottom-contentRect.top));

    glMatrixMode(GL_MODELVIEW);    // select the modelview matrix
    glLoadIdentity();              // reset it

    glMatrixMode(GL_PROJECTION);   // select the projection matrix
    glLoadIdentity();              // reset it

    gluOrtho2D(minX, maxX, minY, maxY);  // define a 2-D orthographic projection matrix

    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
}

bool VideoOutputCoreVideoRep::CreateCoreVideoBuffers()
{
    OSErr err;
    
    // Allocate buffer storage
    mBitmapDataSize = mWidth * mHeight *  2;
    mBitmapData = new char[mBitmapDataSize];

    // Create the CoreVideo pixel buffer descriptions and frames
    err = CVPixelBufferCreateWithBytes( NULL, mWidth, mHeight, k422YpCbCr8CodecType, mBitmapData, mWidth*2, NULL, NULL, NULL, &(mCurrentFrameBuffer));
    if(err != kCVReturnSuccess)
    {
        VERBOSE(VB_IMPORTANT, QString("VideoOutCoreVideo::Init - Failed to create Pixel Buffer err = %1").arg(err));
        return false;
    }
    
    err = CVOpenGLTextureCacheCreate(NULL, 0, mCGLContext, mCGLPixelFormat, 0, &(mTextureCache));
    if(err != kCVReturnSuccess)
    {
        VERBOSE(VB_IMPORTANT, QString("VideoOutCoreVideo::Init - Failed to create OpenGL texture Cache err = %1").arg(err));
        return false;
    }
    
    err = CVOpenGLTextureCacheCreateTextureFromImage(	NULL, mTextureCache, mCurrentFrameBuffer, 0, &(mTexture));
    if(err != kCVReturnSuccess)
    {
        VERBOSE(VB_IMPORTANT, QString("VideoOutCoreVideo::Init - Failed to create OpenGL texture err = %1").arg(err));
        return false;
    }
    
    return true;
}

void VideoOutputCoreVideoRep::DeleteCoreVideoBuffers()
{
    CVOpenGLTextureRelease(mTexture);
    CVOpenGLTextureCacheRelease(mTextureCache);
    CVPixelBufferRelease(mCurrentFrameBuffer);

    if (mBitmapData)
    {
        delete [] mBitmapData;
        mBitmapData = NULL;
        mBitmapDataSize = 0;
    }
}

