/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkDescriptor_DEFINED
#define SkDescriptor_DEFINED

#include "SkMacros.h"
#include "SkNoncopyable.h"
#include "SkOpts.h"
#include "SkTo.h"
#include "SkTypes.h"

#include <memory>
#include <new>

class SkDescriptor : SkNoncopyable {
public:
    static size_t ComputeOverhead(int entryCount) {
        SkASSERT(entryCount >= 0);
        return sizeof(SkDescriptor) + entryCount * sizeof(Entry);
    }

    static std::unique_ptr<SkDescriptor> Alloc(size_t length) {
        SkASSERT(SkAlign4(length) == length);
        return std::unique_ptr<SkDescriptor>(static_cast<SkDescriptor*>(::operator new (length)));
    }

    // Ensure the unsized delete is called.
    void operator delete(void* p) { ::operator delete(p); }

    void init() {
        fLength = sizeof(SkDescriptor);
        fCount  = 0;
    }

    uint32_t getLength() const { return fLength; }

    void* addEntry(uint32_t tag, size_t length, const void* data = nullptr) {
        SkASSERT(tag);
        SkASSERT(SkAlign4(length) == length);
        SkASSERT(this->findEntry(tag, nullptr) == nullptr);

        Entry* entry = (Entry*)((char*)this + fLength);
        entry->fTag = tag;
        entry->fLen = SkToU32(length);
        if (data) {
            memcpy(entry + 1, data, length);
        }

        fCount += 1;
        fLength = SkToU32(fLength + sizeof(Entry) + length);
        return (entry + 1); // return its data
    }

    void computeChecksum() {
        fChecksum = SkDescriptor::ComputeChecksum(this);
    }

#ifdef SK_DEBUG
    void assertChecksum() const {
        SkASSERT(SkDescriptor::ComputeChecksum(this) == fChecksum);
    }
#endif

    const void* findEntry(uint32_t tag, uint32_t* length) const {
        const Entry* entry = (const Entry*)(this + 1);
        int          count = fCount;

        while (--count >= 0) {
            if (entry->fTag == tag) {
                if (length) {
                    *length = entry->fLen;
                }
                return entry + 1;
            }
            entry = (const Entry*)((const char*)(entry + 1) + entry->fLen);
        }
        return nullptr;
    }

    std::unique_ptr<SkDescriptor> copy() const {
        std::unique_ptr<SkDescriptor> desc = SkDescriptor::Alloc(fLength);
        memcpy(desc.get(), this, fLength);
        return desc;
    }

    bool operator==(const SkDescriptor& other) const {
        // probe to see if we have a good checksum algo
//        SkASSERT(a.fChecksum != b.fChecksum || memcmp(&a, &b, a.fLength) == 0);

        // the first value we should look at is the checksum, so this loop
        // should terminate early if they descriptors are different.
        // NOTE: if we wrote a sentinel value at the end of each, we chould
        //       remove the aa < stop test in the loop...
        const uint32_t* aa = (const uint32_t*)this;
        const uint32_t* bb = (const uint32_t*)&other;
        const uint32_t* stop = (const uint32_t*)((const char*)aa + fLength);
        do {
            if (*aa++ != *bb++)
                return false;
        } while (aa < stop);
        return true;
    }
    bool operator!=(const SkDescriptor& other) const { return !(*this == other); }

    uint32_t getChecksum() const { return fChecksum; }

    struct Entry {
        uint32_t fTag;
        uint32_t fLen;
    };

#ifdef SK_DEBUG
    uint32_t getCount() const { return fCount; }
#endif

private:
    uint32_t fChecksum;  // must be first
    uint32_t fLength;    // must be second
    uint32_t fCount;

    static uint32_t ComputeChecksum(const SkDescriptor* desc) {
        const uint32_t* ptr = (const uint32_t*)desc + 1; // skip the checksum field
        size_t len = desc->fLength - sizeof(uint32_t);
        return SkOpts::hash(ptr, len);
    }

    // private so no one can create one except our factories
    SkDescriptor() {}
};

#include "SkScalerContext.h"

class SkAutoDescriptor : SkNoncopyable {
public:
    SkAutoDescriptor() : fDesc(nullptr) {}
    SkAutoDescriptor(size_t size) : fDesc(nullptr) { this->reset(size); }
    SkAutoDescriptor(const SkDescriptor& desc) : fDesc(nullptr) {
        size_t size = desc.getLength();
        this->reset(size);
        memcpy(fDesc, &desc, size);
    }

    ~SkAutoDescriptor() { this->free(); }

    void reset(size_t size) {
        this->free();
        if (size <= sizeof(fStorage)) {
            fDesc = (SkDescriptor*)(void*)fStorage;
        } else {
            fDesc = SkDescriptor::Alloc(size).release();
        }
    }

    SkDescriptor* getDesc() const { SkASSERT(fDesc); return fDesc; }
private:
    void free() {
        if (fDesc != (SkDescriptor*)(void*)fStorage) {
            delete fDesc;
        }
    }

    enum {
        kStorageSize =  sizeof(SkDescriptor)
                        + sizeof(SkDescriptor::Entry) + sizeof(SkScalerContextRec) // for rec
                        + sizeof(SkDescriptor::Entry) + sizeof(void*)              // for typeface
                        + 32   // slop for occational small extras
    };
    SkDescriptor*   fDesc;
    uint32_t        fStorage[(kStorageSize + 3) >> 2];
};
#define SkAutoDescriptor(...) SK_REQUIRE_LOCAL_VAR(SkAutoDescriptor)


#endif
