#include <windows.h>
#include <wchar.h>
#include <stdlib.h>
#include <process.h>

#include "fpv.h"

static const int WM_CHANGE_NOTIFICATION = WM_USER+0;
static const int WM_CHECK_NOTIFICATION  = WM_USER+1;

static void __fastcall AddPathDelimiter(wchar_t *pszPath, int nSize)
{
    int len = wcslen(pszPath);
    if(len > 0 && pszPath[len-1] != '\\'){
        wcsncat_s(pszPath, nSize, L"\\", _TRUNCATE);
    }
}
static const wchar_t* __fastcall ExtractFileName(const wchar_t *cszFileName)
{
    const wchar_t *ret = wcsrchr(cszFileName, L'\\');
    if(!ret){
        return cszFileName;
    }
    return ret + 1;
}

KbFpvSearcher::KbFpvSearcher(void)
{
    InitializeCriticalSection(&m_cs);
    m_szFpvPath[0] = 0;
    m_ppFileNames = NULL;
    m_nCount = 0;
    m_nCapacity = 0;
    m_hFind = INVALID_HANDLE_VALUE;
    m_hEvent = NULL;
    m_hThread = NULL;
    m_uThreadId = 0;
}
KbFpvSearcher::~KbFpvSearcher(void)
{
    if(m_hThread){
        PostThreadMessage(m_uThreadId, WM_QUIT, 0, 0);
        WaitForSingleObject(m_hThread, INFINITE);
        CloseHandle(m_hEvent);
        CloseHandle(m_hThread);
    }
    FreeFileNames();
    if(m_ppFileNames){
        free(m_ppFileNames);
    }
    DeleteCriticalSection(&m_cs);
}
void __fastcall KbFpvSearcher::FreeFileNames(void)
{
    Lock();
    wchar_t **ppFileNames = m_ppFileNames;
    const int nCount = m_nCount;
    int i;
    for(i = 0; i < nCount; i++){
        free(ppFileNames[i]);
    }
    m_nCount = 0;
    Unlock();
}
void __fastcall KbFpvSearcher::SearchFpvInternal(const wchar_t *cszBasePath)
{
    static const wchar_t cszFilter[] = L"*.*";
    wchar_t szSearchPath[MAX_PATH];//検索するパス
    wchar_t szFound[MAX_PATH];     //見つかったパス
    HANDLE hFind;
    WIN32_FIND_DATAW fd;

    wcsncpy_s(szSearchPath, cszBasePath, _TRUNCATE);
    AddPathDelimiter(szSearchPath, _countof(szSearchPath));
    wcsncat_s(szSearchPath, cszFilter, _TRUNCATE);
    hFind = FindFirstFileW(szSearchPath, &fd);
    if(hFind == INVALID_HANDLE_VALUE){
        return;
    }
    do{
        if(fd.cFileName[0] == L'.' &&
            (fd.cFileName[1] == 0 || (fd.cFileName[1] == L'.' && fd.cFileName[2] == 0) )){
            continue;
        }
        if((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)){
            wcsncpy_s(szFound, cszBasePath, _TRUNCATE);
            AddPathDelimiter(szFound, _countof(szFound));
            wcsncat_s(szFound, fd.cFileName, _TRUNCATE);
            SearchFpvInternal(szFound);
        }
        else{
            const wchar_t *cszExt = wcsrchr(fd.cFileName, L'.');
            if(_wcsicmp(cszExt, L".fpv") == 0){
                wcsncpy_s(szFound, cszBasePath, _TRUNCATE);
                AddPathDelimiter(szFound, _countof(szFound));
                wcsncat_s(szFound, fd.cFileName, _TRUNCATE);
                Add(szFound);    
            }
        }
    }while(FindNextFileW(hFind, &fd));
    FindClose(hFind);
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall KbFpvSearcher::Add(const wchar_t *cszFileName)
{
    Lock();
    if(m_nCount >= m_nCapacity){
        int nCapacity = m_nCapacity;
        if(!nCapacity){
            nCapacity = 32;
        }
        else{
            nCapacity *= 2;
        }
        m_ppFileNames = (wchar_t**)realloc(m_ppFileNames, nCapacity*sizeof(wchar_t*));
        m_nCapacity = nCapacity;
    }
    int nLen = wcslen(cszFileName) + 1;
    wchar_t *pszFileName = (wchar_t*)malloc(nLen * sizeof(wchar_t));
    memcpy(pszFileName, cszFileName, nLen * sizeof(wchar_t));
    m_ppFileNames[m_nCount] = pszFileName;
    m_nCount++;
    Unlock();
}
/////////////////////////////////////////////////////////////////////////////
wchar_t* __fastcall KbFpvSearcher::Find(wchar_t* pszDest, 
                                        const wchar_t* cszFpv, 
                                        int nSize)
{
    pszDest[0] = 0;
    const wchar_t *cszExt = wcsrchr(cszFpv, L'.');
    if(_wcsicmp(cszExt, L".fpv") == 0){
        int i;
        const wchar_t *cszFileName = ExtractFileName(cszFpv);
        if(!cszFileName[0]){
            return pszDest;
        }
        Lock();
        const int nCount = m_nCount;
        for(i = 0; i < nCount; i++){
            if(_wcsicmp(ExtractFileName(m_ppFileNames[i]), cszFileName) == 0){
                wcsncpy_s(pszDest, nSize, m_ppFileNames[i], _TRUNCATE);
                break;
            }
        }
        Unlock();
    }
    return pszDest;
}
/////////////////////////////////////////////////////////////////////////////
void __fastcall KbFpvSearcher::SearchFpv(const wchar_t *cszFpvPath)
{
    wchar_t szFpvPath[MAX_PATH];
    wcsncpy_s(szFpvPath, cszFpvPath, _TRUNCATE);
    if(!szFpvPath[0]){
        FreeFileNames();
        m_szFpvPath[0] = 0;
        return;
    }
    {//終端の \ を取り除く
        wchar_t *p = wcsrchr(szFpvPath, L'\\');
        if (p && !p[1] && (p - szFpvPath >= 3)) {
            *p = 0;
        }
    }
    if(!m_hThread){//FPV フォルダ更新監視スレッド
        m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        m_hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, this, 
                                 0,//CREATE_SUSPENDED
                                 &m_uThreadId);
        WaitForSingleObject(m_hEvent, INFINITE);
    }
    if(m_hFind == INVALID_HANDLE_VALUE && m_szFpvPath[0]){
        PostThreadMessage(m_uThreadId, WM_CHANGE_NOTIFICATION, (WPARAM)szFpvPath, (LPARAM)m_hEvent);
        WaitForSingleObject(m_hEvent, INFINITE);
    }
    if(m_hFind != INVALID_HANDLE_VALUE){
        DWORD dwSignal = 0;
        PostThreadMessage(m_uThreadId, WM_CHECK_NOTIFICATION, (WPARAM)&dwSignal, (LPARAM)m_hEvent);
        WaitForSingleObject(m_hEvent, INFINITE);
        if(dwSignal){
            FreeFileNames();
            SearchFpvInternal(szFpvPath);
        }
    }
    if(_wcsicmp(szFpvPath, m_szFpvPath) != 0){
        wcsncpy_s(m_szFpvPath, szFpvPath, _TRUNCATE);
        FreeFileNames();
        SearchFpvInternal(szFpvPath);
        PostThreadMessage(m_uThreadId, WM_CHANGE_NOTIFICATION, (WPARAM)szFpvPath, (LPARAM)m_hEvent);
        WaitForSingleObject(m_hEvent, INFINITE);
    }
}
/////////////////////////////////////////////////////////////////////////////
unsigned WINAPI KbFpvSearcher::ThreadProc(void *pThis)
{//FPV フォルダ更新監視スレッド
    KbFpvSearcher *pSearcher = (KbFpvSearcher*)pThis;
    return pSearcher->ThreadProc();
}
/////////////////////////////////////////////////////////////////////////////
unsigned __fastcall KbFpvSearcher::ThreadProc(void)
{//FPV フォルダ更新監視スレッド
    MSG msg;
    PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
    SetEvent(m_hEvent);
    while(GetMessage(&msg, NULL, 0, 0) > 0){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        if(msg.hwnd){
            continue;
        }
        if(msg.message == WM_CHANGE_NOTIFICATION){
            if(m_hFind != INVALID_HANDLE_VALUE){
                FindCloseChangeNotification(m_hFind);
            }
            m_hFind = FindFirstChangeNotificationW((wchar_t*)msg.wParam, TRUE,
                        FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE);
            SetEvent((HANDLE)msg.lParam);
        }
        else if(msg.message == WM_CHECK_NOTIFICATION){
            if(m_hFind != INVALID_HANDLE_VALUE){
                if(WaitForSingleObject(m_hFind, 0) == WAIT_OBJECT_0){
                    do{
                        FindNextChangeNotification(m_hFind);
                    }while(WaitForSingleObject(m_hFind, 0) == WAIT_OBJECT_0);
                    DWORD *pdwSignal = (DWORD*)msg.wParam;
                    *pdwSignal = 1;
                }
            }
            SetEvent((HANDLE)msg.lParam);
        }
    }
    if(m_hFind != INVALID_HANDLE_VALUE){
        FindCloseChangeNotification(m_hFind);
        m_hFind = INVALID_HANDLE_VALUE;
    }
    return 0;
}
/////////////////////////////////////////////////////////////////////////////
