// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved


#include "MainWindow.h"
#include "ManagerEventHandler.h"
#include "PriorityComparison.h"
#include "UIAnimationSample.h"

#include <strsafe.h>

CMainWindow::CMainWindow() :
    m_hwnd(NULL),
    m_pD2DFactory(NULL),
    m_pRenderTarget(NULL),
    m_pBackgroundBrush(NULL),
    m_pAnimationManager(NULL),
    m_pAnimationTimer(NULL),
    m_pTransitionLibrary(NULL),
    m_uThumbCount(0),
    m_thumbs(NULL),
    m_pLayoutManager(NULL)
{
}

CMainWindow::~CMainWindow()
{   
    // Layout Manager
    
    delete m_pLayoutManager;

    // Thumbnails
    
    delete [] m_thumbs;

    // Animation

    SafeRelease(&m_pAnimationManager);
    SafeRelease(&m_pAnimationTimer);
    SafeRelease(&m_pTransitionLibrary);

    // D2D

    SafeRelease(&m_pD2DFactory);
    SafeRelease(&m_pRenderTarget);
    SafeRelease(&m_pBackgroundBrush);
}

// Creates the CMainWindow window and initializes 
// device-independent resources

HRESULT CMainWindow::Initialize(
    HINSTANCE hInstance
    )
{
    // Client area dimensions, in inches
    const FLOAT CLIENT_WIDTH = 7.0f;
    const FLOAT CLIENT_HEIGHT = 5.0f; 

    HRESULT hr = CreateDeviceIndependentResources();
    if (SUCCEEDED(hr))
    {
        // Register the window class

        WNDCLASSEX wcex = {sizeof(wcex)};
        wcex.style         = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc   = CMainWindow::WndProc;
        wcex.cbWndExtra    = sizeof(LONG_PTR);
        wcex.hInstance     = hInstance;
        wcex.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
        wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wcex.lpszClassName = L"WAMMainWindow";
        
        RegisterClassEx(&wcex);

        // Because CreateWindow function takes its size in pixels, 
        // obtain the DPI and use it to calculate the window size.

        // The D2D factory returns the current system DPI. This is
        // also the value it will use to create its own windows.

        FLOAT dpiX, dpiY;
        m_pD2DFactory->GetDesktopDpi(
            &dpiX,
            &dpiY
            );

        // Create the CMainWindow window

        m_hwnd = CreateWindow(
            wcex.lpszClassName,
            L"Windows Animation - Priority Comparison Demo",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            static_cast<UINT>(dpiX * CLIENT_WIDTH),
            static_cast<UINT>(dpiY * CLIENT_HEIGHT),
            NULL,
            NULL,
            hInstance,
            this
            );
        hr = m_hwnd ? S_OK : E_FAIL;
        if (SUCCEEDED(hr))
        {
            // Initialize Animation

            hr = InitializeAnimation();
            if (SUCCEEDED(hr))
            {
                // Display the window
                ShowWindow(m_hwnd, SW_SHOWNORMAL);
                UpdateWindow(m_hwnd);
            }
        }
    }

    return hr;
}

// Invalidates the client area for redrawing

HRESULT CMainWindow::Invalidate()
{
    BOOL bResult = InvalidateRect(
        m_hwnd,
        NULL,
        FALSE
        );

    HRESULT hr = bResult ? S_OK : E_FAIL;

    return hr;
}

// Creates and initializes the main animation components

HRESULT CMainWindow::InitializeAnimation()
{
    // Create Animation Manager

    HRESULT hr = CoCreateInstance(
        CLSID_UIAnimationManager,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&m_pAnimationManager)
        );
    if (SUCCEEDED(hr))
    {
        // Create Animation Timer

        hr = CoCreateInstance(
            CLSID_UIAnimationTimer,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_pAnimationTimer)
            );
        if (SUCCEEDED(hr))
        {
            // Create Animation Transition Library

            hr = CoCreateInstance(
                CLSID_UIAnimationTransitionLibrary,
                NULL,
                CLSCTX_INPROC_SERVER,
                IID_PPV_ARGS(&m_pTransitionLibrary)
                );
            if (SUCCEEDED(hr))
            {
                // Create and set the ManagerEventHandler to start updating when animations are scheduled

                IUIAnimationManagerEventHandler *pManagerEventHandler;
                hr = CManagerEventHandler::CreateInstance(
                    this,
                    &pManagerEventHandler
                    );
                if (SUCCEEDED(hr))
                {
                    hr = m_pAnimationManager->SetManagerEventHandler(
                        pManagerEventHandler
                        );
                    pManagerEventHandler->Release();
                    if (SUCCEEDED(hr))
                    {
                        // Create the PriorityComparisons for canceling and trimming storyboards

                        IUIAnimationPriorityComparison *pPriorityComparisonCancel;
                        hr = CCancelPriorityComparison::CreateInstance(
                            &pPriorityComparisonCancel
                            );
                        if (SUCCEEDED(hr))
                        {
                            hr = m_pAnimationManager->SetCancelPriorityComparison(
                                pPriorityComparisonCancel
                                );
                            pPriorityComparisonCancel->Release();
                            if (SUCCEEDED(hr))
                            {
                                IUIAnimationPriorityComparison *pPriorityComparisonTrim;
                                hr = CTrimPriorityComparison::CreateInstance(
                                    &pPriorityComparisonTrim
                                    );
                                if (SUCCEEDED(hr))
                                {
                                    hr = m_pAnimationManager->SetTrimPriorityComparison(
                                        pPriorityComparisonTrim
                                        );
                                    pPriorityComparisonTrim->Release();
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    return hr;
}

// Creates resources which are not bound to any device.
// Their lifetime effectively extends for the duration of the app.
// These resources include the Direct2D factory.

HRESULT CMainWindow::CreateDeviceIndependentResources()
{
    // Create a Direct2D factory
    
    HRESULT hr = D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        &m_pD2DFactory
        );

    return hr;
}

// Creates resources which are bound to a particular Direct3D device.
// It's all centralized here, so the resources can be easily recreated
// in the event of Direct3D device loss (e.g. display change, remoting,
// removal of video card, etc). The resources will only be created
// if necessary.
             
HRESULT CMainWindow::CreateDeviceResources()
{
    HRESULT hr = S_OK;
    
    if (m_pRenderTarget == NULL)
    {
        RECT rc;
        GetClientRect(
            m_hwnd,
            &rc
            );

        D2D1_SIZE_U size = D2D1::SizeU(
            rc.right - rc.left,
            rc.bottom - rc.top
            );
 
        // Create a Direct2D render target

        hr = m_pD2DFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(
                m_hwnd,
                size
                ),
            &m_pRenderTarget
            );
        if (SUCCEEDED(hr))
        {
            // Create a gradient brush for the background

            static const D2D1_GRADIENT_STOP stops[] =
            {
                { 0.0f, { 0.75f, 0.75f, 0.75f, 1.0f } },
                { 1.0f, { 0.25f, 0.25f, 0.25f, 1.0f } },
            };

            ID2D1GradientStopCollection *pGradientStops;
            hr = m_pRenderTarget->CreateGradientStopCollection(
                stops,
                ARRAYSIZE(stops),
                &pGradientStops
                );
            if (SUCCEEDED(hr))
            {
                D2D1_SIZE_F sizeRenderTarget = m_pRenderTarget->GetSize();
                hr = m_pRenderTarget->CreateLinearGradientBrush(
                    D2D1::LinearGradientBrushProperties(
                        D2D1::Point2F(
                            sizeRenderTarget.width * 0.5f,
                            0.0f
                            ),
                        D2D1::Point2F(
                            sizeRenderTarget.width * 0.5f,
                            sizeRenderTarget.height * 0.5f
                            )
                        ),
                    D2D1::BrushProperties(
                        ),
                    pGradientStops,
                    &m_pBackgroundBrush
                    );
                if (SUCCEEDED(hr))
                {
                    // Create a brush for outlining the thumbnails

                    hr = m_pRenderTarget->CreateSolidColorBrush(
                        D2D1::ColorF(
                            D2D1::ColorF::White
                            ),
                        &m_pOutlineBrush
                        );
                    if (SUCCEEDED(hr))
                    {
                        m_pOutlineBrush->SetOpacity(
                            0.5f
                            );
                    }
                }
            }
        }
    }

    return hr;
}
                                                                                             
//  Discards device-specific resources that need to be recreated   
//  when a Direct3D device is lost                                      
                                                              
void CMainWindow::DiscardDeviceResources()
{
    SafeRelease(&m_pRenderTarget);
    SafeRelease(&m_pBackgroundBrush);
    SafeRelease(&m_pOutlineBrush);
}

// Locates images in the Pictures library

HRESULT CMainWindow::FindImages()
{
    HRESULT hr = S_OK;
    
    if (m_thumbs == NULL)
    {
        // Walk the Pictures library

        IShellItem *pShellItemPicturesLibrary;
        hr = SHGetKnownFolderItem(
            FOLDERID_PicturesLibrary,
            KF_FLAG_CREATE,
            NULL,
            IID_PPV_ARGS(&pShellItemPicturesLibrary)
            );
        if (SUCCEEDED(hr))
        {
            INamespaceWalk *pNamespaceWalk;
            hr = CoCreateInstance(
                CLSID_NamespaceWalker,
                NULL,
                CLSCTX_INPROC,
                IID_PPV_ARGS(&pNamespaceWalk)
                );
            if (SUCCEEDED(hr))
            {
                hr = pNamespaceWalk->Walk(
                    pShellItemPicturesLibrary,
                    NSWF_NONE_IMPLIES_ALL,
                    1,
                    NULL
                    );
                if (SUCCEEDED(hr))
                {
                    // Retrieve the array of PIDLs gathered in the walk
                
                    UINT itemCount;
                    PIDLIST_ABSOLUTE *ppidls;
                    hr = pNamespaceWalk->GetIDArrayResult(
                        &itemCount,
                        &ppidls
                        );
                    if (SUCCEEDED(hr))
                    {
                        // Create the uninitialized thumbnails
                        m_thumbs = new CThumbnail[itemCount];                 
                    
                        // Get the bitmap for each item and initialize the corresponding thumbnail object
                    
                        for (UINT i = 0; i < itemCount; i++)
                        {
                            IShellItem *pShellItem;
                            hr = SHCreateItemFromIDList(
                                ppidls[i],
                                IID_PPV_ARGS(&pShellItem)
                                );
                            if (SUCCEEDED(hr))
                            {
                                ID2D1Bitmap *pBitmap;
                                hr = DecodeImageFromThumbCache(
                                    pShellItem,
                                    &pBitmap
                                    );
                                if (SUCCEEDED(hr))
                                {
                                    hr = m_thumbs[m_uThumbCount].Initialize(
                                        pBitmap,
                                        m_pAnimationManager,
                                        m_pRenderTarget->GetSize().width * 0.5,
                                        m_pRenderTarget->GetSize().height * 0.5
                                        );
                                    if (SUCCEEDED(hr))
                                    {
                                        m_uThumbCount++;
                                    }
                                        
                                    pBitmap->Release();
                                }
                                
                                pShellItem->Release();
                            }
                        }
                        
                        // The calling function is responsible for freeing the PIDL array
                        FreeIDListArray(ppidls, itemCount);
                        
                        if (SUCCEEDED(hr))
                        {
                            // Arrange the images when they are first loaded

                            m_pLayoutManager = new CLayoutManager();
                            hr = m_pLayoutManager->Initialize(
                                m_pAnimationManager,
                                m_pAnimationTimer,
                                m_pTransitionLibrary,
                                m_uThumbCount,
                                m_thumbs
                                );
                            if (SUCCEEDED(hr))
                            {
                                hr = m_pLayoutManager->Resize(
                                    m_pRenderTarget->GetSize()
                                    );
                            }
                        }
                    }
                }
            
                pNamespaceWalk->Release();
            }
        
            pShellItemPicturesLibrary->Release();
        }
    }
    
    return hr;
}

// Retrieves a bitmap from the thumbnail cache and converts it to
// a D2D bitmap

HRESULT CMainWindow::DecodeImageFromThumbCache(
    IShellItem *pShellItem,
    ID2D1Bitmap **ppBitmap
    )
{
    const UINT THUMB_SIZE = 256;

    // Read the bitmap from the thumbnail cache

    IThumbnailCache *pThumbCache;
    HRESULT hr = CoCreateInstance(
        CLSID_LocalThumbnailCache,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&pThumbCache)
        );
    if (SUCCEEDED(hr))
    {
        ISharedBitmap *pBitmap;
        hr = pThumbCache->GetThumbnail(
            pShellItem,
            THUMB_SIZE,
            WTS_SCALETOREQUESTEDSIZE,
            &pBitmap,
            NULL,
            NULL
            );
        if (SUCCEEDED(hr))
        {
            HBITMAP hBitmap;
            hr = pBitmap->GetSharedBitmap(
                &hBitmap
                );
            if (SUCCEEDED(hr))
            {
                // Create a WIC bitmap from the shared bitmap

                IWICImagingFactory *pFactory;
                hr = CoCreateInstance(
                    CLSID_WICImagingFactory,
                    NULL,
                    CLSCTX_INPROC_SERVER,
                    IID_PPV_ARGS(&pFactory)
                    );
                if (SUCCEEDED(hr))
                {
                    IWICBitmap *pWICBitmap;
                    hr = pFactory->CreateBitmapFromHBITMAP(
                        hBitmap,
                        NULL,
                        WICBitmapIgnoreAlpha,
                        &pWICBitmap
                        );
                    if (SUCCEEDED(hr))
                    {
                        // Create a D2D bitmap from the WIC bitmap

                        hr = m_pRenderTarget->CreateBitmapFromWicBitmap(
                            pWICBitmap,
                            NULL,
                            ppBitmap
                            );
                        pWICBitmap->Release();
                    }

                    pFactory->Release();
                }
            }
            
            pBitmap->Release();
        }
        
        pThumbCache->Release();
    }

    return hr;
}

// The window message handler

LRESULT CALLBACK CMainWindow::WndProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    const int MESSAGE_PROCESSED = 0;

    if (uMsg == WM_CREATE)
    {
        LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
        CMainWindow *pMainWindow = (CMainWindow *)pcs->lpCreateParams;

        SetWindowLongPtrW(
            hwnd,
            GWLP_USERDATA,
            PtrToUlong(pMainWindow)
            );

        return MESSAGE_PROCESSED;
    }

    CMainWindow *pMainWindow = reinterpret_cast<CMainWindow *>(static_cast<LONG_PTR>(
        GetWindowLongPtrW(
            hwnd,
            GWLP_USERDATA
            )));

    if (pMainWindow != NULL)
    {
        switch (uMsg)
        {
        case WM_SIZE:
            {
                UINT width = LOWORD(lParam);
                UINT height = HIWORD(lParam);
                pMainWindow->OnResize(
                    width,
                    height
                    );
            }
            return MESSAGE_PROCESSED;

        case WM_PAINT:
        case WM_DISPLAYCHANGE:
            {
                PAINTSTRUCT ps;
                BeginPaint(
                    hwnd,
                    &ps
                    );

                pMainWindow->OnPaint(
                    ps.hdc,
                    ps.rcPaint
                    );

                EndPaint(
                    hwnd,
                    &ps
                    );
            }
            return MESSAGE_PROCESSED;

        case WM_KEYDOWN:
            {
                UINT uVirtKey = wParam;
                
                pMainWindow->OnKeyDown(
                    uVirtKey
                    );
            }
            return MESSAGE_PROCESSED;

        case WM_DESTROY:
            {
                pMainWindow->OnDestroy();
            
                PostQuitMessage(
                    0
                    );
            }
            return MESSAGE_PROCESSED;
        }
    }
    
    return DefWindowProc(
        hwnd,
        uMsg,
        wParam,
        lParam
        );
}
                                                        
//  Called whenever the client area needs to be drawn

HRESULT CMainWindow::OnPaint(
    HDC hdc,
    const RECT &rcPaint
    )
{
    // Update the animation manager with the current time

    UI_ANIMATION_SECONDS secondsNow;
    HRESULT hr = m_pAnimationTimer->GetTime(
        &secondsNow
        );
    if (SUCCEEDED(hr))
    {
        hr = m_pAnimationManager->Update(
            secondsNow
            );
        if (SUCCEEDED(hr))
        {
            // Read the values of the animation variables and draw the client area
        
            hr = DrawClientArea();
            if (SUCCEEDED(hr))
            {
                // Continue redrawing the client area as long as there are animations scheduled
                
                UI_ANIMATION_MANAGER_STATUS status;
                hr = m_pAnimationManager->GetStatus(
                    &status
                    );
                if (SUCCEEDED(hr))
                {
                    if (status == UI_ANIMATION_MANAGER_BUSY)
                    {
                        InvalidateRect(
                            m_hwnd,
                            NULL,
                            FALSE
                            );
                    }
                }
            }
        }
    }

    return hr;
}
                                        
// Resizes the render target in response to a change in client area size                        

HRESULT CMainWindow::OnResize(
    UINT width,
    UINT height
    )
{
    HRESULT hr = S_OK;

    if (m_pRenderTarget)
    {
        D2D1_SIZE_U size;
        size.width = width;
        size.height = height;
        hr = m_pRenderTarget->Resize(
            size
            );
        if (SUCCEEDED(hr))
        {
            D2D1_SIZE_F sizeRenderTarget = m_pRenderTarget->GetSize();
            m_pBackgroundBrush->SetStartPoint(
                D2D1::Point2F(
                    sizeRenderTarget.width * 0.5f,
                    0.0f
                    )
                );
            m_pBackgroundBrush->SetEndPoint(
                D2D1::Point2F(
                    sizeRenderTarget.width * 0.5f,
                    sizeRenderTarget.height * 0.5f
                    )
                );

            hr = m_pLayoutManager->Resize(
                m_pRenderTarget->GetSize()
                );
        }
    }

    return hr;
}

// Moves the images in various ways depending on the key pressed

HRESULT CMainWindow::OnKeyDown(
    UINT uVirtKey
    )
{
    HRESULT hr = S_OK;

    switch (uVirtKey)
    {
    case VK_UP:
        hr = m_pLayoutManager->Wave(
            UI_ANIMATION_SLOPE_DECREASING
            );
        break;

    case VK_DOWN:
        hr = m_pLayoutManager->Wave(
            UI_ANIMATION_SLOPE_INCREASING
            );
        break;

    case VK_RIGHT:
        hr = m_pLayoutManager->Next();
        break;
    
    case VK_LEFT:
        hr = m_pLayoutManager->Previous();
        break;

    case VK_SPACE:
        hr = m_pLayoutManager->Arrange();
        break;
    
    default:
        break;
    }

    return hr;
}

// Prepares for window destruction

void CMainWindow::OnDestroy()
{
    if (m_pAnimationManager != NULL)
    {
        // Clear the manager event handler, to ensure that the one that points to this object is released
    
        (void)m_pAnimationManager->SetManagerEventHandler(NULL);
    }
}

// Draws the contents of the client area.
//
// Note that this function will automatically discard device-specific
// resources if the Direct3D device disappears during function
// invocation, and will recreate the resources the next time it's
// invoked.

HRESULT CMainWindow::DrawClientArea()
{
    // Begin drawing the client area

    HRESULT hr = CreateDeviceResources();
    if (SUCCEEDED(hr))
    {
        hr = FindImages();
        if (SUCCEEDED(hr))
        {
            m_pRenderTarget->BeginDraw();

            // Retrieve the size of the render target

            D2D1_SIZE_F sizeRenderTarget = m_pRenderTarget->GetSize();
            
            // Paint the background

            m_pRenderTarget->FillRectangle(
                D2D1::RectF(
                    0.0f,
                    0.0f,
                    sizeRenderTarget.width,
                    sizeRenderTarget.height
                    ),
                m_pBackgroundBrush
                );

            // Paint all the thumbnails

            for (UINT i = 0; i < m_uThumbCount; i++)
            {
                hr = m_thumbs[i].Render(
                    m_pRenderTarget,
                    m_pOutlineBrush
                    );
                if (FAILED(hr))
                {
                    break;
                }
            }

            // Can only return one failure HRESULT
            HRESULT hrEndDraw = m_pRenderTarget->EndDraw();
            if (SUCCEEDED(hr))
            {
                hr = hrEndDraw;
            }
        }

        if (hr == D2DERR_RECREATE_TARGET)
        {
            DiscardDeviceResources();
        }
    }
    
    return hr;
}
