/**
 *  @file   FindFile.h
 *  @brief  ワイルドカード指定してファイル一覧を取得. 再帰指定可能.
 *  @author tenk* (Masashi Kitamura)
 *  @note
 *  -   win または linux(unix) 用.
 *  -   fname.h を先にincludeしてから、このファイルをincludeすれば、
 *      fname側のルーチンを用いる. そうでなければ交替関数をこのクラス内に用意.
 *  -   Public Domain Software
 */

#ifndef FINDFILE_H_INCLUDED
#define FINDFILE_H_INCLUDED


#if defined _WIN32
 #pragma once
 #include <windows.h>
 #include <tchar.h>
 #if defined _MSC_VER
  #pragma comment(lib, "User32.lib")            // CharNext()で必要...
  #pragma warning(disable: 4244)
 #endif
#else   // グローバル空間を汚すが、同一マクロの再定義は大丈夫だろうとしてtchar関係の辻褄あわせ.
 #define  _LARGEFILE64_SOURCE
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <sys/stat.h>
 #include <fnmatch.h>
 #ifndef TCHAR
  #define TCHAR     char
  #define _T(x)     x
 #endif
#endif


#include <assert.h>
#include <stddef.h>
#include <time.h>


// パスの長さを設定.
#ifdef FINDFILE_MAX_PATH        // 設定済みならそれを利用.
#elif  defined FNAME_MAX_PATH   // fname.h がinclude済みならそれに従う.
#define FINDFILE_MAX_PATH       FNAME_MAX_PATH
#elif  defined _MAX_PATH        // dos/win系の定義.
#define FINDFILE_MAX_PATH       _MAX_PATH
#else                           // なければ 適当.
#define FINDFILE_MAX_PATH       1024
#endif


// ----------------------------------------------------------------------------

/// ファイル検索用クラス.
template<unsigned N = FINDFILE_MAX_PATH >
class FindFileT {
public:
    FindFileT() : recFlag_(0), srchLen_(0) { pathBuf_[0] = 0, srchName_[0] = 0; }
    template<class FNC>
    FindFileT(const TCHAR* srchName, unsigned flags, FNC& fnc) { scan(srchName, flags, fnc); }

    template<class FNC>
    unsigned scan(const TCHAR* srchName, unsigned flags, FNC fnc);

private:
    template<class FNC>
    unsigned scan_sub(FNC& fnc);

  #ifndef FNAME_INCLUDED
    TCHAR*      fname_baseName(const TCHAR* path);
    TCHAR*      fname_cpy(TCHAR dst[], unsigned sz, const TCHAR* src);
  #endif

private:
    TCHAR       pathBuf_ [ N ];
    TCHAR       srchName_[ N ];
    unsigned    srchLen_;
    bool        recFlag_;
    bool        hiddonFlag_;
};



/** ファイル検索.
 */
template<unsigned N>
template<class FNC>
unsigned    FindFileT<N>::scan(const TCHAR* srchName, unsigned flags, FNC fnc)
{
    fname_cpy(pathBuf_ , N, srchName );
    TCHAR*              baseName    = fname_baseName(pathBuf_);
    fname_cpy(srchName_, N, baseName );
    bool recFlag = (flags & 1) != 0;
    #if 1   // "**" の指定を再帰指定とみなす.
    {
        TCHAR* p = srchName_;
        TCHAR* d = srchName_;
        while (*p) {
            if (*p == '*' && p[1] == '*') {
                recFlag = 1;
                ++p;
            }
            *d++ = *p++;
        }
        *d = 0;
    }
    #endif
    recFlag_    = recFlag;
    hiddonFlag_ = (flags & 2) != 0;

  #ifdef _WIN32
    srchLen_ = _tcslen(srchName_);
  #else
    srchLen_ = strlen(srchName_);
    if (baseName == pathBuf_) {
        fname_cpy(pathBuf_, N, _T("./"));
    }
  #endif

    //func_ = fnc;
    return scan_sub(fnc);
}



/** ファイル検索のサブルーチン. 再帰する.
 */
template<unsigned N>
template<class FNC>
unsigned    FindFileT<N>::scan_sub(FNC& fnc)
{
  #if defined _WIN32
    unsigned            num         = 0;
    WIN32_FIND_DATA     findData    = {0};
    WIN32_FIND_DATA*    pFindData   = &findData;    // new WIN32_FIND_DATA;
    HANDLE              hdl         = FindFirstFile(pathBuf_, pFindData);
    size_t              baseNameSz  = N - _tcslen(pathBuf_);
    TCHAR*              baseName    = fname_baseName(pathBuf_);
    *baseName   = _T('\0');

    if (hdl != INVALID_HANDLE_VALUE) {
        // ファイル名を取得. ※ 隠しファイルは対象外にしておく.
        do {
            fname_cpy(baseName, baseNameSz, pFindData->cFileName );
            if ((pFindData->dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_HIDDEN)) == 0) {
                fnc( pathBuf_, pFindData );
                ++num;
            }
        } while (FindNextFile(hdl, pFindData) != 0);
        FindClose(hdl);
    }

    // ディレクトリ再帰でファイル名を取得.
    if (recFlag_ && baseNameSz >= 16) {
        fname_cpy(baseName, 4, _T("*.*") );
        hdl = FindFirstFile(pathBuf_, pFindData);
        if (hdl != INVALID_HANDLE_VALUE) {
            do {
                fname_cpy(baseName, baseNameSz, pFindData->cFileName );
                if ((pFindData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
                    if (_tcscmp(baseName, _T(".")) == 0 || _tcscmp(baseName, _T("..")) == 0
                        || (pFindData->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))           // 隠しフォルダは対象外.
                    {
                        ;
                    } else {
                        size_t l = _tcslen(baseName);
                        if (l+2+srchLen_ < baseNameSz) {
                            fname_cpy(baseName+l  , baseNameSz-l  , _T("/"));
                            fname_cpy(baseName+l+1, baseNameSz-l-1, srchName_ );
                            num += scan_sub(fnc);
                        }
                    }
                }
            } while (FindNextFile(hdl, pFindData) != 0);
            FindClose(hdl);
        }
    }
    // delete pFindData;
    return num;

  #else // linux/unix系...
    struct dirent** namelist = 0;
    unsigned        num      = 0;
    int             dirNum;
    TCHAR*          baseName;
    size_t          baseNameSz;

    baseName    = fname_baseName(pathBuf_);
    *baseName   = 0;
    baseNameSz  = N - strlen(pathBuf_);
    assert(baseNameSz < FINDFILE_MAX_PATH);

    // ディレクトリエントリの取得.
    baseName[-1] = 0;
    dirNum = scandir(pathBuf_, &namelist, 0, alphasort);
    baseName[-1] = _T('/');

    if (namelist) {
        struct stat statBuf;
        int         i;

        // ファイル名を取得.
        for (i = 0; i < dirNum; ++i) {
            struct dirent* d = namelist[i];
            if (fnmatch(srchName_, d->d_name, FNM_NOESCAPE) == 0) {
                fname_cpy(baseName, baseNameSz, d->d_name );
                if (stat(pathBuf_, &statBuf) >= 0) {
                    if ((statBuf.st_mode & S_IFMT) != S_IFDIR) {
                        fnc( pathBuf_ , &statBuf );
                        ++num;
                    }
                }
            }
        }

        // ディレクトリがあれば再帰.
        if (recFlag_ && baseNameSz >= 16) {
            for (i = 0; i < dirNum; ++i) {
                struct dirent* d = namelist[i];
                fname_cpy(baseName, baseNameSz, d->d_name );
                if (stat(pathBuf_, &statBuf) >= 0 && strcmp(baseName,_T(".")) && strcmp(baseName,_T(".."))) {
                    if ((statBuf.st_mode & S_IFMT) == S_IFDIR) {
                        size_t l = strlen(baseName);
                        if (l+2+srchLen_ < baseNameSz) {
                            fname_cpy(baseName+l  , baseNameSz-l  , _T("/"));
                            fname_cpy(baseName+l+1, baseNameSz-l-1, srchName_ );
                            num += scan_sub(fnc);
                        }
                    }
                }
            }
        }

        // 使ったメモリを開放.
        for (i = 0; i < dirNum; ++i) {
            free( namelist[i] );
        }
        free( namelist );
    }
    return num;
  #endif
}



// ----------------------------------------------------------------------------
// ファイル名関係.

#ifndef FNAME_INCLUDED
/** ファイルパス名中のディレクトリを除いたファイル名の位置を返す.
 */
template<unsigned N>
TCHAR *FindFileT<N>::fname_baseName(const TCHAR *adr) {
    const TCHAR *p = adr;
    assert(adr != 0);
    while (*p) {
      #ifdef _WIN32
        if (*p == ':' || *p == '/'|| *p == '\\') adr = (TCHAR*)p + 1;
        p = CharNext(p);
      #else
        if (*p == ':' || *p == '/'             ) adr = (TCHAR*)p + 1;
        p += 1;
      #endif
    }
    return (TCHAR*)adr;
}


/** ファイル名のコピー.
 */
template<unsigned N>
TCHAR *FindFileT<N>::fname_cpy(TCHAR dst[], unsigned dstSz, const TCHAR* src) {
    const TCHAR*    s = src;
    const TCHAR*    e = s + dstSz-1;
    TCHAR*          d = dst;
    while (s < e && *s)
        *d++ = *s++;
    *d = 0;
    return dst;
}
#endif



// ----------------------------------------------------------------------------
// 検索対象のデータを受け取るためのクラス.

/// ファイル名のみの一覧作成用.
template<class CONTA>
struct FindFile_Get_Name {
public:
    typedef typename CONTA::value_type  value_type;

    FindFile_Get_Name(CONTA& rConta) : rConta_(rConta) {}
    void operator()(const TCHAR* path, const void*) {
        rConta_.push_back( value_type(path) );
    }

private:
    //FindFile_Get_Name(const FindFile_Get_Name&);
    void operator=(const FindFile_Get_Name&);

private:
    CONTA&  rConta_;
};


/// ファイル名とサイズの一覧作成用.
template<class CONTA>
struct FindFile_Get_NameSize {
public:
    typedef typename CONTA::value_type  value_type;

    FindFile_Get_NameSize(CONTA& rConta) : rConta_(rConta) {}
  #ifdef _WIN32
    void operator()(const TCHAR* path, const WIN32_FIND_DATA* f) {
        rConta_.push_back( value_type(path, (((unsigned __int64)f->nFileSizeHigh<<32)|f->nFileSizeLow) ) );
    }
  #else
    void operator()(const TCHAR* path, const struct stat* st) {
        rConta_.push_back( value_type(path, st->st_size) );
    }
  #endif

private:
    void operator=(const FindFile_Get_NameSize&);

private:
    CONTA&  rConta_;
};


/// ファイル名とサイズと最終書き込み時間の一覧作成用.
template<class CONTA>
struct FindFile_Get_NameSizeTime {
public:
    typedef typename CONTA::value_type  value_type;

    FindFile_Get_NameSizeTime(CONTA& rConta) : rConta_(rConta) {}
  #ifdef _WIN32
    void operator()(const TCHAR* path, const WIN32_FIND_DATA* f) {
        unsigned __int64 sz     = (((unsigned __int64)f->nFileSizeHigh<<32) | f->nFileSizeLow);
        unsigned __int64 wrtTim = *(unsigned __int64*)&f->ftLastWriteTime;
        rConta_.push_back( value_type(path, sz, wrtTim ) );
    }
  #else
    void operator()(const TCHAR* path, const struct stat *st) {
        rConta_.push_back( value_type(path, st->st_size, st->st_mtime) );
    }
  #endif

private:
    void operator=(const FindFile_Get_NameSizeTime&);

private:
    CONTA&  rConta_;
};


/// ファイル名とstruct stat 情報の一覧作成用.
template<class CONTA>
struct FindFile_Get_NameStat {
public:
    typedef typename CONTA::value_type  value_type;

    FindFile_Get_NameStat(CONTA& rConta) : rConta_(rConta) {}
  #ifdef _WIN32
    void operator()(const TCHAR* path, const WIN32_FIND_DATA* f) {
        struct _tstat64 st;
        ::_tstat64(path, &st);
        rConta_.push_back( value_type(path, &st ) );
    }
  #else
    void operator()(const TCHAR* path, const struct stat *st) {
        rConta_.push_back( value_type(path, st) );
    }
  #endif

private:
    void operator=(const FindFile_Get_NameStat&);

private:
    CONTA&  rConta_;
};



// ----------------------------------------------------------------------------
// 実際に呼び出す関数.

/** ファイル名の一覧作成.
 *  コンテナの要素は 文字列を受け取れること.
 *  @param  rConta      結果を受け取るコンテナ. push_back()可能なこと.
 *  @param  srchPath    検索パス指定
 *  @param  flags       bit0(1):再帰するならon指定.  bit1(2):隠しファイルも検索するならon指定.
 */
template<class CONTA >
unsigned    FindFile_name(CONTA& rConta, const TCHAR* srchPath, unsigned flags=0) {
    return FindFileT<>().scan( srchPath, flags, FindFile_Get_Name<CONTA>(rConta) );
}



/** ファイル名とサイズの一覧作成.
 *  コンテナの要素は 文字列とサイズを受け取れるクラスであること.
 *  @param  rConta      結果を受け取るコンテナ. push_back()可能なこと.
 *  @param  srchPath    検索パス指定
 *  @param  flags       bit0(1):再帰するならon指定.  bit1(2):隠しファイルも検索するならon指定.
 */
template<class CONTA >
unsigned    FindFile_nameSize(CONTA& rConta, const TCHAR* srchPath, unsigned flags=0) {
    return FindFileT<>().scan( srchPath, flags, FindFile_Get_NameSize< CONTA >(rConta) );
}


/** ファイル名とサイズと最終書き込み時間の一覧作成.
 *  コンテナの要素は 文字列とサイズと時間を受け取れるクラスであること.
 *  @param  rConta      結果を受け取るコンテナ. push_back()可能なこと.
 *  @param  srchPath    検索パス指定
 *  @param  flags       bit0(1):再帰するならon指定.  bit1(2):隠しファイルも検索するならon指定.
 */
template<class CONTA >
unsigned    FindFile_nameSizeTime(CONTA& rConta, const TCHAR* srchPath, unsigned flags=0) {
    return FindFileT<>().scan( srchPath, flags, FindFile_Get_NameSizeTime< CONTA >(rConta) );
}

    #if 0   // FindFile_nameSizeTime 用のクラス例.
    /// ファイル名とサイズと最終書き込み時間の構造体.
    struct findfile_name_size_time_t {
        findfile_name_size_time_t(
            const TCHAR* name=_T(""),
            uint64_t     size=0,
            uint64_t     time=0
        ) : name_(name), size_(size), time_(time)
        {
        }
      #ifdef UNICODE
        std::wstring    name_;
      #else
        std::string     name_;
      #endif
        uint64_t        size_;
        uint64_t        time_;
    };

    typedef std::vector< findfile_name_size_time_t > fildFile_name_size_time_vec_t;
    typedef std::list< findfile_name_size_time_t >   fildFile_name_size_time_list_t;
    #endif


/** ファイル名とstruct _stat64(win) / stat(win以外) の一覧作成.
 *  コンテナの要素は 文字列とstat(64)ポインタサイズを受け取れるクラスであること.
 *  @param  rConta      結果を受け取るコンテナ. push_back()可能なこと.
 *  @param  srchPath    検索パス指定
 *  @param  flags       bit0(1):再帰するならon指定.  bit1(2):隠しファイルも検索するならon指定.
 */
template<class CONTA >
unsigned    FindFile_nameStat(CONTA& rConta, const TCHAR* srchPath, unsigned flags=0) {
    return FindFileT<>().scan( srchPath, flags, FindFile_Get_NameStat< CONTA >(rConta) );
}


#endif  // FINDFILE_H_INCLUDED