//--------------------------------------------------------------------------------------
//
//
// Copyright 2015 ADVANCED MICRO DEVICES, INC.  All Rights Reserved.
//
// AMD is granting you permission to use this software and documentation (if
// any) (collectively, the "Materials") pursuant to the terms and conditions
// of the Software License Agreement included with the Materials.  If you do
// not have a copy of the Software License Agreement, contact your AMD
// representative for a copy.
// You agree that you will not reverse engineer or decompile the Materials,
// in whole or in part, except as allowed by applicable law.
//
// WARRANTY DISCLAIMER: THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND.  AMD DISCLAIMS ALL WARRANTIES, EXPRESS, IMPLIED, OR STATUTORY,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, THAT THE SOFTWARE
// WILL RUN UNINTERRUPTED OR ERROR-FREE OR WARRANTIES ARISING FROM CUSTOM OF
// TRADE OR COURSE OF USAGE.  THE ENTIRE RISK ASSOCIATED WITH THE USE OF THE
// SOFTWARE IS ASSUMED BY YOU.
// Some jurisdictions do not allow the exclusion of implied warranties, so
// the above exclusion may not apply to You.
//
// LIMITATION OF LIABILITY AND INDEMNIFICATION:  AMD AND ITS LICENSORS WILL
// NOT, UNDER ANY CIRCUMSTANCES BE LIABLE TO YOU FOR ANY PUNITIVE, DIRECT,
// INCIDENTAL, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM USE OF
// THE SOFTWARE OR THIS AGREEMENT EVEN IF AMD AND ITS LICENSORS HAVE BEEN
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
// In no event shall AMD's total liability to You for all damages, losses,
// and causes of action (whether in contract, tort (including negligence) or
// otherwise) exceed the amount of $100 USD.  You agree to defend, indemnify
// and hold harmless AMD and its licensors, and any of their directors,
// officers, employees, affiliates or agents from and against any and all
// loss, damage, liability and other expenses (including reasonable attorneys'
// fees), resulting from Your use of the Software or violation of the terms and
// conditions of this Agreement.
//
// U.S. GOVERNMENT RESTRICTED RIGHTS: The Materials are provided with "RESTRICTED
// RIGHTS." Use, duplication, or disclosure by the Government is subject to the
// restrictions as set forth in FAR 52.227-14 and DFAR252.227-7013, et seq., or
// its successor.  Use of the Materials by the Government constitutes
// acknowledgement of AMD's proprietary rights in them.
//
// EXPORT RESTRICTIONS: The Materials may be subject to export restrictions as
// stated in the Software License Agreement.
//
//--------------------------------------------------------------------------------------


#include "os_include.h"
#include <GL/glew.h>

#include "defines.h"
#include "BufferQueue.h"
#include "GPUSink.h"
#include "App.h"
#include "ADLtool.h"


PFNGLWAITMARKERAMDPROC          glWaitMarkerAMD;
PFNGLWRITEMARKERAMDPROC         glWriteMarkerAMD;
PFNGLMAKEBUFFERSRESIDENTAMDPROC glMakeBuffersResidentAMD;
PFNGLBUFFERBUSADDRESSAMDPROC    glBufferBusAddressAMD;


using namespace std;


App::App() :

    m_bRunning(false), m_bStarted(false), m_bResizeSink(false),
    m_pSink(NULL)

{
}


App::~App()
{
    delete m_pSinkWin;
    ADLtool::CleanClass();
}


bool App::init(unsigned int uiWinWidth, unsigned int uiWinHeight, const char *pClassName, std::string& strErrorMessage)
{
    strErrorMessage = "";

    if (!ADLtool::InitClass())
    {
        strErrorMessage = "ERROR, Could not init GLWindow";
        return false;
    }

    if (ADLtool::getNumGPUs() < 1)
    {
        strErrorMessage = "ERROR, Cannot find compatible AMD GPU.";
        return false;
    }

    if (ADLtool::getNumDisplaysOnGPU(0) == 0)
    {
        strErrorMessage = "ERROR, AMD GPUs have no Display connected!";
        return false;
    }

    unsigned int uiDsp = ADLtool::getDisplayOnGPU(0, 0);

    m_pSinkWin = new GLWindow("AMD DirectGMA", pClassName);

    if (!m_pSinkWin->create(uiWinWidth, uiWinHeight, uiDsp, true) )
    {
        strErrorMessage = "ERROR, Could not create GLWindow";
        return false;
    }

    m_pSinkWin->open();

    // create context to load extensions
    m_pSinkWin->createContext();
    m_pSinkWin->makeCurrent();

    if (glewInit() != GLEW_OK)
    {
        strErrorMessage = "ERROR, glew init failed";
        return false;
    }

    // get number of extensions
    int i = 0;
    int nNumExtensions = 0;

    glGetIntegerv(GL_NUM_EXTENSIONS, &nNumExtensions);

    bool bFound = false;
    for (i = 0; i < nNumExtensions; ++i)
    {
        std::string strExt = (char*)glGetStringi(GL_EXTENSIONS, i);

        std::string strExt_lowerCase;
        for(unsigned int j=0; j<strExt.length(); j++)
        {
            strExt_lowerCase.push_back(  (strExt[j] >= 'A' && strExt[j] <= 'Z') ? (strExt[j]-'A'+'a') : (strExt[j])   );
        }

        if (strExt_lowerCase == "gl_amd_bus_addressable_memory")
        {
            bFound = true;
            break;
        }
    }

    if (!bFound)
    {
        strErrorMessage = "ERROR, DirectGMA is disabled. Enable it in AMD FirePro control center under 'AMD FirePro' tab";
        return false;
    }

#if defined (WIN32)
    // Load functions of GL_AMD_bus_addressable_memory
    if (!glMakeBuffersResidentAMD)
        glMakeBuffersResidentAMD = (PFNGLMAKEBUFFERSRESIDENTAMDPROC) wglGetProcAddress("glMakeBuffersResidentAMD");

    if (!glBufferBusAddressAMD)
        glBufferBusAddressAMD = (PFNGLBUFFERBUSADDRESSAMDPROC) wglGetProcAddress("glBufferBusAddressAMD");

    if (!glWaitMarkerAMD)
        glWaitMarkerAMD = (PFNGLWAITMARKERAMDPROC) wglGetProcAddress("glWaitMarkerAMD");

    if (!glWriteMarkerAMD)
        glWriteMarkerAMD = (PFNGLWRITEMARKERAMDPROC) wglGetProcAddress("glWriteMarkerAMD");
#elif defined (LINUX)

    if (!glMakeBuffersResidentAMD)
        glMakeBuffersResidentAMD = (PFNGLMAKEBUFFERSRESIDENTAMDPROC) glXGetProcAddress((GLubyte*)"glMakeBuffersResidentAMD");

    if (!glBufferBusAddressAMD)
        glBufferBusAddressAMD = (PFNGLBUFFERBUSADDRESSAMDPROC) glXGetProcAddress((GLubyte*)"glBufferBusAddressAMD");

    if (!glWaitMarkerAMD)
        glWaitMarkerAMD = (PFNGLWAITMARKERAMDPROC) glXGetProcAddress((GLubyte*)"glWaitMarkerAMD");

    if (!glWriteMarkerAMD)
        glWriteMarkerAMD = (PFNGLWRITEMARKERAMDPROC) glXGetProcAddress((GLubyte*)"glWriteMarkerAMD");

#endif

    if (!(glMakeBuffersResidentAMD && glWaitMarkerAMD && glWriteMarkerAMD && glBufferBusAddressAMD))
    {
        strErrorMessage = "ERROR, Could not load Bus addressable memory functions!";
        return false;
    }

    m_pSinkWin->deleteContext();

    m_bRunning      = false;
    m_bResizeSink   = false;
    m_bStarted      = false;

    m_pSink         = NULL;

    return true;
}


bool App::start()
{
    if (!m_pSinkWin)
        return false;

    m_bRunning = true;

    m_SinkThread.create((THREAD_PROC)SinkThreadFunc,     this);

    m_bStarted = true;

    return true;
}


void App::stop()
{
    if (m_bStarted)
    {
        m_bRunning = false;

        m_SinkThread.join();

        m_bStarted = false;
    }
}

void App::resize(unsigned int uiNewWidth, unsigned int uiNewHeight)
{
    m_uiNewWinWidth = uiNewWidth;
    m_uiNewWinHeight = uiNewHeight;

    // will call resize in GLsin thread
    m_bResizeSink = true;
}


DWORD App::SinkThreadFunc(void *pArg)
{
    App* pApp = static_cast<App *>(pArg);

    pApp->SinkLoop();

    return 0;
}


bool App::SinkLoop()
{
    m_pSinkWin->createContext();
    m_pSinkWin->makeCurrent();

    m_pSink = new GPUSink(m_gentl);

    if (!m_pSink->initGL())
        return false;
    if (!m_pSink->initFG())
        return false;
    m_pSink->createBuffers();
    m_pSink->start();

    m_bResizeSink = true;

    while (m_bRunning)
    {
        // Resize only affects the window and viewport size.
        if (m_bResizeSink)
        {
            m_pSinkWin->resize(m_uiNewWinWidth, m_uiNewWinHeight);
            m_pSink->resize(m_pSinkWin->getWidth(), m_pSinkWin->getHeight());
            m_bResizeSink = false;
        }
        // Copies the data out of the bus addressable memory buffer into a texture object and draws a quad with this texture mapped
        m_pSink->draw();
        m_pSinkWin->swapBuffers();

        processEvents(m_pSinkWin);
    }

    delete m_pSink;

    return true;
}


void App::processEvents(GLWindow *pWin)
{
#if defined (LINUX)
    if (XPending(pWin->getDC()) > 0)
    {

        XEvent event;
        XNextEvent(pWin->getDC(), &event);

        switch (event.type)
        {
           case Expose:
             break;

            case KeyPress:
            {
                 char buffer[10];
                 XLookupString(&event.xkey, buffer, sizeof(buffer), NULL, NULL);

                 // ESC key
                 if (buffer[0] == 27)
                     m_bRunning = false;

                 break;
            }
         }
     }
#endif
}
