/*
 * This file is part of the Aaru Data Preservation Suite.
 * Copyright (c) 2019-2026 Natalia Portillo.
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "aaruformat.h"
#include "log.h"
#include "utarray.h"

/**
 * @brief Processes an index block (version 2) from the image stream.
 *
 * Reads and parses an index block (version 2) from the image, returning an array of index entries.
 * This function handles the intermediate index format used in mid-generation AaruFormat versions,
 * providing compatibility with version 2 image files. It reads the IndexHeader2 structure followed
 * by a sequential list of IndexEntry structures, validating the index identifier for format correctness.
 *
 * @param ctx Pointer to the aaruformat context containing the image stream and header information.
 *
 * @return Returns one of the following values:
 * @retval UT_array* Successfully processed the index block. This is returned when:
 *         - The context and image stream are valid
 *         - The index header is successfully read from the position specified in ctx->header.indexOffset
 *         - The index identifier matches IndexBlock2 (version 2 format identifier)
 *         - All index entries are successfully read and stored in the UT_array
 *         - Memory allocation for the index entries array succeeds
 *         - The returned array contains all index entries from the version 2 index block
 *
 * @retval NULL Index processing failed. This occurs when:
 *         - The context parameter is NULL
 *         - The image stream (ctx->imageStream) is NULL or invalid
 *         - Cannot read the IndexHeader2 structure from the image stream
 *         - The index identifier doesn't match IndexBlock2 (incorrect format or corruption)
 *         - Memory allocation fails for the UT_array structure
 *         - File I/O errors occur while reading index entries
 *
 * @note Index Structure (Version 2):
 *       - IndexHeader2: Contains identifier (IndexBlock2), entry count, and enhanced metadata
 *       - IndexEntry array: Sequential list of entries describing block locations and types
 *       - No CRC validation is performed during processing (use verify_index_v2 for validation)
 *       - No compression support in version 2 index format
 *       - Compatible with mid-generation AaruFormat improvements
 *
 * @note Memory Management:
 *       - Returns a newly allocated UT_array that must be freed by the caller using utarray_free()
 *       - On error, any partially allocated memory is cleaned up before returning NULL
 *       - Each IndexEntry is copied into the array (no reference to original stream data)
 *
 * @note Version Compatibility:
 *       - Supports only IndexBlock2 format (not IndexBlock or IndexBlock3)
 *       - Compatible with intermediate AaruFormat image files
 *       - Does not handle subindex or hierarchical index structures (introduced in v3)
 *
 * @warning The caller is responsible for freeing the returned UT_array using utarray_free().
 *          Failure to free the array will result in memory leaks.
 *
 * @warning This function does not validate the CRC integrity of the index data.
 *          Use verify_index_v2() to ensure index integrity before processing.
 *
 * @warning The function assumes ctx->header.indexOffset points to a valid index block.
 *          Invalid offsets may cause file access errors or reading incorrect data.
 */
UT_array *process_index_v2(aaruformat_context *ctx)
{
    TRACE("Entering process_index_v2(%p)", ctx);

    UT_array  *index_entries = NULL;
    IndexEntry entry;

    if(ctx == NULL || ctx->imageStream == NULL) { return NULL; }

    // Initialize the index entries array
    const UT_icd index_entry_icd = {sizeof(IndexEntry), NULL, NULL, NULL};

    utarray_new(index_entries, &index_entry_icd);

    if(index_entries == NULL)
    {
        FATAL("Could not allocate memory for index entries array.");
        TRACE("Exiting process_index_v2() = NULL");
        return NULL;
    }

    // Read the index header
    TRACE("Reading index header at position %llu", ctx->header.indexOffset);
    if(fseek(ctx->imageStream, ctx->header.indexOffset, SEEK_SET) != 0)
    {
        FATAL("Could not seek to index header at %llu.", ctx->header.indexOffset);
        utarray_free(index_entries);

        TRACE("Exiting process_index_v2() = NULL");
        return NULL;
    }
    IndexHeader2 idx_header;
    if(fread(&idx_header, sizeof(IndexHeader2), 1, ctx->imageStream) != 1)
    {
        FATAL("Could not read index header at %llu.", ctx->header.indexOffset);
        utarray_free(index_entries);

        TRACE("Exiting process_index_v2() = NULL");
        return NULL;
    }

    // Check if the index header is valid
    if(idx_header.identifier != IndexBlock2)
    {
        FATAL("Incorrect index identifier.");
        utarray_free(index_entries);

        TRACE("Exiting process_index_v2() = NULL");
        return NULL;
    }

    for(uint64_t i = 0; i < idx_header.entries; i++)
    {
        if(fread(&entry, sizeof(IndexEntry), 1, ctx->imageStream) != 1)
        {
            FATAL("Could not read index entry %llu at %llu.", (unsigned long long)i, ctx->header.indexOffset);
            utarray_free(index_entries);

            TRACE("Exiting process_index_v2() = NULL");
            return NULL;
        }

        utarray_push_back(index_entries, &entry);
    }

    TRACE("Read %llu index entries from index block at position %llu", (unsigned long long)idx_header.entries,
          ctx->header.indexOffset);
    return index_entries;
}

/**
 * @brief Verifies the integrity of an index block (version 2) in the image stream.
 *
 * Checks the CRC64 of the index block without decompressing it. This function performs
 * comprehensive validation of the version 2 index structure including header validation,
 * data integrity verification, and version-specific CRC calculation. It ensures the index
 * block is valid and uncorrupted before the image can be safely used for data access.
 *
 * @param ctx Pointer to the aaruformat context containing image stream and header information.
 *
 * @return Returns one of the following status codes:
 * @retval AARUF_STATUS_OK (0) Successfully verified index integrity. This is returned when:
 *         - The context and image stream are valid
 *         - The index header is successfully read from ctx->header.indexOffset
 *         - The index identifier matches IndexBlock2 (version 2 format)
 *         - Memory allocation for index entries succeeds
 *         - All index entries are successfully read from the image stream
 *         - CRC64 calculation completes successfully with version-specific endianness handling
 *         - The calculated CRC64 matches the expected CRC64 in the index header
 *
 * @retval AARUF_ERROR_NOT_AARUFORMAT (-1) Invalid context or stream. This occurs when:
 *         - The context parameter is NULL
 *         - The image stream (ctx->imageStream) is NULL or invalid
 *
 * @retval AARUF_ERROR_CANNOT_READ_HEADER (-6) Index header reading failed. This occurs when:
 *         - Cannot read the complete IndexHeader2 structure from the image stream
 *         - File I/O errors prevent accessing the header at ctx->header.indexOffset
 *         - Insufficient data available at the specified index offset
 *
 * @retval AARUF_ERROR_CANNOT_READ_INDEX (-19) Index format or data access errors. This occurs when:
 *         - The index identifier doesn't match IndexBlock2 (wrong format or corruption)
 *         - Cannot read all index entries from the image stream
 *         - File I/O errors during index entry reading
 *         - Index structure is corrupted or truncated
 *
 * @retval AARUF_ERROR_NOT_ENOUGH_MEMORY (-9) Memory allocation failed. This occurs when:
 *         - Cannot allocate memory for the index entries array
 *         - System memory exhaustion prevents loading index data for verification
 *
 * @retval AARUF_ERROR_INVALID_BLOCK_CRC (-18) CRC64 validation failed. This occurs when:
 *         - The calculated CRC64 doesn't match the expected CRC64 in the index header
 *         - Index data corruption is detected
 *         - Data integrity verification fails indicating potential file damage
 *
 * @note CRC64 Validation Process:
 *       - Reads all index entries into memory for CRC calculation
 *       - Calculates CRC64 over the complete index entries array
 *       - Applies version-specific endianness conversion for compatibility
 *       - For imageMajorVersion <= AARUF_VERSION_V1: Uses bswap_64() for byte order correction
 *       - Compares calculated CRC64 with the value stored in the IndexHeader2
 *
 * @note Version 2 Enhancements:
 *       - Uses IndexHeader2 structure with enhanced metadata support
 *       - Maintains compatibility with legacy endianness handling
 *       - Supports improved index entry organization compared to version 1
 *
 * @note Memory Management:
 *       - Allocates temporary memory for index entries during verification
 *       - Automatically frees allocated memory on both success and error conditions
 *       - Memory usage is proportional to the number of index entries
 *
 * @note Verification Scope:
 *       - Validates index header structure and identifier
 *       - Verifies data integrity through CRC64 calculation
 *       - Does not validate individual index entry contents or block references
 *       - Does not check for logical consistency of referenced blocks
 *
 * @warning This function reads the entire index into memory for CRC calculation.
 *          Large indexes may require significant memory allocation.
 *
 * @warning The function assumes ctx->header.indexOffset points to a valid index location.
 *          Invalid offsets will cause file access errors or incorrect validation.
 *
 * @warning CRC validation failure indicates potential data corruption and may suggest
 *          the image file is damaged or has been modified outside of library control.
 */
int32_t verify_index_v2(aaruformat_context *ctx)
{
    TRACE("Entering verify_index_v2(%p)", ctx);

    size_t       read_bytes = 0;
    IndexHeader2 index_header;
    uint64_t     crc64         = 0;
    IndexEntry  *index_entries = NULL;

    if(ctx == NULL || ctx->imageStream == NULL)
    {
        FATAL("Invalid context or image stream.");

        TRACE("Exiting verify_index_v2() = AARUF_ERROR_NOT_AARUFORMAT");
        return AARUF_ERROR_NOT_AARUFORMAT;
    }

    // This will traverse all blocks and check their CRC64 without uncompressing them
    TRACE("Checking index integrity at %llu.", ctx->header.indexOffset);
    if(fseek(ctx->imageStream, ctx->header.indexOffset, SEEK_SET) != 0)
    {
        FATAL("Could not seek to index header at %llu.", ctx->header.indexOffset);

        TRACE("Exiting verify_index_v2() = AARUF_ERROR_CANNOT_READ_HEADER");
        return AARUF_ERROR_CANNOT_READ_HEADER;
    }

    // Read the index header
    TRACE("Reading index header at position %llu", ctx->header.indexOffset);
    read_bytes = fread(&index_header, 1, sizeof(IndexHeader2), ctx->imageStream);

    if(read_bytes != sizeof(IndexHeader2))
    {
        FATAL("Could not read index header.");

        TRACE("Exiting verify_index_v2() = AARUF_ERROR_CANNOT_READ_HEADER");
        return AARUF_ERROR_CANNOT_READ_HEADER;
    }

    if(index_header.identifier != IndexBlock2)
    {
        FATAL("Incorrect index identifier.");

        TRACE("Exiting verify_index_v2() = AARUF_ERROR_CANNOT_READ_INDEX");
        return AARUF_ERROR_CANNOT_READ_INDEX;
    }

    TRACE("Index at %llu contains %llu entries.", ctx->header.indexOffset, (unsigned long long)index_header.entries);

    if(index_header.entries != 0 && index_header.entries > SIZE_MAX / sizeof(IndexEntry))
    {
        FATAL("Index entry count %llu is too large to process.", (unsigned long long)index_header.entries);

        TRACE("Exiting verify_index_v2() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
        return AARUF_ERROR_NOT_ENOUGH_MEMORY;
    }

    const size_t entries_size = (size_t)index_header.entries * sizeof(IndexEntry);

    if(entries_size > 0)
    {
        index_entries = malloc(entries_size);

        if(index_entries == NULL)
        {
            FATAL("Cannot allocate memory for index entries.");

            TRACE("Exiting verify_index_v2() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
            return AARUF_ERROR_NOT_ENOUGH_MEMORY;
        }

        read_bytes = fread(index_entries, 1, entries_size, ctx->imageStream);

        if(read_bytes != entries_size)
        {
            FATAL("Could not read index entries.");
            free(index_entries);

            TRACE("Exiting verify_index_v2() = AARUF_ERROR_CANNOT_READ_INDEX");
            return AARUF_ERROR_CANNOT_READ_INDEX;
        }
    }

    crc64_ctx *crc_ctx = aaruf_crc64_init();

    if(crc_ctx == NULL)
    {
        FATAL("Cannot initialize CRC64 context.");
        free(index_entries);

        TRACE("Exiting verify_index_v2() = AARUF_ERROR_NOT_ENOUGH_MEMORY");
        return AARUF_ERROR_NOT_ENOUGH_MEMORY;
    }

    if(entries_size > 0)
    {
        const uint8_t *cursor    = (const uint8_t *)index_entries;
        size_t         remaining = entries_size;

        while(remaining > 0)
        {
            const uint32_t chunk = remaining > UINT32_MAX ? UINT32_MAX : (uint32_t)remaining;

            if(aaruf_crc64_update(crc_ctx, cursor, chunk) != 0)
            {
                FATAL("Failed to update index CRC.");
                aaruf_crc64_free(crc_ctx);
                free(index_entries);

                TRACE("Exiting verify_index_v2() = AARUF_ERROR_CANNOT_READ_INDEX");
                return AARUF_ERROR_CANNOT_READ_INDEX;
            }

            cursor += chunk;
            remaining -= chunk;
        }
    }

    if(aaruf_crc64_final(crc_ctx, &crc64) != 0)
    {
        FATAL("Failed to finalize index CRC.");
        aaruf_crc64_free(crc_ctx);
        free(index_entries);

        TRACE("Exiting verify_index_v2() = AARUF_ERROR_CANNOT_READ_INDEX");
        return AARUF_ERROR_CANNOT_READ_INDEX;
    }

    aaruf_crc64_free(crc_ctx);

    // Due to how C# wrote it, it is effectively reversed
    if(ctx->header.imageMajorVersion <= AARUF_VERSION_V1) crc64 = bswap_64(crc64);

    if(crc64 != index_header.crc64)
    {
        FATAL("Expected index CRC 0x%16llX but got 0x%16llX.", index_header.crc64, crc64);
        free(index_entries);

        TRACE("Exiting verify_index_v2() = AARUF_ERROR_INVALID_BLOCK_CRC");
        return AARUF_ERROR_INVALID_BLOCK_CRC;
    }

    free(index_entries);

    TRACE("Exiting verify_index_v2() = AARUF_OK");
    return AARUF_STATUS_OK;
}