/*
 * Copyright 2013 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkDataTable_DEFINED
#define SkDataTable_DEFINED

#include "../private/SkTDArray.h"
#include "SkChunkAlloc.h"
#include "SkData.h"
#include "SkString.h"

/**
 *  Like SkData, SkDataTable holds an immutable data buffer. The data buffer is
 *  organized into a table of entries, each with a length, so the entries are
 *  not required to all be the same size.
 */
class SK_API SkDataTable : public SkRefCnt {
public:
    /**
     *  Returns true if the table is empty (i.e. has no entries).
     */
    bool isEmpty() const { return 0 == fCount; }

    /**
     *  Return the number of entries in the table. 0 for an empty table
     */
    int count() const { return fCount; }

    /**
     *  Return the size of the index'th entry in the table. The caller must
     *  ensure that index is valid for this table.
     */
    size_t atSize(int index) const;

    /**
     *  Return a pointer to the data of the index'th entry in the table.
     *  The caller must ensure that index is valid for this table.
     *
     *  @param size If non-null, this returns the byte size of this entry. This
     *              will be the same value that atSize(index) would return.
     */
    const void* at(int index, size_t* size = NULL) const;

    template <typename T>
    const T* atT(int index, size_t* size = NULL) const {
        return reinterpret_cast<const T*>(this->at(index, size));
    }

    /**
     *  Returns the index'th entry as a c-string, and assumes that the trailing
     *  null byte had been copied into the table as well.
     */
    const char* atStr(int index) const {
        size_t size;
        const char* str = this->atT<const char>(index, &size);
        SkASSERT(strlen(str) + 1 == size);
        return str;
    }

    typedef void (*FreeProc)(void* context);

    static sk_sp<SkDataTable> MakeEmpty();

    /**
     *  Return a new DataTable that contains a copy of the data stored in each
     *  "array".
     *
     *  @param ptrs array of points to each element to be copied into the table.
     *  @param sizes array of byte-lengths for each entry in the corresponding
     *               ptrs[] array.
     *  @param count the number of array elements in ptrs[] and sizes[] to copy.
     */
    static sk_sp<SkDataTable> MakeCopyArrays(const void * const * ptrs,
                                             const size_t sizes[], int count);

    /**
     *  Return a new table that contains a copy of the data in array.
     *
     *  @param array contiguous array of data for all elements to be copied.
     *  @param elemSize byte-length for a given element.
     *  @param count the number of entries to be copied out of array. The number
     *               of bytes that will be copied is count * elemSize.
     */
    static sk_sp<SkDataTable> MakeCopyArray(const void* array, size_t elemSize, int count);

    static sk_sp<SkDataTable> MakeArrayProc(const void* array, size_t elemSize, int count,
                                            FreeProc proc, void* context);

private:
    struct Dir {
        const void* fPtr;
        uintptr_t   fSize;
    };

    int         fCount;
    size_t      fElemSize;
    union {
        const Dir*  fDir;
        const char* fElems;
    } fU;

    FreeProc    fFreeProc;
    void*       fFreeProcContext;

    SkDataTable();
    SkDataTable(const void* array, size_t elemSize, int count,
                FreeProc, void* context);
    SkDataTable(const Dir*, int count, FreeProc, void* context);
    virtual ~SkDataTable();

    friend class SkDataTableBuilder;    // access to Dir

    typedef SkRefCnt INHERITED;
};

/**
 *  Helper class that allows for incrementally building up the data needed to
 *  create a SkDataTable.
 */
class SK_API SkDataTableBuilder : SkNoncopyable {
public:
    SkDataTableBuilder(size_t minChunkSize);
    ~SkDataTableBuilder();

    int  count() const { return fDir.count(); }
    size_t minChunkSize() const { return fMinChunkSize; }

    /**
     *  Forget any previously appended entries, setting count() back to 0.
     */
    void reset(size_t minChunkSize);
    void reset() {
        this->reset(fMinChunkSize);
    }

    /**
     *  Copy size-bytes from data, and append it to the growing SkDataTable.
     */
    void append(const void* data, size_t size);

    /**
     *  Helper version of append() passes strlen() + 1 for the size,
     *  so the trailing-zero will be copied as well.
     */
    void appendStr(const char str[]) {
        this->append(str, strlen(str) + 1);
    }

    /**
     *  Helper version of append() passes string.size() + 1 for the size,
     *  so the trailing-zero will be copied as well.
     */
    void appendString(const SkString& string) {
        this->append(string.c_str(), string.size() + 1);
    }

    /**
     *  Return an SkDataTable from the accumulated entries that were added by
     *  calls to append(). This call also clears any accumluated entries from
     *  this builder, so its count() will be 0 after this call.
     */
    sk_sp<SkDataTable> detachDataTable();

private:
    SkTDArray<SkDataTable::Dir> fDir;
    SkChunkAlloc*               fHeap;
    size_t                      fMinChunkSize;
};

#endif
