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

#include "kbunarc_module.h"
#include "unlha/KbUnLha.h"
#include "unzip/KbUnzip.h"
#include "unrar/KbUnRar.h"
#include "uncab/KbUnCab.h"
#include "untar/KbUnTar.h"
#include "un7z/KbUn7z.h"
//#include "untar/KbGzFile.h"

#ifdef _DEBUG
static const DWORD kbunarc_Version = 0x7FFFFFFF; //デバッグ時は優先度最大
#define KBUNARC_VERSION_STR L"0x7FFFFFFF(Debug)"
#else
static const DWORD kbunarc_Version = 20; //プラグインのバージョン
#define KBUNARC_VERSION_STR L"0.20"
#endif

static const wchar_t kbunarc_Description[] = L"General Archive Decoder v" KBUNARC_VERSION_STR ;
static const wchar_t kbunarc_Copyright[] = L"kbunarc.kpi Copyright (C) 2016-2024 Kobarin\n"
                                           L"unrarsrc 7.0.9 Copyright (C) Alexander L. Roshal\n"
                                           L"LZMA SDK 24.07 Copyright (C) Igor Pavlov\n"
                                           L"zlib 1.3.1 Copyright (C) Mark Adler"
                                           ;
// {E6705BAE-3274-4EF3-9271-278582099547}
static const GUID kbunarc_GUID =
{ 0xe6705bae, 0x3274, 0x4ef3, { 0x92, 0x71, 0x27, 0x85, 0x82, 0x9, 0x95, 0x47 } };

static const DWORD kbunarc_MultipleInstance = KPI_MULTINST_INFINITE;//同時に何個でも作成可能
                                              //KPI_MULTINST_ZERO;  //常に別プロセス起動
                                              //KPI_MULTINST_ONE;   //2個目以降は別プロセス起動
                                              //KPI_MULTINST_UNIQUE;//プロセスをまたいでも１つまで
                                              //KPI_MULTINST_ZERO + N;//(N+1)個目以降は別プロセス起動
                                              //↑どれか１つを選択
static const wchar_t kbunarc_SupportExts[] = L".lzh/.lha/.zip/.neo/.mdz/.s3z/.xmz/.itz/.cab/.rar/.rgm/.rsn/.tar/.tgz/.7z";
static const wchar_t kbunarc_SolidExts[] = L".cab/.rar/.rgm/.rsn/.tgz/.7z";
static const DWORD   kbunarc_SupportConfig = 1;

IKpiUnkProvider *g_pProvider = NULL;
static HINSTANCE g_hKpi = NULL;

//Section 名と Key 名、デフォルト値は KbXXX のソースコードから参照出来る様に
//extern にしておく

extern const wchar_t SEC_GENERAL[] = L"General";

extern const wchar_t KEY_SIZELIMIT_ARCHIVE[] = L"SizeLimitArchive";
static const wchar_t KEYDESC_SIZELIMIT_ARCHIVE[] = L"サイズ制限(展開前)";
static const wchar_t HELP_SIZELIMIT_ARCHIVE[] =
            L"この設定は全ての対応形式に対して適用されます。\r\n"
            L"\r\n"
            L"アーカイブ自体のファイルサイズが指定サイズより大きいときはファイルオープンに失敗させます。\r\n"
            L"ファイルの一覧を取得することも出来ません。\r\n"
            L"0 を指定すると制限をなくしますが、巨大なファイルを扱うと処理に時間がかかり、ディスク容量・メモリ"
            L"を大量に消費するため推奨しません。";

extern const wchar_t KEY_SIZELIMIT_TOTAL[] =   L"SizeLimitTotal";
static const wchar_t KEYDESC_SIZELIMIT_TOTAL[] = L"サイズ制限(全展開後合計)";
static const wchar_t HELP_SIZELIMIT_TOTAL[] =
            L"この設定は CAB/RAR/7Z 形式に対してのみ適用されます。\r\n"
            L"\r\n"
            L"CAB/RAR/7Z はデータを全てメモリ上に展開するため、サイズが大きいと大量のメモリを必要とします。\r\n"
            L"対象アーカイブを最後まで展開した場合の合計サイズが指定サイズより大きいときは展開に失敗させます。\r\n"
            L"ファイルの一覧は取得することが出来ます。\r\n"
            L"個別の格納ファイルサイズが小さくても、合計が制限を超えたら全て失敗します。\r\n"
            L"0 を指定すると制限をなくしますが、巨大なファイルを扱うと処理に時間がかかり、ディスク容量・メモリ"
            L"を大量に消費するため推奨しません。";

static const wchar_t SZ_SIZELIMIT_ARCHIVE_MIN[] = L"0";   //
static const wchar_t SZ_SIZELIMIT_ARCHIVE_MAX[] = L"1024";//
static const wchar_t SZ_SIZELIMIT_TOTAL_MIN[] = L"0";     //
static const wchar_t SZ_SIZELIMIT_TOTAL_MAX[] = L"2048";  //
static const wchar_t LIST_SIZELIMIT[] = L"0\t16\t32\t64\t128\t256\t512\t1024";
static const wchar_t LISTDESC_SIZELIMIT[] = L"制限なし\t16MB\t32MB\t64MB\t128MB\t256MB\t512MB\t1GB\t";
static const DWORD   SIZELIMIT_ARCHIVE_DEFAULT =         512;
static const wchar_t SZ_SIZELIMIT_ARCHIVE_DEFAULT[] = L"512";
static const DWORD   SIZELIMIT_TOTAL_DEFAULT = 512;
static const wchar_t SZ_SIZELIMIT_TOTAL_DEFAULT[] = L"512";
//7z
extern const wchar_t KEY_PPMD[] = L"EnablePPMD";
static const wchar_t KEYDESC_PPMD[] = L"PPMDを展開";
extern const BOOL PPMD_DEFAULT = 0;
extern const wchar_t SZ_PPMD_DEFAULT[] = L"0";
static const wchar_t HELP_PPMD[] =
    L"7-zip の圧縮メソッドが PPMD のときに展開処理を行うかどうかです。\r\n"
    L"true にすると展開します。\r\n"
    L"PPMD は展開処理が非常に低速な為、初期値は false です。\r\n"
    L"音楽データのアーカイブに PPMD を使用することは推奨しません。";

//---------------------------------------------------------------------------
static inline BOOL IsLzhExt(const wchar_t *szExt)
{
    return _wcsicmp(szExt, L".lzh") == 0 ||
           _wcsicmp(szExt, L".lha") == 0;
}
static inline BOOL IsZipExt(const wchar_t *szExt)
{
    return _wcsicmp(szExt, L".zip") == 0 ||
		   _wcsicmp(szExt, L".neo") == 0 ||
           _wcsicmp(szExt, L".mdz") == 0 ||
           _wcsicmp(szExt, L".s3z") == 0 ||
           _wcsicmp(szExt, L".xmz") == 0 ||
           _wcsicmp(szExt, L".itz") == 0;
}
static inline BOOL IsRarExt(const wchar_t *szExt)
{
    return _wcsicmp(szExt, L".rar") == 0 ||
           _wcsicmp(szExt, L".rgm") == 0 ||
           _wcsicmp(szExt, L".rsn") == 0;
}
static inline BOOL IsCabExt(const wchar_t *szExt)
{
    return _wcsicmp(szExt, L".cab") == 0;
}
/*
static inline BOOL IsGcaExt(const wchar_t *szExt)
{
    return _wcsicmp(szExt, L".gca") == 0;
}
*/
static inline BOOL IsTarExt(const wchar_t *szExt)
{
    return _wcsicmp(szExt, L".tar") == 0;
}
static inline BOOL Is7zExt(const wchar_t *szExt)
{
    return _wcsicmp(szExt, L".7z") == 0;
}
static inline BOOL IsTgzExt(const wchar_t *szExt)
{
    return _wcsicmp(szExt, L".tgz") == 0;
}
static IKpiUnArc* __fastcall OpenArchive(IKpiFile *pFile, IKpiFolder *pFolder, 
                                     const wchar_t *cszOutputPath, IKpiConfig *pConfig)
{//拡張子からLzh/Zip/Rar/Cab を判定し、適切なものを開く
    wchar_t szFileName[MAX_PATH];

    pFile->GetFileName(szFileName, sizeof(szFileName));
    const wchar_t *cszExt = wcsrchr(szFileName, L'.');
    if(!cszExt){
        return NULL;
    }
    UINT64 qwSize = pFile->GetSize();
    KbUnArc *ret = NULL;
    KbUnArc::UnArcConfig cfg;
    cfg.qwLimitArc = pConfig->GetInt(SEC_GENERAL, KEY_SIZELIMIT_ARCHIVE, SIZELIMIT_ARCHIVE_DEFAULT) * 1024 * 1024;
    if(cfg.qwLimitArc == 0){
        cfg.qwLimitArc = (UINT64)-1;//制限なし
    }
    if (qwSize > cfg.qwLimitArc) {
        return NULL;//制限を超える
    }
    cfg.qwLimitTotal = pConfig->GetInt(SEC_GENERAL, KEY_SIZELIMIT_TOTAL, SIZELIMIT_TOTAL_DEFAULT) * 1024 * 1024;
    cfg.pConfig = pConfig;
    if(IsLzhExt(cszExt)){
        ret = new KbUnLha(&cfg);
    }
    else if(IsZipExt(cszExt)){
        ret = new KbUnZip(&cfg);
    }
    else if(IsRarExt(cszExt)){
        ret = new KbUnRar(&cfg);
    }
    else if(IsCabExt(cszExt)){
        ret = new KbUnCab(&cfg);
    }
    else if(IsTarExt(cszExt) || IsTgzExt(cszExt)){
        ret = new KbUnTar(&cfg);
    }
    else if(Is7zExt(cszExt)){
        ret = new KbUn7z(&cfg);
    }
    else{
        ret = NULL;
    }
    if(ret){
        if(ret->Open(pFile, pFolder, cszOutputPath)){
            return ret;
        }
        ret->Release();
    }
    return NULL;
}
//////////////////////////////////////////////////////////////////////////////
KbUnArcModule::KbUnArcModule(IKpiConfig *pConfig)
    :m_ModuleInfo{
        sizeof(KPI_UNARC_MODULEINFO), //cb(=sizeof(KPI_UNARC_MODULEINFO))
        KPI_UNARC_MODULE_VERSION,     //dwModuleVersion(=KPI_UNARC_MODULE_VERSION)
        kbunarc_Version,
        kbunarc_MultipleInstance,
        kbunarc_GUID,
        kbunarc_Description,
        kbunarc_Copyright,
        kbunarc_SupportExts,
        kbunarc_SolidExts,
        kbunarc_SupportConfig,
        0, 0, 0, 0                      //reserved
     }
{
    m_pConfig = pConfig;
    pConfig->AddRef();
}
//////////////////////////////////////////////////////////////////////////////
KbUnArcModule::~KbUnArcModule(void)
{
    m_pConfig->Release();
    if(g_pProvider){
        g_pProvider->Release();
        g_pProvider = NULL;
    }
}
//////////////////////////////////////////////////////////////////////////////
void  WINAPI KbUnArcModule::GetModuleInfo(const KPI_UNARC_MODULEINFO **ppInfo)
{
    *ppInfo = &m_ModuleInfo;
}
//////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbUnArcModule::Open(IKpiFile   *pFile,  //アーカイブファイル
                                IKpiFolder *pFolder,//アーカイブファイルがあるフォルダ
                                const wchar_t *cszWorkPath,//作業用フォルダ
                                IKpiUnArc **ppUnArc)
{
    if(!g_pProvider){
        *ppUnArc = NULL;
        return FALSE;
    }
    IKpiUnArc *pUnArc = OpenArchive(pFile, pFolder, cszWorkPath, m_pConfig);
    *ppUnArc = pUnArc;
    return pUnArc != NULL;
}
//////////////////////////////////////////////////////////////////////////////
BOOL  WINAPI KbUnArcModule::EnumConfig(IKpiConfigEnumerator *pEnumerator)
{
    static const KPI_CFG_KEY keys[] = {
    //   type, section, key, keydesc, default, min, max, list, listdesc, help
        //サイズ制限
        {KPI_CFG_TYPE_INT, SEC_GENERAL, KEY_SIZELIMIT_ARCHIVE, KEYDESC_SIZELIMIT_ARCHIVE,
            SZ_SIZELIMIT_ARCHIVE_DEFAULT, SZ_SIZELIMIT_ARCHIVE_MIN, SZ_SIZELIMIT_ARCHIVE_MAX,
            LIST_SIZELIMIT, //list
            LISTDESC_SIZELIMIT, //listdesc
            HELP_SIZELIMIT_ARCHIVE},//help
        //サイズ制限(全展開後合計)
        {KPI_CFG_TYPE_INT, SEC_GENERAL, KEY_SIZELIMIT_TOTAL, KEYDESC_SIZELIMIT_TOTAL,
            SZ_SIZELIMIT_TOTAL_DEFAULT, SZ_SIZELIMIT_TOTAL_MIN, SZ_SIZELIMIT_TOTAL_MAX,
            LIST_SIZELIMIT, //list
            LISTDESC_SIZELIMIT, //listdesc
            HELP_SIZELIMIT_TOTAL},//help
        //PPMD
        {KPI_CFG_TYPE_BOOL, SEC_GENERAL, KEY_PPMD, KEYDESC_PPMD,
            SZ_PPMD_DEFAULT, NULL, NULL,
            NULL, //list
            NULL, //listdesc
            HELP_PPMD},//help
        {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
    };
    for(int i = 0; keys[i].cszSection; i++){
        pEnumerator->EnumKey(&keys[i]);
    }
    return TRUE;
}
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI KbUnArcModule::ApplyConfig(const wchar_t *cszSection,
                                        const wchar_t *cszKey,
                                        INT64 nValue,
                                        double dValue,
                                        const wchar_t *cszValue)
{
    m_pConfig->SetStr(cszSection, cszKey, cszValue);
    return KPI_CFGRET_OK;
}
//////////////////////////////////////////////////////////////////////////////
HRESULT WINAPI kpi_CreateInstance(REFIID riid, void **ppvObject, IKpiUnknown *pUnknown)
{
    *ppvObject = NULL;
    if(!IsEqualIID(riid, IID_IKpiUnArcModule)){
        return E_NOINTERFACE;
    }
    if(!g_pProvider){
        pUnknown->QueryInterface(IID_IKpiUnkProvider, (void**)&g_pProvider);
    }
    if(!g_pProvider){
        return E_NOINTERFACE;
    }
    IKpiConfig      *pConfig;
    kpi_CreateConfig(pUnknown, &kbunarc_GUID, NULL, &pConfig);
    IKpiUnArcModule *pUnArcModule = new KbUnArcModule(pConfig);
    *ppvObject = pUnArcModule;
    pConfig->Release();//KbUnArcModule 内で AddRef しているので必ず必要
    return S_OK;
}
//////////////////////////////////////////////////////////////////////////////
