/**
 * @file       patch.c
 * @author     Karim Vergnes <me@thesola.io>
 * @copyright  GPLv2
 * @brief      UMDiff patch algorithms
 *
 * Functions to redirect read requests and reconstruct the result file given
 * a set of UMDiff commands.
 *
 * @warning This file is reused by umd_livepatch. Do not allocate on the heap!
 */

#include "umdiff.h"

#include <string.h>

static umdiff_File *current_file;

static int
_impl_umdiff_Command_read(umdiff_Command cmd, void *dest, long count, long offset,
                          umdiff_ReadCallback read_source,
                          umdiff_ReadCallback read_patchFile)
{
    long rel_offset = offset - (cmd.sector_start * ISO_SECTOR_SIZE);

    //TODO: implement read system
    // 1. perform first truncated read
    // 2. if repeat-length remains, perform second full read
    // 3. if repeat-length still remains, memcpy second read range onwards
    //    -> saved a bunch of i/o calls at the guest ptr's expense!
}

static umdiff_Command *
_impl_umdiff_File_readIndexCmds(umdiff_File *file, long offset_sector,
                                umdiff_ReadCallback read_patchFile)
{
    int res;
    int index = offset_sector / 1024;

    // stateful optimization: don't re-read the same commands
    if (index == file->last_index)
        return 0;

    if (file->mode == umdiff_FileFlags_HEADER_ONLY) {
        res = read_patchFile(file->commands, sizeof(umdiff_Command) * 1024,
                             file->hdr.index[index]);
        return file->commands;
    } else {
        return file->commands + file->hdr.index[index] - umdiff_File_$COMMANDS_START;
    }

    file->last_index = index;
}

/**
 * Virtual read callback for @ref umdiff_File with @ref umdiff_FileFlags_LOAD_FULL.
 *
 * Automatically set by @ref umdiff_File_readPatched as needed.
 */
int
_impl_umdiff_ReadCallback_fullFile(void *dest, long count, long offset)
{
    // Undo relative-to-absolute translation from _impl_umdiff_Command_read
    long ds = current_file->hdr.data_start;
    long real_offset = offset - ds;

    memcpy(dest, current_file->data + real_offset, count);

    return count;
}

int
umdiff_File_readPatched(umdiff_File *file, void *dest, long count, long offset,
                        umdiff_ReadCallback read_source,
                        umdiff_ReadCallback read_patchFile)
{
    int res;
    umdiff_Command *commands;
    umdiff_Command *curCommand;

#define $offset_sectors (offset / ISO_SECTOR_SIZE)

    current_file = file;

    if (file->mode == umdiff_FileFlags_LOAD_FULL) {
        read_patchFile = _impl_umdiff_ReadCallback_fullFile;
    }

    while (count > 0) {
        commands = _impl_umdiff_File_readIndexCmds(file, $offset_sectors,
                                                   read_patchFile);
        curCommand = 0;

        for (int i = 0; i < 1024; i++) {
            if (commands[i].sector_start <= $offset_sectors) {
                curCommand = &commands[i];
                break;
            }
        }
        res = _impl_umdiff_Command_read(*curCommand, dest, count, offset,
                                        read_source, read_patchFile);
        dest += res;
        count -= res;
    }

    return res;
}

// vim: ft=c.doxygen