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

#ifndef SkReflected_DEFINED
#define SkReflected_DEFINED

#include "include/core/SkColor.h"
#include "include/core/SkRefCnt.h"
#include "include/private/SkTArray.h"

#include <functional>   // std::function
#include <string.h>

class SkFieldVisitor;
struct SkPoint;
class SkString;

/**
 * Classes and macros for a lightweight reflection system.
 *
 * Classes that derive from SkReflected have several features:
 *   - Access to an SkReflected::Type instance, via static GetType() or virtual getType()
 *     The Type instance can be used to create additional instances (fFactory), get the name
 *     of the type, and answer queries of the form "is X derived from Y".
 *   - Given a string containing a type name, SkReflected can create an instance of that type.
 *   - SkReflected::VisitTypes can be used to enumerate all Types.
 *
 * Together, this simplifies the implementation of serialization and other dynamic type factories.
 *
 * Finally, all SkReflected-derived types must implement visitFields, which provides field-level
 * reflection, in conjunction with SkFieldVisitor. See SkFieldVisitor, below.
 *
 * To create a new reflected class:
 *   - Derive the class (directly or indirectly) from SkReflected.
 *   - Ensure that the class can be default constructed.
 *   - In the public area of the class declaration, add REFLECTED(<ClassName>, <BaseClassName>).
 *     If the class is abstract, use REFLECTED_ABSTRACT(<ClassName>, <BaseClassName>) instead.
 *   - Add a one-time call to REGISTER_REFLECTED(<ClassName>) at initialization time.
 *   - Implement visitFields(), as described below.
 */
class SkReflected : public SkRefCnt {
public:
    typedef sk_sp<SkReflected>(*Factory)();
    struct Type {
        const char* fName;
        const Type* fBase;
        Factory     fFactory;
        bool        fRegistered = false;

        bool isDerivedFrom(const Type* t) const {
            const Type* base = fBase;
            while (base) {
                if (base == t) {
                    return true;
                }
                base = base->fBase;
            }
            return false;
        }
    };

    virtual const Type* getType() const = 0;
    static const Type* GetType() {
        static Type gType{ "SkReflected", nullptr, nullptr };
        RegisterOnce(&gType);
        return &gType;
    }

    bool isOfType(const Type* t) const {
        const Type* thisType = this->getType();
        return thisType == t || thisType->isDerivedFrom(t);
    }

    static sk_sp<SkReflected> CreateInstance(const char* name) {
        for (const Type* type : gTypes) {
            if (0 == strcmp(name, type->fName)) {
                return type->fFactory();
            }
        }
        return nullptr;
    }

    virtual void visitFields(SkFieldVisitor*) = 0;

    static void VisitTypes(std::function<void(const Type*)> visitor);

protected:
    static void RegisterOnce(Type* type) {
        if (!type->fRegistered) {
            gTypes.push_back(type);
            type->fRegistered = true;
        }
    }

private:
    static SkSTArray<16, const Type*, true> gTypes;
};

#define REFLECTED(TYPE, BASE)                                    \
    static sk_sp<SkReflected> CreateProc() {                     \
        return sk_sp<SkReflected>(new TYPE());                   \
    }                                                            \
    static const Type* GetType() {                               \
        static Type gType{ #TYPE, BASE::GetType(), CreateProc }; \
        RegisterOnce(&gType);                                    \
        return &gType;                                           \
    }                                                            \
    const Type* getType() const override { return GetType(); }

#define REFLECTED_ABSTRACT(TYPE, BASE)                          \
    static const Type* GetType() {                              \
        static Type gType{ #TYPE, BASE::GetType(), nullptr };   \
        RegisterOnce(&gType);                                   \
        return &gType;                                          \
    }                                                           \
    const Type* getType() const override { return GetType(); }

#define REGISTER_REFLECTED(TYPE) TYPE::GetType()

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

/**
 * SkFieldVisitor is an interface that can be implemented by any class to visit all fields of
 * SkReflected types, and of types that implement the visitFields() function.
 *
 * Classes implementing the interface must supply implementations of virtual functions that visit
 * basic types (float, int, bool, SkString), as well as helper methods for entering the scope of
 * an object or array.
 *
 * All visit functions supply a field name, and a non-constant reference to an actual field.
 * This allows visitors to serialize or deserialize collections of objects, or perform edits on
 * existing objects.
 *
 * Classes that implement visitFields (typically derived from SkReflected) should simply call
 * visit() for each of their fields, passing a (unique) field name, and the actual field. If your
 * class has derived fields, it's best to only visit() the fields that you would serialize, then
 * enforce any constraints afterwards.
 *
 * See SkParticleSerialization.h for example visitors that perform serialization to and from JSON.
 */
class SkFieldVisitor {
public:
    virtual ~SkFieldVisitor() {}

    // Visit functions for primitive types, to be implemented by derived visitors.
    virtual void visit(const char*, float&) = 0;
    virtual void visit(const char*, int&) = 0;
    virtual void visit(const char*, bool&) = 0;
    virtual void visit(const char*, SkString&) = 0;

    // Specialization for SkTArrays. In conjunction with the enterArray/exitArray virtuals, this
    // allows visitors to resize an array (for deserialization), and apply a single edit operation
    // (remove a single element). Each element of the array is visited as normal.
    template <typename T, bool MEM_MOVE>
    void visit(const char* name, SkTArray<T, MEM_MOVE>& arr) {
        arr.resize_back(this->enterArray(name, arr.count()));
        for (int i = 0; i < arr.count(); ++i) {
            this->visit(nullptr, arr[i]);
        }
        this->exitArray().apply(arr);
    }

    // Specialization for sk_sp pointers to types derived from SkReflected. Those types are known
    // to implement visitFields. This allows the visitor to modify the contents of the object, or
    // even replace it with an entirely new object. The virtual function uses SkReflected as a
    // common type, but uses SkReflected::Type to communicate the required base-class. In this way,
    // the new object can be verified to match the type of the original (templated) pointer.
    template <typename T>
    void visit(const char* name, sk_sp<T>& obj) {
        this->enterObject(name);

        sk_sp<SkReflected> newObj = obj;
        this->visit(newObj, T::GetType());
        if (newObj != obj) {
            if (!newObj || newObj->isOfType(T::GetType())) {
                obj.reset(static_cast<T*>(newObj.release()));
            } else {
                obj.reset();
            }
        }

        if (obj) {
            obj->visitFields(this);
        }
        this->exitObject();
    }

protected:
    // Helper struct to allow exitArray to specify a single operation performed on the array.
    struct ArrayEdit {
        enum class Verb {
            kNone,
            kRemove,
        };

        Verb fVerb = Verb::kNone;
        int fIndex = 0;

        template <typename T, bool MEM_MOVE>
        void apply(SkTArray<T, MEM_MOVE>& arr) const {
            switch (fVerb) {
            case Verb::kNone:
                break;
            case Verb::kRemove:
                for (int i = fIndex; i < arr.count() - 1; ++i) {
                    arr[i] = arr[i + 1];
                }
                arr.pop_back();
                break;
            }
        }
    };

private:
    virtual void enterObject(const char* name) = 0;
    virtual void exitObject() = 0;

    virtual int enterArray(const char* name, int oldCount) = 0;
    virtual ArrayEdit exitArray() = 0;

    virtual void visit(sk_sp<SkReflected>&, const SkReflected::Type* baseType) = 0;
};

#endif // SkReflected_DEFINED
