/*
 * 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 SkResources_DEFINED
#define SkResources_DEFINED

#include "include/core/SkData.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSamplingOptions.h"
#include "include/core/SkString.h"
#include "include/core/SkTypeface.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkMutex.h"
#include "src/core/SkTHash.h"

#include <memory>

class SkAnimCodecPlayer;
class SkCodec;
class SkImage;

namespace skresources {

/**
 * Image asset proxy interface.
 */
class SK_API ImageAsset : public SkRefCnt {
public:
    /**
     * Returns true if the image asset is animated.
     */
    virtual bool isMultiFrame() = 0;

    /**
     * DEPRECATED: override getFrameData() instead.
     *
     * Returns the SkImage for a given frame.
     *
     * If the image asset is static, getFrame() is only called once, at animation load time.
     * Otherwise, this gets invoked every time the animation time is adjusted (on every seek).
     *
     * Embedders should cache and serve the same SkImage whenever possible, for efficiency.
     *
     * @param t   Frame time code, in seconds, relative to the image layer timeline origin
     *            (in-point).
     */
    virtual sk_sp<SkImage> getFrame(float t);

    // Describes how the frame image is to be scaled to the animation-declared asset size.
    enum class SizeFit {
        // See SkMatrix::ScaleToFit
        kFill   = SkMatrix::kFill_ScaleToFit,
        kStart  = SkMatrix::kStart_ScaleToFit,
        kCenter = SkMatrix::kCenter_ScaleToFit,
        kEnd    = SkMatrix::kEnd_ScaleToFit,

        // No scaling.
        kNone,
    };

    struct FrameData {
        // SkImage payload.
        sk_sp<SkImage>    image;
        // Resampling parameters.
        SkSamplingOptions sampling;
        // Additional image transform to be applied before AE scaling rules.
        SkMatrix          matrix = SkMatrix::I();
        // Strategy for image size -> AE asset size scaling.
        SizeFit           scaling = SizeFit::kCenter;
    };

    /**
     * Returns the payload for a given frame.
     *
     * If the image asset is static, getFrameData() is only called once, at animation load time.
     * Otherwise, this gets invoked every time the animation time is adjusted (on every seek).
     *
     * Embedders should cache and serve the same SkImage whenever possible, for efficiency.
     *
     * @param t   Frame time code, in seconds, relative to the image layer timeline origin
     *            (in-point).
     */
    virtual FrameData getFrameData(float t);
};

enum class ImageDecodeStrategy {
    // Images are decoded on-the-fly, at rasterization time.
    // Large images may cause jank as decoding is expensive (and can thrash internal caches).
    kLazyDecode,
    // Force-decode all images upfront, at the cost of potentially more RAM and slower
    // animation build times.
    kPreDecode,
};

class MultiFrameImageAsset final : public ImageAsset {
public:
    // Clients must call SkCodec::Register() to load the required decoding image codecs before
    // calling Make. For example:
    //     SkCodec::Register(SkPngDecoder::Decoder());
    static sk_sp<MultiFrameImageAsset> Make(sk_sp<SkData>,
                                            ImageDecodeStrategy = ImageDecodeStrategy::kLazyDecode);
    // If the client has already decoded the data, they can use this constructor.
    static sk_sp<MultiFrameImageAsset> Make(std::unique_ptr<SkCodec>,
                                            ImageDecodeStrategy = ImageDecodeStrategy::kLazyDecode);


    bool isMultiFrame() override;

    sk_sp<SkImage> getFrame(float t) override;

private:
    explicit MultiFrameImageAsset(std::unique_ptr<SkAnimCodecPlayer>, ImageDecodeStrategy);

    sk_sp<SkImage> generateFrame(float t);

    std::unique_ptr<SkAnimCodecPlayer> fPlayer;
    sk_sp<SkImage>                     fCachedFrame;
    ImageDecodeStrategy fStrategy;

    using INHERITED = ImageAsset;
};

/**
 * External track (e.g. audio playback) interface.
 *
 * Used to wrap data payload and playback controllers.
 */
class ExternalTrackAsset : public SkRefCnt {
public:
    /**
     * Playback control callback, emitted for each corresponding Animation::seek().
     *
     * @param t  Frame time code, in seconds, relative to the layer's timeline origin
     *           (in-point).
     *
     * Negative |t| values are used to signal off state (stop playback outside layer span).
     */
    virtual void seek(float t) = 0;
};

/**
 * ResourceProvider is an interface that lets rich-content modules defer loading of external
 * resources (images, fonts, etc.) to embedding clients.
 */
class SK_API ResourceProvider : public SkRefCnt {
public:
    /**
     * Load a generic resource (currently only nested animations) specified by |path| + |name|,
     * and return as an SkData.
     */
    virtual sk_sp<SkData> load(const char[] /* resource_path */,
                               const char[] /* resource_name */) const {
        return nullptr;
    }

    /**
     * Load an image asset specified by |path| + |name|, and returns the corresponding
     * ImageAsset proxy.
     */
    virtual sk_sp<ImageAsset> loadImageAsset(const char[] /* resource_path */,
                                             const char[] /* resource_name */,
                                             const char[] /* resource_id   */) const {
        return nullptr;
    }

    /**
     * Load an external audio track specified by |path|/|name|/|id|.
     */
    virtual sk_sp<ExternalTrackAsset> loadAudioAsset(const char[] /* resource_path */,
                                                     const char[] /* resource_name */,
                                                     const char[] /* resource_id   */) {
        return nullptr;
    }

    /**
     * DEPRECATED: implement loadTypeface() instead.
     *
     * Load an external font and return as SkData.
     *
     * @param name  font name    ("fName" Lottie property)
     * @param url   web font URL ("fPath" Lottie property)
     *
     * -- Note --
     *
     *   This mechanism assumes monolithic fonts (single data blob).  Some web font providers may
     *   serve multiple font blobs, segmented for various unicode ranges, depending on user agent
     *   capabilities (woff, woff2).  In that case, the embedder would need to advertise no user
     *   agent capabilities when fetching the URL, in order to receive full font data.
     */
    virtual sk_sp<SkData> loadFont(const char[] /* name */,
                                   const char[] /* url  */) const {
        return nullptr;
    }

    /**
     * Load an external font and return as SkTypeface.
     *
     * @param name  font name
     * @param url   web font URL
     */
    virtual sk_sp<SkTypeface> loadTypeface(const char[] /* name */,
                                           const char[] /* url  */) const {
        return nullptr;
    }
};

class FileResourceProvider final : public ResourceProvider {
public:
    // To decode images, clients must call SkCodecs::Register() before calling Make.
    static sk_sp<FileResourceProvider> Make(SkString base_dir,
                                            ImageDecodeStrategy = ImageDecodeStrategy::kLazyDecode);

    sk_sp<SkData> load(const char resource_path[], const char resource_name[]) const override;

    sk_sp<ImageAsset> loadImageAsset(const char[], const char[], const char[]) const override;

private:
    FileResourceProvider(SkString, ImageDecodeStrategy);

    const SkString fDir;
    const ImageDecodeStrategy fStrategy;

    using INHERITED = ResourceProvider;
};

class ResourceProviderProxyBase : public ResourceProvider {
protected:
    explicit ResourceProviderProxyBase(sk_sp<ResourceProvider>);

    sk_sp<SkData> load(const char[], const char[]) const override;
    sk_sp<ImageAsset> loadImageAsset(const char[], const char[], const char[]) const override;
    sk_sp<SkTypeface> loadTypeface(const char[], const char[]) const override;
    sk_sp<SkData> loadFont(const char[], const char[]) const override;
    sk_sp<ExternalTrackAsset> loadAudioAsset(const char[], const char[], const char[]) override;

protected:
    const sk_sp<ResourceProvider> fProxy;
};

class SK_API CachingResourceProvider final : public ResourceProviderProxyBase {
public:
    static sk_sp<CachingResourceProvider> Make(sk_sp<ResourceProvider> rp) {
        return rp ? sk_sp<CachingResourceProvider>(new CachingResourceProvider(std::move(rp)))
                  : nullptr;
    }

private:
    explicit CachingResourceProvider(sk_sp<ResourceProvider>);

    sk_sp<ImageAsset> loadImageAsset(const char[], const char[], const char[]) const override;

    mutable SkMutex                                             fMutex;
    mutable skia_private::THashMap<SkString, sk_sp<ImageAsset>> fImageCache;

    using INHERITED = ResourceProviderProxyBase;
};

class SK_API DataURIResourceProviderProxy final : public ResourceProviderProxyBase {
public:
    // If font data is supplied via base64 encoding, this needs a provided SkFontMgr to process
    // that font data into an SkTypeface. To decode images, clients must call SkCodecs::Register()
    // before calling Make.
    static sk_sp<DataURIResourceProviderProxy> Make(
            sk_sp<ResourceProvider> rp,
            ImageDecodeStrategy = ImageDecodeStrategy::kLazyDecode,
            sk_sp<const SkFontMgr> fontMgr = nullptr);

private:
    DataURIResourceProviderProxy(sk_sp<ResourceProvider>,
                                 ImageDecodeStrategy,
                                 sk_sp<const SkFontMgr> fontMgr);

    sk_sp<ImageAsset> loadImageAsset(const char[], const char[], const char[]) const override;
    sk_sp<SkTypeface> loadTypeface(const char[], const char[]) const override;

    const ImageDecodeStrategy fStrategy;
    sk_sp<const SkFontMgr> fFontMgr;

    using INHERITED = ResourceProviderProxyBase;
};

} // namespace skresources

#endif // SkResources_DEFINED
