##########################################################################
#
# Copyright 2008-2009 VMware, Inc.
# All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
##########################################################################/


"""WGL tracing code generator."""


from gltrace import GlTracer
from specs.stdapi import Module, API, Void
from specs.glapi import glapi
from specs.wglapi import wglapi


class WglTracer(GlTracer):

    getProcAddressFunctionNames = [
        "wglGetProcAddress",
    ]

    createContextFunctionNames = [
        'wglCreateContext',
        'wglCreateContextAttribsARB',
        'wglCreateLayerContext',
    ]

    destroyContextFunctionNames = [
        'wglDeleteContext',
    ]

    makeCurrentFunctionNames = [
        'wglMakeCurrent',
        'wglMakeContextCurrentARB',
        'wglMakeContextCurrentEXT',
    ]

    def traceFunctionImplBody(self, function):
        if function.name.startswith('wgl'):
            # When implementing WGL extensions OpenGL ICDs often have no
            # alternative to calling back into OPENGL32.DLL's wgl* entry points
            # due to lack of extensibility in the ICD interface.  These
            # internal calls are not only visually confusing but can actually
            # cause problems when tracing, and replaying.  A particularly nasty
            # case is wglCreateContextAttribsARB which ends up calling
            # wglCreateContext/wglCreateLayerContext to obtain a HGLRC that's
            # recognizable by OPENGL32.DLL.  Therefore we need to detect and
            # dispatch internal calls, without further ado.
            print(r'    if (_reentrant) {')
            self.invokeFunction(function)
            if function.type is not Void:
                print('    return _result;')
            print(r'    }')
            print(r'    ReentryScope _reentry;')
            print(r'    (void)_reentry;')
            print()

        if function.name in self.destroyContextFunctionNames:
            # Unlike other GL APIs like EGL or GLX, WGL will make the context
            # inactive if it's currently the active context.
            print('    if (_wglGetCurrentContext() == hglrc) {')
            print('        gltrace::clearContext();')
            print('    }')
            print('    gltrace::releaseContext((uintptr_t)hglrc);')

        # Emit a fake string marker describing the current GDI font
        if function.name.startswith('wglUseFont'):
            print(r'    HFONT hFont = static_cast<HFONT>(GetCurrentObject(hdc, OBJ_FONT));')
            print(r'    if (hFont != nullptr) {')
            print(r'        LOGFONT lf;')
            print(r'        if (GetObject(hFont, sizeof lf, &lf) != 0) {')
            print(r'            std::ostringstream ss;')
            print(r'            ss << "lfFaceName = " << lf.lfFaceName')
            print(r'               << ", lfHeight = " << lf.lfHeight')
            print(r'               << ", lfWeight = " << lf.lfWeight;')
            print(r'            if (lf.lfItalic) ss << ", lfItalic = 1";')
            print(r'            if (lf.lfUnderline) ss << ", lfUnderline = 1";')
            print(r'            if (lf.lfStrikeOut) ss << ", lfStrikeOut = 1";')
            print(r'            _fakeStringMarker(ss.str());')
            print(r'        }')
            print(r'    }')
            print()

        # Emit fake glBitmap calls in the trace on wglUseFontBitmapsA.
        # This enables to capture the real bitmaps and replay them outside Windows.
        #
        # XXX: In spite what
        # https://msdn.microsoft.com/en-us/library/windows/desktop/dd374392.aspx
        # implies, GetGlyphOutline(GGO_BITMAP) does not seem to work with
        # certain fonts.  The only solution is to draw the font charactors with
        # a HBITMAP like the old Mesa fxwgl.c code used to do.  That too, seems
        # to be the way that opengl32.dll implements wglUseFontBitmaps.
        #
        if function.name == 'wglUseFontBitmapsA':

            self.invokeFunction(function)

            # Emit a fake string marker with the original call
            print(r'    {')
            print(r'        std::ostringstream ss;')
            print(r'        ss << __FUNCTION__ << "(hdc = " << hdc << ", first = " << first << ", count = " << count << ", listBase = " << listBase << ") = " << (_result ? "TRUE" : "FALSE");')
            print(r'        _fakeStringMarker(ss.str());')
            print(r'    }')
            print()

            glNewList = glapi.getFunctionByName('glNewList')
            glBitmap = glapi.getFunctionByName('glBitmap')
            glEndList = glapi.getFunctionByName('glEndList')

            print(r'    if (_result) {')

            print(r'        assert(hFont != nullptr);')
            print()

            print(r'        BOOL bRet;')
            print(r'        TEXTMETRIC tm;')
            print(r'        bRet = GetTextMetricsA(hdc, &tm);')
            print(r'        assert(bRet);')
            print()
            print(r'        HDC memDC = CreateCompatibleDC(hdc);')
            print()
            print(r'        SetMapMode(memDC, MM_TEXT);')
            print(r'        SetTextAlign(memDC, TA_BASELINE);')
            print(r'        SetBkColor(memDC, RGB(0, 0, 0));')
            print(r'        SetBkMode(memDC, OPAQUE);')
            print(r'        SetTextColor(memDC, RGB(255,255,255));')
            print()
            print(r'        BITMAPINFO * bmi = (BITMAPINFO *)malloc(offsetof(BITMAPINFO, bmiColors[2]));')
            print(r'        ZeroMemory(&bmi->bmiHeader, sizeof bmi->bmiHeader);')
            print(r'        bmi->bmiHeader.biSize = sizeof bmi->bmiHeader;')
            print(r'        bmi->bmiHeader.biPlanes = 1;')
            print(r'        bmi->bmiHeader.biBitCount = 1;')
            print(r'        bmi->bmiHeader.biCompression = BI_RGB;')
            print(r'        bmi->bmiColors[0].rgbBlue     = 0;')
            print(r'        bmi->bmiColors[0].rgbGreen    = 0;')
            print(r'        bmi->bmiColors[0].rgbRed      = 0;')
            print(r'        bmi->bmiColors[0].rgbReserved = 0;')
            print(r'        bmi->bmiColors[1].rgbBlue     = 255;')
            print(r'        bmi->bmiColors[1].rgbGreen    = 255;')
            print(r'        bmi->bmiColors[1].rgbRed      = 255;')
            print(r'        bmi->bmiColors[1].rgbReserved = 0;')
            print()
            print(r'        for (DWORD i = 0; i < count; ++i) {')
            print(r'            _fake_glNewList(listBase + i, GL_COMPILE);')
            print()
            print(r'            char cChar = first + i;')
            print()
            print(r'            // TODO: Use GetCharABSWidths')
            print(r'            // http://www.codeproject.com/Articles/14915/Width-of-text-in-italic-font')
            print(r'            // https://support.microsoft.com/en-us/kb/94646')
            print(r'            SIZE size;')
            print(r'            bRet = GetTextExtentPoint32A(hdc, &cChar, 1, &size);')
            print(r'            if (bRet) {')
            print(r'                assert(size.cx >= 0);')
            print(r'                assert(size.cy >= 0);')
            print()
            print(r'                // Round width to 32 pixels')
            print(r'                int nWidth = (size.cx + 0x1f) & ~0x1f;')
            print(r'                int nHeight = size.cy;')
            print()
            print(r'                DWORD dwBytes = nWidth / 8 * nHeight;')
            print(r'                LPVOID lpvBits = NULL;')
            print(r'                if (dwBytes) {')
            print(r'                    lpvBits = malloc(dwBytes);')
            print()
            print(r'                    HBITMAP memBM = CreateCompatibleBitmap(memDC, nWidth, nHeight);')
            print()
            print(r'                    HGDIOBJ origBM = SelectObject(memDC, memBM);')
            print()
            print(r'                    PatBlt(memDC, 0, 0, nWidth, nHeight, BLACKNESS);')
            print(r'                    SelectObject(memDC, hFont);')
            print()
            print(r'                    bmi->bmiHeader.biWidth  = nWidth;')
            print(r'                    bmi->bmiHeader.biHeight = nHeight;')
            print()
            print(r'                    bRet = TextOutA(memDC, 0, tm.tmAscent, &cChar, 1);')
            print(r'                    assert(bRet);')
            print()
            print(r'                    SelectObject(memDC, origBM);')
            print()
            print(r'                    int nScanLines = GetDIBits(memDC, memBM, 0, nHeight, lpvBits, bmi, DIB_RGB_COLORS);')
            print(r'                    assert(nScanLines == nHeight);')
            print()
            print(r'                    DeleteObject(memBM);')
            print(r'                }')
            print()
            print(r'                GLsizei width  = nWidth;')
            print(r'                GLfloat height = nHeight;')
            print(r'                GLfloat xorig  = 0;')
            print(r'                GLfloat yorig  = tm.tmDescent;')
            print(r'                GLfloat xmove  = size.cx;')
            print(r'                GLfloat ymove  = 0;')
            print(r'                const GLubyte *bitmap = (const GLubyte *)lpvBits;')
            print(r'                if (bitmap == NULL) {')
            print(r'                    // We still need to emit an empty glBitmap for empty characters like spaces;')
            print(r'                    width = height = xorig = yorig = 0;')
            print(r'                }')

            # FIXME: glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
            # FIXME: glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
            print()
            print(r'                _fake_glBitmap(width, height, xorig, yorig, xmove, ymove, bitmap);')
            print()
            print(r'                free(lpvBits);')
            print(r'            }')
            print(r'            _fake_glEndList();')
            print(r'        }')
            print()
            print(r'        DeleteDC(memDC);')
            print(r'        free(bmi);')

            print(r'    } // _result')
            return

        GlTracer.traceFunctionImplBody(self, function)

        if function.name in self.createContextFunctionNames:
            print('    if (_result)')
            print('        gltrace::createContext((uintptr_t)_result);')

        if function.name in self.makeCurrentFunctionNames:
            print('    if (_result) {')
            print('        if (hglrc != NULL)')
            print('            gltrace::setContext((uintptr_t)hglrc);')
            print('        else')
            print('            gltrace::clearContext();')
            print('    }')


if __name__ == '__main__':
    print()
    print('#define _GDI32_')
    print()
    print('#include <string.h>')
    print('#include <windows.h>')
    print()
    print('#include <sstream>')
    print()
    print('#include "trace_writer_local.hpp"')
    print('#include "os.hpp"')
    print()
    print('// To validate our prototypes')
    print('#define GL_GLEXT_PROTOTYPES')
    print('#define WGL_GLXEXT_PROTOTYPES')
    print()
    print('#include "glproc.hpp"')
    print('#include "glsize.hpp"')
    print()
    print('static OS_THREAD_LOCAL uintptr_t _reentrant;')
    print()
    print('// Helper class to track reentries in function scope.')
    print('struct ReentryScope {')
    print('inline ReentryScope() { _reentrant = 1; }')
    print('inline ~ReentryScope() { _reentrant = 0; }')
    print('};')
    print()
    module = Module()
    module.mergeModule(glapi)
    module.mergeModule(wglapi)
    api = API()
    api.addModule(module)
    tracer = WglTracer()
    tracer.traceApi(api)
