// DFUDlg.cpp : implementation file
//
//-----------------------------------------------------------------------------
//
// Module Name: DFUDlg.cpp
//
// Description : Defines the initialization routines for the DLL.       
//
// Version: 1.1.2.0
//
// Date : 2011/03/03
//
// Copyright (c) 1999-2011 Megawin Technology Co., Ltd.
//
// Author: Tony Chu
//
// NOTICE: This software is licensed, not sold.  Any modification of this
// inf file to change the reference to an operating system or to change the
// reference to a device is not permitted unless authorized in writing by
// Megawin Technology Co., Ltd.
//
//-----------------------------------------------------------------------------
#include "stdafx.h"
#include "DFU.h"
#include "DFUDlg.h"
#include <process.h>
#include <initguid.h>
#include <dbt.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


#define UPGRADE_SUCCESSFULLY    0
#define DFU_RESET_TO_AP_FAIL    1
#define DFU_DOWNLOAD_FAIL       2
#define DFU_RESET_TO_ISP_FAIL   3

extern CDFUApp theApp;

unsigned int uiDownloadThreadId;        // Download thread id
unsigned long hDownloadThread;          // Download thread handle
unsigned int _stdcall Thread_Download (LPVOID lpParam );        // Download function
BOOL blnInDownload;                     // flag In Download
BOOL blnInProcess;                      // flag In Process

DEFINE_GUID( __GUID_DEVINTERFACE_USB_HID_DEVICE, 
             0x4d1e55b2L, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);

HDEVNOTIFY g_NotifyDevHandle; 
HANDLE hGoEvent = NULL;
DWORD dwWaitTime = 0;

struct THREAD_INFO
{
    DWORD Vid;
	DWORD Pid;
	DWORD CheckSum;
	DWORD CodeSize;
	LPBYTE pDownloadBuffer;
} ThreadInfo;

/////////////////////////////////////////////////////////////////////////////
// CDFUDlg dialog

CDFUDlg::CDFUDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CDFUDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CDFUDlg)
	m_strFileName = _T("");
	m_strPID = _T("030B");
	m_strVID = _T("0E6A");
	m_strCheckSum = _T("0x0000");
	m_strCodeSize = _T("0x0000");
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CDFUDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CDFUDlg)
	//DDX_Control(pDX, IDC_EDIT_ADDRESS, m_CtrlAddress);
	DDX_Control(pDX, IDC_EDIT_VID, m_CtrlVID);
	DDX_Control(pDX, IDC_EDIT_PID, m_CtrlPID);
	DDX_Control(pDX, IDC_EDIT_BUFFER, m_CtrlBinaryCodeBuffer);
	DDX_Text(pDX, IDC_EDIT_PATH, m_strFileName);
	DDX_Text(pDX, IDC_EDIT_PID, m_strPID);
	DDV_MaxChars(pDX, m_strPID, 4);
	DDX_Text(pDX, IDC_EDIT_VID, m_strVID);
	DDV_MaxChars(pDX, m_strVID, 4);
	DDX_Text(pDX, IDC_EDIT_CHECK_SUM, m_strCheckSum);
	DDX_Text(pDX, IDC_EDIT_CODE_SIZE, m_strCodeSize);	
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CDFUDlg, CDialog)
	//{{AFX_MSG_MAP(CDFUDlg)
	ON_BN_CLICKED(IDC_BUTTON_LOAD, OnButtonLoad)
	ON_BN_CLICKED(IDC_BUTTON_UPDATE, OnButtonUpdate)
	ON_BN_CLICKED(IDC_CHK_AUTO_RELOAD_CODE, OnChkAutoReloadCode)
	ON_WM_ACTIVATE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDFUDlg message handlers

BOOL CDFUDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// Initial code buffer attributes
	m_CtrlBinaryCodeBuffer.SetAddressSize(4, FALSE);
	m_CtrlBinaryCodeBuffer.SetShowAddress(TRUE, FALSE);
	m_CtrlBinaryCodeBuffer.SetShowAscii(TRUE, FALSE);
	m_CtrlBinaryCodeBuffer.SetAdrCol(RGB(0xC8, 0xC8, 0xC8), RGB(0x00, 0x00, 0x00), FALSE);
	m_CtrlBinaryCodeBuffer.SetHexCol(RGB(0xC8, 0xC8, 0xC8), RGB(0x00, 0x00, 0x00), FALSE);
	m_CtrlBinaryCodeBuffer.SetAsciiCol(RGB(0xC8, 0xC8, 0xC8), RGB(0x00, 0x00, 0x00), FALSE);
	m_CtrlBinaryCodeBuffer.SetBytesPerRow(16, FALSE);
	m_CtrlBinaryCodeBuffer.SetNotUsedCol(RGB(0xC8, 0xC8, 0xC8));
	m_CtrlBinaryCodeBuffer.SetReadonly(TRUE);
	m_CtrlBinaryCodeBuffer.SetFont(GetFont());
	
	m_bAutoReloadCode = FALSE;
	GetDlgItem(IDC_EDT_MODIFIED_DATE)->ShowWindow(FALSE);
 
    RegisterNotification();
    //hGoEvent = CreateEvent(NULL, TRUE, FALSE, "{58DAC563-4884-4a16-95C7-5546260CD49C}");
	hGoEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	return TRUE;  // return TRUE  unless you set the focus to a control
}
/*
 *-----------------------------------------------------------------------------
 * OnButtonLoad                                                            
 *-----------------------------------------------------------------------------
 * description: Display CFileDialog to select one file.
 *              If this file is a hex file ,it  will be transfered.
 *              Then display the content in m_CtrlBinaryCodeBuffer.
 * Parameter: None
 * Return: None
 *-----------------------------------------------------------------------------
 */
void CDFUDlg::OnButtonLoad()
{
	CFileDialog fd(TRUE);

	fd.m_ofn.lpstrFilter = _T("*.bin; *.hex; *.rom\0*.hex;*.bin;*.rom\0All file (*.*)\0*.*\0");
	if (fd.DoModal() == IDOK)
	{
		UpdateData();
		LPBYTE pBuffer = NULL;
		DWORD dwLength = 0;
		DWORD dwCheckSum = 0;
		BOOL bDataEnable(FALSE);
		m_strFileName = fd.GetPathName();
		if (!fd.GetFileExt().CompareNoCase("hex"))
		{
			CHexFile hFile;
			if (hFile.Open(fd.GetPathName()))
			{
				bDataEnable = TRUE;
				dwLength = hFile.GetLength();
				pBuffer = new BYTE[dwLength];
				hFile.ReadHuge(pBuffer, dwLength);
				hFile.Close();
			}
		}
		else
		{
			CFile hFile;
			if (hFile.Open(fd.GetPathName(), CFile::modeRead))
			{
				bDataEnable = TRUE;
				dwLength = hFile.GetLength();
				pBuffer = new BYTE[dwLength];
				hFile.ReadHuge(pBuffer, dwLength);
				hFile.Close();
			}
		}
		if (bDataEnable)
		{
			m_CtrlBinaryCodeBuffer.SetData(pBuffer, dwLength);
			m_strCodeSize.Format("0x%X", dwLength);
			__asm
			{
				mov ecx, dwLength
				mov edx, pBuffer
				add edx, ecx
L_Next:			dec	edx
				movzx eax, byte ptr[edx]
				add	dwCheckSum, eax
				loop L_Next
			}
			m_strCheckSum.Format("0x%04X", dwCheckSum&0xffff);
		}
		else
		{
			AfxMessageBox("Cannot open \"" + fd.GetPathName() + "\" !");
		}
		if (pBuffer)
			delete []pBuffer;
		UpdateData(FALSE);
	}
}
/*
 *-----------------------------------------------------------------------------
 * OnButtonUpdate                                                            
 *-----------------------------------------------------------------------------
 * description: Create a thread to do a download job.
 *              When download is in progress, get the percentage and show it.
 * Parameter: None
 * Return: None
 *-----------------------------------------------------------------------------
 */
void CDFUDlg::OnButtonUpdate()
{
    CString strTmp;		
	int CurValue;
	DWORD ExitCode;
    
	if (blnInProcess == TRUE)
		return;
	UpdateData();
	sscanf(m_strVID, "%x", &(ThreadInfo.Vid));
	sscanf(m_strPID, "%x", &(ThreadInfo.Pid));
    sscanf(m_strCheckSum, "0x%x", &(ThreadInfo.CheckSum));
	ThreadInfo.CodeSize = m_CtrlBinaryCodeBuffer.GetDataSize();

    if (ThreadInfo.CodeSize == 0)
	{
		MessageBox(_T("Buffer size is zero !"));
	    return;
	}

	if (ThreadInfo.pDownloadBuffer != NULL)
		delete [](ThreadInfo.pDownloadBuffer);
	ThreadInfo.pDownloadBuffer = new BYTE[ThreadInfo.CodeSize];
	if (ThreadInfo.pDownloadBuffer == NULL)
	{
		MessageBox(_T("Out of memory !"));
	    return;
	}
	m_CtrlBinaryCodeBuffer.GetData(ThreadInfo.pDownloadBuffer, ThreadInfo.CodeSize);

    if (QueryDfuRecord() == TRUE)
	{
        dwWaitTime = 10000;		
	}
    else
	{
        dwWaitTime = 20000;		
	}

	hDownloadThread = _beginthreadex( NULL,                        // no security attributes 
			 						  0,                           // use default stack size  
									  Thread_Download,             // thread function 
									  &ThreadInfo,                        // argument to thread function 
									  CREATE_SUSPENDED,            // use default creation flags 
									  &uiDownloadThreadId);// returns the thread identifier 

	if ( MessageBox(_T(" Upgrade Firmware ? "),
		            _T("Confirm File"),
		            MB_ICONWARNING | MB_YESNO) == IDYES)
    {
		strTmp.Format("Searching device...");	
		((CEdit*)GetDlgItem(IDC_edtProcessCount))->SetWindowText(strTmp);  
		blnInProcess = TRUE;
        ResumeThread((void*)hDownloadThread);
	}
	else
	{		
		ResumeThread((void*)hDownloadThread);
		CloseHandle((void*)hDownloadThread);
		return;
    }
    while(blnInProcess == TRUE)	
	{
	    if (blnInDownload == TRUE)
		{
		    CurValue = DFU_Get_ProcessCount();
			if (0xFF != CurValue)
			{
			    strTmp.Format("ProcessCount = %d%%",CurValue);
				((CEdit*)GetDlgItem(IDC_edtProcessCount))->SetWindowText(strTmp);
			}				
			Sleep(20);
		}
		MSG msg;
		while (::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
		{
		    if (!AfxGetApp()->PumpMessage())
			{
				::PostQuitMessage(0);	
				break;		
			}
		}
	}
    CurValue = DFU_Get_ProcessCount();
	if (CurValue != 0xFF)
	{
	   strTmp.Format("ProcessCount = %d%%",CurValue);
	   ((CEdit*)GetDlgItem(IDC_edtProcessCount))->SetWindowText(strTmp);
	}
	WaitForSingleObject((void*)hDownloadThread, 20000);   // Wait for 20 seconds
	GetExitCodeThread((void*)hDownloadThread, &ExitCode);
	switch (ExitCode)
	{
	case UPGRADE_SUCCESSFULLY : 
		     MessageBox(_T("Upgrade successfully !"), _T("Firmware Upgrade"), MB_OK | MB_ICONINFORMATION);
		     break;
    case DFU_RESET_TO_AP_FAIL: 
		     MessageBox(_T("DFU_Reset_To_AP fail !"), _T("Firmware Upgrade"), MB_OK | MB_ICONSTOP);
		     break;
	case DFU_DOWNLOAD_FAIL: 
		     MessageBox(_T("DFU_Download fail !"), _T("Firmware Upgrade"), MB_OK | MB_ICONSTOP);	
		     break;
	case DFU_RESET_TO_ISP_FAIL: 
		     MessageBox(_T("DFU_Reset_To_ISP fail !"), _T("Firmware Upgrade"), MB_OK | MB_ICONSTOP);	
		     break;	
	}
	CloseHandle((void*)hDownloadThread);

    //UnregisterDeviceNotification(g_NotifyDevHandle);
    //CloseHandle(hGoEvent);
    delete [](ThreadInfo.pDownloadBuffer);
	ThreadInfo.pDownloadBuffer = NULL;
	((CEdit*)(theApp.GetMainWnd()->GetDlgItem(IDC_edtProcessCount)))->SetWindowText("");
}

/*
 *-----------------------------------------------------------------------------
 * Thread_Download                                                            
 *-----------------------------------------------------------------------------
 * description: Create a thread to do a download job.
 *              When download is in progress, get the percentage and show it.
 * Parameter: None
 * Return: None
 *-----------------------------------------------------------------------------
 */
unsigned int _stdcall Thread_Download (LPVOID lpParam )
{
	DWORD dwAddr = 0;   // Must
	BOOL Result = 0;
    DWORD dwWaitResult = WAIT_TIMEOUT;
    THREAD_INFO * pInfo = (THREAD_INFO *)lpParam;

	if (blnInProcess == FALSE)
		return 0;

    ResetEvent(hGoEvent);
	Result = DFU_Reset_To_ISP(pInfo->Vid, pInfo->Pid);	
	if (Result == DEVICE_HAS_BEEN_IN_ISP)
	{
		Result = TRUE;
	}
	else
	{
	    dwWaitResult = WaitForSingleObject(hGoEvent, dwWaitTime);
        if (dwWaitResult == WAIT_OBJECT_0)
		{    
            Result = TRUE;
		}
	}
	if ( Result == TRUE )
	{ 
		blnInDownload = TRUE;
		Result = DFU_Download(pInfo->pDownloadBuffer, 0, pInfo->CodeSize, pInfo->CheckSum);   // The second parameter is never used again. Fill it with 0.
		blnInDownload = FALSE;
		if ( Result == TRUE )
		{
            ResetEvent(hGoEvent); 			    			
		    Result = DFU_Reset_To_AP();    						
	        dwWaitResult = WaitForSingleObject(hGoEvent, dwWaitTime);
            if (dwWaitResult == WAIT_OBJECT_0)
            {                    
			    Result = UPGRADE_SUCCESSFULLY;
            }
			else
			{
				Result = DFU_RESET_TO_AP_FAIL;
			}
		}
		else
		{
			Result = DFU_DOWNLOAD_FAIL;
		}
	}
	else
	{
		Result = DFU_RESET_TO_ISP_FAIL;	    
	}
	blnInProcess = FALSE;
	return Result;

}
/*
 *-----------------------------------------------------------------------------
 * OnChkAutoReloadCode                                                            
 *-----------------------------------------------------------------------------
 * description: Get the CheckBox State to enable or disable auto reload function
 * Parameter: None
 * Return: None
 *-----------------------------------------------------------------------------
 */
void CDFUDlg::OnChkAutoReloadCode() 
{
	UINT uiState = BST_UNCHECKED;
	//uiState = IsDlgButtonChecked(IDC_CHK_AUTO_RELOAD_CODE);
	uiState = ((CButton*)GetDlgItem(IDC_CHK_AUTO_RELOAD_CODE))->GetCheck();
	if (uiState == BST_CHECKED)
		m_bAutoReloadCode = TRUE;
	else
        m_bAutoReloadCode = FALSE;
	//GetDlgItem(IDC_EDT_MODIFIED_DATE)->ShowWindow(m_bAutoReloadCode);
}

/*
 *-----------------------------------------------------------------------------
 * OnActivate                                                            
 *-----------------------------------------------------------------------------
 * description: When this app is from inactive to active, and m_bAutoReloadCode 
 *              is TRUE, reload the file again if the user had loaded.
 * Parameter: None
 * Return: None
 *-----------------------------------------------------------------------------
 */
void CDFUDlg::OnActivate(UINT nState, CWnd * pWndOther, BOOL bMinimized)
{	
	int iPosition = 0;
	CString FileExt = "";
	if (nState != WA_INACTIVE)
	{
		if ((m_bAutoReloadCode == TRUE) && (m_strFileName.GetLength() > 0))
		{
    		LPBYTE pBuffer = NULL;
	    	DWORD dwLength = 0;
		    DWORD dwCheckSum = 0;
		    BOOL bDataEnable(FALSE);
			iPosition = m_strFileName.ReverseFind('.');
			if (iPosition != -1)
				FileExt = m_strFileName.Mid(iPosition + 1);		    
		    if (!FileExt.CompareNoCase("hex"))
			{
			    CHexFile hFile;
			    if (hFile.Open(m_strFileName))
				{
				    bDataEnable = TRUE;
				    dwLength = hFile.GetLength();
				    pBuffer = new BYTE[dwLength];
				    hFile.ReadHuge(pBuffer, dwLength);
					
				    hFile.Close();
				}
			}
		    else
			{
			    CFile hFile;
			    if (hFile.Open(m_strFileName, CFile::modeRead))
				{
  				    bDataEnable = TRUE;
				    dwLength = hFile.GetLength();
				    pBuffer = new BYTE[dwLength];
				    hFile.ReadHuge(pBuffer, dwLength);
				    hFile.Close();
				}
			}
		    if (bDataEnable)
			{
			    m_CtrlBinaryCodeBuffer.SetData(pBuffer, dwLength);
			    m_strCodeSize.Format("0x%X", dwLength);
			    __asm
				{
  				    mov ecx, dwLength
				    mov edx, pBuffer
				    add edx, ecx
L_Next:	    		dec	edx
			    	movzx eax, byte ptr[edx]
				    add	dwCheckSum, eax
				    loop L_Next
				}
			    m_strCheckSum.Format("0x%04X", dwCheckSum&0xffff);
			}
		    else
			{
				CString tmp;
				tmp = m_strFileName;
				m_strFileName = "";
                SetDlgItemText(IDC_EDIT_PATH, _T(""));

			    AfxMessageBox("Cannot open \"" + tmp + "\" !");
                m_CtrlBinaryCodeBuffer.SetData(NULL, 0);
			}
		    if (pBuffer)
    			delete []pBuffer;
		    UpdateData(FALSE);		
            ShowFileLastModifiedTime(m_strFileName);
		}
    }
}
/*
 *-----------------------------------------------------------------------------
 * ShowFileLastModifiedTime                                                            
 *-----------------------------------------------------------------------------
 * description: When reload the file, display the Last Modified Date. 
 * Parameter: None
 * Return: None
 *-----------------------------------------------------------------------------
 */
DWORD CDFUDlg::ShowFileLastModifiedTime(CString &strFileName)
{    
	CFileStatus FileStatus;
	CString strTime;
    if (CFile::GetStatus(strFileName, FileStatus))
	{
		strTime = FileStatus.m_mtime.Format("Last Modified Date : %Y/%m/%d %p %I:%M:%S ");
		SetDlgItemText(IDC_EDT_MODIFIED_DATE, strTime);
		
	}    
    return TRUE;
}
/*
 *-----------------------------------------------------------------------------
 * CheckDfuRecord.
 *-----------------------------------------------------------------------------
 * description: Query the registry to check if dfu interface has been exist.
 *
 * Parameter: None
 * Return: TRUE or FALSE;
 *-----------------------------------------------------------------------------
 */
BOOL CDFUDlg::QueryDfuRecord()
{
    BOOL bExist = FALSE;

    TCHAR tRegPath[MAX_PATH] = _T("SYSTEM\\CurrentControlSet\\Enum\\HID\\Vid_0e6a&Pid_030b");    
    LONG lResult = 0;
    HKEY hKeyLM;
    
    lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                            tRegPath,
                            0,
                            KEY_READ,
                            &hKeyLM);
    
    if (lResult == ERROR_SUCCESS)
    {
        bExist = TRUE;
    }
    RegCloseKey(hKeyLM);
 
    return bExist;
}
/*
 *-----------------------------------------------------------------------------
 * RegisterNotification.
 *-----------------------------------------------------------------------------
 * description: The app need to know if target is removed from USB bus.
 *              So need to register a notification.
 * Parameter: None
 * Return: TRUE or FALSE;
 *-----------------------------------------------------------------------------
 */
BOOL CDFUDlg::RegisterNotification()
{
    BOOL bResult = TRUE;
    DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;

    // Register to receive notification when a USB device is plugged in.
	ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));

    NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);

    NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;

    memcpy( &(NotificationFilter.dbcc_classguid),
            &(__GUID_DEVINTERFACE_USB_HID_DEVICE),
            sizeof(struct _GUID));

    g_NotifyDevHandle = RegisterDeviceNotification(this->m_hWnd,
                                                  &NotificationFilter,
                                                  DEVICE_NOTIFY_WINDOW_HANDLE);
    if (g_NotifyDevHandle == NULL)
	{		
        //ShowErrorMessage(&m_hWnd, "Register HID Notification Fail !", GetLastError(), NULL); 
        bResult = FALSE;
	}
       
    return bResult;
}

LRESULT CDFUDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
    PDEV_BROADCAST_DEVICEINTERFACE pdbd;
	BOOL bDone = FALSE;
	char UsbPortName[] = "\\\\?\\HID#Vid_0e6a&Pid_030b#";
    int iResult = 0;
    if (message == WM_DEVICECHANGE)
    {    
        switch(wParam)
        {
        case DBT_DEVICEARRIVAL:    			
			 if (PDEV_BROADCAST_HDR(lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
			 {	
			     pdbd = (PDEV_BROADCAST_DEVICEINTERFACE)lParam; 
                 iResult = _strnicmp(pdbd->dbcc_name, UsbPortName, strlen(UsbPortName));
                 if (iResult == 0)
                 {
                     SetEvent(hGoEvent);
                 }			
			 }
             break;
        case DBT_DEVICEREMOVECOMPLETE:
			 if (PDEV_BROADCAST_HDR(lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
			 {	
			     pdbd = (PDEV_BROADCAST_DEVICEINTERFACE)lParam; 
                 iResult = _strnicmp(pdbd->dbcc_name, UsbPortName, strlen(UsbPortName));
                 if (iResult == 0)
                 {
                     SetEvent(hGoEvent);
                 }			
			 }
             break;
        }        
    }	
	return CDialog::WindowProc(message, wParam, lParam);
}

BOOL CDFUDlg::DestroyWindow() 
{
	// TODO: Add your specialized code here and/or call the base class
    UnregisterDeviceNotification(g_NotifyDevHandle);
    CloseHandle(hGoEvent);	
	return CDialog::DestroyWindow();
}
