#include <cmath>

using namespace std;

// MythTV videout headers
#include "videoout_opengl.h"
#include "util-x11.h"
#include "videodisplayprofile.h"

// MythTV General headers
#include "../libavcodec/avcodec.h"
#include "mythconfig.h"
#include "mythcontext.h"
#include "filtermanager.h"
#include "NuppelVideoPlayer.h"
#define IGNORE_TV_PLAY_REC
#include "tv.h"

#define LOC QString("VideoOutputOpengl: ")
#define LOC_ERR QString("VideoOutputOpengl Error: ")

#ifndef GL_TEXTURE_RECTANGLE_ARB
#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 
#endif

#ifndef GL_TEXTURE_RECTANGLE_EXT
#define GL_TEXTURE_RECTANGLE_EXT 0x84F5
#endif

#ifndef GL_TEXTURE_RECTANGLE_NV
#define GL_TEXTURE_RECTANGLE_NV 0x84F5
#endif

VideoOutputOpengl::VideoOutputOpengl()
    : VideoOutput(),
      display_res(NULL), global_lock(true),

      XJ_win(0), XJ_curwin(0), XJ_disp(NULL),
      XJ_screen_num(0), XJ_started(false),
      my_context(NULL), max_texture_size(0), texture_rects(false),
      my_gl_texture(GL_TEXTURE_2D), actually_draw_pip(false)
{
    bzero(&av_pause_frame, sizeof(av_pause_frame));

    // If using custom display resolutions, display_res will point
    // to a singleton instance of the DisplayRes class
    if (gContext->GetNumSetting("UseVideoModes", 0))
        display_res = DisplayRes::GetDisplayRes();

    use_colourcontrols  = gContext->GetNumSetting(
                            "UseOutputPictureControls", 0);
    allowpreviewepg = false;    
}

VideoOutputOpengl::~VideoOutputOpengl()
{
    if (my_context)
        X11S(glXDestroyContext(XJ_disp, my_context));
    if (XJ_win)
        X11S(XDestroyWindow(XJ_disp, XJ_win));

    DeleteBuffers(true);

    // Switch back to desired resolution for GUI
    if (display_res)
        display_res->SwitchToGUI();
}

bool VideoOutputOpengl::Init(int width, int height, float aspect, 
                         WId winid, int winx, int winy, int winw, 
                         int winh, WId embedid)
{
    if (winid <= 0)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Invalid Window ID.");
        return false;
    }
    XJ_disp = MythXOpenDisplay();
    if (!XJ_disp)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to open display.");
    }

    X11L;
    XJ_screen_num = DefaultScreen(XJ_disp);
    XJ_curwin     = winid;
    XJ_win        = winid;
    X11U;

    // Basic setup
    VideoOutput::Init(width, height, aspect,
                      winid, winx, winy, winw, winh,
                      embedid);
    if (!InitGlxContext(winid, display_visible_rect.width(),
                        display_visible_rect.height()))
            return false;
    // Set resolution/measurements (check XRandR, Xinerama, config settings)
    InitDisplayMeasurements(width, height);
    MoveResize();

    if (!CheckExtensions())
        return false;
    InitOpenGL();
    if (!CreateVideoTexture(&glVideo, video_dim, display_video_rect))
        return false;
    if (!LoadFragmentProgram())
        return false;
    if (!InitSetupBuffers())
        return false;

    XJ_started = true;
    db_vdisp_profile->SetVideoRenderer("opengl");
    return true;
}

bool VideoOutputOpengl::CheckExtensions()
{
    X11L;    
    glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context);
    QString extensions(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
    glXMakeContextCurrent( XJ_disp, None, None, NULL );
    X11U;

    texture_rects = true;
    if (extensions.contains("GL_NV_texture_rectangle"))
    {
        VERBOSE(VB_PLAYBACK, LOC + "Using NV NPOT texture extension");
        my_gl_texture = GL_TEXTURE_RECTANGLE_NV;
    }
    else if (extensions.contains("GL_ARB_texture_rectangle"))
    {
        VERBOSE(VB_PLAYBACK, LOC + "Using ARB NPOT texture extension");
        my_gl_texture = GL_TEXTURE_RECTANGLE_ARB;
    }
    else if (extensions.contains("GL_EXT_texture_rectangle"))
    {
        VERBOSE(VB_PLAYBACK, LOC + "Using EXT NPOT texture extension");
        my_gl_texture = GL_TEXTURE_RECTANGLE_EXT;
    }
    else
    {
        texture_rects = false;
    }

    if (!extensions.contains("GL_ARB_fragment_program"))
    {
        VERBOSE(VB_GENERAL, "GL_ARB_fragment_program not available.");
        return false;
    }
    return true;
}

void VideoOutputOpengl::SetViewPort()
{
    X11L;    
    glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context);

    glViewport( 0, 0, display_visible_rect.width(), display_visible_rect.height() );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glOrtho( 0, display_visible_rect.width(), display_visible_rect.height(), 0, 1, -1 );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    glFlush();

    glXMakeContextCurrent( XJ_disp, None, None, NULL );
    X11U;
}

void VideoOutputOpengl::InitOpenGL()
{
    SetViewPort();

    X11L;    
    glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context);

    glDisable( GL_BLEND );
    glDisable( GL_DEPTH_TEST );
    glDepthMask( GL_FALSE );
    glDisable( GL_CULL_FACE );
    glEnable( my_gl_texture );

    glShadeModel( GL_FLAT );
    glDisable( GL_POLYGON_SMOOTH );
    glDisable( GL_LINE_SMOOTH );
    glDisable( GL_POINT_SMOOTH );

    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
    glClear( GL_COLOR_BUFFER_BIT );

    glFlush(); 

    glGenTextures( 1, &glVideo.tex );
    glGenTextures( 1, &glPip.tex );

    glGetIntegerv( GL_MAX_TEXTURE_SIZE, &max_texture_size );
    if (max_texture_size == 0)
        max_texture_size = 512;
    VERBOSE(VB_PLAYBACK, LOC + 
            QString("Maximum supported texture size: %1 x %2")
            .arg(max_texture_size).arg(max_texture_size));
    VERBOSE(VB_PLAYBACK, LOC + "OpenGL state initialised.");

    glXMakeContextCurrent( XJ_disp, None, None, NULL );
    X11U;
}

bool VideoOutputOpengl::LoadFragmentProgram()
{
    GLint errorpos;
    QString program =
        "!!ARBfp1.0\n"
        "OPTION ARB_precision_hint_fastest;"
        "ATTRIB ytex = fragment.texcoord[0];"
        "PARAM  off  = program.env[1];"
        "TEMP res, tmp, tmp2;"
        "TEX res, ytex, texture[0], %1;"
        "MAD tmp2, ytex, {0.5, 0.5}, off.wyww;"
        "TEX tmp.x, tmp2, texture[0], %1;"
        "ADD tmp2, tmp2, off.xwww;"
        "TEX tmp.y, tmp2, texture[0], %1;";
    QString prog_colour_adjust =
        "PARAM  adj  = program.env[0];"
        "SUB res, res, 0.5;"
        "MAD res, res, adj.yyyy, adj.xxxx;"
        "SUB tmp, tmp, { 0.5, 0.5 };"
        "MAD tmp, adj.zzzz, tmp, 0.5;";
    QString prog_convert =
        "MAD res, res, 1.164, -0.063;"
        "SUB tmp, tmp, { 0.5, 0.5 };"
        "MAD res, { 0, -.392, 2.017 }, tmp.xxxw, res;"
        "MAD result.color, { 1.596, -.813, 0 }, tmp.yyyw, res;"
        "END";

    program.replace("%1", texture_rects ? "RECT" : "2D");
    if (use_colourcontrols)
        program += prog_colour_adjust;
    program += prog_convert;

    X11L;    
    glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context);
    glGenProgramsARB   ( 1, &frag_prog );
    glBindProgramARB   ( GL_FRAGMENT_PROGRAM_ARB, frag_prog );
    glProgramStringARB ( GL_FRAGMENT_PROGRAM_ARB,
                         GL_PROGRAM_FORMAT_ASCII_ARB,
                         program.length(), program.latin1() );
    glGetIntegerv ( GL_PROGRAM_ERROR_POSITION_ARB, &errorpos );
    glEnable ( GL_FRAGMENT_PROGRAM_ARB );

    glXMakeContextCurrent( XJ_disp, None, None, NULL );
    X11U;

    if (errorpos != -1)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR +
                QString("Fragment Program compile error: position %1:'%2'")
                .arg(errorpos)
                .arg(program.mid(errorpos)));
        return false;
    }
    VERBOSE(VB_PLAYBACK, LOC + "Fragment program loaded.");
    return true;
}

bool VideoOutputOpengl::CreateVideoTexture(GLframe *frame, QSize size, QRect pos)
{
    int width, height;

    if (texture_rects)
    {
        width  = size.width();
        height = size.height() * 3 / 2;
    }
    else
    {
        width = height = kMinOpenglTexSize;
        while (width  < size.width())  { width  *= 2; }
        while (height < size.height()) { height *= 2; }
    
        if ((size.height() * 1.5) > height)
            height *= 2;
    }
    
    if (width > max_texture_size || height > max_texture_size)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR +
            "Frame larger than maximum texture size.");
        return false;
    }

    if (width != frame->tex_width || height != frame->tex_height)
    {
        scratchspace = new unsigned char[(width
                                        * height * 4) + 4];
        memset(scratchspace, 0 , width * height * 4);
    
        if (!CreateTexture(width, height, &frame->tex, scratchspace))
        {
            VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to create texture.");
            return false;
        }
        delete scratchspace;
    }

    frame->position   = pos;
    frame->vid_width  = size.width();
    frame->vid_height = size.height();
    frame->tex_width  = width;
    frame->tex_height = height;
    frame->uOffset    = size.width() * size.height();
    frame->vOffset    = frame->uOffset * 5 / 4;
    VERBOSE(VB_PLAYBACK, LOC + QString("Created texture (%1x%2)")
            .arg(width).arg(height));
    return true;
}

bool VideoOutputOpengl::CreateTexture(GLint width,
                                   GLint height,
                                   GLuint *tex,
                                   unsigned char *data)
{
    GLint check;

    X11L;    
    glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context);

    glBindTexture( my_gl_texture, *tex );
    glTexParameteri (my_gl_texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri (my_gl_texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri (my_gl_texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri (my_gl_texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D( my_gl_texture, 0, GL_RGBA8,
                width, height,
                0, GL_RGB , GL_UNSIGNED_BYTE, data );
    glGetTexLevelParameteriv(my_gl_texture, 0, GL_TEXTURE_WIDTH, &check);
    glXMakeContextCurrent(XJ_disp, None, None, NULL);
    X11U;
    if (check !=width)
        return false;
    return true;
}

inline void DrawVideoQuad(GLframe frame, bool texture_rects,
                          int my_gl_texture)
{
    float right = (float)frame.vid_width;
    float bottom  = (float)frame.vid_height;
    if (!texture_rects)
    {
        right  /= frame.tex_width;
        bottom /= frame.tex_height;
    }

    glProgramEnvParameter4fARB (GL_FRAGMENT_PROGRAM_ARB, 1,
          right / 2.0f, bottom, 0.0f, 0.0f); 
    glBindTexture( my_gl_texture, frame.tex );
    glBegin( GL_QUADS );
        glTexCoord2f(0.0f, 0.0f);
        glVertex2f( frame.position.left(), frame.position.top());
        glTexCoord2f(right, 0.0f);
        glVertex2f( frame.position.right(), frame.position.top());
        glTexCoord2f(right, bottom); 
        glVertex2f( frame.position.right(), frame.position.bottom());
        glTexCoord2f(0.0f, bottom); 
        glVertex2f( frame.position.left(), frame.position.bottom());
    glEnd();
}

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

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

    framesPlayed = buffer->frameNumber + 1;

    // TODO should cope with YUV422P, rgb24, argb32 etc
    if (buffer->codec != FMT_YV12)
        return;

    UpdateVideoTexture(glVideo, buffer);
    
    X11L;    
    glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context);
    glClear(GL_COLOR_BUFFER_BIT);

    if (use_colourcontrols)
    {
        glProgramEnvParameter4fARB (GL_FRAGMENT_PROGRAM_ARB, 0,
          ((float) db_pict_attr[kPictureAttribute_Brightness] / 50 ) - 0.5,
          ((float) db_pict_attr[kPictureAttribute_Contrast] / 50),       
          ((float) db_pict_attr[kPictureAttribute_Colour] / 50),
          0.0f); 
    }

    DrawVideoQuad(glVideo, texture_rects, my_gl_texture);
    if (actually_draw_pip)
        DrawVideoQuad(glPip, texture_rects, my_gl_texture);
    glFlush();

    glXMakeContextCurrent(XJ_disp, None, None, NULL);
    X11U;
}

void VideoOutputOpengl::ShowPip(VideoFrame *frame, NuppelVideoPlayer *pipplayer)
{
    (void) frame;
    if (!pipplayer)
    {
        actually_draw_pip = false;
        glPip.vid_width = glPip.vid_height = 0;   
        return;
    }

    actually_draw_pip = false;
    int pipw, piph;
    VideoFrame *pipimage = pipplayer->GetCurrentFrame(pipw, piph);
    float pipVideoAspect = pipplayer->GetVideoAspect();
    uint  pipVideoWidth  = pipplayer->GetVideoWidth();
    uint  pipVideoHeight = pipplayer->GetVideoHeight();

    // If PiP is not initialized to values we like, silently ignore the frame.
    if ((pipVideoAspect <= 0) || !pipimage || 
        !pipimage->buf || pipimage->codec != FMT_YV12)
    {
        pipplayer->ReleaseCurrentFrame(pipimage);
        return;
    }

    QRect position;
    int tmph = (display_visible_rect.height() * db_pip_size) / 100;
    float pixel_adj = ((float)display_visible_rect.width() / 
                        (float)display_visible_rect.height()) / display_aspect;
    position.setHeight(tmph);
    position.setWidth((int)(tmph * pipVideoAspect * pixel_adj));

    // Figure out where to put the Picture-in-Picture window
    int xoff = (int)(display_visible_rect.width() * 0.07);  // inside 'safe area'
    int yoff = (int)(display_visible_rect.height() * 0.07);
    switch (db_pip_location)
    {
        default:
        case kPIPTopLeft:
                break;
        case kPIPBottomLeft:
                yoff = display_visible_rect.height() - position.height() - yoff;
                break;
        case kPIPTopRight:
                xoff = display_visible_rect.width()  - position.width() - xoff;
                break;
        case kPIPBottomRight:
                xoff = display_visible_rect.width()  - position.width() - xoff;
                yoff = display_visible_rect.height() - position.height() - xoff;
                break;
    }
    position.moveBy(xoff, yoff);

    if (glPip.vid_width  != pipVideoWidth ||
        glPip.vid_height != pipVideoHeight)
    {
        if (!CreateVideoTexture(&glPip, QSize(pipVideoWidth, pipVideoHeight), position))
        {
            pipplayer->ReleaseCurrentFrame(pipimage);
            return;
        }
    }
    glPip.position = position;
    UpdateVideoTexture(glPip, pipimage);
    actually_draw_pip = true;
    pipplayer->ReleaseCurrentFrame(pipimage);
}

void VideoOutputOpengl::UpdateVideoTexture(GLframe vid, VideoFrame *buffer)
{
    if ((buffer->width * buffer->height) != vid.uOffset)
        return;
    X11L;    
    glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context);

    glActiveTexture( GL_TEXTURE0);
    glBindTexture(   my_gl_texture, vid.tex );
    glTexSubImage2D( my_gl_texture, 0,
                     0,
                     0,
                     vid.vid_width,
                     vid.vid_height,
                     GL_LUMINANCE,
                     GL_UNSIGNED_BYTE,
                     buffer->buf);
    glTexSubImage2D( my_gl_texture, 0,
                     0,
                     vid.vid_height ,
                     vid.vid_width / 2,
                     vid.vid_height / 2,
                     GL_LUMINANCE,
                     GL_UNSIGNED_BYTE,
                     buffer->buf + vid.uOffset);
    glTexSubImage2D( my_gl_texture, 0,
                     vid.vid_width / 2,
                     vid.vid_height ,
                     vid.vid_width / 2,
                     vid.vid_height / 2,
                     GL_LUMINANCE,
                     GL_UNSIGNED_BYTE,
                     buffer->buf + vid.vOffset);
    glXMakeContextCurrent(XJ_disp, None, None, NULL);
    X11U;
}

void VideoOutputOpengl::Show(FrameScanType )
{
    X11L;    
    glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context);
    glXSwapBuffers(XJ_disp, XJ_curwin);
    glXMakeContextCurrent(XJ_disp, None, None, NULL);
    X11U;
}

void VideoOutputOpengl::DrawUnusedRects(bool sync)
{
    (void) sync;
}

void VideoOutputOpengl::Zoom(int direction)
{
    VideoOutput::Zoom(direction);
    MoveResize();
}

void VideoOutputOpengl::InputChanged(int width, int height,
                                  float aspect, MythCodecID av_codec_id)
{
    VERBOSE(VB_PLAYBACK, LOC + QString("Input changed (%1x%2:%3)")
            .arg(width).arg(height).arg(aspect));
    VideoOutput::InputChanged(width, height, aspect, av_codec_id);
    ResizeForVideo((uint) width, (uint) height);
    MoveResize();
    CreateVideoTexture(&glVideo, QSize(width, height), display_video_rect);
    CreateBuffers();
    CreatePauseFrame();
}

void VideoOutputOpengl::MoveResize(void)
{
    VideoOutput::MoveResize();
    glVideo.position = display_video_rect;
    SetViewPort();
}

bool VideoOutputOpengl::InitSetupBuffers(void)
{
    vbuffers.Init(31, true, 1, 12, 4, 2, false);
    CreateBuffers();
    CreatePauseFrame();
    return true;
}

void VideoOutputOpengl::CreateBuffers(void)
{
    if (!vbuffers.CreateBuffers(glVideo.vid_width, glVideo.vid_height))
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to create buffers");
    
}

void VideoOutputOpengl::CreatePauseFrame(void)
{
    vbuffers.LockFrame(&av_pause_frame, "CreatePauseFrame");
    if (av_pause_frame.buf)
    {
        delete [] av_pause_frame.buf;
        av_pause_frame.buf = NULL;
    }
    av_pause_frame.height       = vbuffers.GetScratchFrame()->height;
    av_pause_frame.width        = vbuffers.GetScratchFrame()->width;
    av_pause_frame.bpp          = vbuffers.GetScratchFrame()->bpp;
    av_pause_frame.size         = vbuffers.GetScratchFrame()->size;
    av_pause_frame.frameNumber  = vbuffers.GetScratchFrame()->frameNumber;
    av_pause_frame.buf          = new unsigned char[av_pause_frame.size];
    av_pause_frame.qscale_table = NULL;
    av_pause_frame.qstride      = 0;

    vbuffers.UnlockFrame(&av_pause_frame, "CreatePauseFrame");
}

void VideoOutputOpengl::DeleteBuffers(bool delete_pause_frame)
{
    DiscardFrames(true);
    vbuffers.DeleteBuffers();
    if (delete_pause_frame)
    {
        if (av_pause_frame.buf)
        {
            delete [] av_pause_frame.buf;
            av_pause_frame.buf = NULL;
        }
        if (av_pause_frame.qscale_table)
        {
            delete [] av_pause_frame.qscale_table;
            av_pause_frame.qscale_table = NULL;
        }
    }
}

void VideoOutputOpengl::EmbedInWidget(WId wid, int x, int y, int w, int h)
{
    (void) wid;
    (void) x;
    (void) y;
    (void) w;
    (void) h;
}

void VideoOutputOpengl::StopEmbedding(void)
{
}

float VideoOutputOpengl::GetDisplayAspect(void)
{
    return display_aspect;
}

void VideoOutputOpengl::UpdatePauseFrame(void)
{
    vbuffers.LockFrame(&av_pause_frame, "UpdatePauseFrame -- pause");

    vbuffers.begin_lock(kVideoBuffer_used);
    VideoFrame *used_frame = NULL;
    if (vbuffers.size(kVideoBuffer_used) > 0)
    {
        used_frame = vbuffers.head(kVideoBuffer_used);
        if (!vbuffers.TryLockFrame(used_frame, "UpdatePauseFrame -- used"))
            used_frame = NULL;
    }
    if (used_frame)
    {
        CopyFrame(&av_pause_frame, used_frame);
        vbuffers.UnlockFrame(used_frame, "UpdatePauseFrame -- used");
    }
    vbuffers.end_lock();
    if (!used_frame &&
        vbuffers.TryLockFrame(vbuffers.GetScratchFrame(),
                              "UpdatePauseFrame -- scratch"))
    {
        vbuffers.GetScratchFrame()->frameNumber = framesPlayed - 1;
        CopyFrame(&av_pause_frame, vbuffers.GetScratchFrame());
        vbuffers.UnlockFrame(vbuffers.GetScratchFrame(),
                             "UpdatePauseFrame -- scratch");
    }
    vbuffers.UnlockFrame(&av_pause_frame, "UpdatePauseFrame - used");
}

void VideoOutputOpengl::ProcessFrame(VideoFrame *frame, OSD *osd,
                                 FilterChain *filterList,
                                 NuppelVideoPlayer *pipPlayer)
{
    bool pauseframe = false;
    if (!frame)
    {
        frame = vbuffers.GetScratchFrame();
        CopyFrame(vbuffers.GetScratchFrame(), &av_pause_frame);
        pauseframe = true;
    }

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

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

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

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

int VideoOutputOpengl::SetPictureAttribute(int attributeType, int newValue)
{
    if (kPictureAttribute_Hue == attributeType)
        return -1;
    SetPictureAttributeDBValue(attributeType, newValue);
    return newValue;
}

bool VideoOutputOpengl::InitGlxContext(WId winid, int width, int height)
{
    // set up the opengl rendering environment

    int ndummy;
    int ret;
    X11S(ret = glXQueryExtension(XJ_disp, &ndummy, &ndummy));
     if (!ret)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "OpenGL extension not present.");
        return false;
    }

    int attribList[] = {GLX_RGBA,
                        GLX_DEPTH_SIZE, 0,
                        GLX_DOUBLEBUFFER, 1,
                        GLX_RED_SIZE, 1,
                        GLX_GREEN_SIZE, 1,
                        GLX_BLUE_SIZE, 1,
                        None};

    XVisualInfo *vis;
    XSetWindowAttributes swa;
    Window w;

    X11S(vis = glXChooseVisual(XJ_disp, XJ_screen_num, attribList));
    if (vis == NULL) 
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "No appropriate visual found");
        return false;
    }
    X11S(swa.colormap = XCreateColormap(XJ_disp,
                                        RootWindow(XJ_disp, vis->screen),
                                        vis->visual, AllocNone));
    if (swa.colormap == 0)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to create colormap");
        return false;
    }

    X11S(w = XCreateWindow(XJ_disp, winid, 0, 0,
                           width, height, 0, vis->depth,
                           InputOutput, vis->visual, CWColormap, &swa));
    if (w == 0)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to create window");
        return false;
    }

    XJ_curwin = XJ_win = w;

    X11S(my_context = glXCreateContext(XJ_disp, vis, None, GL_TRUE));
    if (my_context == NULL)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to create Glx context");
        return false;
    }
    X11S(ret = glXMakeContextCurrent(XJ_disp, XJ_curwin, XJ_curwin, my_context));
    if (!ret)
    {
        VERBOSE(VB_PLAYBACK, LOC_ERR + "Failed to make Glx context current.");
        return false;
    }

    X11S(glXMakeContextCurrent(XJ_disp, None, None, NULL));
    X11S(XFree(vis));
    X11S(XMapWindow(XJ_disp, XJ_win));
    VERBOSE(VB_PLAYBACK, LOC + QString("Created window (%1 x %2)").arg(width).arg(height));
    return true;
}

int VideoOutputOpengl::GetRefreshRate(void)
{
    if (!XJ_started)
        return -1;

    XF86VidModeModeLine mode_line;
    int dot_clock;

    int ret = False;
    X11S(ret = XF86VidModeGetModeLine(XJ_disp, XJ_screen_num,
                                      &dot_clock, &mode_line));
    if (!ret)
    {
        VERBOSE(VB_IMPORTANT, LOC_ERR + "GetRefreshRate(): "
                "X11 ModeLine query failed");
        return -1;
    }

    double rate = (double)((double)(dot_clock * 1000.0) /
                           (double)(mode_line.htotal * mode_line.vtotal));

    // Assume 60Hz if we can't otherwise determine it.
    if (rate == 0)
        rate = 60;

    if (rate < 20 || rate > 200)
    {
        VERBOSE(VB_PLAYBACK, LOC + QString("Unreasonable refresh rate %1Hz "
                                           "reported by X").arg(rate));
        rate = 60;
    }

    rate = 1000000.0 / rate;

    return (int)rate;

}
void VideoOutputOpengl::ResizeForGui(void)
{
    if (display_res)
        display_res->SwitchToGUI();
}

void VideoOutputOpengl::ResizeForVideo(uint width, uint height)
{
    if ((width == 1920 || width == 1440) && height == 1088)
        height = 1080; // ATSC 1920x1080

    if (display_res && display_res->SwitchToVideo(width, height))
    {
        // Switching to custom display resolution succeeded
        // Make a note of the new size
        display_dim = QSize(display_res->GetPhysicalWidth(),
                            display_res->GetPhysicalHeight());
        display_aspect = display_res->GetAspectRatio();

        bool fullscreen = !gContext->GetNumSetting("GuiSizeForTV", 0);
        
        // if width && height are zero users expect fullscreen playback
        if (!fullscreen)
        {
            int gui_width = 0, gui_height = 0;
            gContext->GetResolutionSetting("Gui", gui_width, gui_height);
            fullscreen |= (0 == gui_width && 0 == gui_height);
        }

        if (fullscreen)
        {
            QSize sz(display_res->GetWidth(), display_res->GetHeight());
            display_visible_rect = QRect(QPoint(0,0), sz);
            // Resize X window to fill new resolution
            X11S(XMoveResizeWindow(XJ_disp, XJ_win,
                                   display_visible_rect.left(),
                                   display_visible_rect.top(),
                                   display_visible_rect.width(),
                                   display_visible_rect.height()));
        }
    }
}

void VideoOutputOpengl::ResizeForVideo(void)
{
    ResizeForVideo(glVideo.vid_width, glVideo.vid_height);
}

void VideoOutputOpengl::InitDisplayMeasurements(uint width, uint height)
{
    if (display_res)
    {
        // The very first Resize needs to be the maximum possible
        // desired res, because X will mask off anything outside
        // the initial dimensions
        X11S(XMoveResizeWindow(XJ_disp, XJ_win, 0, 0,
                               display_res->GetMaxWidth(),
                               display_res->GetMaxHeight()));
        ResizeForVideo(width, height);
    }
    else
    {
        display_dim = QSize(DisplayWidthMM(XJ_disp, XJ_screen_num),
                            DisplayHeightMM(XJ_disp, XJ_screen_num));

        if (db_display_dim.width() > 0 && db_display_dim.height() > 0)
            display_dim = db_display_dim;
    }

    // Fetch pixel width and height of the display
    int xbase, ybase, w, h;
    gContext->GetScreenBounds(xbase, ybase, w, h);
    // Determine window dimensions in pixels
    int window_w = w, window_h = h;
    if (gContext->GetNumSetting("GuiSizeForTV", 0))
        gContext->GetResolutionSetting("Gui", window_w,  window_h);
    else
        gContext->GetScreenBounds(xbase, ybase, window_w, window_h);
    window_w = (window_w) ? window_w : w;
    window_h = (window_h) ? window_h : h;
    float pixel_aspect = ((float)w) / ((float)h);

    VERBOSE(VB_PLAYBACK, LOC + QString(
                "Pixel dimensions: Screen %1x%2, window %3x%4")
            .arg(w).arg(h).arg(window_w).arg(window_h));

    // Determine if we are using Xinerama
    int event_base, error_base;
    bool usingXinerama = false;
    X11S(usingXinerama = 
         (XineramaQueryExtension(XJ_disp, &event_base, &error_base) &&
          XineramaIsActive(XJ_disp)));

    // If the dimensions are invalid, assume square pixels and 17" screen.
    // Only print warning if this isn't Xinerama, we will fix Xinerama later.
    if (((display_dim.width() <= 0) || (display_dim.height() <= 0)) &&
        !usingXinerama)
    {
        VERBOSE(VB_GENERAL, LOC + "Physical size of display unknown."
                "\n\t\t\tAssuming 17\" monitor with square pixels.");
        display_dim.setHeight(300);
        display_dim.setWidth((int) round(300 * pixel_aspect));
    }

    // If we are using Xinerama the display dimensions can not be trusted.
    // We need to use the Xinerama monitor aspect ratio from the DB to set
    // the physical screen width. This assumes the height is correct, which
    // is more or less true in the typical side-by-side monitor setup.
    if (usingXinerama)
    {
        float displayAspect = gContext->GetFloatSettingOnHost(
            "XineramaMonitorAspectRatio",
            gContext->GetHostName(), pixel_aspect);
        int height = display_dim.height();
        if (height <= 0)
            display_dim.setHeight(height = 300);
        display_dim.setWidth((int) round(height * displayAspect));
    }

    VERBOSE(VB_PLAYBACK, LOC +
            QString("Estimated display dimensions: %1x%2 mm  Aspect: %3")
            .arg(display_dim.width()).arg(display_dim.height())
            .arg(((float) display_dim.width()) / display_dim.height()));

    // We must now scale the display measurements to our window size.
    // If we are running fullscreen this is a no-op.
    display_dim = QSize((display_dim.width()  * window_w) / w,
                        (display_dim.height() * window_h) / h);

    // Now that we know the physical monitor size, we can
    // calculate the display aspect ratio pretty simply...
    display_aspect = ((float)display_dim.width()) / display_dim.height();

    // If we are using XRandR, use the aspect ratio from it instead...
    if (display_res)
        display_aspect = display_res->GetAspectRatio();

    VERBOSE(VB_PLAYBACK, LOC +
            QString("Estimated window dimensions: %1x%2 mm  Aspect: %3")
            .arg(display_dim.width()).arg(display_dim.height())
            .arg(display_aspect));
}

