/* -*- C++ -*-
 *
 *  SarReader.cpp - Reader from a SAR archive
 *
 *  Copyright (c) 2001-2014 Ogapee. All rights reserved.
 *
 *  ogapee@aqua.dti2.ne.jp
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "SarReader.h"
#define WRITE_LENGTH 4096

#if defined(PSP)
extern int psp_power_resume_number;
#endif

SarReader::SarReader(const char* path, const unsigned char* key_table)
    : DirectReader(path, key_table)
{
    root_archive_info = last_archive_info = &archive_info;
    num_of_sar_archives = 0;
}

SarReader::~SarReader()
{
    close();
}

int SarReader::open(const char* name)
{
    ArchiveInfo* info = new ArchiveInfo();

    if ((info->file_handle = fopen(name, "rb")) == NULL) {
        delete info;
        return -1;
    }

    info->file_name = new char[strlen(name) + 1];
    memcpy(info->file_name, name, strlen(name) + 1);

    readArchive(info);

    last_archive_info->next = info;
    last_archive_info = last_archive_info->next;
    num_of_sar_archives++;

    return 0;
}

void SarReader::readArchive(ArchiveInfo* ai, int archive_type, unsigned int offset)
{
    unsigned int i;

    /* Read header */
    for (i = 0; i < offset; i++)
        readChar(ai->file_handle); // for commands "ns2" and "ns3"

    if (archive_type == ARCHIVE_TYPE_NS2) {
        // new archive type since NScr2.91
        // - header starts with base_offset (byte-swapped), followed by
        //   filename data - doesn't tell how many files!
        // - filenames are surrounded by ""s
        // - new NS2 filename def: "filename", length (4bytes, swapped)
        // - no compression type? really, no compression.
        // - not sure if NS2 uses key_table or not, using default funcs for now
        ai->base_offset = swapLong(readLong(ai->file_handle));
        ai->base_offset += offset;

        // need to parse the whole header to see how many files there are
        ai->num_of_files = 0;
        long unsigned int cur_offset = offset + 4;
        // there's an extra byte at the end of the header, not sure what for
        while (1) {
            unsigned char ch = key_table[fgetc(ai->file_handle)];
            if (ch != '"') break;
            cur_offset++;
            do cur_offset++;
            while ((ch = key_table[fgetc(ai->file_handle)]) != '"');
            cur_offset += 4;
            readLong(ai->file_handle);
            ai->num_of_files++;
        }
        ai->fi_list = new FileInfo[ai->num_of_files];

        // now go back to the beginning and read the file info
        cur_offset = ai->base_offset;
        fseek(ai->file_handle, 4 + offset, SEEK_SET);
        for (i = 0; i < ai->num_of_files; i++) {
            unsigned int count = 0;
            // skip the beginning double-quote
            unsigned char ch = key_table[fgetc(ai->file_handle)];
            while ((ch = key_table[fgetc(ai->file_handle)]) != '"') {
                if ('a' <= ch && ch <= 'z') ch += 'A' - 'a';
                ai->fi_list[i].name[count++] = ch;
            }
            ai->fi_list[i].name[count] = '\0';
            ai->fi_list[i].compression_type = getRegisteredCompressionType(ai->fi_list[i].name);
            ai->fi_list[i].offset = cur_offset;
            ai->fi_list[i].length = swapLong(readLong(ai->file_handle));
            ai->fi_list[i].original_length = ai->fi_list[i].length;
            cur_offset += ai->fi_list[i].length;
        }
    }
    else {
        // old NSA filename def: filename, ending '\0' byte , compr-type byte,
        // start (4byte), length (4byte))
        ai->num_of_files = readShort(ai->file_handle);
        ai->fi_list = new FileInfo[ai->num_of_files];

        ai->base_offset = readLong(ai->file_handle);
        ai->base_offset += offset;

        for (i = 0; i < ai->num_of_files; i++) {
            unsigned char ch;
            int count = 0;

            while ((ch = key_table[fgetc(ai->file_handle)])) {
                if ('a' <= ch && ch <= 'z') ch += 'A' - 'a';
                ai->fi_list[i].name[count++] = ch;
            }
            ai->fi_list[i].name[count] = ch;

            if (archive_type == ARCHIVE_TYPE_NSA)
                ai->fi_list[i].compression_type = readChar(ai->file_handle);
            else
                ai->fi_list[i].compression_type = NO_COMPRESSION;
            ai->fi_list[i].offset = readLong(ai->file_handle) + ai->base_offset;
            ai->fi_list[i].length = readLong(ai->file_handle);

            if (archive_type == ARCHIVE_TYPE_NSA) {
                ai->fi_list[i].original_length = readLong(ai->file_handle);
            }
            else {
                ai->fi_list[i].original_length = ai->fi_list[i].length;
            }

            /* Registered Plugin check */
            if (ai->fi_list[i].compression_type == NO_COMPRESSION)
                ai->fi_list[i].compression_type = getRegisteredCompressionType(ai->fi_list[i].name);

            if (ai->fi_list[i].compression_type == NBZ_COMPRESSION ||
                ai->fi_list[i].compression_type == SPB_COMPRESSION) {
                // Delaying checking decompressed file length to prevent
                // massive random access in the archives at the start-up.
                ai->fi_list[i].original_length = 0;
            }
        }
    }
}

int SarReader::writeHeaderSub(ArchiveInfo* ai, FILE* fp, int archive_type, int nsa_offset)
{
    unsigned int i, j;

    fseek(fp, 0L, SEEK_SET);
    for (int k = 0; k < nsa_offset; k++)
        fputc(0, fp);
    writeShort(fp, ai->num_of_files);
    writeLong(fp, ai->base_offset - nsa_offset);

    for (i = 0; i < ai->num_of_files; i++) {
        for (j = 0; ai->fi_list[i].name[j]; j++)
            fputc(ai->fi_list[i].name[j], fp);
        fputc(ai->fi_list[i].name[j], fp);

        if (archive_type >= ARCHIVE_TYPE_NSA)
            writeChar(fp, ai->fi_list[i].compression_type);

        writeLong(fp, ai->fi_list[i].offset - ai->base_offset);
        writeLong(fp, ai->fi_list[i].length);

        if (archive_type >= ARCHIVE_TYPE_NSA) {
            writeLong(fp, ai->fi_list[i].original_length);
        }
    }

    return 0;
}

int SarReader::writeHeader(FILE* fp)
{
    ArchiveInfo* ai = archive_info.next;
    return writeHeaderSub(ai, fp);
}

size_t SarReader::putFileSub(ArchiveInfo* ai, FILE* fp, int no, size_t offset, size_t length, size_t original_length, int compression_type, bool modified_flag, unsigned char* buffer)
{
    ai->fi_list[no].compression_type = compression_type;
    ai->fi_list[no].length = length;
    ai->fi_list[no].original_length = original_length;

    fseek(fp, offset, SEEK_SET);
    if (modified_flag) {
        if (ai->fi_list[no].compression_type == NBZ_COMPRESSION) {
            writeLong(fp, ai->fi_list[no].original_length);
            fseek(ai->file_handle, ai->fi_list[no].offset + 2, SEEK_SET);
            if (readChar(ai->file_handle) != 'B' || readChar(ai->file_handle) != 'Z') { // in case the original is not compressed in NBZ
                ai->fi_list[no].length = encodeNBZ(fp, length, buffer) + 4;
                ai->fi_list[no].offset = offset;
                return ai->fi_list[no].length;
            }
        }
        else {
            ai->fi_list[no].compression_type = NO_COMPRESSION;
        }
    }
    else {
        fseek(ai->file_handle, ai->fi_list[no].offset, SEEK_SET);
        fread(buffer, 1, ai->fi_list[no].length, ai->file_handle);
    }

    size_t len = ai->fi_list[no].length, c;
    while (len > 0) {
        if (len > WRITE_LENGTH)
            c = WRITE_LENGTH;
        else
            c = len;
        len -= c;
        fwrite(buffer, 1, c, fp);
        buffer += c;
    }

    ai->fi_list[no].offset = offset;

    return ai->fi_list[no].length;
}

size_t SarReader::putFile(FILE* fp, int no, size_t offset, size_t length, size_t original_length, bool modified_flag, unsigned char* buffer)
{
    ArchiveInfo* ai = archive_info.next;
    return putFileSub(ai, fp, no, offset, length, original_length, ai->fi_list[no].compression_type, modified_flag, buffer);
}

int SarReader::close()
{
    ArchiveInfo* info = archive_info.next;

    for (int i = 0; i < num_of_sar_archives; i++) {
        last_archive_info = info;
        info = info->next;
        delete last_archive_info;
    }
    return 0;
}

const char* SarReader::getArchiveName() const
{
    return "sar";
}

int SarReader::getNumFiles()
{
    ArchiveInfo* info = archive_info.next;
    int num = 0;

    for (int i = 0; i < num_of_sar_archives; i++) {
        num += info->num_of_files;
        info = info->next;
    }

    return num;
}

int SarReader::getIndexFromFile(ArchiveInfo* ai, const char* file_name)
{
    unsigned int i, len;

    len = strlen(file_name);
    if (len > MAX_FILE_NAME_LENGTH) len = MAX_FILE_NAME_LENGTH;
    memcpy(capital_name, file_name, len);
    capital_name[len] = '\0';

    for (i = 0; i < len; i++) {
        if ('a' <= capital_name[i] && capital_name[i] <= 'z')
            capital_name[i] += 'A' - 'a';
        else if (capital_name[i] == '/')
            capital_name[i] = '\\';
    }
    for (i = 0; i < ai->num_of_files; i++) {
        if (!strcmp(capital_name, ai->fi_list[i].name)) break;
    }

    return i;
}

size_t SarReader::getFileLength(const char* file_name)
{
    size_t ret;
    if ((ret = DirectReader::getFileLength(file_name))) return ret;

    ArchiveInfo* info = archive_info.next;
    unsigned int j = 0;
    for (int i = 0; i < num_of_sar_archives; i++) {
        j = getIndexFromFile(info, file_name);
        if (j != info->num_of_files) break;
        info = info->next;
    }
    if (!info) return 0;

    if (info->fi_list[j].original_length != 0)
        return info->fi_list[j].original_length;

    int type = info->fi_list[j].compression_type;
    if (type == NO_COMPRESSION)
        type = getRegisteredCompressionType(file_name);
    if (type == NBZ_COMPRESSION || type == SPB_COMPRESSION) {
        info->fi_list[j].original_length = getDecompressedFileLength(type, info->file_handle, info->fi_list[j].offset);
    }

    return info->fi_list[j].original_length;
}

size_t SarReader::getFileSub(ArchiveInfo* ai, const char* file_name, unsigned char* buf)
{
    unsigned int i = getIndexFromFile(ai, file_name);
    if (i == ai->num_of_files) return 0;

#if defined(PSP)
    if (ai->power_resume_number != psp_power_resume_number) {
        FILE* fp = fopen(ai->file_name, "rb");
        ai->file_handle = fp;
        ai->power_resume_number = psp_power_resume_number;
    }
#endif

    int type = ai->fi_list[i].compression_type;
    if (type == NO_COMPRESSION) type = getRegisteredCompressionType(file_name);

    if (type == NBZ_COMPRESSION) {
        return decodeNBZ(ai->file_handle, ai->fi_list[i].offset, buf);
    }
    else if (type == LZSS_COMPRESSION) {
        return decodeLZSS(ai, i, buf);
    }
    else if (type == SPB_COMPRESSION) {
        return decodeSPB(ai->file_handle, ai->fi_list[i].offset, buf);
    }

    fseek(ai->file_handle, ai->fi_list[i].offset, SEEK_SET);
    size_t ret = fread(buf, 1, ai->fi_list[i].length, ai->file_handle);
    if (key_table_flag)
        for (size_t j = 0; j < ret; j++) buf[j] = key_table[buf[j]];
    return ret;
}

size_t SarReader::getFile(const char* file_name, unsigned char* buf, int* location)
{
    size_t ret;
    if ((ret = DirectReader::getFile(file_name, buf, location))) return ret;

    ArchiveInfo* info = archive_info.next;
    size_t j = 0;
    for (int i = 0; i < num_of_sar_archives; i++) {
        if ((j = getFileSub(info, file_name, buf)) > 0) break;
        info = info->next;
    }
    if (location) *location = ARCHIVE_TYPE_SAR;

    return j;
}

SarReader::FileInfo SarReader::getFileByIndex(unsigned int index)
{
    ArchiveInfo* info = archive_info.next;
    for (int i = 0; i < num_of_sar_archives; i++) {
        if (index < info->num_of_files) return info->fi_list[index];
        index -= info->num_of_files;
        info = info->next;
    }
    fprintf(stderr, "SarReader::getFileByIndex  Index %d is out of range\n", index);

    return archive_info.fi_list[index];
}
