//========================================================================
//
// CachedFile.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright 2009 Stefan Thomas <thomas@eload24.com>
// Copyright 2010, 2011 Hib Eris <hib@hiberis.nl>
// Copyright 2010, 2018, 2019 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2013 Julien Nabet <serval2412@yahoo.fr>
//
//========================================================================

#include <config.h>
#include "CachedFile.h"

//------------------------------------------------------------------------
// CachedFile
//------------------------------------------------------------------------

CachedFile::CachedFile(CachedFileLoader *cacheLoader, GooString *uriA)
{
    uri = uriA;
    loader = cacheLoader;

    streamPos = 0;
    chunks = new std::vector<Chunk>();
    length = 0;

    length = loader->init(uri, this);
    refCnt = 1;

    if (length != ((size_t)-1)) {
        chunks->resize(length / CachedFileChunkSize + 1);
    } else {
        error(errInternal, -1, "Failed to initialize file cache for '{0:t}'.", uri);
        chunks->resize(0);
    }
}

CachedFile::~CachedFile()
{
    delete uri;
    delete loader;
    delete chunks;
}

void CachedFile::incRefCnt()
{
    refCnt++;
}

void CachedFile::decRefCnt()
{
    if (--refCnt == 0)
        delete this;
}

long int CachedFile::tell()
{
    return streamPos;
}

int CachedFile::seek(long int offset, int origin)
{
    if (origin == SEEK_SET) {
        streamPos = offset;
    } else if (origin == SEEK_CUR) {
        streamPos += offset;
    } else {
        streamPos = length + offset;
    }

    if (streamPos > length) {
        streamPos = 0;
        return 1;
    }

    return 0;
}

int CachedFile::cache(const std::vector<ByteRange> &origRanges)
{
    std::vector<int> loadChunks;
    int numChunks = length / CachedFileChunkSize + 1;
    std::vector<bool> chunkNeeded(numChunks);
    int startChunk, endChunk;
    std::vector<ByteRange> chunk_ranges, all;
    ByteRange range;
    const std::vector<ByteRange> *ranges = &origRanges;

    if (ranges->empty()) {
        range.offset = 0;
        range.length = length;
        all.push_back(range);
        ranges = &all;
    }

    for (int i = 0; i < numChunks; ++i)
        chunkNeeded[i] = false;
    for (const ByteRange &r : *ranges) {

        if (r.length == 0)
            continue;
        if (r.offset >= length)
            continue;

        const size_t start = r.offset;
        size_t end = start + r.length - 1;
        if (end >= length)
            end = length - 1;

        startChunk = start / CachedFileChunkSize;
        endChunk = end / CachedFileChunkSize;
        for (int chunk = startChunk; chunk <= endChunk; chunk++) {
            if ((*chunks)[chunk].state == chunkStateNew) {
                chunkNeeded[chunk] = true;
            }
        }
    }

    int chunk = 0;
    while (chunk < numChunks) {
        while (!chunkNeeded[chunk] && (++chunk != numChunks))
            ;
        if (chunk == numChunks)
            break;
        startChunk = chunk;
        loadChunks.push_back(chunk);

        while ((++chunk != numChunks) && chunkNeeded[chunk]) {
            loadChunks.push_back(chunk);
        }
        endChunk = chunk - 1;

        range.offset = startChunk * CachedFileChunkSize;
        range.length = (endChunk - startChunk + 1) * CachedFileChunkSize;

        chunk_ranges.push_back(range);
    }

    if (chunk_ranges.size() > 0) {
        CachedFileWriter writer = CachedFileWriter(this, &loadChunks);
        return loader->load(chunk_ranges, &writer);
    }

    return 0;
}

size_t CachedFile::read(void *ptr, size_t unitsize, size_t count)
{
    size_t bytes = unitsize * count;
    if (length < (streamPos + bytes)) {
        bytes = length - streamPos;
    }

    if (bytes == 0)
        return 0;

    // Load data
    if (cache(streamPos, bytes) != 0)
        return 0;

    // Copy data to buffer
    size_t toCopy = bytes;
    while (toCopy) {
        int chunk = streamPos / CachedFileChunkSize;
        int offset = streamPos % CachedFileChunkSize;
        size_t len = CachedFileChunkSize - offset;

        if (len > toCopy)
            len = toCopy;

        memcpy(ptr, (*chunks)[chunk].data + offset, len);
        streamPos += len;
        toCopy -= len;
        ptr = (char *)ptr + len;
    }

    return bytes;
}

int CachedFile::cache(size_t rangeOffset, size_t rangeLength)
{
    std::vector<ByteRange> r;
    ByteRange range;
    range.offset = rangeOffset;
    range.length = rangeLength;
    r.push_back(range);
    return cache(r);
}

//------------------------------------------------------------------------
// CachedFileWriter
//------------------------------------------------------------------------

CachedFileWriter::CachedFileWriter(CachedFile *cachedFileA, std::vector<int> *chunksA)
{
    cachedFile = cachedFileA;
    chunks = chunksA;

    if (chunks) {
        offset = 0;
        it = (*chunks).begin();
    }
}

CachedFileWriter::~CachedFileWriter() { }

size_t CachedFileWriter::write(const char *ptr, size_t size)
{
    const char *cp = ptr;
    size_t len = size;
    size_t nfree, ncopy;
    size_t written = 0;
    size_t chunk;

    if (!len)
        return 0;

    while (len) {
        if (chunks) {
            if (offset == CachedFileChunkSize) {
                ++it;
                if (it == (*chunks).end())
                    return written;
                offset = 0;
            }
            chunk = *it;
        } else {
            offset = cachedFile->length % CachedFileChunkSize;
            chunk = cachedFile->length / CachedFileChunkSize;
        }

        if (chunk >= cachedFile->chunks->size()) {
            cachedFile->chunks->resize(chunk + 1);
        }

        nfree = CachedFileChunkSize - offset;
        ncopy = (len >= nfree) ? nfree : len;
        memcpy(&((*cachedFile->chunks)[chunk].data[offset]), cp, ncopy);
        len -= ncopy;
        cp += ncopy;
        offset += ncopy;
        written += ncopy;

        if (!chunks) {
            cachedFile->length += ncopy;
        }

        if (offset == CachedFileChunkSize) {
            (*cachedFile->chunks)[chunk].state = CachedFile::chunkStateLoaded;
        }
    }

    if ((chunk == (cachedFile->length / CachedFileChunkSize)) && (offset == (cachedFile->length % CachedFileChunkSize))) {
        (*cachedFile->chunks)[chunk].state = CachedFile::chunkStateLoaded;
    }

    return written;
}

//------------------------------------------------------------------------
