#include <windows.h>
#pragma hdrstop
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "KbUnRar.h"

///////////////////////////////////////////////////////////////////////////////
static const wchar_t *g_cszMethods[] = {
    L"storing",
    L"fastest",
    L"fast",
    L"normal",
    L"good",
    L"best",
    L""//NULL
};
///////////////////////////////////////////////////////////////////////////////
KbUnRar::KbUnRar(UnArcConfig *pConfig)
    : KbUnArc(pConfig)
{
    m_pFile = NULL;
    m_hArc = NULL;
    ZeroMemory(&m_ArchiveData, sizeof(m_ArchiveData));
    ZeroMemory(&m_ArchiveInfo, sizeof(m_ArchiveInfo));
    m_hHeap = HeapCreate(HEAP_NO_SERIALIZE, 65536, 0);
    m_ppRarInfo = NULL;
    m_dwCount = 0;
    m_dwCapacity = 0;
    m_qwTotalSize = 0;
}
///////////////////////////////////////////////////////////////////////////////
KbUnRar::~KbUnRar(void)
{
    if(m_hArc){
        RARCloseArchive(m_hArc);
    }
    if (m_ArchiveData.ArcNameW) {
        free(m_ArchiveData.ArcNameW);
    }
    if(m_pFile){
        m_pFile->Release();
    }
    const DWORD dwCount = m_dwCount;
    DWORD i;
    for(i = 0; i < dwCount; i++){
        RarInfo *pRarInfo = m_ppRarInfo[i];
        if(pRarInfo->pData){
            free(pRarInfo->pData);
            pRarInfo->pData = NULL;
        }
    }
    HeapDestroy(m_hHeap);
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbUnRar::Open(IKpiFile *pFile, IKpiFolder *pFolder, const wchar_t *cszOutputPath)
{
    //return 0;
    const wchar_t *cszFileName;
    if(!pFile->GetRealFileW(&cszFileName)){
        return FALSE;
    }
    m_ArchiveData.ArcNameW = _wcsdup(cszFileName);
    m_ArchiveData.OpenMode = RAR_OM_LIST;
    HANDLE hArc = RAROpenArchiveEx(&m_ArchiveData);
    if(!hArc){
        free(m_ArchiveData.ArcNameW);
        m_ArchiveData.ArcNameW = NULL;
        return FALSE;
    }
    //RARSetCallback(hArc, ProcessDataProc, (LPARAM)this);
    do{
        RARHeaderDataEx header = {0};//RARReadHeaderEx 呼び出し前に初期化が必要
        if(RARReadHeaderEx(hArc, &header) != 0){
            break;
        }
        if((header.FileAttr & FILE_ATTRIBUTE_DIRECTORY) != 0){
            if (RARProcessFileW(hArc, RAR_TEST, NULL, NULL) != 0) {
                break;
            }
            continue;
        }
        if (m_dwCount >= m_dwCapacity) {
            m_dwCapacity += 32;
            if (m_ppRarInfo) {
                m_ppRarInfo = (RarInfo**)HeapReAlloc(m_hHeap,
                                                     HEAP_ZERO_MEMORY,
                                                     m_ppRarInfo,
                                                     sizeof(RarInfo*)*m_dwCapacity);
            }
            else {
                m_ppRarInfo = (RarInfo**)HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY,
                                                    sizeof(RarInfo*)*m_dwCapacity);
            }
        }
        size_t nFileNameSize = (wcslen(header.FileNameW) + 1) * sizeof(wchar_t);
        RarInfo *pRarInfo = (RarInfo*)HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, sizeof(RarInfo) + nFileNameSize);
        pRarInfo->fi.cb = sizeof(KPI_FILEINFO);
        pRarInfo->fi.qwCompressedSize = ((UINT64)header.PackSize | ((UINT64)header.PackSizeHigh << 32));
        pRarInfo->fi.qwOriginalSize = ((UINT64)header.UnpSize | ((UINT64)header.UnpSizeHigh << 32));
        m_qwTotalSize += pRarInfo->fi.qwOriginalSize;
        pRarInfo->pData = NULL;
        pRarInfo->qwDataSize = 0;
        memcpy(pRarInfo->szFileName, header.FileNameW, nFileNameSize);
        kbBSlashToSlash(pRarInfo->szFileName);
        pRarInfo->fi.cszFileName = pRarInfo->szFileName;
        DosDateTimeToFileTime(header.FileTime>>16, header.FileTime, &pRarInfo->fi.ft);
        int method = header.Method - 0x30;
        if(method < 0 || method > 6){
            method = 6;
        }
        pRarInfo->fi.cszMethod = g_cszMethods[method];

        m_ppRarInfo[m_dwCount++] = pRarInfo;
        if(RARProcessFileW(hArc, RAR_TEST, NULL, NULL) != 0){
            break;
        }
    }while(1);
    RARCloseArchive(hArc);
    if(m_ppRarInfo){
        m_ArchiveInfo.cb = sizeof(KPI_ARCHIVEINFO);
        m_ArchiveInfo.dwType = 0;
        m_ArchiveInfo.dwSolid = (m_ArchiveData.Flags & 0x0008) != 0;
        m_ArchiveInfo.dwCount = m_dwCount;
        m_pFile = pFile;
        pFile->AddRef();
        wcsncpy_s(m_szOutputPath, cszOutputPath, _TRUNCATE);
        return TRUE;
    }
    else{
        free(m_ArchiveData.ArcNameW);
        m_ArchiveData.ArcNameW = NULL;
        return FALSE;
    }
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbUnRar::GetArchiveInfo(const KPI_ARCHIVEINFO **ppArchiveInfo)
{
    *ppArchiveInfo = &m_ArchiveInfo;
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbUnRar::EnumFileInfo(DWORD dwIndex, const KPI_FILEINFO **ppFileInfo)
{
    if(dwIndex < m_dwCount){
        *ppFileInfo = &m_ppRarInfo[dwIndex]->fi;
        return TRUE;
    }
    *ppFileInfo = NULL;
    return FALSE;
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KbUnRar::Extract(DWORD dwIndex, IKpiFile **ppFile, UINT64 qwSize)
{
    extern IKpiUnkProvider *g_pProvider;
    *ppFile = NULL;
    if(dwIndex >= m_dwCount){
        return FALSE;
    }
    if(m_qwTotalSize > m_Config.qwLimitTotal){
        return FALSE;
    }
    if(!m_hArc){
        if(!m_ArchiveData.ArcNameW){
            return FALSE;
        }
        m_ArchiveData.OpenMode = RAR_OM_EXTRACT;
        m_hArc = RAROpenArchiveEx(&m_ArchiveData);
        if(!m_hArc){
            return false;
        }
    }
    RarInfo *pInfo = m_ppRarInfo[dwIndex];
    if(!pInfo->pData){
    //まだ解凍していない
        while(1){
            RARHeaderDataEx header = {0};//RARReadHeaderEx 呼び出し前に初期化が必要
            if (RARReadHeaderEx(m_hArc, &header) != 0) {
                return FALSE;
            }
            if((header.FileAttr & FILE_ATTRIBUTE_DIRECTORY) != 0){
                if (RARProcessFileW(m_hArc, RAR_TEST, NULL, NULL) != 0) {
                    break;
                }
                continue;
            }
            kbBSlashToSlash(header.FileNameW);
            DWORD i;
            RarInfo *pRarInfo = NULL;
            for(i = 0; i < m_dwCount; i++){
                if(_wcsicmp(m_ppRarInfo[i]->fi.cszFileName, header.FileNameW) == 0){
                    pRarInfo = m_ppRarInfo[i];
                    break;
                }
            }
            if(!pRarInfo){//おかしい
                return FALSE;
            }
            UINT64 qwSize = ((UINT64)header.UnpSize | ((UINT64)header.UnpSizeHigh << 32));
            RARSetCallback(m_hArc, ProcessDataProc, (LPARAM)pRarInfo);
            if(RARProcessFileW(m_hArc, RAR_TEST, NULL/*m_szOutputPath*/, NULL/*szDest*/) != 0){
                return FALSE;
            }
            if(i == dwIndex){
                break;
            }
        }
    }
    pInfo = m_ppRarInfo[dwIndex];
    if(pInfo->pData){//メモリから IKpiFile を作成
    //第4引数の cszFileName(pInfo->szFileName)は階層構造の区切り文字は不要(あってもなくても良い)
    //(必要に応じて、本体によって階層構造が再現された状態で実ファイルが生成される)
    //第2引数の pBuffer(pInfo->pData)はコピーされないため、IKpiUnArc が解放されるまで有効にしておかなければならない
        kpi_CreateMemoryFile(g_pProvider, pInfo->pData, pInfo->qwDataSize, pInfo->szFileName, ppFile);
        return *ppFile != NULL;
    }
    return FALSE;
}
///////////////////////////////////////////////////////////////////////////////
int CALLBACK KbUnRar::ProcessDataProc(UINT msg, LPARAM UserData, LPARAM P1, LPARAM P2)
{
    if(msg == UCM_PROCESSDATA){
        RarInfo *pRarInfo = (RarInfo*)UserData;
        if(!pRarInfo){
            return -1;
        }
        if(!pRarInfo->pData){
            UINT64 qwSize = pRarInfo->fi.qwOriginalSize;
            if(qwSize < P2){//有り得ない筈
                return -1;
            }
            pRarInfo->pData = (BYTE*)malloc(pRarInfo->fi.qwOriginalSize);
        }
        if(pRarInfo->qwDataSize + P2 <= pRarInfo->fi.qwOriginalSize){
            memcpy(pRarInfo->pData + pRarInfo->qwDataSize, (BYTE*)P1, P2);
            pRarInfo->qwDataSize += P2;
        }
        else{//有り得ない筈
            ZeroMemory(pRarInfo->pData, pRarInfo->fi.qwOriginalSize);
            return -1;
        }
        return 1;
    }
    return 1;
    //return ((KbUnRar*)UserData)->ProcessDataProcInternal(msg, P1, P2);
}
///////////////////////////////////////////////////////////////////////////////
int __fastcall KbUnRar::ProcessDataProcInternal(UINT msg, LPARAM P1, LPARAM P2)
{
    return 0;
}
///////////////////////////////////////////////////////////////////////////////
