//              This is a multi-threaded app with two primary threads.  One
//              sits in the message loop, waiting specifically for WM_PAINT
//              messages which are generated by the other thread, on which
//              the actual unit test runs.
//
//              When the window thread receives an update message, it takes
//              a snapshot of the unit test state (protected by a mutex),
//              and redraws the screen accordingly.
//
//              When the unit test thread wants a resource to be drawn in
//              the main window, it places the handle to that resource (for
//              example, an HMETAFILEPICT) in the ctest object associated
//              with the window thread, then fires a screen update.  In
//              doing so, ownership of the resource is transfered from the
//              unit test thread to the window thread.  By using this
//              mechanism, the window thread can draw the resource at its
//              leisure, while the unit test proceeds.  The onus is on
//              the window thread to clean up any resources which have
//              been deposited in its care when it exists.
//
//              If the window thread receives a WM_CLOSE message, it must
//              first check to see that the unit test thread has completed.
//              If not, it spins in a RETRY/CANCEL loop until the unit test
//              thread has completed, or until the user selects CANCEL, at
//              which point execution proceeds, ignoring the WM_CLOSE.
//
//                  "OVER-ENGINEERED, AND BUILT TO STAY THAT WAY" (tm)
//


#include "headers.hxx"
#pragma hdrstop

CCacheTestApp ctest;    // Application instance
TestInstance  inst;     // Test instance

//
// Prototype for the entry-point of the unit test thread
//

unsigned long __stdcall testmain(void *);

//+---------------------------------------------------------------------------
//
//  Function:   WinMain
//
//  Synopsis:   windows entry point
//
//  Arguments:  [hInst]       --
//              [hPrevInst]   --
//              [lpszCmdLine] --
//              [nCmdShow]    --
//
//  Returns:    int
//
//  History:    05-Sep-94  davepl   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
     MSG message;

     //
     // Initialize the application
     //

     if (SUCCEEDED(ctest.Initialize(hInst, hPrevInst, lpszCmdLine)))
     {
	  //
	  // Show and update the window
	  //

	  ShowWindow(ctest.Window(), nCmdShow);
	  UpdateWindow(ctest.Window());

	  //
	  // The main message loop
	  //

	  while (GetMessage(&message, NULL, NULL, NULL))
	  {
	       TranslateMessage(&message);
	       DispatchMessage(&message);
	  }
     }
     else
     {
	  return(0);
     }

     return(message.wParam);
}

//+---------------------------------------------------------------------------
//
//  Member:     CCacheTestApp::CCacheTestApp
//
//  Synopsis:   Constructor
//
//  Arguments:  (none)
//
//  Returns:    nothing
//
//  History:    05-Sep-94   Davepl   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

CCacheTestApp::CCacheTestApp ()
{

}

//+---------------------------------------------------------------------------
//
//  Member:     CCacheTestApp::Initialize
//
//  Synopsis:   initializes the application
//
//  Arguments:  [hInst]       -- current instance
//              [hPrevInst]   -- previous instance
//              [lpszCmdLine] -- command line parameters
//
//  Returns:    HRESULT
//
//  History:    05-Sep-94   Davepl   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

HRESULT CCacheTestApp::Initialize (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine)
{
     HRESULT      hr = S_OK;

     //
     // Register the window class
     //

     if (hPrevInst == NULL)
     {
	  WNDCLASS wndclass;

	  wndclass.style         = 0;
	  wndclass.lpfnWndProc   = CacheTestAppWndProc;

	  wndclass.cbClsExtra    = 0;
	  wndclass.cbWndExtra    = 0;

	  wndclass.hInstance     = hInst;
	  wndclass.hIcon         = LoadIcon(hInst, IDI_EXCLAMATION);
	  wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
	  wndclass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
	  wndclass.lpszMenuName  = NULL;
	  wndclass.lpszClassName = CTESTAPPCLASS;

	  if (RegisterClass(&wndclass) == 0)
	  {
	       hr = HRESULT_FROM_WIN32(GetLastError());
	  }
     }

     //
     // Create the mutex
     //

     m_hMutex = CreateMutex(NULL, FALSE, NULL);
     if (NULL == m_hMutex)
     {
	hr = HRESULT_FROM_WIN32(GetLastError());
     }

     //
     // Create the window
     //

     if (SUCCEEDED(hr))
     {
	  if ((m_hWnd = CreateWindowEx(0L,
				       CTESTAPPCLASS,
				       CTESTAPPTITLE,
				       WS_OVERLAPPEDWINDOW,
				       CW_USEDEFAULT,
				       0,
				       CW_USEDEFAULT,
				       0,
				       NULL,
				       NULL,
				       hInst,
				       NULL)) == NULL)
	  {
	       hr = HRESULT_FROM_WIN32(GetLastError());
	  }
     }

     return(hr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CCacheTestApp::~CCacheTestApp
//
//  Synopsis:   Destructor
//
//  Arguments:  (none)
//
//  Returns:    nothing
//
//  History:    05-Sep-94   Davepl   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

CCacheTestApp::~CCacheTestApp ()
{

}

//+---------------------------------------------------------------------------
//
//  Function:   CacheTestAppWndProc
//
//  Synopsis:   window procedure
//
//  Arguments:  [hWnd]    -- window
//              [message] -- message id
//              [wParam]  -- parameter
//              [lParam]  -- parameter
//
//  Returns:    LRESULT
//
//  History:    05-Sep-94   Davepl   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
LRESULT FAR PASCAL CacheTestAppWndProc (HWND hWnd,
					UINT message,
					WPARAM wParam,
					LPARAM lParam)
{
     //
     // Process the messages
     //

     switch (message)
     {
     case WM_CREATE:

	//
	// The unit test window is opening.  Create another thread
	// on which the unit test itself runs, while this thread
	// continues to spin, waiting for redraws, close, and so
	// on...
	//

	ctest.m_hTest = CreateThread(NULL,
				     0,
				     testmain,
				     NULL,
				     0,
				     &ctest.m_dwThreadID);

	if (NULL == ctest.m_hTest)
	{
	    mprintf("Unable to begin unit test\n");
	    PostQuitMessage(0);
	}

	break;


     case WM_PAINT:
	  {
	       PAINTSTRUCT ps;
	       HDC         hDC;

	       //
	       // Get the DC for painting
	       //

	       hDC = BeginPaint(hWnd, &ps);
	       if (hDC)
	       {
		    //
		    // Draw the metafile
		    //

		    inst.Draw(hDC);

		    EndPaint(hWnd, &ps);
	       }
	  }
	  break;

     case WM_SIZE:

	  //
	  // Invalidate the rectangle
	  //

	  InvalidateRect(hWnd, NULL, TRUE);
	  return DefWindowProc(hWnd, message, wParam, lParam);
	  break;

     case WM_CLOSE:

	{
	    //
	    // The user has tried to exit the main program.  Before we
	    // can shut down, we must wait until the child thread has
	    // completed.  We allow the user to keep retrying on the
	    // thread, or to "cancel" and wait until later.  We do not
	    // provide the option of terminating the child thread while
	    // it is still busy.
	    //

	    DWORD dwStatus = 0;

	    if (FALSE == GetExitCodeThread(ctest.m_hTest, &dwStatus))
	    {
		mprintf("Could not get thread information!");
		break;
	    }
	    else
	    {
		INT response = IDRETRY;

		while (STILL_ACTIVE == dwStatus)
		{
		    response = MessageBox(ctest.Window(),
			       "The child thread has not yet completed.",
			       "Cannot Shutdown",
			       MB_RETRYCANCEL);

		    if (IDCANCEL == response)
		    {
			break;
		    }
		
		}
	    }
	
	    //
	    // Destroy the window if the child has gone away
	    //

	    if (STILL_ACTIVE != dwStatus)
	    {
		DestroyWindow(hWnd);
	    }

	    break;
	}

     case WM_DESTROY:
	
	  PostQuitMessage(0);
	  break;


     default:
	  return DefWindowProc(hWnd, message, wParam, lParam);
     }

     return NULL;
}

//+----------------------------------------------------------------------------
//
//      Member:
//
//      Synopsis:
//
//      Arguments:
//
//      Returns:        HRESULT
//
//      Notes:
//
//      History:        23-Aug-94  Davepl       Created
//
//-----------------------------------------------------------------------------

unsigned long __stdcall testmain(void *)
{
    HRESULT hr;

    hr = inst.CreateAndInit( OLESTR("mystg") );

    if (S_OK != hr)
    {
	mprintf("Cache Unit Test Failed [CreateAndInit] hr = %x\n", hr);
	goto exit;
    }

    hr = inst.EnumeratorTest();
    if (S_OK != hr)
    {
	mprintf("Cache Unit Test Failed [EnumeratorTest] hr = %x\n", hr);
	goto exit;
    }

    hr = inst.MultiCache(50);
    if (S_OK != hr)
    {
	mprintf("Cache Unit Test Failed [MultiCache] hr = %x\n", hr);
	goto exit;
    }

    hr = inst.CacheDataTest("TIGER.BMP", "TIGERNPH.WMF");
    if (S_OK != hr)
    {
	mprintf("Cache Unit Test Failed [CacheDataTest] hr = %x\n", hr);
	goto exit;
    }

exit:

    PostMessage(ctest.Window(), WM_CLOSE, (WPARAM) hr, 0);
    return (ULONG) hr;

}


//+----------------------------------------------------------------------------
//
//      Member:         TestInstance::TestInstance
//
//      Synopsis:       Constructor
//
//      Arguments:
//
//      Returns:
//
//      Notes:
//
//      History:        23-Aug-94  Davepl       Created
//
//-----------------------------------------------------------------------------

TestInstance::TestInstance()
{

    m_pStorage        = NULL;
    m_pPersistStorage = NULL;
    m_pOleCache       = NULL;
    m_pOleCache2      = NULL;
    m_pDataObject     = NULL;
    m_pViewObject     = NULL;
    m_State           = TEST_STARTING;
}

TestInstance::~TestInstance()
{
    //
    // Release our interface pointers
    //

    if (m_pDataObject)
    {
	m_pDataObject->Release();
    }

    if (m_pViewObject)
    {
	m_pViewObject->Release();
    }

    if (m_pPersistStorage)
    {
	m_pPersistStorage->Release();
    }

    if (m_pOleCache2)
    {
	m_pOleCache2->Release();
    }

    if (m_pOleCache)
    {
	m_pOleCache->Release();
    }

    if (m_pStorage)
    {
	m_pStorage->Release();
    }
}

//+----------------------------------------------------------------------------
//
//      Member:         TestInstance::CreateAndInit
//
//      Synopsis:       Creates a cache and sets up internal interface ptrs
//
//      Arguments:      (none)
//
//      Returns:        HRESULT
//
//      Notes:
//
//      History:        23-Aug-94  Davepl       Created
//
//-----------------------------------------------------------------------------

HRESULT TestInstance::CreateAndInit(LPOLESTR lpwszStgName)
{
    HRESULT hr;

    TraceLog Log(this, "TestInstance::CreateAndInit", GS_CACHE, VB_MINIMAL);
    Log.OnEntry (" ( %p ) \n", lpwszStgName);
    Log.OnExit  (" ( %X ) \n", &hr);
	
    //
    // Create the storage on which we will instantiate our cache
    //

    // BUGBUG use correct strcpy fn

    wcscpy(m_wszStorage, lpwszStgName);

    hr = StgCreateDocfile(lpwszStgName,
			  STGM_DIRECT |
			  STGM_READWRITE |
			  STGM_SHARE_EXCLUSIVE |
			  STGM_CREATE,
			  0,
			  &m_pStorage);

    //
    // Create a Data Cache on our IStorage
    //

    if (S_OK == hr)
    {
	hr = CreateDataCache(NULL,
			 CLSID_NULL,
			 IID_IPersistStorage,
			 (void **)&m_pPersistStorage);
    }

    if (S_OK == hr)
    {
	hr = m_pPersistStorage->InitNew(m_pStorage);
    }

    //
    // Get an IOleCache interface pointer to the cache
    //

    if (S_OK == hr)
    {
	hr = m_pPersistStorage->QueryInterface(IID_IOleCache,
					       (void **) &m_pOleCache);
    }

    //
    // Get an IOleCache2 interface pointer to the cache
    //

    if (S_OK == hr)
    {
	hr = m_pPersistStorage->QueryInterface(IID_IOleCache2,
					       (void **) &m_pOleCache2);
    }

    //
    // Get an IViewObject interface pointer to the cache
    //

    if (S_OK == hr)
    {
	hr = m_pPersistStorage->QueryInterface(IID_IViewObject,
					       (void **) &m_pViewObject);
    }

    //
    // Get an IDataObject interface pointer to the cache
    //

    if (S_OK == hr)
    {
	hr = m_pPersistStorage->QueryInterface(IID_IDataObject,
					       (void **) &m_pDataObject);
    }

    return hr;
}

//+----------------------------------------------------------------------------
//
//      Member:         TestInstance::SaveAndReload
//
//      Synopsis:       Saves the cache to its storage and reloads it
//                      right back.
//
//      Arguments:      (none)
//
//      Returns:        HRESULT
//
//      Notes:          Once saved, the behavior of DiscardCache will
//                      change, since each node present at the time of
//                      save will have a stream from which it can demand
//                      load its data.
//
//                      Since each interface pointer is released and
//                      reaquired, the pointer values will (likely) change
//                      during this call; hence, so _not_ cache the pointers
//                      locally around this call.
//
//      History:        23-Aug-94  Davepl       Created
//
//-----------------------------------------------------------------------------

HRESULT TestInstance::SaveAndReload()
{
    HRESULT hr;

    TraceLog Log(NULL, "TestInstance::SaveAndReload", GS_CACHE, VB_MINIMAL);
    Log.OnEntry ();
    Log.OnExit  (" ( %X )\n", &hr);

    SetCurrentState(SAVE_AND_RELOAD);

    hr = m_pPersistStorage->Save(m_pStorage, TRUE);

    if (S_OK == hr)
    {
       hr = m_pPersistStorage->SaveCompleted(NULL);
    }

    // Release our hold on the storage and the cache

    if (S_OK == hr)
    {
	m_pViewObject->Release();
	m_pViewObject = NULL;
	m_pDataObject->Release();
	m_pDataObject = NULL;

	m_pStorage->Release();
	m_pStorage = NULL;

	m_pPersistStorage->Release();
	m_pPersistStorage = NULL;

	m_pOleCache2->Release();
	m_pOleCache2 = NULL;

	m_pOleCache->Release();
	m_pOleCache = NULL;


	//
	// Reload the cache and QI to get our interfaces back
	//

	hr = StgOpenStorage(m_wszStorage,
			NULL,
			STGM_DIRECT |
			STGM_READWRITE |
			STGM_SHARE_EXCLUSIVE,
			NULL,
			0,
			&m_pStorage);

	//
	// Create a Data Cache on our IStorage
	//

	if (S_OK == hr)
	{
	    hr = CreateDataCache(NULL,
				 CLSID_NULL,
				 IID_IPersistStorage,
				(void **)&m_pPersistStorage);
	}
	
	if (S_OK == hr)
	{
	     hr = m_pPersistStorage->Load(m_pStorage);
	}

	//
	// Get an IOleCache interface pointer to the cache
	//

	if (S_OK == hr)
	{
	    hr = m_pPersistStorage->QueryInterface(IID_IOleCache,
					       (void **) &m_pOleCache);
	}
	
	//
	// Get an IOleCache2 interface pointer to the cache
	//

	if (S_OK == hr)
	{
	    hr = m_pPersistStorage->QueryInterface(IID_IOleCache2,
					       (void **) &m_pOleCache2);
	}

	//
	// Get an IViewObject interface pointer to the cache
	//

	if (S_OK == hr)
	{
	    hr = m_pPersistStorage->QueryInterface(IID_IViewObject,
					       (void **) &m_pViewObject);
	}

	//
	// Get an IDataObject interface pointer to the cache
	//

	if (S_OK == hr)
	{
	    hr = m_pPersistStorage->QueryInterface(IID_IDataObject,
					       (void **) &m_pDataObject);
	}
    }

    return hr;
}


//+----------------------------------------------------------------------------
//
//      Member:         TestInstance::CacheDataTest
//
//      Synopsis:       Checks the cache for data integrity
//
//      Arguments:      lpszBMPFileName - Name of .BMP file to use for test
//                      lpszWMFFileName - Name of .WMF file to use for test
//
//      Returns:        HRESULT
//
//      Notes:
//
//      History:        04-Sep-94  Davepl       Created
//
//-----------------------------------------------------------------------------

HRESULT TestInstance::CacheDataTest(char * lpszBMPFileName, char * lpszWMFFileName)
{
    HRESULT hr = S_OK;

    TraceLog Log(NULL, "TestInstance::CacheDataTest", GS_CACHE, VB_MINIMAL);
    Log.OnEntry (" ( BMP=%s, WMF=%s  )\n", lpszBMPFileName, lpszWMFFileName);
    Log.OnExit  (" ( %X )\n", &hr);

    SetCurrentState(DATA_TEST);

    CBitmapFile bmpFile;
    HGLOBAL     hDIB = NULL;
	
    //
    // Allocate an hglobal to hold our metafilepict structure
    //

    HGLOBAL hMFPICT = GlobalAlloc(GMEM_FIXED, sizeof(METAFILEPICT));
    if (NULL == hMFPICT)
    {
	hr = HRESULT_FROM_WIN32(GetLastError());
    }
    METAFILEPICT * pMFPICT = (METAFILEPICT *) hMFPICT;

    //
    // Load the bitmap from disk
    //

    if (S_OK == hr)
    {
	hr = bmpFile.LoadBitmapFile(lpszBMPFileName);
    }

    //
    // Create a DIB on an HGLOBAL from the bitmap
    //

    if (S_OK == hr)
    {
	hr = bmpFile.CreateDIBInHGlobal(&hDIB);
    }

    //
    // Add DIB and MF nodes to the cache
    //

    DWORD dwDIBCon;
    DWORD dwMFCon;

    if (S_OK == hr)
    {
	hr = AddDIBCacheNode(&dwDIBCon);
    }

    if (S_OK == hr)
    {
	hr = AddMFCacheNode(&dwMFCon);
    }

    //
    // Load the metafile from disk, then set up the
    // METAFILEPICT structure
    //

    if (S_OK == hr)
    {
	pMFPICT->hMF = GetMetaFileA(lpszWMFFileName);
	if (NULL == pMFPICT->hMF)
	{
	    hr = HRESULT_FROM_WIN32(GetLastError());
	}
	else
	{
	    //
	    // We deem the metafile to have the same extents
	    // as the the DIB.  This is completely arbitrary,
	    // but might aid in tracking down extents problems.
	    // After all, we have to pick _some_ value, so it
	    // might as well be a useful one...
	    //

	    pMFPICT->xExt = ConvWidthInPelsToLHM(NULL, bmpFile.GetDIBHeight());
	    pMFPICT->yExt = ConvHeightInPelsToLHM(NULL, bmpFile.GetDIBWidth());
	    pMFPICT->mm   = MM_ANISOTROPIC;
	}
    }

    //
    // Place the nodes in the cache.  We keep ownership of the handles,
    // which will force the cache to duplicate it.  We can then compare
    // our original with whatever we later get back from the cache.
    //

    FORMATETC fetcDIB =
		     {
			CF_DIB,
			NULL,
			DVASPECT_CONTENT,
			DEF_LINDEX,
			TYMED_HGLOBAL
		     };

    STGMEDIUM stgmDIB;

    FORMATETC fetcMF =
		     {
			CF_METAFILEPICT,
			NULL,
			DVASPECT_CONTENT,
			DEF_LINDEX,
			TYMED_MFPICT
		     };

    STGMEDIUM stgmMF;



    if (S_OK == hr)
    {
	stgmDIB.tymed   = TYMED_HGLOBAL;
	stgmDIB.hGlobal = hDIB;

	hr = m_pOleCache->SetData(&fetcDIB, &stgmDIB, FALSE);
    }

    if (S_OK == hr)
    {
	stgmMF.tymed = TYMED_MFPICT,
	stgmMF.hMetaFilePict = hMFPICT;

	hr = m_pOleCache->SetData(&fetcMF, &stgmMF, FALSE);
    }

    //
    // If we were able to place the data in the cache, check
    // to make sure whatever is in the cache matches our
    // original.
    //

    if (S_OK == hr)
    {
	hr = CompareDIB(hDIB);
	
	if (S_OK == hr)
	{
	   hr = CompareMF(hMFPICT);
	}
    }

    //
    // Save and Reload the cache to test persistance
    //

    if (S_OK == hr)
    {
	hr = SaveAndReload();
    }

    if (S_OK == hr)
    {
	SetCurrentState(DATA_TEST);
    }

    //
    // Compare the data again
    //

    if (S_OK == hr)
    {
	hr = CompareDIB(hDIB);
	
	if (S_OK == hr)
	{
	   hr = CompareMF(hMFPICT);
	}
    }

    //
    // Discard the cache.
    //

    if (S_OK == hr)
    {
	hr = m_pOleCache2->DiscardCache(DISCARDCACHE_NOSAVE);
    }

    //
    // Now compare again against the current presentations,
    // which would have to be demand-loaded after the discard.
    //
	
    if (S_OK == hr)
    {
	hr = CompareDIB(hDIB);
	
	if (S_OK == hr)
	{
	   hr = CompareMF(hMFPICT);
	}
    }


    //
    // Try to draw the cache's best presentation (which should
    // be metafile at this point) into a metafile DC which we
    // will then hand off to the window thread for drawing.
    //

    if (S_OK == hr)
    {
	hr = DrawCacheToMetaFilePict(&ctest.m_hMFP, FALSE);
	
	if (S_OK == hr)
	{
	    SetCurrentState(DRAW_METAFILE_NOW);
	}
    }

    //
    // Now draw the metafile tiled 4 times into the display
    // metafile, and hand it off...
    //

    if (S_OK == hr)
    {
	hr = DrawCacheToMetaFilePict(&ctest.m_hMFPTILED, TRUE);
	
	if (S_OK == hr)
	{
	    SetCurrentState(DRAW_METAFILETILED_NOW);
	}
    }

    //
    // Uncache the metafile node, which will leave the DIB node
    // as the best (and only) node left for drawing
    //

    if (S_OK == hr)
    {
	hr = UncacheFormat(CF_METAFILEPICT);
    }

    //
    // Now draw the DIB into a metafile and hand that off
    // to the window thread for drawing
    //

    if (S_OK == hr)
    {
	hr = DrawCacheToMetaFilePict(&ctest.m_hMFPDIB, FALSE);
	
	if (S_OK == hr)
	{
	    SetCurrentState(DRAW_DIB_NOW);
	}
    }

    //
    // Now draw the DIB again, this time tiled into the mf
    //
																	
    if (S_OK == hr)
    {
	hr = DrawCacheToMetaFilePict(&ctest.m_hMFPDIBTILED, TRUE);
	
	if (S_OK == hr)
	{
	    SetCurrentState(DRAW_DIBTILED_NOW);
	}
    }

    //
    // Cleanup our local DIB
    //

    if (hDIB)
    {
	GlobalFree(hDIB);
    }

    //
    // Cleaup our local metafile
    //

    if (pMFPICT)
    {
	if (pMFPICT->hMF)
	{
	    if (FALSE == DeleteMetaFile(pMFPICT->hMF))
	    {
		hr = HRESULT_FROM_WIN32(GetLastError());
	    }
	}

	GlobalFree(hMFPICT);
    }

    return hr;
}

HRESULT TestInstance::CompareDIB(HGLOBAL hDIB)
{
    return S_OK;
}

HRESULT TestInstance::CompareMF(HMETAFILEPICT hMFPICT)
{
    return S_OK;
}


//+----------------------------------------------------------------------------
//
//      Member:         TestInstance::DrawCacheToMetaFilePict
//
//      Synopsis:       Draws the cache's current best presentation to
//                      a metafile, contained in a metafilepict structure,
//                      which is allocated off of the hGlobal pointer passed
//                      in by the caller
//
//      Arguments:      [phGlobal] - The ptr to the hglobal to allocate on
//                      [fTile]    - If true, the pres is tiled into the mf
//
//      Returns:        HRESULT
//
//      Notes:
//
//      History:        06-Sep-94  Davepl       Created
//
//-----------------------------------------------------------------------------

HRESULT TestInstance::DrawCacheToMetaFilePict(HGLOBAL *phGlobal, BOOL fTile)
{
    HRESULT hr = S_OK;

    TraceLog Log(NULL, "TestInstance::DrawCacheToMetaFilePict", GS_CACHE, VB_MINIMAL);
    Log.OnEntry (" ( %p, %d  )\n", phGlobal, fTile);
    Log.OnExit  (" ( %X )\n", &hr);

    //
    // Create a metafile, and have the cache draw its metafile
    // into _our_ metafile.
    //

    //
    // First, set up the METAFILEPICT structure.
    // Since ANISOTROPIC mode allows arbitrary scaling extents, we
    // pick 1000 as a nice arbitrary size.
    //
    //
	
    METAFILEPICT *pmfp = NULL;
    if (S_OK == hr)
    {
	*phGlobal = GlobalAlloc(GMEM_FIXED, sizeof(METAFILEPICT));
	if (NULL == *phGlobal)
	{
	    hr = HRESULT_FROM_WIN32(GetLastError());
	}
	else
	{
	    pmfp = (METAFILEPICT *) GlobalLock(*phGlobal);
	    if (NULL == pmfp)
	    {
		GlobalFree(*phGlobal);
		*phGlobal = NULL;
		hr = HRESULT_FROM_WIN32(GetLastError());
	    }
	    else
	    {
		pmfp->xExt = 1000;
		pmfp->yExt = 1000;
		pmfp->mm   = MM_ANISOTROPIC;
	    }
	}
    }

    //
    // Now create the metafile within the METAFILEPICT structure,
    // and ask the cache to draw to it.
    //

    HDC mfdc;
    if (S_OK == hr)
    {
	mfdc = CreateMetaFile(NULL);
	if (NULL == mfdc)
	{
	    hr = HRESULT_FROM_WIN32(GetLastError());
	    GlobalUnlock(*phGlobal);
	    GlobalFree(*phGlobal);
	    *phGlobal = NULL;
	}
    }

    //
    // If we are not tiling the metafile, we draw it exactly once,
    // scaled to fit the entire output metafile
    //

    if (S_OK == hr  && FALSE == fTile)
    {
	RECTL rcBounds  = {0, 0, 1000, 1000};
	RECTL rcWBounds = {0, 0, 1000, 1000};

	SetMapMode(mfdc, MM_ANISOTROPIC);
	SetWindowExtEx(mfdc, 1000, 1000, NULL);
	SetWindowOrgEx(mfdc, 0, 0, NULL);

	hr = m_pViewObject->Draw(DVASPECT_CONTENT, // Aspect
				 DEF_LINDEX,       // LIndex
				 NULL,             // pvAspect
				 NULL,             // ptd
				 NULL,             // hicTargetDev
				 mfdc,             // hdc to draw to
				 &rcBounds,        // rectange to draw to
				 &rcWBounds,       // bounds of our mfdc
				 NULL,             // callback fn
				 0);               // continue param
	
    }

    //
    // If we are tiling the metafile (which tests the ability of
    // the cache to offset and scale the presentation to a rect within
    // a larger metafile rect), we draw it once in each of the four
    // corners
    //

    if (S_OK == hr && TRUE == fTile)
    {
	RECTL rcBounds;
	RECTL rcWBounds = {0, 0, 1000, 1000};

	SetMapMode(mfdc, MM_ANISOTROPIC);
	SetWindowExtEx(mfdc, 1000, 1000, NULL);
	SetWindowOrgEx(mfdc, 0, 0, NULL);

	for (int a=0; a < 4 && S_OK == hr; a++)
	{
	    switch(a)
	    {
		case 0:         // Upper left hand tile

		    rcBounds.left   = 0;
		    rcBounds.top    = 0;
		    rcBounds.right  = 500;
		    rcBounds.bottom = 500;
		    break;

		case 1:         // Upper right hand tile

		    rcBounds.left   = 500;
		    rcBounds.top    = 0;
		    rcBounds.right  = 1000;
		    rcBounds.bottom = 500;
		    break;

		case 2:         // Lower left hand tile

		    rcBounds.left   = 0;
		    rcBounds.top    = 500;
		    rcBounds.right  = 500;
		    rcBounds.bottom = 1000;
		    break;

		case 3:         // Lower right hand tile

		    rcBounds.left   = 500;
		    rcBounds.top    = 500;
		    rcBounds.right  = 1000;
		    rcBounds.bottom = 1000;
		    break;
	    }
	
	    hr = m_pViewObject->Draw(DVASPECT_CONTENT, // Aspect
				     DEF_LINDEX,       // LIndex
				     NULL,             // pvAspect
				     NULL,             // ptd
				     NULL,             // hicTargetDev
				     mfdc,             // hdc to draw to
				     &rcBounds,        // rectange to draw to
				     &rcWBounds,       // bounds of our mfdc
				     NULL,             // callback fn
				     0);               // continue param
	}
    }
			
    //
    // If the draw failed, clean up the metafile DC now
    //
			
    if (S_OK != hr)
    {
	GlobalUnlock(*phGlobal);
	GlobalFree(*phGlobal);

	HMETAFILE temp = CloseMetaFile(mfdc);
	if (temp)
	{
	    DeleteMetaFile(temp);
	}
    }

    //
    // Finish up the metafile and prepare to return it to the caller
    //

    if (S_OK == hr)
    {
	pmfp->hMF = CloseMetaFile(mfdc);

	if (pmfp->hMF)
	{
	    GlobalUnlock(*phGlobal);
	}
	else
	{
	    hr = HRESULT_FROM_WIN32(GetLastError());
	    GlobalUnlock(*phGlobal);
	    GlobalFree(*phGlobal);
	    *phGlobal = NULL;
	}
    }

    return hr;
}

//+----------------------------------------------------------------------------
//
//      Member:         TestInstance::GetCurrentState
//
//      Synopsis:       Returns the state of the unit test (for drawing)
//
//      Arguments:      (none)
//
//      Returns:        HRESULT
//
//      Notes:
//
//      History:        04-Sep-94  Davepl       Created
//
//-----------------------------------------------------------------------------

TEST_STATE TestInstance::GetCurrentState()
{
    //
    // In order to avoid race conditions, we have a mutex around the
    // current state of the unit test (required because this member
    // function will be running on the window's thread, not the current
    // test instance thread.)
    //

    DWORD dwResult = WaitForSingleObject(ctest.Mutex(), INFINITE);
    if (WAIT_FAILED == dwResult)
    {
	return INVALID_STATE;
    }

    TEST_STATE tsSnapshot = m_State;

    ReleaseMutex(ctest.Mutex());

    return tsSnapshot;
}

//+----------------------------------------------------------------------------
//
//      Member:         TestInstance::SetCurrentState
//
//      Synopsis:       Sets the current (drawing) state of the unit test
//
//      Arguments:      [state] - the state to set
//
//      Returns:        HRESULT
//
//      Notes:
//
//      History:        04-Sep-94  Davepl       Created
//
//-----------------------------------------------------------------------------

void TestInstance::SetCurrentState(TEST_STATE state)
{
    //
    // In order to avoid race conditions, we have a mutex around the
    // current state of the unit test (required because this member
    // function will be running on the window's thread, not the current
    // test instance thread.)
    //

    DWORD dwResult = WaitForSingleObject(ctest.Mutex(), INFINITE);
    if (WAIT_FAILED == dwResult)
    {
	return;
    }

    m_State = state;

    ReleaseMutex(ctest.Mutex());

    //
    // Invalid the main window so it will redraw itself with the new
    // state of the test.
    //

    InvalidateRgn(ctest.Window(), NULL, FALSE);
    UpdateWindow(ctest.Window());
	
}

//+----------------------------------------------------------------------------
//
//      Member:         TestInstance::Draw
//
//      Synopsis:       Draws the current state of the unit test
//
//      Arguments:      [hdc]   - The DC to draw to
//
//      Returns:        HRESULT
//
//      Notes:          The DC is supplied, but the main window is assumed
//
//      History:        04-Sep-94  Davepl       Created
//
//-----------------------------------------------------------------------------

static char szStarting[]      = "Test is starting...";
static char szInvalid[]       = "The state of the test has become invalid...";
static char szEnumerator[]    = "Testing the cache enumerator...";
static char szSaveReload[]    = "Saving and reloading the cache and its data...";
static char szDataTest[]      = "Testing data integrity within the cache...";
static char szMulti[]         = "Testing a large number of simultaneous cache nodes...";
static char szMetafile[]      = "MF -> MF";
static char szMetafileTiled[] = "MF -> MF (Tiled)";
static char szDib[]           = "";     // Dib contains its own title

void TestInstance::Draw(HDC hdc)
{
    //
    // Retrieve the current state of the unit test
    //

    TEST_STATE tsState = GetCurrentState();

    //
    // Clear the window
    //

    RECT rect;
    if (TRUE == GetClientRect(ctest.Window(), &rect))
    {
	FillRect(hdc, &rect, (HBRUSH) GetStockObject(LTGRAY_BRUSH));
    }

    //
    // Draw the current state
    //

    int iBackMode = SetBkMode(hdc, TRANSPARENT);

    switch(tsState)
    {
	case TEST_STARTING:

	    TextOut(hdc, 10, 10, szStarting, strlen(szStarting));
	    break;

	case TESTING_ENUMERATOR:
	
	    TextOut(hdc, 10, 10, szEnumerator, strlen(szEnumerator));
	    break;

	case SAVE_AND_RELOAD:

	    TextOut(hdc, 10, 10, szSaveReload, strlen(szSaveReload));
	    break;

	case DATA_TEST:

	    TextOut(hdc, 10, 10, szDataTest, strlen(szDataTest));
	    break;

	case MULTI_CACHE:

	    TextOut(hdc, 10, 10, szMulti, strlen(szMulti));
	    break;

	case DRAW_METAFILE_NOW:
	case DRAW_METAFILETILED_NOW:
	case DRAW_DIB_NOW:
	case DRAW_DIBTILED_NOW:
	{
	    // We know now that we have to draw a metafile, so
	    // determine which of the metafiles we should be drawing,
	    // and set a handle (so we can reuse the draw code) and
	    // the description text appropriately.

	    HGLOBAL hMFP;
	    char * szDesc;
	
	    if (DRAW_METAFILE_NOW == tsState)
	    {
		hMFP = ctest.m_hMFP;
		szDesc = szMetafile;
	    }
	    else if (DRAW_METAFILETILED_NOW == tsState)
	    {
		hMFP = ctest.m_hMFPTILED;
		szDesc = szMetafileTiled;
	    }
	    else if (DRAW_DIB_NOW == tsState)
	    {
		hMFP = ctest.m_hMFPDIB;
		szDesc = szDib;
	    }
	    else if (DRAW_DIBTILED_NOW == tsState)
	    {
		hMFP = ctest.m_hMFPDIBTILED;
		szDesc = szDib;
	    }

	    TextOut(hdc, 10, 10, szDesc, strlen(szDesc));
			
	    //
	    // Now actually draw the metafile to our main window
	    //
		
	    if (hMFP)
	    {
		METAFILEPICT *pMFP = (METAFILEPICT *) GlobalLock(hMFP);
		if (NULL == pMFP)
		{
		    mprintf("Unable to lock metafile handle");
		    break;
		}

		int save = SaveDC(hdc);

		SetMapMode(hdc, pMFP->mm);
		SetWindowExtEx(hdc, pMFP->xExt, pMFP->yExt, NULL);
		
		RECT client;
		GetClientRect(ctest.Window(), &client);

		SetViewportExtEx(hdc, client.right, client.bottom, NULL);
		SetWindowOrgEx(hdc, 0, 0, NULL);
		SetViewportOrgEx(hdc, client.left, client.top, NULL);
		
		PlayMetaFile(hdc, pMFP->hMF);
		
		RestoreDC(hdc, save);

	    }
	    break;
	}

	case INVALID_STATE:
	default:

	    TextOut(hdc, 10, 10, szInvalid, strlen(szInvalid));
	    break;

    }

    SetBkMode(hdc, iBackMode);

}

