/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Support for the VolumeUsage sub-class in DiskFS.
 */
#include "StdAfx.h"
#include "DiskImgPriv.h"


/*
 * Initialize structures for a block-structured disk.
 */
DIError DiskFS::VolumeUsage::Create(long numBlocks)
{
    if (numBlocks <= 0 || numBlocks > 32*1024*1024)     // 16GB
        return kDIErrInvalidArg;

    fByBlocks = true;
    fNumSectors = -1;
    fTotalChunks = numBlocks;
    fListSize = numBlocks;
    fList = new unsigned char[fListSize];
    if (fList == NULL)
        return kDIErrMalloc;

    memset(fList, 0, fListSize);

    return kDIErrNone;
}

/*
 * Initialize structures for a track/sector-structured disk.
 */
DIError DiskFS::VolumeUsage::Create(long numTracks, long numSectors)
{
    long count = numTracks * numSectors;
    if (numTracks <= 0 || count <= 0 || count > 32*1024*1024)
        return kDIErrInvalidArg;

    fByBlocks = false;
    fNumSectors = numSectors;
    fTotalChunks = count;
    fListSize = count;
    fList = new unsigned char[fListSize];
    if (fList == NULL)
        return kDIErrMalloc;

    memset(fList, 0, fListSize);

    return kDIErrNone;
}

/*
 * Return the state of a particular chunk.
 */
DIError DiskFS::VolumeUsage::GetChunkState(long block, ChunkState* pState) const
{
    if (!fByBlocks)
        return kDIErrInvalidArg;
    return GetChunkStateIdx(block, pState);
}

DIError DiskFS::VolumeUsage::GetChunkState(long track, long sector,
    ChunkState* pState) const
{
    if (fByBlocks)
        return kDIErrInvalidArg;
    if (track < 0 || sector < 0 || sector >= fNumSectors)
        return kDIErrInvalidArg;
    return GetChunkStateIdx(track * fNumSectors + sector, pState);
}

DIError DiskFS::VolumeUsage::GetChunkStateIdx(int idx, ChunkState* pState) const
{
    if (fList == NULL || idx < 0 || idx >= fListSize) {
        assert(false);
        return kDIErrInvalidArg;
    }

    unsigned char val = fList[idx];
    pState->isUsed = (val & kChunkUsedFlag) != 0;
    pState->isMarkedUsed = (val & kChunkMarkedUsedFlag) != 0;
    pState->purpose = (ChunkPurpose)(val & kChunkPurposeMask);

    return kDIErrNone;
}

/*
 * Set the state of a particular chunk.
 */
DIError DiskFS::VolumeUsage::SetChunkState(long block, const ChunkState* pState)
{
    if (!fByBlocks)
        return kDIErrInvalidArg;
    return SetChunkStateIdx(block, pState);
}

DIError DiskFS::VolumeUsage::SetChunkState(long track, long sector,
    const ChunkState* pState)
{
    if (fByBlocks)
        return kDIErrInvalidArg;
    if (track < 0 || sector < 0 || sector >= fNumSectors)
        return kDIErrInvalidArg;
    return SetChunkStateIdx(track * fNumSectors + sector, pState);
}

DIError DiskFS::VolumeUsage::SetChunkStateIdx(int idx, const ChunkState* pState)
{
    if (fList == NULL || idx < 0 || idx >= fListSize) {
        assert(false);
        return kDIErrInvalidArg;
    }

    unsigned char val = 0;
    if (pState->isUsed) {
        if ((pState->purpose & ~kChunkPurposeMask) != 0) {
            assert(false);
            return kDIErrInvalidArg;
        }
        val |= kChunkUsedFlag;
        val |= (int)pState->purpose;
    }
    if (pState->isMarkedUsed)
        val |= kChunkMarkedUsedFlag;

    fList[idx] = val;

    return kDIErrNone;
}

/*
 * Count up the #of free chunks.
 */
long DiskFS::VolumeUsage::GetActualFreeChunks(void) const
{
    ChunkState cstate;  // could probably do this bitwise...
    int freeCount = 0;
    int funkyCount = 0;

    for (int i = 0; i < fTotalChunks; i++) {
        if (GetChunkStateIdx(i, &cstate) != kDIErrNone) {
            assert(false);
            return -1;
        }

        if (!cstate.isUsed && !cstate.isMarkedUsed)
            freeCount++;

        if ((!cstate.isUsed && cstate.isMarkedUsed) ||
            (cstate.isUsed && !cstate.isMarkedUsed) ||
            (cstate.isUsed && cstate.purpose == kChunkPurposeConflict))
        {
            funkyCount++;
        }
    }

    LOGI(" VU total=%ld free=%d funky=%d",
        fTotalChunks, freeCount, funkyCount);

    return freeCount;
}

/*
 * Convert a ChunkState into a single, hopefully meaningful, character.
 *
 * Possible states:
 *  '.' - !inuse, !marked (free space)
 *  'X' - !inuse, marked (could be embedded volume)
 *  '!' - inuse, !marked (danger!)
 *  '#' - inuse, marked, used by more than one thing
 *  'S' - inuse, marked, used by system (directories, volume bit map)
 *  'I' - inuse, marked, used by file structure (index block)
 *  'F' - inuse, marked, used by file
 */
char DiskFS::VolumeUsage::StateToChar(ChunkState* pState) const
{
    if (!pState->isUsed && !pState->isMarkedUsed)
        return '.';
    if (!pState->isUsed && pState->isMarkedUsed)
        return 'X';
    if (pState->isUsed && !pState->isMarkedUsed)
        return '!';
    assert(pState->isUsed && pState->isMarkedUsed);
    if (pState->purpose == kChunkPurposeUnknown)
        return '?';
    if (pState->purpose == kChunkPurposeConflict)
        return '#';
    if (pState->purpose == kChunkPurposeSystem)
        return 'S';
    if (pState->purpose == kChunkPurposeVolumeDir)
        return 'V';
    if (pState->purpose == kChunkPurposeSubdir)
        return 'D';
    if (pState->purpose == kChunkPurposeUserData)
        return 'F';
    if (pState->purpose == kChunkPurposeFileStruct)
        return 'I';
    if (pState->purpose == kChunkPurposeEmbedded)
        return 'E';

    assert(false);
    return '?';
}

/*
 * Dump the list.
 */
void DiskFS::VolumeUsage::Dump(void) const
{
#define kMapInit "--------------------------------"
    if (fList == NULL) {
        LOGI(" VU asked to dump empty list?");
        return;
    }

    LOGI(" VU VolumeUsage dump (%ld free chunks):",
        GetActualFreeChunks());

    if (fByBlocks) {
        ChunkState cstate;
        char freemap[32+1] = kMapInit;
        int block;
        const int kEntriesPerLine = 32;     // use 20 to match Copy][+

        for (block = 0; block < fTotalChunks; block++) {
            if (GetChunkState(block, &cstate) != kDIErrNone) {
                assert(false);
                return;
            }

            freemap[block % kEntriesPerLine] = StateToChar(&cstate);
            if ((block % kEntriesPerLine) == kEntriesPerLine-1) {
                LOGI("   0x%04x: %s", block-(kEntriesPerLine-1), freemap);
            }
        }
        if ((block % kEntriesPerLine) != 0) {
            memset(freemap + (block % kEntriesPerLine), '-',
                kEntriesPerLine - (block % kEntriesPerLine));
            LOGI("   0x%04x: %s", block-(kEntriesPerLine-1), freemap);
        }
    } else {
        ChunkState cstate;
        char freemap[32+1] = kMapInit;
        long numTracks = fTotalChunks / fNumSectors;
        int track, sector;

        if (fNumSectors > 32) {
            LOGI(" VU too many sectors (%ld)", fNumSectors);
            return;
        }

        LOGI("   map 0123456789abcdef");

        for (track = 0; track < numTracks; track++) {
            for (sector = 0; sector < fNumSectors; sector++) {
                if (GetChunkState(track, sector, &cstate) != kDIErrNone) {
                    assert(false);
                    return;
                }
                freemap[sector] = StateToChar(&cstate);
            }
            LOGI("   %2d: %s", track, freemap);
        }
    }
}
