#include <windows.h>
#pragma hdrstop
#include <stdlib.h>
#include <process.h>
#include <malloc.h>
#pragma warning(disable:4530)
#include "lzma/c/Precomp.h"

#include "KbUn7z.h"

static enum { //7zDec.c より
    k_Copy = 0,
    k_Delta = 3,
    k_LZMA2 = 0x21,
    k_LZMA = 0x30101,
    k_BCJ = 0x3030103,
    k_BCJ2 = 0x303011B,
    k_PPC = 0x3030205,
    k_IA64 = 0x3030401,
    k_ARM = 0x3030501,
    k_ARMT = 0x3030701,
    k_SPARC = 0x3030805,
    k_PPMD = 0x30401,
    k_BZip2 = 0x40202,  //7zHeader.h より
    k_Deflate = 0x40108,//7zHeader.h より
};
static const wchar_t* __fastcall GetMethodFromID(int MethodID)
{
    switch (MethodID) {
    case k_Copy:  return L"Copy"; break;
    case k_Delta: return L"Delta"; break;
    case k_LZMA2: return L"LZMA2"; break;
    case k_LZMA:  return L"LZMA"; break;
    case k_PPMD:  return L"PPMD"; break;//ここまでは解凍可能
    //ここから下は解凍不可(ファイル名の一覧は取得可)
    case k_BCJ:   return L"BCJ"; break;
    case k_BCJ2:  return L"BCJ2"; break;
    case k_PPC:   return L"PPC"; break;
    case k_IA64:  return L"IA64"; break;
    case k_ARM:   return L"ARM"; break;
    case k_ARMT:  return L"ARMT"; break;
    case k_SPARC: return L"SPARC"; break;
    case k_BZip2: return L"BZip2"; break;
    case k_Deflate: return L"Deflate"; break;
    default: break;
    }
    return L"Unknown";
};
SRes KbUn7z::LookInStream::look(const ILookInStream *p, const void **buf, size_t *size)
{//7zstream.c::LookToRead2_Look_Exact 相当
    LookInStream *stream = (LookInStream*)p;
    SRes res = SZ_OK;
    size_t size2 = stream->size - stream->pos;
    if (size2 == 0 && *size != 0)
    {
        stream->pos = 0;
        stream->size = 0;
        if (*size > sizeof(stream->buf))
            *size = sizeof(stream->buf);
        *size = stream->pFile->Read(stream->buf, *size);
        size2 = stream->size = *size;
    }
    if (*size > size2)
        *size = size2;
    *buf = stream->buf + stream->pos;
    return res;
}
SRes KbUn7z::LookInStream::skip(const ILookInStream *p, size_t offset)
{//7zstream.c::LookToRead2_Skip 相当
    LookInStream *stream = (LookInStream*)p;
    stream->pos += offset;
    return SZ_OK;
}
SRes KbUn7z::LookInStream::read(const ILookInStream *p, void *buf, size_t *size)
{//7zstream.c::LookToRead2_Read 相当
    LookInStream *stream = (LookInStream*)p;
    size_t rem = stream->size - stream->pos;
    if (rem == 0){
        *size = stream->pFile->Read(buf, *size);
        return SZ_OK;
    }
    if (rem > *size){
        rem = *size;
    }
    memcpy(buf, stream->buf + stream->pos, rem);
    stream->pos += rem;
    *size = rem;
    return SZ_OK;
}
SRes KbUn7z::LookInStream::seek(const ILookInStream *p, Int64 *pos, ESzSeek origin)
{//7zstream.c::LookToRead2_Seek 相当
    LookInStream *stream = (LookInStream*)p;
    stream->pos = stream->size = 0;
    UINT64 ret = stream->pFile->Seek(*pos, origin);
    if(ret == KPI_FILE_EOF){
        return SZ_ERROR_READ;
    }
    *pos = ret;
    return SZ_OK;
}
///////////////////////////////////////////////////////////////////////////////
static LONG g_lInterlock = -1;
KbUn7z::KbUn7z(UnArcConfig *pConfig)
    :KbUnArc(pConfig)
{
    if(InterlockedIncrement(&g_lInterlock) == 0){
        CrcGenerateTable();
    }
    else{
        InterlockedDecrement(&g_lInterlock);
        while(InterlockedIncrement(&g_lInterlock) == 0){
            Sleep(0);
            InterlockedDecrement(&g_lInterlock);
        }
    }
    m_szOutputPath[0] = 0;
    m_pFile = NULL;

    m_hHeap = HeapCreate(HEAP_NO_SERIALIZE, 65536, 0);
    m_ppSevenZipInfo = NULL;
    ZeroMemory(&m_szAr, sizeof(m_szAr));
    m_blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
    m_outBufferSize = 0;  /* it can have any value before first call (if outBuffer = 0) */
    m_outBuffers = NULL;
    m_allocMain.Alloc = un7z_alloc;
    m_allocMain.Free = un7z_free;
    m_allocTemp.Alloc = un7z_alloc;
    m_allocTemp.Free = un7z_free;
    m_qwTotalSize = 0;
    extern const wchar_t SEC_GENERAL[];
    extern const wchar_t KEY_PPMD[];
    extern const BOOL PPMD_DEFAULT;
    m_bEnablePPMD = m_Config.pConfig->GetInt(SEC_GENERAL, KEY_PPMD, PPMD_DEFAULT) != 0;
    m_bIsPPMD = FALSE;
}
///////////////////////////////////////////////////////////////////////////////
KbUn7z::~KbUn7z(void)
{
    DWORD i;
    /*
    const DWORD dwCount = m_szAr.NumFiles;
    for(i = 0; i < dwCount; i++){
        SevenZipInfo *pInfo = m_ppSevenZipInfo[i];
        //m_outBuffers 内のメモリを参照してるだけなので
        //ここで解放してはいけない
        if(pInfo->pData){
            free(pInfo->pData);
        }
    }
    */
    if(m_outBuffers){
        for(i = 0; i < m_szAr.db.NumFolders; i++){
            un7z_free(&m_allocMain, m_outBuffers[i]);
        }
    }
    SzArEx_Free(&m_szAr, &m_allocMain);
    if(m_hHeap){//m_ppSevenZipInfo 等はここで全て解放される
        HeapDestroy(m_hHeap);
    }
    if(m_pFile){
        m_pFile->Release();
    }
}
///////////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbUn7z::Open(IKpiFile *pFile, IKpiFolder *pFolder, const wchar_t *cszOutputPath)
{
    m_lookStream.pFile = pFile;
    SzArEx_Init(&m_szAr);

    SRes res;
    res = SzArEx_Open(&m_szAr, &m_lookStream, &m_allocMain, &m_allocTemp);
    if(res != SZ_OK){
        SzArEx_Free(&m_szAr, &m_allocMain);
        m_lookStream.pFile = NULL;
        return FALSE;
    }
    if(m_szAr.NumFiles == 0){
        SzArEx_Free(&m_szAr, &m_allocMain);
        m_lookStream.pFile = NULL;
        return FALSE;
    }
    m_hHeap = HeapCreate(HEAP_NO_SERIALIZE, 65536, 0);
    m_ppSevenZipInfo = (SevenZipInfo**)HeapAlloc(m_hHeap,
                                                 HEAP_ZERO_MEMORY,
                                                 sizeof(SevenZipInfo*)*m_szAr.NumFiles);
    UInt32 i;
    const wchar_t *cszMethod = NULL;
    for (i = 0; i < m_szAr.NumFiles; i++)
    {
        size_t len = SzArEx_GetFileNameUtf16(&m_szAr, i, NULL);//len=終端の 0 を含むサイズ
        BOOL bIsDir = (SzArEx_IsDir(&m_szAr, i) != 0);
        SevenZipInfo *pInfo = (SevenZipInfo*)HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, 
                    sizeof(SevenZipInfo) + (len + bIsDir)*sizeof(wchar_t));//(bIsDir)=末尾に / を付加する分
        pInfo->fi.cb = sizeof(KPI_FILEINFO);
        pInfo->fi.qwOriginalSize = SzArEx_GetFileSize(&m_szAr, i);
        pInfo->bIsDir = bIsDir;
        pInfo->pData = NULL;
        pInfo->qwDataSize = 0;
        m_qwTotalSize += pInfo->fi.qwOriginalSize;
        SzArEx_GetFileNameUtf16(&m_szAr, i, (UInt16*)pInfo->szFileName);
        kbBSlashToSlash(pInfo->szFileName);
        if(bIsDir){//末尾に / 付加
           if(len > 1 && pInfo->szFileName[len-2] != L'/'){
                pInfo->szFileName[len-1] = L'/';
                pInfo->szFileName[len] = 0;
            }
        }
        pInfo->fi.cszFileName = pInfo->szFileName;
        if (SzBitWithVals_Check(&m_szAr.MTime, i)){
            FileTimeToLocalFileTime((const FILETIME*)&m_szAr.MTime.Vals[i], &pInfo->fi.ft);
        }
        m_ppSevenZipInfo[i] = pInfo;
        if(pInfo->bIsDir){
            continue;
        }
        //圧縮メソッドを取得
        //fileIndex と folderIndex, coderIndex の関係が不明なので
        //最初に取得出来た MethodID を圧縮メソッドとして採用する
        int folderIndex = m_szAr.FileToFolder[i];
        if(folderIndex < 0){
            folderIndex = 0;
        }
        if (cszMethod) {//既に取得済み
            pInfo->fi.cszMethod = cszMethod;
            continue;
        }
        CSzFolder folder;
        CSzData sd;
        SRes res;
        sd.Data = m_szAr.db.CodersData + m_szAr.db.FoCodersOffsets[folderIndex];
        sd.Size = m_szAr.db.FoCodersOffsets[(size_t)folderIndex + 1] - m_szAr.db.FoCodersOffsets[folderIndex];
        res = SzGetNextFolderItem(&folder, &sd);
        if(res == 0 && folder.NumCoders >= 1){
        //folder.NumCoders != 1 の場合には未対応(Coders[0]のMethodID値のみ取得)
        //(fileIndex と folderIndex, coderIndex の対応関係が分からないので)
            cszMethod = GetMethodFromID(folder.Coders[0].MethodID);
            if(folder.Coders[0].MethodID == k_PPMD){
                m_bIsPPMD = TRUE;
            }
        }
        else{
            cszMethod = L"";
        }
        pInfo->fi.cszMethod = cszMethod;
    }
    m_pFile = pFile;
    pFile->AddRef();
    m_ArchiveInfo.cb = sizeof(m_ArchiveInfo);
    m_ArchiveInfo.dwType = 0;
    m_ArchiveInfo.dwCount = m_szAr.NumFiles;
    m_ArchiveInfo.dwSolid = TRUE;
    return TRUE;
}
DWORD __fastcall KbUn7z::Find(const wchar_t* cszFileName)
{
    DWORD i;
    const DWORD dwCount = m_szAr.NumFiles;
    for(i = 0; i < dwCount; i++){
        if(_wcsicmp(m_ppSevenZipInfo[i]->fi.cszFileName, cszFileName) == 0){
            return i;
        }
    }
    return -1;
}
///////////////////////////////////////////////////////////////////////////////
KbUn7z::SevenZipInfo* __fastcall KbUn7z::FindSevenZipInfo(const wchar_t *cszFileName)
{
    wchar_t szFileName[MAX_PATH];
    wcsncpy_s(szFileName, cszFileName, _TRUNCATE);
    kbBSlashToSlash(szFileName);
    DWORD dwIndex = Find(szFileName);
    if(dwIndex != 0xFFFFFFFF){
        return m_ppSevenZipInfo[dwIndex];
    }
    return NULL;
}
BOOL WINAPI KbUn7z::GetArchiveInfo(const KPI_ARCHIVEINFO **ppArchiveInfo)
{
    *ppArchiveInfo = &m_ArchiveInfo;
    return TRUE;
}
BOOL WINAPI KbUn7z::EnumFileInfo(DWORD dwIndex, const KPI_FILEINFO **ppFileInfo)
{
    if(dwIndex < m_szAr.NumFiles){
        *ppFileInfo = &m_ppSevenZipInfo[dwIndex]->fi;
        return TRUE;
    }
    *ppFileInfo = NULL;
    return FALSE;
}
BOOL WINAPI KbUn7z::Extract(DWORD dwIndex, IKpiFile **ppFile, UINT64 qwSize)
{
    extern IKpiUnkProvider *g_pProvider;
    *ppFile = NULL;
    if(dwIndex >= m_szAr.NumFiles){
        return FALSE;
    }
    if(m_qwTotalSize > m_Config.qwLimitTotal){
        return FALSE;//サイズが制限を超えてたら失敗させる
    }
    if(m_bIsPPMD && !m_bEnablePPMD){
        return FALSE;//PPMD が無効の場合は失敗させる
    }
    if(!m_outBuffers){
        m_outBuffers = (BYTE**)HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, 
                            m_szAr.db.NumFolders * sizeof(BYTE*));
    }
    SevenZipInfo *pInfo = m_ppSevenZipInfo[dwIndex];
    if(pInfo->nState == 0){
        DWORD i;
        for (i = 0; i <= dwIndex; i++){
            pInfo = m_ppSevenZipInfo[i];
            if(pInfo->nState < 0){
                break;
            }
            if(pInfo->nState > 0){
                continue;
            }
            if(pInfo->bIsDir){
                pInfo->nState = 1;
                continue;
            }
            int folderIndex = m_szAr.FileToFolder[i];
            if (folderIndex < 0) {
                folderIndex = 0;
            }
            size_t offset = 0;
            size_t outSizeProcessed = 0;
            SRes res;
            res = SzArEx_Extract(&m_szAr, &m_lookStream, i,
                &m_blockIndex, &m_outBuffers[folderIndex], &m_outBufferSize,
                &offset, &outSizeProcessed,
                &m_allocMain, &m_allocTemp);
            if(res != SZ_OK){//失敗
                pInfo->nState = -1;
                break;
            }
            pInfo->nState = 1;
            //m_outBuffers[folderIndex] 上のメモリを参照する
            //pInfo->pData を個別に解放してはいけない
            pInfo->pData = m_outBuffers[folderIndex] + offset;
            pInfo->qwDataSize = outSizeProcessed;
        }
    }
    pInfo = m_ppSevenZipInfo[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;
}
