#ifdef WIN32

#include <lmerr.h>
#include <assert.h>

#include "D3D9_Renderer_Impl.h"
#include "D3D9_Resource.h"
#include "D3D9_Common.h"
#include "D3D9_Overlay.h"

namespace videonext { namespace media {

void get_error_msg(DWORD dwErrorMsgId, char *str, unsigned str_len);

D3D9_Render_Impl::D3D9_Render_Impl()
    : hwnd_(0)
    , d3d9_(0)
    , device_(0)
    , offscreen_surface_(0)
    , offscreen_surface_mem_(0)
    , texture_surface_(0)
    , texture_(0)
    , vertex_buffer_(0)
    , vertex_shader_(0)
    , pixel_shader_(0)
    , vertex_constant_table_(0)
    , pixel_constant_table_(0)
    , video_width_(0)
    , video_width_visible_(0)      
    , video_height_(0)
    , window_width_(0)
    , window_height_(0)
    , pixel_format_(D3DFMT_UNKNOWN)
    , fill_mode_((Image_Fill_Mode)-1)
{
}

/*virtual*/ D3D9_Render_Impl::~D3D9_Render_Impl()
{
    destroy();

    delete[] offscreen_surface_mem_; 
}
        
/*virtual*/ int D3D9_Render_Impl::init(void* window_handle)
{
    hwnd_ = (HWND)window_handle;
    
    d3d9_ = Direct3DCreate9(D3D_SDK_VERSION); 
    if (!d3d9_)
        return E_FAIL;

    HR(IDirect3D9_GetAdapterDisplayMode(d3d9_, D3DADAPTER_DEFAULT, &display_mode_));

    D3DCAPS9 device_caps;
    HR(IDirect3D9_GetDeviceCaps(d3d9_, D3DADAPTER_DEFAULT, VN_D3DDEVTYPE, &device_caps));

    
    DWORD dwBehaviorFlags = D3DCREATE_MULTITHREADED |
        D3DCREATE_FPU_PRESERVE;

    if (device_caps.VertexProcessingCaps != 0 )
    {
        dwBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
    }
    else
    {
        av_log(NULL, AV_LOG_WARNING, "Only software processing supported by device");
        dwBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
    }
    
    HR(get_present_params(&present_params_, TRUE));
    
    HR(IDirect3D9_CreateDevice(d3d9_, D3DADAPTER_DEFAULT, VN_D3DDEVTYPE, hwnd_, dwBehaviorFlags, &present_params_, &device_));

    // define pixel format
    VN_PLAYER_PIXEL_FORMAT pix_fmt = get_preferred_pixel_format();
    if (pix_fmt == PIX_YUV420P)
    {
        pixel_format_ = D3DFMT_YV12;
        printf("D3DFMT_YV12 supported\n");
    }
    else
    {
        pixel_format_ = D3DFMT_X8R8G8B8;
        printf("Only D3DFMT_X8R8G8B8 supported\n");
    }
        
    HR(create_resources());
        
    char *error = 0;
    HRESULT hr = set_vertex_shader(IDR_HLSL_VERTEX_SHADER, "vs_main", "vs_3_0", &error);
    if (FAILED(hr))
    {
        av_log(NULL, AV_LOG_ERROR, "set_vertex_shader failed: %s\n", DXGetErrorString9(hr));
        if (error)
        {
            av_log(NULL, AV_LOG_ERROR, "%s", error);
            delete error;
        }
        return hr;
    }
  
    HR(apply_world_view_proj("WorldViewProj"));
    D3DXMATRIX matIdentity;
    D3DXMatrixIdentity(&matIdentity);
    HR(set_vertex_shader_matrix(&matIdentity, "RotationZMatrix"));
    BOOL flip_vertically   = FALSE;
    BOOL flip_horizontally = FALSE;
    HR(set_vertex_shader_constant("FlipVertically", (void*)&flip_vertically, sizeof(BOOL)));
    HR(set_vertex_shader_constant("FlipHorizontally", (void*)&flip_horizontally, sizeof(BOOL)));

    // pixel shader
    error = 0;
    hr = set_pixel_shader(IDR_HLSL_PIXEL_SHADER, "main", "ps_3_0", &error);
    if (FAILED(hr))
    {
        av_log(NULL, AV_LOG_ERROR, "set_pixel_shader failed: %s\n", DXGetErrorString9(hr));
        if (error)
        {
            av_log(NULL, AV_LOG_ERROR, "%s", error);
            delete error;
        }
        return hr;
    }
    BOOL apply_zoom = FALSE;
    HR(set_pixel_shader_constant("ApplyZoom", (void*)&apply_zoom, sizeof(BOOL)));
    BOOL apply_adjust = FALSE;
    HR(set_pixel_shader_constant("ApplyAdjust", (void*)&apply_adjust, sizeof(BOOL)));
    
    return 0;
}

/*virtual*/ VN_PLAYER_PIXEL_FORMAT D3D9_Render_Impl::get_preferred_pixel_format() const
{
    VN_PLAYER_PIXEL_FORMAT fmt = PIX_RGB32; // should be supprted by any HAL
    if (SUCCEEDED(check_format_conversion(D3DFMT_YV12)))
    {
        // Check if we can create surface
        LPDIRECT3DSURFACE9 dummy_surface = 0;
        if (SUCCEEDED(IDirect3DDevice9_CreateOffscreenPlainSurface(device_, 640, 480, D3DFMT_YV12, D3DPOOL_DEFAULT, &dummy_surface, NULL)))
            fmt = PIX_YUV420P;

        IDirect3D9_SafeRelease(dummy_surface);

    }

    return fmt;
}

/*virtual*/ int D3D9_Render_Impl::process(const vn_player_frame_t *frame)
{
    if (!offscreen_surface_)
    {
        unsigned width  = frame->width;
        video_width_visible_ = width;
        
        if (pixel_format_ == D3DFMT_YV12 && frame->data_size[0] > width)
        {
            width = frame->data_size[0];
            video_width_visible_ = frame->width - 2;
            av_log(NULL, AV_LOG_WARNING, "Reduce visible image width from %d to %d\n", frame->data_size[0], video_width_visible_);
        }

        HR(create_video_surface(width, frame->height));
        
    }

    HR(fill_buffer(frame->data[0], frame->data[2], frame->data[1]));

    return 0;
}
    
/*virtual*/ int D3D9_Render_Impl::draw(Image_Fill_Mode mode, Image_Calibration *ic, Image_Pan_Zoom *pz)
{
    if (!offscreen_surface_)
        return 0;
    
    RECT rect;
    ::GetClientRect(hwnd_, &rect);
    int current_window_width  =  rect.right - rect.left;
    int current_window_height = rect.bottom - rect.top;

    if (current_window_width == 0 || current_window_height == 0)
        return 0;
    
    if (window_width_ != current_window_width
        || window_height_ != current_window_height)
    {
        fprintf(stderr, "Window size changed to %dx%d\n", current_window_width, current_window_height);

        destroy_resources();
	HR(get_present_params(&present_params_, TRUE));
	HR(IDirect3DDevice9_Reset(device_, &present_params_));
	HR(create_resources());
        HR(create_video_surface(video_width_, video_height_));

        HR(apply_world_view_proj("WorldViewProj"));
        D3DXMATRIX matIdentity;
        D3DXMatrixIdentity(&matIdentity);
        HR(set_vertex_shader_matrix(&matIdentity, "RotationZMatrix"));
        
        // reset to defaults
        fill_mode_ = (Image_Fill_Mode)-1;
        ic_ = Image_Calibration();
        pz_ = Image_Pan_Zoom();
        
        InvalidateRect(hwnd_, 0, FALSE); // HACK
    }

    // Apply ImageCalibration stuff
    if (ic != 0 && *ic != ic_)
    {
        ic_ = *ic;

        BOOL apply_adjust = TRUE;        
        HR(set_pixel_shader_constant("ApplyAdjust", (void*)&apply_adjust, sizeof(BOOL)));
        HR(set_pixel_shader_constant("Contrast",    (void*)&ic_.contrast, sizeof(float)));
        HR(set_pixel_shader_constant("Saturation",  (void*)&ic_.saturation, sizeof(float)));
        HR(set_pixel_shader_constant("Brightness",  (void*)&ic_.brightness, sizeof(float)));
        float hue = 0.0f;
        HR(set_pixel_shader_constant("HueShift", (void*)&hue, sizeof(hue)));

        printf("Apply image calibration: (c: %.4f, b: %.4f, s: %.4f)\n", ic_.contrast, ic_.brightness, ic_.saturation);
    }
    else if (ic == 0 && ic_ != Image_Calibration())
    {
        BOOL apply_adjust = FALSE;
        HR(set_pixel_shader_constant("ApplyAdjust", (void*)&apply_adjust, sizeof(BOOL)));

        ic_ = Image_Calibration(); // reset to default

        printf("Disable image calibration\n");

    }
    // Apply Pan&Zoom stuff
    if (pz != 0 && *pz != pz_)
    {
        pz_ = *pz;
        if (pz_.pan_x < 0)
            pz_.pan_x = 0;
        if (pz_.pan_y < 0)
            pz_.pan_y = 0;
        if (pz_.pan_x > 1)
            pz_.pan_x = 1;
        if (pz_.pan_y > 1)
            pz_.pan_y = 1;
        
        BOOL apply_zoom = TRUE;
        HR(set_pixel_shader_constant("ApplyZoom", (void*)&apply_zoom, sizeof(BOOL)));
        HR(set_pixel_shader_constant("PanX", (void*)&pz_.pan_x, sizeof(float)));
        HR(set_pixel_shader_constant("PanY", (void*)&pz_.pan_y, sizeof(float)));
        HR(set_pixel_shader_constant("Zoom", (void*)&pz_.scale, sizeof(float)));


        if (pz_.mode == VN_IMAGE_ZOOM_NONE)
        {
            HR(IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_MAGFILTER, D3DTEXF_NONE));
            HR(IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_MINFILTER, D3DTEXF_NONE));
        }
        else
        {
            HR(IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR));
            HR(IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR));   
        }
                
        printf("Apply pan&zoom: (panx: %.4f, pany: %.4f, scale: %.4f, filter: %s)\n", pz_.pan_x, pz_.pan_y, pz_.scale, (pz_.mode == VN_IMAGE_ZOOM_NONE ? "NONE" : "BILINEAR"));
    }
    else if (pz == 0 && pz_ != Image_Pan_Zoom())
    {
        printf("Disable pan&zoom\n");

        BOOL apply_zoom = FALSE;
        HR(set_pixel_shader_constant("ApplyZoom", (void*)&apply_zoom, sizeof(BOOL)));

        pz_ = Image_Pan_Zoom(); // reset to default
        HR(IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR));
        HR(IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR));
    }
    
    // if (pz_ != Image_Pan_Zoom())
    //     mode = Stretch;
    
    // apply FillMode
    if (fill_mode_ != mode)
    {
        RECT rect = {0, 0, window_width_, window_height_};

        if (mode == Keep_Aspect_Ratio)
            apply_aspect_ratio(rect, (FLOAT)video_width_, (FLOAT)video_height_);
        
        HR(set_pixel_shader_constant("AspectTop", (void*)&rect.top, sizeof(int)));
        HR(set_pixel_shader_constant("AspectLeft", (void*)&rect.left, sizeof(int)));
        HR(set_pixel_shader_constant("AspectBottom", (void*)&rect.bottom, sizeof(int)));
        HR(set_pixel_shader_constant("AspectRight", (void*)&rect.right, sizeof(int)));        

        printf("Apply fill mode: (%ld,%ld,%ld,%ld)\n", rect.left, rect.top, rect.right, rect.bottom);
        
        fill_mode_ = mode;
    }

    parse_metadata(0);
    
    HR(check_device());

    HR(create_scene());

    return present();
}
    
HRESULT D3D9_Render_Impl::get_present_params(D3DPRESENT_PARAMETERS* params, BOOL bWindowed)
{
    UINT height, width;

    if (bWindowed) // windowed mode
    {
        RECT rect;
        ::GetClientRect(hwnd_, &rect);
        height = rect.bottom - rect.top;
        width  = rect.right - rect.left;
        window_width_  = width;
        window_height_ = height;
    }
    else   // fullscreen mode
    {
        width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
        height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    }

    D3DPRESENT_PARAMETERS presentParams = {0};
    presentParams.Flags                  = D3DPRESENTFLAG_VIDEO | D3DPRESENTFLAG_OVERLAY_YCbCr_BT709;
    presentParams.Windowed               = bWindowed;
    presentParams.hDeviceWindow          = hwnd_;
    presentParams.BackBufferWidth        = width;
    presentParams.BackBufferHeight       = height;
    presentParams.SwapEffect             = D3DSWAPEFFECT_COPY;
    presentParams.MultiSampleType        = D3DMULTISAMPLE_NONE;
    presentParams.PresentationInterval   = D3DPRESENT_INTERVAL_DEFAULT;
    presentParams.BackBufferFormat       = display_mode_.Format;
    presentParams.BackBufferCount        = 1;
    presentParams.EnableAutoDepthStencil = FALSE;

    memcpy(params, &presentParams, sizeof(D3DPRESENT_PARAMETERS));

    return S_OK;
}

HRESULT D3D9_Render_Impl::create_resources()
{    
    IDirect3DDevice9_SetRenderState(device_, D3DRS_CULLMODE, D3DCULL_NONE);
    IDirect3DDevice9_SetRenderState(device_, D3DRS_ZENABLE, D3DZB_FALSE);
    IDirect3DDevice9_SetRenderState(device_, D3DRS_LIGHTING, FALSE);
    IDirect3DDevice9_SetRenderState(device_, D3DRS_DITHERENABLE, TRUE);

    IDirect3DDevice9_SetRenderState(device_, D3DRS_MULTISAMPLEANTIALIAS, TRUE);
    IDirect3DDevice9_SetRenderState(device_, D3DRS_ALPHABLENDENABLE, TRUE);
    IDirect3DDevice9_SetRenderState(device_, D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    IDirect3DDevice9_SetRenderState(device_, D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    
    IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
    IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
    IDirect3DDevice9_SetSamplerState(device_, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
    
    IDirect3DDevice9_SetTextureStageState(device_, 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
    IDirect3DDevice9_SetTextureStageState(device_, 0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    IDirect3DDevice9_SetTextureStageState(device_, 0, D3DTSS_COLORARG2, D3DTA_SPECULAR);

    IDirect3DDevice9_SetTextureStageState(device_, 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
    IDirect3DDevice9_SetTextureStageState(device_, 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
    IDirect3DDevice9_SetTextureStageState(device_, 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);

    printf("create_render_target: %dx%d\n", window_width_, window_height_);
    HR(create_render_target());
    
    return setup_matrices();
}

HRESULT D3D9_Render_Impl::destroy_resources()
{
    IDirect3D9_SafeRelease(texture_surface_);
    IDirect3D9_SafeRelease(texture_);
    IDirect3D9_SafeRelease(vertex_buffer_);

    if (offscreen_surface_)
    {
        D3DLOCKED_RECT d3drect;
        HR(IDirect3DSurface9_LockRect(offscreen_surface_, &d3drect, NULL, 0));
        unsigned char* pict = (unsigned char*)d3drect.pBits;

        delete[] offscreen_surface_mem_; 
        
        switch ((int)pixel_format_)
        {
            case D3DFMT_YV12:
            {
                unsigned pict_size = (video_height_ * d3drect.Pitch) + (video_height_ * d3drect.Pitch)/2;
                offscreen_surface_mem_ = new unsigned char[pict_size];
                memcpy(offscreen_surface_mem_, pict, pict_size);
                break;
            }
            case D3DFMT_X8R8G8B8:
            {
                unsigned pict_size = (video_height_ * d3drect.Pitch);
                offscreen_surface_mem_ = new unsigned char[pict_size];
                memcpy(offscreen_surface_mem_, pict, pict_size);
                break;
            }
            default:
            {
                av_log(NULL, AV_LOG_ERROR, "%s:%d Unknown pixel format: %d\n", __FILE__, __LINE__, pixel_format_);
                break;
            }
        }
                
        HR(IDirect3DSurface9_UnlockRect(offscreen_surface_));
    }
    IDirect3D9_SafeRelease(offscreen_surface_);

    overlays_.clear();
    
    return S_OK;
}

HRESULT D3D9_Render_Impl::setup_matrices()
{
    D3DXMATRIX matOrtho; 
    D3DXMATRIX matIdentity;

    D3DXMatrixOrthoOffCenterLH(&matOrtho, 0, window_width_, window_height_, 0, 0.0f, 1.0f);
    D3DXMatrixIdentity(&matIdentity);

    HR(IDirect3DDevice9_SetTransform(device_, D3DTS_PROJECTION, &matOrtho));
    HR(IDirect3DDevice9_SetTransform(device_, D3DTS_WORLD, &matIdentity));
    
    return IDirect3DDevice9_SetTransform(device_, D3DTS_VIEW, &matIdentity);
}

HRESULT D3D9_Render_Impl::create_render_target()
{
    HR(IDirect3DDevice9_CreateTexture(device_, window_width_, window_height_, 1, D3DUSAGE_RENDERTARGET, display_mode_.Format, D3DPOOL_DEFAULT, &texture_, NULL));
    HR(IDirect3DTexture9_GetSurfaceLevel(texture_, 0, &texture_surface_));
    HR(IDirect3DDevice9_CreateVertexBuffer(device_, sizeof(VERTEX) * 4, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &vertex_buffer_, NULL));
    
    VERTEX vertexArray[] =
        {
            { D3DXVECTOR3(0, 0, 0),                          /*1.0f,*/ D3DCOLOR_ARGB(255, 255, 255, 255), D3DXVECTOR2(0, 0) },  // top left
            { D3DXVECTOR3(window_width_, 0, 0),              /*1.0f,*/ D3DCOLOR_ARGB(255, 255, 255, 255), D3DXVECTOR2(1, 0) },  // top right
            { D3DXVECTOR3(window_width_, window_height_, 0), /*1.0f,*/ D3DCOLOR_ARGB(255, 255, 255, 255), D3DXVECTOR2(1, 1) },  // bottom right
            { D3DXVECTOR3(0, window_height_, 0),             /*1.0f,*/ D3DCOLOR_ARGB(255, 255, 255, 255), D3DXVECTOR2(0, 1) },  // bottom left
        };


    VERTEX *vertices;
    HR(IDirect3DVertexBuffer9_Lock(vertex_buffer_, 0, 0, (void**)&vertices, D3DLOCK_DISCARD));
    
    memcpy(vertices, vertexArray, sizeof(vertexArray));
    
    return IDirect3DVertexBuffer9_Unlock(vertex_buffer_);
}

HRESULT D3D9_Render_Impl::check_format_conversion(D3DFORMAT format) const
{
    HRESULT hr = IDirect3D9_CheckDeviceFormat(d3d9_, D3DADAPTER_DEFAULT, VN_D3DDEVTYPE, display_mode_.Format, 0, D3DRTYPE_SURFACE, format);

    if (FAILED(hr))
        return hr;
    
    return IDirect3D9_CheckDeviceFormatConversion(d3d9_, D3DADAPTER_DEFAULT, VN_D3DDEVTYPE, format, display_mode_.Format);
}

HRESULT D3D9_Render_Impl::create_video_surface(int width, int height)
{
    video_width_  = width;
    video_height_ = height;

    printf("Create video surface: %dx%d\n", width, height);

    HR(IDirect3DDevice9_CreateOffscreenPlainSurface(device_, width, height, pixel_format_, D3DPOOL_DEFAULT, &offscreen_surface_, NULL));

    HR(IDirect3DDevice9_ColorFill(device_, offscreen_surface_, NULL, D3DCOLOR_ARGB(0xFF, 0, 0, 0)));
    
    if (offscreen_surface_mem_ != 0)
    {
        D3DLOCKED_RECT d3drect;
        HR(IDirect3DSurface9_LockRect(offscreen_surface_, &d3drect, NULL, 0));
        BYTE* pict = (BYTE*)d3drect.pBits;

        switch ((int)pixel_format_)
        {
            case D3DFMT_YV12:
            {
                unsigned pict_size = (video_height_ * d3drect.Pitch) + (video_height_ * d3drect.Pitch)/2;
                memcpy(pict, offscreen_surface_mem_, pict_size);
                break;
            }
            case D3DFMT_X8R8G8B8:
            {
                unsigned pict_size = video_height_ * d3drect.Pitch;
                memcpy(pict, offscreen_surface_mem_, pict_size);
                break;
            }
            default:
            {
                av_log(NULL, AV_LOG_ERROR, "%s:%d Unknown pixel format: %d\n", __FILE__, __LINE__, pixel_format_);
                break;
            }
        }
        
        HR(IDirect3DSurface9_UnlockRect(offscreen_surface_));
    }
    
    return 0;
}

HRESULT D3D9_Render_Impl::fill_buffer(BYTE* pY, BYTE* pV, BYTE* pU)
{
    D3DLOCKED_RECT d3drect;
    HR(IDirect3DSurface9_LockRect(offscreen_surface_, &d3drect, NULL, 0));

    int newHeight  = video_height_;
    int newWidth   = video_width_;

    BYTE* pict = (BYTE*)d3drect.pBits;
    BYTE* Y = pY;
    BYTE* V = pV;
    BYTE* U = pU;

    switch ((int)pixel_format_)
    {
        case D3DFMT_YV12:
        {
            for (int y = 0 ; y < newHeight ; y++)
            {
                memcpy(pict, Y, newWidth);
                pict += d3drect.Pitch;
                Y += newWidth;
            }
            for (int y = 0 ; y < newHeight / 2 ; y++)
            {
                memcpy(pict, V, newWidth / 2);
                pict += d3drect.Pitch / 2;
                V += newWidth / 2;
            }
            for (int y = 0 ; y < newHeight / 2; y++)
            {
                memcpy(pict, U, newWidth / 2);
                pict += d3drect.Pitch / 2;
                U += newWidth / 2;
            }	
//            printf("copy %dx%d, pitch: %d (size: %d)\n", newWidth, newHeight, d3drect.Pitch, pict-(BYTE*)d3drect.pBits);

            break;
        }
        case D3DFMT_NV12:
        {    
            for (int y = 0 ; y < newHeight ; y++)
            {
                memcpy(pict, Y, newWidth);
                pict += d3drect.Pitch;
                Y += newWidth;
            }
            for (int y = 0 ; y < newHeight / 2 ; y++)
            {
                memcpy(pict, V, newWidth);
                pict += d3drect.Pitch;
                V += newWidth;
            }
            break;
        }   
        case D3DFMT_A8R8G8B8:
        case D3DFMT_X8R8G8B8:
        {
            size_t widthInBytes = newWidth * 4;
            for (int y = 0; y < newHeight; y++, pict += d3drect.Pitch, Y += widthInBytes)
                memcpy(pict, Y, widthInBytes);
            break;
        }

        default:
            av_log(NULL, AV_LOG_ERROR, "Unknown format: %d", pixel_format_);
            break;
    }
     
    return IDirect3DSurface9_UnlockRect(offscreen_surface_);
}

HRESULT D3D9_Render_Impl::present()
{
    RECT dst_rect;
    ::GetClientRect(hwnd_, &dst_rect);

    if (fill_mode_ == Keep_Aspect_Ratio)
        apply_aspect_ratio(dst_rect, (FLOAT)video_width_, (FLOAT)video_height_);

    HR(IDirect3DDevice9_ColorFill(device_, texture_surface_, NULL, D3DCOLOR_ARGB(0xFF, 0, 0, 0)));
#ifdef VN_RENDERER_DEBUG
    HR(IDirect3DDevice9_StretchRect(device_, offscreen_surface_, NULL, texture_surface_, &rect, D3DTEXF_NONE));
#else
    D3DTEXTUREFILTERTYPE filter = D3DTEXF_LINEAR;
    if (pz_.mode == VN_IMAGE_ZOOM_NONE)
        filter = D3DTEXF_NONE;
    RECT src_rect = {0, 0, video_width_visible_, video_height_};
    HR(IDirect3DDevice9_StretchRect(device_, offscreen_surface_, &src_rect, texture_surface_, &dst_rect, filter));
#endif   

    return IDirect3DDevice9_Present(device_, NULL, NULL, NULL, NULL);
}

void D3D9_Render_Impl::apply_aspect_ratio(RECT& rendertTargetArea, FLOAT frameWidth, FLOAT frameHeight)
{
    const float aspectRatio = frameWidth / frameHeight;

    const float targetW = fabs((FLOAT)(rendertTargetArea.right - rendertTargetArea.left));
    const float targetH = fabs((FLOAT)(rendertTargetArea.bottom - rendertTargetArea.top));

    float tempH = targetW / aspectRatio;    
            
    if (tempH <= targetH)
    {               
        float deltaH = fabs(tempH - targetH) / 2;
        rendertTargetArea.top += deltaH;
        rendertTargetArea.bottom -= deltaH;
    }
    else
    {
        float tempW = targetH * aspectRatio;    
        float deltaW = fabs(tempW - targetW) / 2;

        rendertTargetArea.left += deltaW;
        rendertTargetArea.right -= deltaW;
    }
}

HRESULT D3D9_Render_Impl::check_device()
{
    return IDirect3DDevice9_TestCooperativeLevel(device_);
}

HRESULT D3D9_Render_Impl::create_scene()
{
    HRESULT hr = IDirect3DDevice9_Clear(device_, D3DADAPTER_DEFAULT, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0);

    HR(IDirect3DDevice9_BeginScene(device_));

    hr = IDirect3DDevice9_SetVertexShader(device_, vertex_shader_);
    if (FAILED(hr))
    {
        av_log(NULL, AV_LOG_ERROR, "IDirect3DDevice9_SetVertexShader failed\n");

        IDirect3DDevice9_EndScene(device_);
        return hr;
    }
    
    hr = IDirect3DDevice9_SetPixelShader(device_, pixel_shader_);
    if (FAILED(hr))
    {
        av_log(NULL, AV_LOG_ERROR, "IDirect3DDevice9_SetPixelShader failed\n");

        IDirect3DDevice9_EndScene(device_);
        return hr;
    }

    hr = IDirect3DDevice9_SetTexture(device_, 0, (LPDIRECT3DBASETEXTURE9)texture_);
    if (FAILED(hr))
    {
        av_log(NULL, AV_LOG_ERROR, "IDirect3DDevice9_SetTexture failed\n");

        IDirect3DDevice9_EndScene(device_);
        return hr;
    }

    hr = IDirect3DDevice9_SetStreamSource(device_, 0, vertex_buffer_, 0, sizeof(VERTEX));
    if (FAILED(hr))
    {
        av_log(NULL, AV_LOG_ERROR, "IDirect3DDevice9_SetStreamSource failed\n");

        IDirect3DDevice9_EndScene(device_);
        return hr;
    }

    hr = IDirect3DDevice9_SetFVF(device_, D3DFVF_CUSTOMVERTEX);
    if (FAILED(hr))
    {
        av_log(NULL, AV_LOG_ERROR, "IDirect3DDevice9_SetFVF failed\n");

        IDirect3DDevice9_EndScene(device_);
        return hr;
    }

    hr = IDirect3DDevice9_DrawPrimitive(device_, D3DPT_TRIANGLEFAN, 0, 2);
    if(FAILED(hr))
    {
        av_log(NULL, AV_LOG_ERROR, "IDirect3DDevice9_DrawPrimitive failed\n");

        IDirect3DDevice9_EndScene(device_);
        return hr;
    }

    overlays_.draw();

    return IDirect3DDevice9_EndScene(device_);
}

void D3D9_Render_Impl::destroy()
{
    IDirect3D9_SafeRelease(offscreen_surface_);
    IDirect3D9_SafeRelease(texture_surface_);
    IDirect3D9_SafeRelease(texture_);
    IDirect3D9_SafeRelease(vertex_buffer_);
    IDirect3D9_SafeRelease(vertex_shader_); 
    IDirect3D9_SafeRelease(vertex_constant_table_); 
    IDirect3D9_SafeRelease(pixel_constant_table_); 
    IDirect3D9_SafeRelease(pixel_shader_);

    IDirect3D9_SafeRelease(device_);
    IDirect3D9_SafeRelease(d3d9_);
}

HRESULT D3D9_Render_Impl::apply_world_view_proj(LPCSTR matrixName)
{
    D3DXMATRIX matOrtho;
    HR(IDirect3DDevice9_GetTransform(device_, D3DTS_PROJECTION, &matOrtho));

    return ID3DXConstantTable_SetMatrix(vertex_constant_table_, device_, matrixName, &matOrtho);
}

HRESULT D3D9_Render_Impl::set_pixel_shader(int pixelShaderName, LPCSTR entryPoint, LPCSTR shaderModel, LPSTR* ppError)
{
    ID3DXBuffer* code = 0;
    ID3DXBuffer* errMsg = 0;
    DWORD flags = 0;
    
#ifdef VN_RENDERER_DEBUG
    flags = D3DXSHADER_DEBUG|D3DXSHADER_SKIPOPTIMIZATION;
#endif

    HRESULT hr = D3DXCompileShaderFromResource(GetModuleHandle("VNMediaClient"), MAKEINTRESOURCE(pixelShaderName), NULL, NULL, entryPoint, shaderModel, flags, &code, &errMsg, &pixel_constant_table_);
    if (FAILED(hr))
    {	
        if (errMsg != NULL)
        {
            size_t len = ID3DXBuffer_GetBufferSize(errMsg) + 1;
            *ppError = new char[len];		
            memcpy(*ppError, ID3DXBuffer_GetBufferPointer(errMsg), len);	
        }

        IDirect3D9_SafeRelease(code);
        IDirect3D9_SafeRelease(errMsg);
        
        return hr;
    }

    hr = IDirect3DDevice9_CreatePixelShader(device_,  (DWORD*)ID3DXBuffer_GetBufferPointer(code), &pixel_shader_);

    IDirect3D9_SafeRelease(code);
    IDirect3D9_SafeRelease(errMsg);
                                             
    return hr;
}

HRESULT D3D9_Render_Impl::set_pixel_shader(DWORD* buffer)
{
    HR(D3DXGetShaderConstantTable(buffer, &pixel_constant_table_));
    return IDirect3DDevice9_CreatePixelShader(device_, buffer, &pixel_shader_);
}

HRESULT D3D9_Render_Impl::set_pixel_shader_constant(LPCSTR name, LPVOID value, UINT size)
{
    return ID3DXConstantTable_SetValue(pixel_constant_table_, device_, name, value, size);
}
        
HRESULT D3D9_Render_Impl::set_pixel_shader_vector(D3DXVECTOR4* vector, LPCSTR name)
{
    return ID3DXConstantTable_SetVector(pixel_constant_table_, device_, name, vector);
}

HRESULT D3D9_Render_Impl::set_pixel_shader_matrix(D3DXMATRIX* matrix, LPCSTR name)
{
    return ID3DXConstantTable_SetMatrix(pixel_constant_table_, device_, name, matrix);
}

HRESULT D3D9_Render_Impl::set_vertex_shader(int vertexShaderName, LPCSTR entryPoint, LPCSTR shaderModel, LPSTR* ppError)
{
    ID3DXBuffer* code = 0;
    ID3DXBuffer* errMsg = 0;
    DWORD flags = 0;
    
#ifdef VN_RENDERER_DEBUG
    flags = D3DXSHADER_DEBUG|D3DXSHADER_SKIPOPTIMIZATION;
#endif
    
//    HRESULT hr = D3DXCompileShaderFromFile(pVertexShaderName, NULL, NULL, entryPoint, shaderModel, flags, &code, &errMsg, &vertex_constant_table_);
    HRESULT hr = D3DXCompileShaderFromResource(GetModuleHandle("VNMediaClient"), MAKEINTRESOURCE(vertexShaderName), NULL, NULL, entryPoint, shaderModel, flags, &code, &errMsg, &vertex_constant_table_);
    
    if (FAILED(hr))
    {	
        if (errMsg != NULL)
        {
            size_t len = ID3DXBuffer_GetBufferSize(errMsg) + 1;
            *ppError = new char[len];		
            memcpy(*ppError, ID3DXBuffer_GetBufferPointer(errMsg), len);	
        }

        IDirect3D9_SafeRelease(code);
        IDirect3D9_SafeRelease(errMsg);
        
        return hr;
    }

    hr = IDirect3DDevice9_CreateVertexShader(device_,  (DWORD*)ID3DXBuffer_GetBufferPointer(code), &vertex_shader_);

    IDirect3D9_SafeRelease(code);
    IDirect3D9_SafeRelease(errMsg);
                                             
    return hr;
}

HRESULT D3D9_Render_Impl::set_vertex_shader(DWORD* buffer)
{
    HR(D3DXGetShaderConstantTable(buffer, &vertex_constant_table_));

    return IDirect3DDevice9_CreateVertexShader(device_, buffer, &vertex_shader_);
}

HRESULT D3D9_Render_Impl::set_vertex_shader_constant(LPCSTR name, LPVOID value, UINT size)
{
    return ID3DXConstantTable_SetValue(vertex_constant_table_, device_, name, value, size);
}

HRESULT D3D9_Render_Impl::set_vertex_shader_vector(D3DXVECTOR4* vector, LPCSTR name)
{
    return ID3DXConstantTable_SetVector(vertex_constant_table_, device_, name, vector);
}

HRESULT D3D9_Render_Impl::set_vertex_shader_matrix(D3DXMATRIX* matrix, LPCSTR name)
{
    return ID3DXConstantTable_SetMatrix(vertex_constant_table_, device_, name, matrix);
}

void D3D9_Render_Impl::parse_metadata(const vn_player_frame_t *frame)
{
    overlays_.clear();

    // RECT rect1 = {10, 10, 300, 300};
    // D3D9_Rectangle_Overlay *r1 = new D3D9_Rectangle_Overlay(device_, rect1, 1, D3DCOLOR_ARGB(100, 0, 0, 255));
    // overlays_.add_overlay(r1, reinterpret_cast<uint64_t>(r1));

    // RECT rect2 = {300, 100, 400, 400};
    // D3D9_Fill_Rectangle_Overlay *fr1 = new D3D9_Fill_Rectangle_Overlay(device_, rect2, D3DCOLOR_ARGB(100, 0, 0, 255));
    // overlays_.add_overlay(fr1, reinterpret_cast<uint64_t>(fr1));



    
    // D3D9_Ellipse_Overlay *e1 = new D3D9_Ellipse_Overlay(device_, 300, 200, 100, 50, 1, D3DCOLOR_ARGB(100, 0, 0, 0));
    // overlays_.add_overlay(e1, reinterpret_cast<uint64_t>(e1));

    // D3D9_Fill_Ellipse_Overlay *fe1 = new D3D9_Fill_Ellipse_Overlay(device_, 600, 200, 100, 50, D3DCOLOR_ARGB(100, 255, 0, 255));
    // overlays_.add_overlay(fe1, reinterpret_cast<uint64_t>(fe1));
    
    // POINT po1[5] = { {200, 200}, {300, 20}, {200, 150}, {180, 100}, {100, 500},  };
    // D3D9_Polygon_Overlay *p1 = new D3D9_Polygon_Overlay(device_, po1, 5, 1, D3DCOLOR_ARGB(100, 255, 0, 255));
    // overlays_.add_overlay(p1, reinterpret_cast<uint64_t>(p1));
    
    // POINT po2[5] = { {20, 20}, {200, 20}, {200, 100}, {150, 100}, {100, 400},  };
    // D3D9_Fill_Polygon_Overlay *fp1 = new D3D9_Fill_Polygon_Overlay(device_, po2, 5, D3DCOLOR_ARGB(100, 255, 0, 255));
    // overlays_.add_overlay(fp1, reinterpret_cast<uint64_t>(fp1));

}

void get_error_msg(DWORD dwErrorMsgId, char *str, unsigned str_len)
{
#define ERRMSGBUFFERSIZE 256
    DWORD ret = 0;    // Temp space to hold a return value.
    HINSTANCE hInst;  // Instance handle for DLL.
    HLOCAL pBuffer;   // Buffer to hold the textual error description.
    
    if (HRESULT_FACILITY(dwErrorMsgId) == FACILITY_MSMQ)
    {
        // MSMQ errors only (see winerror.h for facility info).
        // Load the MSMQ library containing the error message strings.
	hInst = LoadLibrary( TEXT("MQUTIL.DLL") );
	if (hInst != 0)
	{
            // hInst not NULL if the library was successfully loaded.
            // Get the text string for a message definition
            ret = FormatMessage( 
                FORMAT_MESSAGE_ALLOCATE_BUFFER | // Function will handle memory allocation.
                FORMAT_MESSAGE_FROM_HMODULE | // Using a module's message table.
                FORMAT_MESSAGE_IGNORE_INSERTS, 
                hInst, // Handle to the DLL.
                dwErrorMsgId, // Message identifier.
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language.
                (LPTSTR)&pBuffer, // Buffer that will hold the text string.
                ERRMSGBUFFERSIZE, // Allocate at least this many chars for pBuffer.
                NULL // No insert values.
                );
	} // hInst not NULL if the library was successfully loaded.

    } // MSMQ errors only.

    else if (dwErrorMsgId >= NERR_BASE && dwErrorMsgId <= MAX_NERR)
    {
        // Could be a network error.
        // Load the library containing network messages.
	hInst = LoadLibrary( TEXT("NETMSG.DLL") );
	if (hInst != 0)
	{
            // Not NULL if successfully loaded.
            // Get a text string for the message definition.
            ret = FormatMessage(  
                FORMAT_MESSAGE_ALLOCATE_BUFFER | // The function will allocate memory for the message.
                FORMAT_MESSAGE_FROM_HMODULE | // Message definition is in a module.
                FORMAT_MESSAGE_IGNORE_INSERTS,  // No inserts used.
                hInst, // Handle to the module containing the definition.
                dwErrorMsgId, // Message identifier.
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language.
                (LPTSTR)&pBuffer, // Buffer to hold the text string.
                ERRMSGBUFFERSIZE, // Smallest size that will be allocated for pBuffer.
                NULL // No inserts.
                );
	} // Not NULL if successfully loaded.

    } // Could be a network error.
    else
    {
        // Unknown message source.
        // Get the message string from the system.
	ret = FormatMessage(  
            FORMAT_MESSAGE_ALLOCATE_BUFFER | // The function will allocate space for pBuffer.
            FORMAT_MESSAGE_FROM_SYSTEM | // System wide message.
            FORMAT_MESSAGE_IGNORE_INSERTS, // No inserts.
            NULL, // Message is not in a module.
            dwErrorMsgId, // Message identifier.
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language.
            (LPTSTR)&pBuffer, // Buffer to hold the text string.
            ERRMSGBUFFERSIZE, // The function will allocate at least this much for pBuffer.
            NULL // No inserts.
            );
    }

    if (ret)
        snprintf(str, str_len, "%s", (LPTSTR)pBuffer );        
    else
        snprintf(str, str_len, "ERRORNUMBER: %ld\n", dwErrorMsgId);
        
    LocalFree (pBuffer);
}


}}

#endif
