/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ZIP_PKG_FILE_H
#define ZIP_PKG_FILE_H

#include <map>
#include "pkg_pkgfile.h"
#include "pkg_utils.h"

namespace Hpackage {
// Local file header: descript in APPNOTE-6.3.4
//    local file header signature     4 bytes  (0x04034b50)
//    version needed to extract       2 bytes
//    general purpose bit flag        2 bytes
//    compression method              2 bytes
//    last mod file time              2 bytes
//    last mod file date              2 bytes
//    crc-32                          4 bytes
//    compressed size                 4 bytes
//    uncompressed size               4 bytes
//    file name length                2 bytes
//    extra field length              2 bytes
struct __attribute__((packed)) LocalFileHeader {
    uint32_t signature = 0;
    uint16_t versionNeeded = 0;
    uint16_t flags = 0;
    uint16_t compressionMethod = 0;
    uint16_t modifiedTime = 0;
    uint16_t modifiedDate = 0;
    uint32_t crc = 0;
    uint32_t compressedSize = 0;
    uint32_t uncompressedSize = 0;
    uint16_t nameSize = 0;
    uint16_t extraSize = 0;
};

// central file header
//    Central File header:
//    central file header signature   4 bytes  (0x02014b50)
//    version made by                 2 bytes
//    version needed to extract       2 bytes
//    general purpose bit flag        2 bytes
//    compression method              2 bytes
//    last mod file time              2 bytes
//    last mod file date              2 bytes
//    crc-32                          4 bytes
//    compressed size                 4 bytes
//    uncompressed size               4 bytes
//    file name length                2 bytes
//    extra field length              2 bytes
//    file comment length             2 bytes
//    disk number start               2 bytes
//    internal file attributes        2 bytes
//    external file attributes        4 bytes
//    relative offset of local header 4 bytes
struct __attribute__((packed)) CentralDirEntry {
    uint32_t signature = 0;
    uint16_t versionMade = 0;
    uint16_t versionNeeded = 0;
    uint16_t flags = 0; // general purpose bit flag
    uint16_t compressionMethod = 0;
    uint16_t modifiedTime = 0;
    uint16_t modifiedDate = 0;
    uint32_t crc = 0;
    uint32_t compressedSize = 0;
    uint32_t uncompressedSize = 0;
    uint16_t nameSize = 0;
    uint16_t extraSize = 0;
    uint16_t commentSize = 0;
    uint16_t diskNumStart = 0;
    uint16_t internalAttr = 0;
    uint32_t externalAttr = 0;
    uint32_t localHeaderOffset = 0;
};

struct __attribute__((packed)) EndCentralDir {
    uint32_t signature = 0;
    uint16_t numDisk = 0;
    uint16_t startDiskOfCentralDir = 0;
    uint16_t totalEntriesInThisDisk = 0;
    uint16_t totalEntries = 0;
    uint32_t sizeOfCentralDir = 0;
    uint32_t offset = 0;
    uint16_t commentLen = 0;
};

struct __attribute__((packed)) DataDescriptor {
    uint32_t signature = 0;
    uint32_t crc = 0;
    uint32_t compressedSize = 0;
    uint32_t uncompressedSize = 0;
};

struct __attribute__((packed)) Zip64EndCentralDirRecord {
    uint32_t signature = 0; // 0x06064b50
    uint64_t size = 0;
    uint16_t versionMadeBy = 0;
    uint16_t versionNeedToExtra = 0;
    uint32_t numberOfThisDisk = 0;
    uint32_t startOfthisDisk = 0;
    uint64_t totalEntriesInThisDisk = 0;
    uint64_t totalEntries = 0;
    uint64_t sizeOfCentralDir = 0;
    uint64_t offset = 0;
};

struct __attribute__((packed)) Zip64EndCentralDirLocator {
    uint32_t signature = 0; // 0x07064b50
    uint32_t numberOfDisk = 0;
    uint64_t endOfCentralDirectoryRecord = 0;
    uint32_t totalNumberOfDisks = 0;
};

class ZipFileEntry : public PkgEntry {
public:
    ZipFileEntry(PkgFilePtr pkgFile, uint32_t nodeId) : PkgEntry(pkgFile, nodeId) {}

    ~ZipFileEntry() override {}

    int32_t Init(const PkgManager::FileInfoPtr fileInfo, PkgStreamPtr inStream) override;

    const FileInfo *GetFileInfo() const override
    {
        return &fileInfo_.fileInfo;
    };

    int32_t EncodeHeader(PkgStreamPtr inStream, size_t startOffset, size_t &encodeLen) override;

    int32_t Pack(PkgStreamPtr inStream, size_t startOffset, size_t &encodeLen) override;

    int32_t Unpack(PkgStreamPtr outStream) override;

    int32_t DecodeHeader(PkgBuffer &buffer, size_t headerOffset, size_t dataOffset,
        size_t &decodeLen) override;

    int32_t EncodeCentralDirEntry(const PkgStreamPtr stream, size_t startOffset, size_t &encodeLen);

    int32_t DoDecodeCentralDirEntry(PkgBuffer &buffer, size_t &decodeLen,
        size_t currLen, uint16_t nameSize, uint16_t extraSize);

    int32_t DecodeCentralDirEntry(PkgStreamPtr inStream, PkgBuffer &buffer, size_t currentPos,
        size_t &decodeLen);

protected:
    ZipFileInfo fileInfo_ {};
    uint32_t crc32_ {0};

private:
    int32_t DecodeLocalFileHeaderCheck(PkgStreamPtr inStream, PkgBuffer &data, size_t currentPos);

    int32_t DecodeLocalFileHeader(PkgStreamPtr inStream, PkgBuffer &data, size_t currentPos,
        size_t &decodeLen);

    int32_t EncodeLocalFileHeader(uint8_t *buffer, size_t bufferLen, bool hasDataDesc, size_t nameLen);

    int32_t EncodeDataDescriptor(const PkgStreamPtr stream, size_t startOffset, uint32_t &encodeLen) const;

    void CombineTimeAndDate(time_t &time, uint16_t modifiedTime, uint16_t modifiedDate) const;

    int32_t PackStream(PkgStreamPtr inStream, size_t startOffset, size_t &encodeLen,
        const PkgAlgorithm::PkgAlgorithmPtr algorithm, const PkgStreamPtr outStream);
};

class ZipPkgFile : public PkgFileImpl {
public:
    explicit ZipPkgFile(PkgManager::PkgManagerPtr manager, PkgStreamPtr stream, PkgInfoPtr header = nullptr)
        : PkgFileImpl(manager, stream, PkgFile::PKG_TYPE_ZIP)
    {
        UNUSED(header);
        pkgInfo_.signMethod = PKG_SIGN_METHOD_RSA;
        pkgInfo_.digestMethod = PKG_DIGEST_TYPE_SHA256;
    }
    ~ZipPkgFile() override {}

    int32_t AddEntry(const PkgManager::FileInfoPtr file, const PkgStreamPtr inStream) override;

    int32_t SavePackage(size_t &signOffset) override;
    int32_t GetFileLength(size_t &fileLen);
    int32_t LoadPackage(std::vector<std::string> &fileNames, VerifyFunction verifier = nullptr) override;

private:
    int32_t LoadPackage(std::vector<std::string> &fileNames, PkgBuffer &buffer,
        uint32_t endDirLen, size_t endDirPos, size_t &readLen);
    int32_t ParseFileEntries(std::vector<std::string> &fileNames, const EndCentralDir &endDir,
        size_t currentPos, size_t fileLen);

private:
    PkgInfo pkgInfo_ {};
    size_t currentOffset_ = 0;
};
} // namespace Hpackage
#endif
