/*
 * PROJECT:   Registry manipulation library
 * LICENSE:   GPL - See COPYING in the top level directory
 * COPYRIGHT: Copyright 2005 Filip Navara <navaraf@reactos.org>
 *            Copyright 2001 - 2005 Eric Kohl
 */

#include "cmlib.h"
#define NDEBUG
#include <debug.h>

static BOOLEAN CMAPI
HvpWriteLog(
    PHHIVE RegistryHive)
{
    ULONG FileOffset;
    UINT32 BufferSize;
    UINT32 BitmapSize;
    PUCHAR Buffer;
    PUCHAR Ptr;
    ULONG BlockIndex;
    ULONG LastIndex;
    PVOID BlockPtr;
    BOOLEAN Success;
    static ULONG PrintCount = 0;

    if (PrintCount++ == 0)
    {
        UNIMPLEMENTED;
    }
    return TRUE;

    ASSERT(RegistryHive->ReadOnly == FALSE);
    ASSERT(RegistryHive->BaseBlock->Length ==
           RegistryHive->Storage[Stable].Length * HBLOCK_SIZE);

    DPRINT("HvpWriteLog called\n");

    if (RegistryHive->BaseBlock->Sequence1 !=
        RegistryHive->BaseBlock->Sequence2)
    {
        return FALSE;
    }

    BitmapSize = RegistryHive->DirtyVector.SizeOfBitMap;
    BufferSize = HV_LOG_HEADER_SIZE + sizeof(ULONG) + BitmapSize;
    BufferSize = ROUND_UP(BufferSize, HBLOCK_SIZE);

    DPRINT("Bitmap size %u  buffer size: %u\n", BitmapSize, BufferSize);

    Buffer = RegistryHive->Allocate(BufferSize, TRUE, TAG_CM);
    if (Buffer == NULL)
    {
        return FALSE;
    }

    /* Update first update counter and CheckSum */
    RegistryHive->BaseBlock->Type = HFILE_TYPE_LOG;
    RegistryHive->BaseBlock->Sequence1++;
    RegistryHive->BaseBlock->CheckSum =
        HvpHiveHeaderChecksum(RegistryHive->BaseBlock);

    /* Copy hive header */
    RtlCopyMemory(Buffer, RegistryHive->BaseBlock, HV_LOG_HEADER_SIZE);
    Ptr = Buffer + HV_LOG_HEADER_SIZE;
    RtlCopyMemory(Ptr, "DIRT", 4);
    Ptr += 4;
    RtlCopyMemory(Ptr, RegistryHive->DirtyVector.Buffer, BitmapSize);

    /* Write hive block and block bitmap */
    FileOffset = 0;
    Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG,
                                      &FileOffset, Buffer, BufferSize);
    RegistryHive->Free(Buffer, 0);

    if (!Success)
    {
        return FALSE;
    }

    /* Write dirty blocks */
    FileOffset = BufferSize;
    BlockIndex = 0;
    while (BlockIndex < RegistryHive->Storage[Stable].Length)
    {
        LastIndex = BlockIndex;
        BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex);
        if (BlockIndex == ~0U || BlockIndex < LastIndex)
        {
            break;
        }

        BlockPtr = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress;

        /* Write hive block */
        Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG,
                                         &FileOffset, BlockPtr, HBLOCK_SIZE);
        if (!Success)
        {
            return FALSE;
        }

        BlockIndex++;
        FileOffset += HBLOCK_SIZE;
    }

    Success = RegistryHive->FileSetSize(RegistryHive, HFILE_TYPE_LOG, FileOffset, FileOffset);
    if (!Success)
    {
        DPRINT("FileSetSize failed\n");
        return FALSE;
    }

    /* Flush the log file */
    Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0);
    if (!Success)
    {
        DPRINT("FileFlush failed\n");
    }

    /* Update second update counter and CheckSum */
    RegistryHive->BaseBlock->Sequence2++;
    RegistryHive->BaseBlock->CheckSum =
        HvpHiveHeaderChecksum(RegistryHive->BaseBlock);

    /* Write hive header again with updated sequence counter. */
    FileOffset = 0;
    Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_LOG,
                                      &FileOffset, RegistryHive->BaseBlock,
                                      HV_LOG_HEADER_SIZE);
    if (!Success)
    {
        return FALSE;
    }

    /* Flush the log file */
    Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_LOG, NULL, 0);
    if (!Success)
    {
        DPRINT("FileFlush failed\n");
    }

    return TRUE;
}

static BOOLEAN CMAPI
HvpWriteHive(
    PHHIVE RegistryHive,
    BOOLEAN OnlyDirty)
{
    ULONG FileOffset;
    ULONG BlockIndex;
    ULONG LastIndex;
    PVOID BlockPtr;
    BOOLEAN Success;

    ASSERT(RegistryHive->ReadOnly == FALSE);
    ASSERT(RegistryHive->BaseBlock->Length ==
           RegistryHive->Storage[Stable].Length * HBLOCK_SIZE);

    DPRINT("HvpWriteHive called\n");

    if (RegistryHive->BaseBlock->Sequence1 !=
        RegistryHive->BaseBlock->Sequence2)
    {
        return FALSE;
    }

    /* Update first update counter and CheckSum */
    RegistryHive->BaseBlock->Type = HFILE_TYPE_PRIMARY;
    RegistryHive->BaseBlock->Sequence1++;
    RegistryHive->BaseBlock->CheckSum =
        HvpHiveHeaderChecksum(RegistryHive->BaseBlock);

    /* Write hive block */
    FileOffset = 0;
    Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
                                      &FileOffset, RegistryHive->BaseBlock,
                                      sizeof(HBASE_BLOCK));
    if (!Success)
    {
        return FALSE;
    }

    BlockIndex = 0;
    while (BlockIndex < RegistryHive->Storage[Stable].Length)
    {
        if (OnlyDirty)
        {
            LastIndex = BlockIndex;
            BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex);
            if (BlockIndex == ~0U || BlockIndex < LastIndex)
            {
                break;
            }
        }

        BlockPtr = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress;
        FileOffset = (BlockIndex + 1) * HBLOCK_SIZE;

        /* Write hive block */
        Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
                                          &FileOffset, BlockPtr, HBLOCK_SIZE);
        if (!Success)
        {
            return FALSE;
        }

        BlockIndex++;
    }

    Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0);
    if (!Success)
    {
        DPRINT("FileFlush failed\n");
    }

    /* Update second update counter and CheckSum */
    RegistryHive->BaseBlock->Sequence2++;
    RegistryHive->BaseBlock->CheckSum =
        HvpHiveHeaderChecksum(RegistryHive->BaseBlock);

    /* Write hive block */
    FileOffset = 0;
    Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
                                      &FileOffset, RegistryHive->BaseBlock,
                                      sizeof(HBASE_BLOCK));
    if (!Success)
    {
        return FALSE;
    }

    Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0);
    if (!Success)
    {
        DPRINT("FileFlush failed\n");
    }

    return TRUE;
}

BOOLEAN CMAPI
HvSyncHive(
    PHHIVE RegistryHive)
{
    ASSERT(RegistryHive->ReadOnly == FALSE);

    if (RtlFindSetBits(&RegistryHive->DirtyVector, 1, 0) == ~0U)
    {
        return TRUE;
    }

    /* Update hive header modification time */
    KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp);

    /* Update log file */
    if (!HvpWriteLog(RegistryHive))
    {
        return FALSE;
    }

    /* Update hive file */
    if (!HvpWriteHive(RegistryHive, TRUE))
    {
        return FALSE;
    }

    /* Clear dirty bitmap. */
    RtlClearAllBits(&RegistryHive->DirtyVector);
    RegistryHive->DirtyCount = 0;

    return TRUE;
}

BOOLEAN
CMAPI
HvHiveWillShrink(IN PHHIVE RegistryHive)
{
    /* No shrinking yet */
    UNIMPLEMENTED_ONCE;
    return FALSE;
}

BOOLEAN CMAPI
HvWriteHive(
    PHHIVE RegistryHive)
{
    ASSERT(RegistryHive->ReadOnly == FALSE);

    /* Update hive header modification time */
    KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp);

    /* Update hive file */
    if (!HvpWriteHive(RegistryHive, FALSE))
    {
        return FALSE;
    }

    return TRUE;
}