#include <windows.h>
#include <stdlib.h>
#include "kbvgm_gzfile.h"

#define REGTYPE     '0' /* Regular file */
#define AREGTYPE    '\0'/* Regular file; old V7 format */
#define LNKTYPE     '1' /* Link */
#define SYMTYPE     '2' /* Symbolic link */
#define CHRTYPE     '3' /* Character special */
#define BLKTYPE     '4' /* Block special */
#define DIRTYPE     '5' /* Directory */
#define FIFOTYPE    '6' /* FIFO special */
#define CONTTYPE    '7' /* Continguous file */
#define LONGLINK    'L' /* Long name Link by tantan*/

#pragma pack(push, 1) //1バイト境界
static struct TarHeader
{
    union {
        char dummy[512];
        struct {
            char name[100];
            char mode[8];
            char uid[8];
            char gid[8];
            char size[12];
            char mtime[12];
            char chksum[8];
            char typeflag;
            char linkname[100];
            char magic[6];
            char version[2];
            char uname[32];
            char gname[32];
            char devmajor[8];
            char devminor[8];
            /* Following fields were added by GNUtar */
            char atime[12];
            char ctime[12];
            char offset[12];
        };
    };
};
#pragma pack(pop)
static DWORD __fastcall TarStrToDWORD(const char* s, int len)
{//8 進文字列を DWORD に変換
    int i;
    DWORD ret = 0;
    for (i = 0; i < len; i++) {
        if ('0' <= s[i] && s[i] <= '7') {
            ret = (ret << 3) + (s[i] - '0');
        }
        else if (s[i] != ' ') {
            break;
        }
    }
    return ret;
}
static BOOL __fastcall TarCheckSum(const TarHeader* pTarHeader)
{//チェックサムが正しければ TRUE を返す
    const BYTE* dat = (const BYTE*)pTarHeader;
    DWORD dwCheckSum = 0;
    DWORD i = 0;
    while (i < 148) {
        dwCheckSum += dat[i++];
    }
    while (i < 156) {//pTarHeader->chksum
        dwCheckSum += ' ';
        i++;
    }
    while (i < 512) {
        dwCheckSum += dat[i++];
    }
    return dwCheckSum == TarStrToDWORD(pTarHeader->chksum, sizeof(pTarHeader->chksum));
}
///////////////////////////////////////////////////////////////////////////
KbGzFile::KbGzFile(void)
    : KbKpiFileImpl()
{
    m_pFile = NULL;
    m_dwSize = 0;
    m_qwPos = 0;
    m_dwStart = 0;
    m_dwEnd = 0;
    ZeroMemory(&m_zStream, sizeof(m_zStream));
}
///////////////////////////////////////////////////////////////////////////
KbGzFile::~KbGzFile(void)
{
    inflateEnd(&m_zStream);
    if(m_pFile)m_pFile->Release();
}
///////////////////////////////////////////////////////////////////////////
BOOL __fastcall KbGzFile::Open(IKpiFile *pFile)
{
    BYTE header[10];
    pFile->Seek(0, FILE_BEGIN);
    if(pFile->Read(header, 10) != 10){
        return FALSE;
    }
    if(header[0] != 0x1f || header[1] != 0x8b){//gz ではない
        return 0;
    }
    DWORD dwSize = 0;
    pFile->Seek(-4, FILE_END);
    if(pFile->Read(&dwSize, 4) != 4){
        return FALSE;
    }
    pFile->Seek(0, FILE_BEGIN);
    pFile->AddRef();
    m_pFile = pFile;
    m_dwSize = m_dwEnd = dwSize;
    m_dwStart = 0;
    SkipTarHeader();
    return TRUE;
}
///////////////////////////////////////////////////////////////////////////
DWORD  WINAPI KbGzFile::Read(void *pBuffer, DWORD dwSize)
{
    if(!m_zStream.state){
        if (inflateInit2(&m_zStream, 0x20 | 15) != Z_OK){
            return 0;
        }
    }
    DWORD dwRet = 0;
    while(dwRet < dwSize){
        if(m_zStream.avail_in == 0){
            int size = m_pFile->Read(m_temp, sizeof(m_temp));
            m_zStream.avail_in = size;
            m_zStream.next_in = m_temp;
            if(size == 0){
                break;
            }
        }
        m_zStream.avail_out = dwSize - dwRet;
        m_zStream.next_out = ((BYTE*)pBuffer) + dwRet;
        int err = inflate(&m_zStream, Z_SYNC_FLUSH);
        int bytes_written = m_zStream.total_out - m_qwPos;
        if(m_qwPos + bytes_written > m_dwEnd){//m_dwEnd より後ろのデータは捨てる
            bytes_written = m_dwEnd - m_qwPos;
            err = Z_STREAM_END;
        }
        dwRet += bytes_written;
        m_qwPos += bytes_written;
        if(err != Z_OK || bytes_written == 0){
            break;
        }
    }
    return dwRet;
}
///////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbGzFile::Seek(__int64 i64Pos, DWORD dwOrigin)
{
    switch(dwOrigin){
        case FILE_BEGIN: break;
        case FILE_CURRENT: i64Pos += m_qwPos;break;
        case FILE_END: i64Pos += m_dwSize;break;
    }
    i64Pos += m_dwStart;
    if(i64Pos == m_qwPos)return m_qwPos;
    if(i64Pos < m_qwPos){//戻す場合は開き直す
        inflateEnd(&m_zStream);
        ZeroMemory(&m_zStream, sizeof(m_zStream));
        m_pFile->Seek(0, FILE_BEGIN);
        m_qwPos = 0;
    }
    //空読み(低速だがどうせ呼ばれない)
    //実際には TAR ヘッダを調べる為に本クラス内部からのみ呼ばれる
    //頻繁に呼ばれるようになったら kbunarc.kpi の KbGzFile.cpp を組み込む
    const int block_size = 8192;//block_size バイトずつ空読み
    BYTE dummy[block_size];
    DWORD read;
    while(m_qwPos < i64Pos){//Read の呼び出しごとに m_qwPos が更新される
        UINT64 num_bytes = i64Pos - m_qwPos;
        if(num_bytes >  block_size){
            num_bytes = block_size;
        }
        read = Read(dummy, num_bytes);//空読み
        if(read < num_bytes){
            break;
        }
    }
    return m_qwPos;
}
///////////////////////////////////////////////////////////////////////////
UINT64 WINAPI KbGzFile::GetSize(void)
{
    return m_dwEnd - m_dwStart;
}
///////////////////////////////////////////////////////////////////////////
void KbGzFile::SkipTarHeader(void)
{
    TarHeader header;
    DWORD dwStart = 0;      //Tar ヘッダがないときの開始位置
    DWORD dwSize = m_dwSize;//Tar ヘッダがないときのサイズ
    while (1){//通常のファイルが見つかるまでヘッダを飛ばす
        DWORD read = Read(&header, sizeof(header));
        if (read != sizeof(header) ||
            !TarCheckSum(&header)) {
            break;
        }
        dwStart += sizeof(header);
        dwSize = TarStrToDWORD(header.size, sizeof(header.size));
        if(header.typeflag == REGTYPE || //通常のファイル
           header.typeflag == AREGTYPE) {//通常のファイル(V7フォーマット)
           //最初に見つかった通常のファイルだけを対象とする
           break;
        }
        DWORD dwOffset = (dwSize + 511 & ~511);//次のヘッダの(現在位置から見た)開始位置
        dwStart = Seek(dwOffset, FILE_CURRENT);//この時点で m_dwStart と m_dwEnd は設定しないこと
        dwSize = 0;
    }
    m_dwStart = dwStart;
    m_dwEnd = dwStart + dwSize;
    Seek(0, FILE_BEGIN);
}
///////////////////////////////////////////////////////////////////////////
