#include "SkWriter32.h"

struct SkWriter32::Block {
    Block*  fNext;
    size_t  fSize;
    size_t  fAllocated;
    
    size_t  available() const { return fSize - fAllocated; }
    char*   base() { return (char*)(this + 1); }
    const char* base() const { return (const char*)(this + 1); }
    
    uint32_t* alloc(size_t size)
    {
        SkASSERT(SkAlign4(size) == size);
        SkASSERT(this->available() >= size);
        void* ptr = this->base() + fAllocated;
        fAllocated += size;
        SkASSERT(fAllocated <= fSize);
        return (uint32_t*)ptr;
    }
    
    uint32_t* peek32(size_t offset)
    {
        SkASSERT(offset <= fAllocated + 4);
        void* ptr = this->base() + offset;
        return (uint32_t*)ptr;
    }

    static Block* Create(size_t size)
    {
        SkASSERT(SkAlign4(size) == size);
        Block* block = (Block*)sk_malloc_throw(sizeof(Block) + size);
        block->fNext = NULL;
        block->fSize = size;
        block->fAllocated = 0;
        return block;
    }
};

///////////////////////////////////////////////////////////////////////////////

SkWriter32::~SkWriter32()
{
    this->reset();
}

void SkWriter32::reset()
{
    Block* block = fHead;    
    while (block)
    {
        Block* next = block->fNext;
        sk_free(block);
        block = next;
    }
    fHead = fTail = NULL;
    fSize = 0;
}

uint32_t* SkWriter32::reserve(size_t size)
{
    SkASSERT(SkAlign4(size) == size);
    
    Block* block = fTail;

    if (NULL == block)
    {
        SkASSERT(NULL == fHead);
        fHead = fTail = block = Block::Create(SkMax32(size, fMinSize));
    }
    else if (block->available() < size)
    {
        fTail = Block::Create(SkMax32(size, fMinSize));
        block->fNext = fTail;
        block = fTail;
    }
    
    fSize += size;

    return block->alloc(size);
}

uint32_t* SkWriter32::peek32(size_t offset)
{
    SkASSERT(SkAlign4(offset) == offset);
    SkASSERT(offset <= fSize);

    Block* block = fHead;
    SkASSERT(NULL != block);
    
    while (offset >= block->fAllocated)
    {
        offset -= block->fAllocated;
        block = block->fNext;
        SkASSERT(NULL != block);
    }
    return block->peek32(offset);
}

void SkWriter32::flatten(void* dst) const
{
    const Block* block = fHead;
    SkDEBUGCODE(size_t total = 0;)

    while (block)
    {
        size_t allocated = block->fAllocated;
        memcpy(dst, block->base(), allocated);
        dst = (char*)dst + allocated;
        block = block->fNext;

        SkDEBUGCODE(total += allocated;)
        SkASSERT(total <= fSize);
    }
    SkASSERT(total == fSize);
}

void SkWriter32::writePad(const void* src, size_t size) {
    size_t alignedSize = SkAlign4(size);
    char* dst = (char*)this->reserve(alignedSize);
    memcpy(dst, src, size);
    dst += size;
    int n = alignedSize - size;
    while (--n >= 0) {
        *dst++ = 0;
    }
}

#include "SkStream.h"

size_t SkWriter32::readFromStream(SkStream* stream, size_t length) {
    char scratch[1024];
    const size_t MAX = sizeof(scratch);
    size_t remaining = length;
    
    while (remaining != 0) {
        size_t n = remaining;
        if (n > MAX) {
            n = MAX;
        }
        size_t bytes = stream->read(scratch, n);
        this->writePad(scratch, bytes);
        remaining -= bytes;
        if (bytes != n) {
            break;
        }
    }
    return length - remaining;
}

bool SkWriter32::writeToStream(SkWStream* stream) {
    const Block* block = fHead;    
    while (block) {
        if (!stream->write(block->base(), block->fAllocated)) {
            return false;
        }
        block = block->fNext;
    }
    return true;
}