##########################################################################
#
# Copyright 2011 Jose Fonseca
# 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.
#
##########################################################################/


"""D3D retracer generator."""


import sys
from dllretrace import DllRetracer as Retracer
from specs.stdapi import API
from specs.d3d import ddraw, HWND
from specs.ddraw import DDCREATE_LPGUID


class D3DRetracer(Retracer):

    def retraceApi(self, api):
        print '// Swizzling mapping for lock addresses'
        print 'static std::map<void *, void *> _maps;'
        print
        # TODO: Keep a table of windows
        print 'static HWND g_hWnd;'
        print

        Retracer.retraceApi(self, api)

    def invokeInterfaceMethod(self, interface, method):
        # keep track of the last used device for state dumping
        if interface.name in ('IDirect3DDevice7',):
            if method.name == 'Release':
                print r'    if (call.ret->toUInt() == 0) {'
                print r'        d3d7Dumper.unbindDevice(_this);'
                print r'    }'
            else:
                print r'    d3d7Dumper.bindDevice(_this);'

        # create windows as neccessary
        hWndArg = method.getArgByType(HWND)
        if hWndArg is not None:
            # FIXME: Try to guess the window size (e.g., from IDirectDrawSurface7::Blt)
            print r'    if (!g_hWnd) {'
            print r'        g_hWnd = d3dretrace::createWindow(512, 512);'
            print r'    }'
            print r'    %s = g_hWnd;' % hWndArg.name


        if method.name == 'Lock':
            # Reset _DONOTWAIT flags. Otherwise they may fail, and we have no
            # way to cope with it (other than retry).
            mapFlagsArg = method.getArgByName('dwFlags')
            if mapFlagsArg is not None:
                print r'    dwFlags &= DDLOCK_DONOTWAIT;'
                print r'    dwFlags |= DDLOCK_WAIT;'

        Retracer.invokeInterfaceMethod(self, interface, method)

        if method.name == 'CreateDevice':
            print r'    if (FAILED(_result)) {'
            print r'        exit(1);'
            print r'    }'

        # notify frame has been completed
        # process events after presents
        if interface.name == 'IDirectDrawSurface7' and method.name == 'Blt':
            print r'    DDSCAPS2 ddsCaps;'
            print r'    if (SUCCEEDED(_this->GetCaps(&ddsCaps)) &&'
            print r'        (ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE)) {'
            print r'        retrace::frameComplete(call);'
            print r'        d3dretrace::processEvents();'
            print r'    }'

        if method.name == 'Lock':
            print '    VOID *_pbData = NULL;'
            print '    size_t _MappedSize = 0;'
            # FIXME: determine the mapping size
            #print '    _getMapInfo(_this, %s, _pbData, _MappedSize);' % ', '.join(method.argNames()[:-1])
            print '    if (_MappedSize) {'
            print '        _maps[_this] = _pbData;'
            # TODO: check pitches match
            print '    } else {'
            print '        return;'
            print '    }'
        
        if method.name == 'Unlock':
            print '    VOID *_pbData = 0;'
            print '    _pbData = _maps[_this];'
            print '    if (_pbData) {'
            print '        retrace::delRegionByPointer(_pbData);'
            print '        _maps[_this] = 0;'
            print '    }'

    def extractArg(self, function, arg, arg_type, lvalue, rvalue):
        # Handle DDCREATE_* flags
        if arg.type is DDCREATE_LPGUID:
            print '    if (%s.toArray()) {' % rvalue
            Retracer.extractArg(self, function, arg, arg_type, lvalue, rvalue)
            print '    } else {'
            print '        %s = static_cast<%s>(%s.toPointer());' % (lvalue, arg_type, rvalue)
            print '    }'
            return

        Retracer.extractArg(self, function, arg, arg_type, lvalue, rvalue)


def main():
    print r'#include <string.h>'
    print
    print r'#include <iostream>'
    print
    print r'#include "d3dretrace.hpp"'
    print

    api = API()
    
    print r'#include "d3dimports.hpp"'
    api.addModule(ddraw)
    print
    print '''static d3dretrace::D3DDumper<IDirect3DDevice7> d3d7Dumper;'''
    print

    retracer = D3DRetracer()
    retracer.table_name = 'd3dretrace::ddraw_callbacks'
    retracer.retraceApi(api)


if __name__ == '__main__':
    main()
