///////////////////////////////////////////////////////////////////////
// Header file: "SetupDlg.h"
//
// (C) Copyright 2001, RightMark Gathering.  All Rights Reserved.
//
// @Author: Konstantin Mironovich (km) develop@rightmark3d.org
//
// Purpose: interface for the CSetupDlg class
//
// History: 
//   2001/11/10 created. (km)
//
///////////////////////////////////////////////////////////////////////

#pragma once

#include "resource.h"       // main symbols
#include "adetect.h"
#include "ConfigMgr.h"

typedef CSimpleArray<CString> TStringList;

class CPageHelper
{
public:
    CPageHelper(void);

protected:
    static bool m_IsCentered;
};

//////////////////////////////////////////////////////////////////////
// CPageBase - provides common helpers for CPropertyPages below

template <class T>
class CPageBase : public CPageHelper
    , public CPropertyPageImpl<T>
    , public CWinDataExchangeEx<T>
{
public:

    LRESULT OnCtxHelp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        ATLTRACE(_T("CPageOne::OnCtxHelp() (lp=%08x)\n"), lParam);
        LPHELPINFO phi = (LPHELPINFO)lParam;

        CString str((LPCTSTR)IDS_FILEHELP);
        if (0 != phi->dwContextId)
            WinHelp(str, HELP_CONTEXTPOPUP, phi->dwContextId);

        //bHandled = FALSE;
        return 0;
    }
    LRESULT OnUiChange(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
        ATLTRACE(_T("CPageBase::OnUiChange() (id %d)\n"), wID);

        SetModified();
        return 0;
    }
    BOOL CenterWindow(void)
    {
        if (m_IsCentered) 
            return true;

        // @TRICKY: - just to make Franky happy..
        GetPropertySheet().SetDlgItemText(IDOK, _T("Run"));

        m_IsCentered = true;
        return GetPropertySheet().CenterWindow(::GetDesktopWindow());
    }
    CString& GetTimeStamp(CString& str)
    {
        SYSTEMTIME st;
        
        ::GetLocalTime(&st);
        str.Format(_T("cpu%4u%02u%02u_%02u%02u%02u")
                  , st.wYear, st.wMonth, st.wDay 
                  , st.wHour, st.wMinute, st.wSecond);
        return str;
    }
};


// Property Page #0 displays some copyright information
//

class CPageAbout : public CPageBase<CPageAbout>
{
public:
    enum { IDD = IDD_PROP_PAGE0 };

BEGIN_DDX_MAP(CPageAbout)
    DDX_CHECK(IDC_CHECK_SPLASH, m_IsSplash)
    DDX_BITMAP(IDC_LOGO, m_strLogo) \
END_DDX_MAP() 

BEGIN_MSG_MAP(CPageAbout)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    MESSAGE_HANDLER(WM_HELP, OnCtxHelp)
    COMMAND_ID_HANDLER(IDC_CHECK_SPLASH, OnUiChange)
    CHAIN_MSG_MAP(CPropertyPageImpl<CPageAbout>)
END_MSG_MAP()

    LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        CenterWindow();

        // get and set screen properties
        ATLTRACE(_T("CPageAbout::OnInitDialog()\n"));

        //m_Url.SetLabel();
        m_Url.SetHyperLink(_T(CPURM_HOMEPAGE));
        m_Url.SubclassWindow(GetDlgItem(IDC_STATIC_URL));

        // create custom font
        m_font = (HFONT)SendMessage(WM_GETFONT);
        if (!m_font.IsNull())
        {
            LOGFONT lf;
            if (m_font.GetLogFont(lf))
            {
                // bold font
                lf.lfWeight = FW_BOLD;
                m_font.Detach();
                m_font.CreateFontIndirect(&lf);
                SendDlgItemMessage(IDC_STATIC_ABOUT, WM_SETFONT, (WPARAM)(HFONT)m_font);
            }
        }
        
        TCHAR data[256];
        DWORD size = sizeof(data);
        UINT logo;
        CString str;
        CProfile cfg(_T(VR_PROFILE));

        _Module.GetCpuCaps(CPU_VENDOR_LOGO, &logo);
        str.Format(_T("%d"), logo);

        cfg.Open(_T("logo"));
        cfg.QueryValue(data, str, &size);
        m_strLogo = _T("logo\\");
        m_strLogo += data;

        DoDataExchange(DDX_LOAD);
        
        return TRUE;
    }
    BOOL OnApply()
    {
        ATLTRACE(_T("CPageAbout::OnApply()\n"));
        DoDataExchange(DDX_SAVE);
        SetModified(FALSE);
        return TRUE;
    }

public:
    int m_IsSplash;
    
private:
    CFont m_font;
    CHyperLink m_Url;
    CString m_strLogo;
};

// Property Page #1 displays system information as text in
// IDC_STATIC_OS, IDC_STATIC_CPU, IDC_STATIC_FEATURES, IDC_STATIC_MEM
// static controls.
//

class CPageOne : public CPageBase<CPageOne>
{
public:
    enum { IDD = IDD_PROP_PAGE1 };
    
BEGIN_DDX_MAP(CPageOne)
    // @NOTE: to use DDX_Text(UINT nID, CString& strText,...) version
    // we should include <atlmisc.h> before <atlddx.h>
    DDX_TEXT(IDC_STATIC_OS, m_strOsInfo)
    DDX_TEXT(IDC_STATIC_MEM, m_strMemInfo)
    DDX_TEXT(IDC_STATIC_CPU, m_strCpuInfo)
    DDX_TEXT(IDC_STATIC_FEATURES, m_strCpuFeatures)
END_DDX_MAP() 

BEGIN_MSG_MAP(CPageOne)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
    COMMAND_ID_HANDLER(IDHELP, OnTestHelp)
    CHAIN_MSG_MAP(CPropertyPageImpl<CPageOne>)
END_MSG_MAP()

    LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        CenterWindow();
        
        // get and set screen properties
        ATLTRACE(_T("CPageOne::OnInitDialog()\n"));
        
        m_strCpuFeatures.Format(IDS_PATTERN_FEATURES
                               , _Module.m_IsMMX ? _T("  MMX") : _T("")
                               , _Module.m_IsSSE ? _T(", SSE") : _T("")
                               , _Module.m_IsSSE2 ? _T(", SSE2") : _T("")
                               , _T(""), _T(""));

        m_strMemInfo.Format(IDS_PATTERN_MEM
                           , _Module.m_MemTotalPhys, _Module.m_MemAvailPhys);

        DoDataExchange(DDX_LOAD);

        return TRUE;
    }
    LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL)
    {
        WinHelp(NULL, HELP_QUIT, 0);
        return 0;
    }
    LRESULT OnTestHelp(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
        ATLTRACE(_T("CPageOne::OnHelp()\n"));

        CString str((LPCTSTR)IDS_FILEHELP);
        WinHelp(str, HELP_CONTENTS, 0);
        return 0;
    }

public:
    CString m_strOsInfo;
    CString m_strMemInfo;
    CString m_strCpuInfo;
    CString m_strCpuFeatures;
};

// Property Page #2 displays renderer system parameters:
// videomodes, viewport, instruction set, shadows, texture
//

class CPageTwo : public CPageBase<CPageTwo>
{
public:
    enum { IDD = IDD_PROP_PAGE2 };

BEGIN_DDX_MAP(CPageTwo)
    DDX_COMBO_INDEX(IDC_COMBO_SCREEN, m_ScreenMode)
    DDX_INT_SPIN(IDC_EDIT_VIEWX, m_ViewSizeX, 10, 100)
    DDX_INT_SPIN(IDC_EDIT_VIEWY, m_ViewSizeY, 10, 100)
    DDX_CHECK(IDC_CHECK_WINDOW, m_Windowed)
    DDX_CHECK(IDC_CHECK_CAMERA, m_Camera)
    DDX_CHECK(IDC_CHECK_SPECIAL, m_Special)
    DDX_RADIO(IDC_RADIO_MMX, m_CommandSet)
    DDX_CHECK2(IDC_CHECK_SHADOW, m_Shadow, IDC_CHECK_QUALITY)
    DDX_CHECK(IDC_CHECK_QUALITY, m_ShadowQuality)
    DDX_CHECK(IDC_CHECK_TEXTURE, m_Texture)
END_DDX_MAP() 

BEGIN_MSG_MAP(CPageTwo)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    MESSAGE_HANDLER(WM_HELP, OnCtxHelp)
    COMMAND_HANDLER(IDC_COMBO_SCREEN, CBN_SELCHANGE, OnUiChange)
    COMMAND_HANDLER(IDC_EDIT_VIEWX, EN_CHANGE, OnUiChange)
    COMMAND_HANDLER(IDC_EDIT_VIEWY, EN_CHANGE, OnUiChange)
    COMMAND_RANGE_HANDLER(IDC_RADIO_MMX, IDC_CHECK_SPECIAL, OnUiChange)
    CHAIN_MSG_MAP(CPropertyPageImpl<CPageTwo>)
END_MSG_MAP()

    LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        CenterWindow();

        // get and set screen properties
        ATLTRACE(_T("CPageTwo::OnInitDialog()\n"));

        int IsWindowed;
        CProfile cfg(_T(VR_PROFILE));

        cfg.Open(_T("common"));
        cfg.QueryValue(IsWindowed, _T("Windowed"));

        if (!IsWindowed)
        {
            ::SendMessage(GetDlgItem(IDC_CHECK_WINDOW), BM_SETCHECK, BST_UNCHECKED, 0L);
            ::EnableWindow(GetDlgItem(IDC_CHECK_WINDOW), FALSE);
        }
        
        ::EnableWindow(GetDlgItem(IDC_RADIO_EMMX), _Module.m_IsEMMX);
        ::EnableWindow(GetDlgItem(IDC_RADIO_SSE), _Module.m_IsSSE);

        if (FALSE == _Module.m_IsSSE)
        {
            m_ShadowQuality = FALSE;
            ::EnableWindow(GetDlgItem(IDC_CHECK_QUALITY), FALSE);
        }

        ExecuteDlgInit(IDD);        // Dialog initialization support

        // set display mode list
        LPDISPLAYINFO pdi = _Module.m_dml.GetData();
        int num = _Module.m_dml.GetSize();

        HWND hCtl = GetDlgItem(IDC_COMBO_SCREEN);
        ::SendMessage(hCtl, CB_RESETCONTENT, 0, 0); // Purge combo droplist

        for (int i=0; i < num; i++)
        {
            CString str;

            // 0, 1 - reserved in Win9x
            if (pdi[i].frequency < 2)
                str.Format(_T("%u x %u, default refresh")
                          , pdi[i].width, pdi[i].height);
            else
                str.Format(_T("%u x %u, @%u Hz"), pdi[i].width
                          , pdi[i].height, pdi[i].frequency);

            ::SendMessage(hCtl, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)str);
        }
        ::SendMessage(hCtl, CB_SETCURSEL, m_ScreenMode, 0);

        DoDataExchange(DDX_LOAD);

        return TRUE;
    }
    BOOL OnApply()
    {
        ATLTRACE(_T("CPageTwo::OnApply()\n"));
        if (DoDataExchange(DDX_SAVE))
        {
            SetModified(FALSE);
            return TRUE;
        }
        return FALSE;
    }

public:
    int m_ScreenMode;
    int m_ViewSizeX;
    int m_ViewSizeY;
    int m_Camera;
    int m_Special;
    int m_Windowed;
    int m_CommandSet;
    int m_Shadow;
    int m_ShadowQuality;
    int m_Texture;
};

// Property Page #3 displays math solver parameters
//

class CPageThree : public CPageBase<CPageThree>
{
public:
    enum { IDD = IDD_PROP_PAGE3 };

BEGIN_DDX_MAP(CPageThree)
    DDX_INT_SPIN(IDC_EDIT_START, m_StartTime, STARTFRAME_MIN, STARTFRAME_MAX)
    DDX_INT_SPIN(IDC_EDIT_STOP, m_TestTime, TESTFRAME_MIN, TESTFRAME_MAX)
    DDX_INT_SPIN(IDC_EDIT_STEP, m_TimeStep, TIMESTEP_MIN, TIMESTEP_MAX)
    DDX_INT_SPIN(IDC_EDIT_ITER, m_IterNum, ITER_NUM_MIN, ITER_NUM_MAX)
    DDX_RADIO(IDC_RADIO_FPU, m_CommandSet)
    DDX_COMBO_INDEX(IDC_COMBO_SCENE, m_Index)
END_DDX_MAP() 

BEGIN_MSG_MAP(CPageThree)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    MESSAGE_HANDLER(WM_HELP, OnCtxHelp)
    COMMAND_HANDLER(IDC_EDIT_START, EN_CHANGE, OnEditChange)
    COMMAND_HANDLER(IDC_EDIT_STOP, EN_CHANGE, OnEditChange)
    COMMAND_HANDLER(IDC_EDIT_STEP, EN_CHANGE, OnEditChange)
    COMMAND_HANDLER(IDC_EDIT_ITER, EN_CHANGE, OnEditChange)
    COMMAND_RANGE_HANDLER(IDC_RADIO_FPU, IDC_RADIO_DEMO, OnUiChange)
    COMMAND_HANDLER(IDC_COMBO_SCENE, CBN_SELCHANGE, OnCbChange)
    CHAIN_MSG_MAP(CPropertyPageImpl<CPageThree>)
END_MSG_MAP()

    LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        CenterWindow();

        // get and set screen properties
        ATLTRACE(_T("CPageThree::OnInitDialog()\n"));

        ::EnableWindow(GetDlgItem(IDC_RADIO_SSE2), _Module.m_IsSSE2);

        HWND hCtl = GetDlgItem(IDC_COMBO_SCENE);
        ::SendMessage(hCtl, CB_RESETCONTENT, 0, 0); // Purge combo droplist
        int num   = m_tnl.GetSize();

        for (int n,i=0; i < num; i++)
        {
            n = ::SendMessage(hCtl, CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)m_tnl[i]);
                ::SendMessage(hCtl, CB_SETITEMDATA, n, (LPARAM)i);
        }

        if (num > 0)
        {
            ::SendMessage(hCtl, CB_SETCURSEL, m_Index, 0);
            m_Model = ::SendMessage(hCtl, CB_GETITEMDATA, m_Index, 0);
            SetDlgItemText(IDC_STATIC_INFO, m_tdl[m_Model]);
        }
        else
        {
            ::EnableWindow(hCtl, FALSE);
            CString str((LPCTSTR)IDS_E_MODELS);
            SetDlgItemText(IDC_STATIC_INFO, str);
        }
        
        // create small font
        int fontSize;
        CString strFont;
        DWORD size = 32;

        CProfile cfg(_T(VR_PROFILE));

        cfg.Open(_T("common"));
        cfg.QueryValue(fontSize, _T("FontSize"));
        cfg.QueryValue(strFont.GetBufferSetLength(size), _T("FontName"), &size);

        m_font = (HFONT)SendMessage(WM_GETFONT);
        if (!m_font.IsNull())
        {
            LOGFONT lf;
            if (m_font.GetLogFont(lf))
            {
                if (fontSize)
                    lf.lfHeight = fontSize;
                lf.lfOutPrecision = OUT_TT_PRECIS;
                lf.lfQuality = PROOF_QUALITY;
                lstrcpy(lf.lfFaceName, strFont);
                m_font.Detach();
                m_font.CreateFontIndirect(&lf);
            }
        }
        SendDlgItemMessage(IDC_STATIC_INFO, WM_SETFONT, (WPARAM)(HFONT)m_font);
        DoDataExchange(DDX_LOAD);

        return TRUE;
    }
    LRESULT OnEditChange(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
        ATLTRACE(_T("CPageThree::OnEditChange() (id %d)\n"), wID);

        SetModified();
        return 0;
    }
    LRESULT OnCbChange(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
        ATLTRACE(_T("CPageThree::OnCbChange() (id %d)\n"), wID);
        SetModified();

        // really we'd select an index in string array, not in combobox. 
        // the maybe differ due to c-box sorting option.
        HWND hCtl = GetDlgItem(IDC_COMBO_SCENE);
        m_Index = ::SendMessage(hCtl, CB_GETCURSEL, 0, 0);
        m_Model = ::SendMessage(hCtl, CB_GETITEMDATA, m_Index, 0);

        SetDlgItemText(IDC_STATIC_INFO, m_tdl[m_Model]);

        return 0;
    }
    BOOL OnApply()
    {
        ATLTRACE(_T("CPageThree::OnApply()\n"));
        if (DoDataExchange(DDX_SAVE))
        {
            SetModified(FALSE);
            return TRUE;
        }
        return FALSE;
    }

public:
    int m_CommandSet;
    int m_StartTime;
    int m_TestTime;
    int m_TimeStep;
    int m_IterNum;
    int m_Index;
    int m_Model;

    TStringList m_tfl;          // test file list
    TStringList m_tnl;          // test name list
    TStringList m_tdl;          // test description list

private:
    CFont m_font;
};

// Property Page #4 displays test results diagramm
//

class CPageFour : public CPageBase<CPageFour>
{
public:
    enum { IDD = IDD_PROP_PAGE4 };

BEGIN_DDX_MAP(CPageFour)
    // @NOTE: to use DDX_Text(UINT nID, CString& strText,...) version
    // we should include <atlmisc.h> before <atlddx.h>
END_DDX_MAP() 

BEGIN_MSG_MAP(CPageFour)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    MESSAGE_HANDLER(WM_HELP, OnCtxHelp)
    COMMAND_ID_HANDLER(IDC_BUTTON_SAVE, OnSave)
    CHAIN_MSG_MAP(CPropertyPageImpl<CPageFour>)
END_MSG_MAP()

    LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        CenterWindow();

        ATLTRACE(_T("CPageFour::OnInitDialog()\n"));

        LPRESULTS pr = _Module.GetResults();
        CHECK_NULL(pr, TRUE);

        double diagram[4][3] = {0};
        UINT frequency;
        _Module.GetCpuFrequency(&frequency, true);

        for (int i=0; i<4; i++)
        {
            double clock = (double)frequency * pr[i].framesqu;
            double rsum  = (double)(pr[i].prerenderingtickcount 
                                  + pr[i].renderingtickcount);
            double total = (double)(pr[i].solvingtickcount) + rsum;

            diagram[i][0] = (pr[i].solvingtickcount)? clock 
                          /  pr[i].solvingtickcount : 0;
            diagram[i][1] = (rsum)? clock / rsum : 0;
            diagram[i][2] = (total)? clock / total : 0;
        }

        try
        {
            DrawDiagram(GetDlgItem(IDC_PICTURE), diagram);
        }
        catch(CException* e) 
        {
            int ret = e->ReportError();
            e->Delete();
        }
        
        return TRUE;
    }
/*
CLR_BISQUE          RGB(0xFF,0xE4,0xC4)
CLR_BURLYWOOD       RGB(0xDE,0xB8,0x87)
CLR_ORCHID          RGB(0xDA,0x70,0xD6)
CLR_PURPLE          RGB(0x80,0x00,0x80)
CLR_SANDYBROWN      RGB(0xF4,0xA4,0x60)
CLR_SADDLEBROWN     RGB(0x8B,0x45,0x13)
CLR_SKYBLUE         RGB(0x87,0xCE,0xEB)
CLR_STEELBLUE       RGB(0x46,0x82,0xB4)
CLR_YELLOWGREEN     RGB(0x9A,0xCD,0x32)
CLR_SEAGREEN        RGB(0x2E,0x8B,0x57)
CLR_MEDIUMVIOLETRED RGB(0xC7,0x15,0x85)
CLR_MAROON          RGB(0x80,0x00,0x00)
CLR_INDIANRED       RGB(0xCD,0x5C,0x5C)
CLR_INDIGO          RGB(0x4B,0x00,0x82)
CLR_NAVY            RGB(0x00,0x00,0x80)
CLR_KHAKY           RGB(0xF0,0xE6,0x8C)
CLR_DARKKHAKY       RGB(0xBD,0xB7,0x6B)
*/

#define MARGIN_LEFT         20
#define MARGIN_RIGHT        10
#define MARGIN_TOP          32
#define MARGIN_BOTTOM       0
#define MARGIN_LR           (MARGIN_LEFT + MARGIN_RIGHT)
#define MARGIN_TB           (MARGIN_TOP + MARGIN_BOTTOM)
#define FONT_HEIGHT_DIGITS  14
#define FONT_HEIGHT_LEGEND  12
#define FONT_DIGITS         "Arial Narrow"
#define FONT_LEGEND         "Arial"

    LRESULT DrawDiagram(HWND hWnd, double data[4][3])
    {
        CBitmapHandle bmp;
        CDC dcMem;
        CPen pen;
        CBrush br;
        CRect rcPic;
        CRect rc;
        CRect diagram[4][3];
        COLORREF pencolor[4] = {CLR_BURLYWOOD,CLR_PURPLE,CLR_DARKKHAKY,CLR_STEELBLUE};
        COLORREF foncolor[4] = {CLR_BISQUE,CLR_ORCHID,CLR_KHAKY,CLR_SKYBLUE};
        static LPCTSTR lg[4] = {_T("MMX/FPU"), _T("SSE/FPU"), _T("MMX/SSE2"), _T("SSE/SSE2")};
        
        ::GetClientRect(hWnd, rcPic);

        // find maximum value
        double maxval = 0;
        for (int j,i=0; i<4; i++)
        for (j=0; j<3; j++)
        {
            if (maxval < data[i][j])
                maxval = data[i][j];
        }
        
        double scale = (double)(rcPic.bottom - MARGIN_TB) / maxval;
        double pitch = (double)(rcPic.right - MARGIN_LR) * 0.33;
        int width    = (int)(pitch * 0.25) - 2;
        int shift    = (int)(pitch * 0.25) - 1;

        for (i=0; i<4; i++)
        for (j=0; j<3; j++)
        {
            diagram[i][j].left   = (int)(shift * i + pitch * j + MARGIN_LEFT);
            diagram[i][j].right  = diagram[i][j].left + width;
            diagram[i][j].bottom = rcPic.bottom - MARGIN_BOTTOM;
            diagram[i][j].top    = diagram[i][j].bottom - (int)(data[i][j] * scale + 1);
        }

        // create memory DC
        CClientDC dc(hWnd);
        dcMem.CreateCompatibleDC(dc);
        
        // create mono or color bitmap
        bmp.CreateCompatibleBitmap(dc, rcPic.right, rcPic.bottom);
        ATLASSERT(bmp.m_hBitmap != NULL);

        // draw image into memory DC--fill BG white first
        HBITMAP hbmSave = dcMem.SelectBitmap(bmp);

        dcMem.PatBlt(0, 0, rcPic.right, rcPic.bottom, WHITENESS);

        for (i=0; i<4; i++)
        {
            if (!br.IsNull()) br.DeleteObject();
            if (!pen.IsNull()) pen.DeleteObject();
            dcMem.SelectBrush(br.CreateSolidBrush(foncolor[i]));
            dcMem.SelectPen(pen.CreatePen(PS_SOLID, 1, pencolor[i]));

            for (j=0; j<3; j++)
            {
                dcMem.Rectangle(diagram[i][j]);
            }
            // draw legend color box
            rc = rcPic;
            rc.left = rc.right = rcPic.Width()/4*i + MARGIN_LEFT;
            rc.top = rc.bottom = 4;
            rc.InflateRect(0, 0, width/2, FONT_HEIGHT_LEGEND/2);
            dcMem.Rectangle(rc);
        }
        
        // draw text into memory DC
        CString str;
        CFont fntDigits;
        CFont fntLegend;
        fntDigits.CreateFont(FONT_HEIGHT_DIGITS, 0, 0, 0, FW_BOLD, 0, 0, 0
                            , DEFAULT_CHARSET, OUT_TT_PRECIS
                            , CLIP_DEFAULT_PRECIS, PROOF_QUALITY
                            , DEFAULT_PITCH, _T(FONT_DIGITS));
        
        fntLegend.CreateFont(FONT_HEIGHT_LEGEND, 0, 0, 0, FW_BOLD, 0, 0, 0
                            , DEFAULT_CHARSET, OUT_TT_PRECIS
                            , CLIP_DEFAULT_PRECIS, PROOF_QUALITY
                            , DEFAULT_PITCH, _T(FONT_LEGEND));

        dcMem.SetBkMode(TRANSPARENT);
        //dcMem.SetBkMode(OPAQUE);
        //dcMem.SetBkColor(CLR_WHITE);
        dcMem.SetTextColor(CLR_BLACK);

        for (i=0; i<4; i++)
        {
            dcMem.SelectFont(fntDigits);
            for (j=0; j<3; j++)
            {
                if (data[i][j])
                    str.Format(_T("%.1f"), data[i][j]);
                else
                    str = _T("0.0");

                // draw data values text
                rc = diagram[i][j];
                rc.InflateRect(1, FONT_HEIGHT_DIGITS, 0, 0);
                dcMem.DrawText((LPCTSTR)str, str.GetLength(), rc
                              , DT_TOP|DT_LEFT|DT_SINGLELINE|DT_NOCLIP);
            }
            // draw legend text
            dcMem.SelectFont(fntLegend);
            rc = rcPic;
            rc.left = rcPic.Width()/4*i + shift + MARGIN_LEFT;
            rc.top  = 0;
            dcMem.DrawText(lg[i], -1, rc, DT_TOP|DT_LEFT|DT_SINGLELINE|DT_NOCLIP);
        }

        dcMem.SelectBitmap(hbmSave);   // restore
        
        return SendDlgItemMessage(IDC_PICTURE, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)bmp.m_hBitmap);
    }
    LRESULT OnSave(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
        ATLTRACE(_T("CPageFour::OnSave()\n"));

        CString str;

        CFileDialog fd(FALSE, _T("bmp"), GetTimeStamp(str)
                     , OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_ENABLESIZING
                     , _T("Windows Bitmap files (*.bmp)\0*.bmp\0All files (*.*)\0*.*\0")
                     , m_hWnd);

        if (IDCANCEL == fd.DoModal())
            return 0;

        BITMAPFILEHEADER fh = {0};
        BYTE bi[52] = {0};  // BITMAPINFO + 3 DWORD masks
        PBITMAPINFO pbi = (PBITMAPINFO)&bi;
        PBITMAPINFOHEADER pbih = &pbi->bmiHeader;
        LPBYTE pBits;

        CBitmapHandle bmp;
        CClientDC dc(GetDlgItem(IDC_PICTURE));

        pbih->biSize = sizeof(BITMAPINFOHEADER);

        bmp = (HBITMAP)SendDlgItemMessage(IDC_PICTURE, STM_GETIMAGE, IMAGE_BITMAP, 0);
        bmp.GetDIBits(dc, 0, 0, NULL, pbi, DIB_RGB_COLORS);
        
        ATLTRY(pBits = new BYTE[pbih->biSizeImage]);
        CHECK_NULL(pBits, 0);

        bmp.GetDIBits(dc, 0, pbih->biHeight, pBits, pbi, DIB_RGB_COLORS);

        ofstream out(fd.m_szFileName, ios::binary);

        if (!out.is_open())
        {
            DELETE_PTR(pBits);
            _Module.Error((LPCTSTR)IDS_E_FILECREATE);
            return 0;
        }

        int extra = (BI_BITFIELDS == pbih->biCompression)
                  ? 3 * sizeof(DWORD) : 0;

        fh.bfType = 0x4d42;         // "BM"
        fh.bfSize = sizeof(fh) + pbih->biSize + pbih->biSizeImage + extra;
        fh.bfOffBits = sizeof(fh) + pbih->biSize + extra;

        out.write((LPCSTR)&fh, sizeof(fh));
        out.write((LPCSTR)pbih, sizeof(BITMAPINFOHEADER) + extra);
        out.write(pBits, pbih->biSizeImage);

        if(!out.good())
        {
            _Module.Error((LPCTSTR)IDS_E_FILEWRITE);
        }

        out.close();        // first close, then delete data
        DELETE_PTR(pBits);
        
        return 0;
    }

#undef MARGIN_LEFT
#undef MARGIN_RIGHT
#undef MARGIN_TOP
#undef MARGIN_BOTTOM
#undef MARGIN_LR
#undef MARGIN_TB
#undef FONT_HEIGHT

};

// Property Page #5 displays final test report
//

class CPageFive : public CPageBase<CPageFive>
{
public:
    enum { IDD = IDD_PROP_PAGE5 };

BEGIN_DDX_MAP(CPageFive)
    // @NOTE: to use DDX_Text(UINT nID, CString& strText,...) version
    // we should include <atlmisc.h> before <atlddx.h>
    DDX_TEXT(IDC_STATIC_INFO1, m_strInf1)
    DDX_TEXT(IDC_STATIC_INFO2, m_strInf2)
    DDX_TEXT(IDC_STATIC_INFO3, m_strInf3)
    DDX_TEXT(IDC_STATIC_INFO4, m_strInf4)
    DDX_TEXT(IDC_STATIC_INFO5, m_strReport)
END_DDX_MAP() 

BEGIN_MSG_MAP(CPageFive)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    MESSAGE_HANDLER(WM_HELP, OnCtxHelp)
    COMMAND_ID_HANDLER(IDC_BUTTON_SAVE, OnSave)
    CHAIN_MSG_MAP(CPropertyPageImpl<CPageFive>)
END_MSG_MAP()

    LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
    {
        CenterWindow();

        ATLTRACE(_T("CPageFive::OnInitDialog()\n"));
        
        LPRESULTS pr = _Module.GetLastResult();

        if (pr)
        {
            UINT frequency;

            _Module.GetCpuFrequency(&frequency, true);

            try
            {
            double solving   = (double)pr->solvingtickcount / pr->framesqu;
            double prerender = (double)pr->prerenderingtickcount / pr->framesqu;
            double render    = (double)pr->renderingtickcount / pr->framesqu;
            double clock     = (double)frequency;


            m_strInf1.Format(_T("%g (%.2f)"), (double)pr->solvingtickcount / clock, clock / solving);
            m_strInf2.Format(_T("%g (%.2f)"), (double)pr->prerenderingtickcount / clock, clock / prerender);
            m_strInf3.Format(_T("%g (%.2f)"), (double)pr->renderingtickcount / clock, clock / render);
            m_strInf4.Format(_T("%g (%.2f)"), (double)pr->framesqu, clock / (solving + prerender + render));
            }
            catch(CException* e) 
            {
                int ret = e->ReportError();
                e->Delete();
            }
        }

        DoDataExchange(DDX_LOAD);
        
        return TRUE;
    }
    LRESULT OnSave(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
        ATLTRACE(_T("CPageFive::OnSave()\n"));

        CString str;

        CFileDialog fd(FALSE, _T("htm"), GetTimeStamp(str)
                     , OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_ENABLESIZING
                     , _T("HTML files (*.htm)\0*.htm\0HTML files (*.html)\0*.html\0All files (*.*)\0*.*\0")
                     , m_hWnd);

        if (IDCANCEL == fd.DoModal())
            return 0;

        ofstream out(fd.m_szFileName, ios::trunc);

        if (!out.is_open())
        {
            _Module.Error((LPCTSTR)IDS_E_FILECREATE);
            return 0;
        }

        // write standard prolog..
        str.LoadString(IDS_PROLOG);
        out << str << endl;

        // write actual data..
        DWORD size = 256;
        CProfile cfg(_T(VR_PROFILE));

        cfg.Open(_T("common"));
        cfg.QueryValue(str.GetBufferSetLength(size), _T("URL"), &size);
        cfg.Close();

        out << "&nbsp;Find the latest reference results on <a href=\"";
        out << str << "\">" << str << "</a><p>" << endl;


        SYSTEMTIME st;
        ::GetLocalTime(&st);
        str.Format(_T("(%2u/%02u/%4u, %02u:%02u:%02u)")
                  , st.wDay, st.wMonth, st.wYear 
                  , st.wHour, st.wMinute, st.wSecond);
        out << "<tr><th colspan=2>CPU Test results " << str << "</th></tr>" << endl;
        out << "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" << endl;

        str = _Module.m_szOsInfo;
        out << "<tr><td>System:</td><td>" << str << "</td></tr>" << endl;

        str = _Module.m_szCpuInfo;
        out << "<tr><td>CPU:</td><td>" << str << "</td></tr>" << endl;

        out << "<tr><td>Memory:</td><td>" << _Module.m_MemTotalPhys << "K total, ";
        out << _Module.m_MemAvailPhys << "K free";
        out << ", (add type, clock here..)</td></tr>" << endl;
        out << "<tr><td>Chipset:</td><td>(add ID string here)</td></tr>" << endl;
        out << "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" << endl;

        LPDISPLAYINFO pdi = _Module.m_dml.GetData();
        int n = _Module.m_ScreenMode;

        if (pdi[n].frequency < 2)   // 0, 1 - reserved in Win9x
            str.Format("%u x %u, default refresh", pdi[n].width, pdi[n].height);
        else
            str.Format("%u x %u @%u Hz", pdi[n].width, pdi[n].height, pdi[n].frequency);
        out << "<tr><td>Display mode:</td><td>" << str << ", 32bpp</td></tr>" << endl;

        str.Format("%u x %u percent", _Module.GetData().rp.vp.cx, _Module.GetData().rp.vp.cy);
        out << "<tr><td>Viewport size:</td><td>" << str << "</td></tr>" << endl;

        str = _Module.GetData().rp.camera ? "Yes" : "No";
        out << "<tr><td>Dynamic camera:</td><td>" << str << "</td></tr>" << endl;

        str = _Module.GetData().rp.flag & RENDER_OFFSCREEN ? "Yes" : "No";
        out << "<tr><td>Offscreen rendering:</td><td>"<< str <<"</td></tr>" << endl;

        switch (_Module.GetData().rp.is)
        {
        case IS_FPU_MMX:  str = "FPU + General MMX"; break;
        case IS_FPU_EMMX: str = "FPU + MMX Extensions"; break;
        case IS_SSE:      str = "SSE + Enhanced MMX"; break;
        }
        out << "<tr><td>Renderer Instruction Set:</td><td>" << str << "</td></tr>" << endl;

        if (_Module.GetData().rp.flag & RENDER_SHADOWS)
        {
            str = "Yes";
            if (_Module.GetData().rp.flag & RENDER_SHADOWS_SUPER)
                str += ", quality";
        }
        else
            str = "No";
        out << "<tr><td>Shadows:</td><td>" << str << "</td></tr>" << endl;

        str = _Module.GetData().rp.flag & RENDER_TEXTURE ? "Yes" : "No";
        out << "<tr><td>Textures:</td><td>Yes</td></tr>" << endl;
        out << "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" << endl;

        str.Format("%u / %u", _Module.GetData().tp.ttp.cx, _Module.GetData().tp.ttp.cy);
        out << "<tr><td>Test start/stop frames:</td><td>" << str << "</td></tr>" << endl;

        switch (_Module.GetData().tp.tis)
        {
        case IS_FPU_MMX: str = "FPU";  break;
        case IS_SSE2:    str = "SSE2"; break;
        }
        out << "<tr><td>Solver Instuction Set:</td><td>" << str << "</td></tr>" << endl;

        out << "<tr><td>Testing Model:</td><td>" << _Module.GetData().tp.model << "</td></tr>" << endl;
        out << "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" << endl;

        LPRESULTS pr = _Module.GetLastResult();
        UINT frequency;

        _Module.GetCpuFrequency(&frequency, true);

        try
        {

        double solving   = (double)pr->solvingtickcount / pr->framesqu;
        double prerender = (double)pr->prerenderingtickcount / pr->framesqu;
        double render    = (double)pr->renderingtickcount / pr->framesqu;
        double clock     = (double)frequency;

        str.Format("%g (%.2f fps)", (double)pr->solvingtickcount / clock, clock / solving);
        out << "<tr><td>Math Solving Time:</td><td>" << str << "</td></tr>" << endl;

        str.Format("%g (%.2f fps)", (double)pr->prerenderingtickcount / clock, clock / prerender);
        out << "<tr><td>Prerendering time:</td><td>" << str << "</td></tr>" << endl;

        str.Format("%g (%.2f fps)", (double)pr->renderingtickcount / clock, clock / render);
        out << "<tr><td>Rendering time:</td><td>" << str << "</td></tr>" << endl;

        str.Format("%g (%.2f fps)", (double)pr->framesqu, clock / (solving + prerender + render));
        out << "<tr><td>Number of rendered frames:</td><td>" << str << "</td></tr>" << endl;

        }
        catch(CException* e) 
        {
            int ret = e->ReportError();
            e->Delete();
        }

        out << "<tr><td>&nbsp;</td><td>&nbsp;</td></tr>" << endl;

        // write standard epilog..
        str.LoadString(IDS_EPILOG);
        out << str << endl;

        if(!out.good())
        {
            _Module.Error((LPCTSTR)IDS_E_FILEWRITE);
        }
        out.close();

        return 0;
    }

public:
    CString m_strInf1;
    CString m_strInf2;
    CString m_strInf3;
    CString m_strInf4;
    CString m_strReport;
};

class CSetupDlg : public CPropertySheetImpl<CSetupDlg>
{
public:
    CSetupDlg(int startPage)
    {
        m_page0.m_IsSplash      = _Module.m_IsSplash;
        m_page1.m_strOsInfo     = _Module.m_szOsInfo;
        m_page1.m_strCpuInfo    = _Module.m_szCpuInfo;
        m_page2.m_ScreenMode    = _Module.m_ScreenMode;
        m_page2.m_ViewSizeX     = 100;
        m_page2.m_ViewSizeY     = 75;
        m_page2.m_Camera        = 0;
        m_page2.m_Special       = 0;
        m_page2.m_Windowed      = 0;
        m_page2.m_CommandSet    = (_Module.m_IsSSE)? 2 : (_Module.m_IsEMMX)? 1 : 0;
        m_page2.m_Shadow        = 1;
        m_page2.m_ShadowQuality = 1;
        m_page2.m_Texture       = 1;
        m_page3.m_StartTime     = 100;
        m_page3.m_TestTime      = 600;
        m_page3.m_TimeStep      = 500;
        m_page3.m_IterNum       = 10;
        m_page3.m_CommandSet    = (_Module.m_IsSSE2)? 1 : 0;
        m_page3.m_Index         = 0;
        m_page3.m_Model         = -1;   // no model selected

        CollectTaskInfo();

        AddPage(m_page0);
        AddPage(m_page1);
        AddPage(m_page2);
        AddPage(m_page3);

        LPRESULTS pr = _Module.GetResults();
        if (pr)
        {
            // add pages if results are available
            AddPage(m_page4);
            AddPage(m_page5);
        }
        
        if (startPage > 3)
        {
            ATLTRACE(_T("CSetupDlg: invalid start page.\n"));
            startPage = 0;
        }
        
        SetActivePage(m_Page = startPage);
        
        m_strCaption.LoadString(IDS_SETUP_CAPTION);
        SetTitle(m_strCaption);
        
        m_psh.hIcon = (HICON)::LoadImage(_Module.GetModuleInstance()
                                        , MAKEINTRESOURCE(IDR_MAINFRAME)
                                        , IMAGE_ICON
                                        , ::GetSystemMetrics(SM_CXSMICON)
                                        , ::GetSystemMetrics(SM_CYSMICON)
                                        , LR_DEFAULTCOLOR);

        m_psh.dwFlags |= (/*PSH_NOAPPLYNOW|*/PSH_USEHICON);
    }

BEGIN_MSG_MAP(CSetupDlg)
    MESSAGE_HANDLER(WM_COMMAND, OnCommand)
    CHAIN_MSG_MAP(CPropertySheetImpl<CSetupDlg>)
END_MSG_MAP()

    LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
    {
        ATLTRACE(_T("OnCommand() (msg=%x, wp=%x, lp=%x)\n"), uMsg, wParam, lParam);
        LRESULT lRet = DefWindowProc(uMsg, wParam, lParam);
        if(HIWORD(wParam) == BN_CLICKED && 
          (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL))
        {
          m_nRet = LOWORD(wParam);  // save actual return button
          m_Page = GetActiveIndex();
        }

        return lRet;
    }
    bool CollectTaskInfo(void)
    {
        // get current language
        CString strLang;
        DWORD size = 16;

        CProfile cfg(_T(VR_PROFILE));

        cfg.Open(_T("common"));
        cfg.QueryValue(strLang.GetBufferSetLength(size), _T("Language"), &size);

        // find model files and collect information about them
        //
        CFindFile ff;
        CString strPath;
        CString str;
        
        TStringList* ptfl = &(m_page3.m_tfl);
        TStringList* ptnl = &(m_page3.m_tnl);
        TStringList* ptdl = &(m_page3.m_tdl);

        ::GetModuleFileName(_Module.GetModuleInstance()
                           , str.GetBufferSetLength(MAX_PATH)
                           , MAX_PATH * sizeof(TCHAR));
        strPath = str.Left(str.ReverseFind(_T('\\')) + 1) + _T("models\\");

        if (!ff.FindFile(strPath + _T("*.sph"))) 
        {
            ATLTRACE(_T("No %s files found.\n"), _T("*.sph"));
            return false;
        }

        do
        { 
            str = ff.GetFileTitle();
            ptfl->Add(str);     // add test filename

            str = strPath + str + _T(".") + strLang;

            ifstream in(str, ios::nocreate|ios::binary);

            if (in.is_open())
            {
                long len = in.seekg(0, ios::end).tellg();
                in.seekg(0, ios::beg);
                // get short description - first line
                in.getline(str.GetBuffer(len), (len)*sizeof(TCHAR));
                str.ReleaseBuffer();
                str.Replace('\r', ' ');
                ptnl->Add(str);     // add test short descripton

                /*
                str.Empty();
                do 
                {
                    TCHAR buf[1024];

                    in.getline(buf, sizeof(buf));
                    if (in.eof()) break;
                    str += buf;
                    str.TrimRight();
                    str += _T(" "); // add single whitespace..

                } while (true);
                */
                in.getline(str.GetBuffer(len), (len)*sizeof(TCHAR),'\0');
                ptdl->Add(str);     // add test description
                in.close();
            }

        } while (ff.FindNextFile());

        if (ptnl->GetSize())
        {
            m_page3.m_Model = 0;   // at least one model was found.
        }

        ff.Close();

        return true;
    }

public:
    CPageAbout m_page0;
    CPageOne   m_page1;
    CPageTwo   m_page2;
    CPageThree m_page3;
    CPageFour  m_page4;
    CPageFive  m_page5;

    int m_nRet;
    int m_Page;

private:
    CSetupDlg() {}

    CString m_strCaption;
};

