/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_layers_CompositorAnimationStorage_h
#define mozilla_layers_CompositorAnimationStorage_h

#include "mozilla/layers/AnimationStorageData.h"
#include "mozilla/layers/LayersMessages.h"  // for TransformData, etc
#include "mozilla/webrender/webrender_ffi.h"
#include "mozilla/Variant.h"
#include "nsClassHashtable.h"
#include "X11UndefineNone.h"
#include <memory>
#include <unordered_map>
#include <unordered_set>

namespace mozilla {
namespace layers {
class APZSampler;
class Animation;
class CompositorBridgeParent;
class OMTAController;

using AnimationArray = nsTArray<layers::Animation>;
using SampledAnimationArray = AutoTArray<RefPtr<StyleAnimationValue>, 1>;

struct AnimationTransform {
  /*
   * This transform is calculated from frame used for WebRender and used by
   * getOMTAStyle() for OMTA testing.
   */
  gfx::Matrix4x4 mFrameTransform;
  TransformData mData;

  /*
   * Store the previous sampled transform-like animation values.
   * It's unfortunate we have to keep the previous sampled animation value for
   * replacing the running transition, because we can not re-create the
   * AnimationValues from the matrix.
   * Note: We expect the length is one in most cases.
   */
  SampledAnimationArray mAnimationValues;
};

struct AnimatedValue final {
  typedef Variant<AnimationTransform, float, nscolor> AnimatedValueType;

  const AnimatedValueType& Value() const { return mValue; }
  const AnimationTransform& Transform() const {
    return mValue.as<AnimationTransform>();
  }
  const float& Opacity() const { return mValue.as<float>(); }
  const nscolor& Color() const { return mValue.as<nscolor>(); }
  template <typename T>
  bool Is() const {
    return mValue.is<T>();
  }

  AnimatedValue(const gfx::Matrix4x4& aFrameTransform,
                const TransformData& aData, SampledAnimationArray&& aValue)
      : mValue(AsVariant(
            AnimationTransform{aFrameTransform, aData, std::move(aValue)})) {}

  explicit AnimatedValue(const float& aValue) : mValue(AsVariant(aValue)) {}

  explicit AnimatedValue(nscolor aValue) : mValue(AsVariant(aValue)) {}

  // Note: Only transforms need to store the sampled AnimationValue because it's
  // impossible to re-create the AnimationValue from the matrix.
  void SetTransform(const gfx::Matrix4x4& aFrameTransform,
                    const TransformData& aData,
                    SampledAnimationArray&& aValue) {
    MOZ_ASSERT(mValue.is<AnimationTransform>());
    AnimationTransform& previous = mValue.as<AnimationTransform>();
    previous.mFrameTransform = aFrameTransform;
    if (previous.mData != aData) {
      previous.mData = aData;
    }
    previous.mAnimationValues = std::move(aValue);
  }
  void SetOpacity(float aOpacity) {
    MOZ_ASSERT(mValue.is<float>());
    mValue.as<float>() = aOpacity;
  }
  void SetColor(nscolor aColor) {
    MOZ_ASSERT(mValue.is<nscolor>());
    mValue.as<nscolor>() = aColor;
  }

  already_AddRefed<StyleAnimationValue> AsAnimationValue(
      NonCustomCSSPropertyId) const;

 private:
  AnimatedValueType mValue;
};

struct WrAnimations {
  nsTArray<wr::WrOpacityProperty> mOpacityArrays;
  nsTArray<wr::WrTransformProperty> mTransformArrays;
  nsTArray<wr::WrColorProperty> mColorArrays;
};

// CompositorAnimationStorage stores the animations and animated values
// keyed by a CompositorAnimationsId. The "animations" are a representation of
// an entire animation over time, while the "animated values" are values sampled
// from the animations at a particular point in time.
//
// There is one CompositorAnimationStorage per CompositorBridgeParent (i.e.
// one per browser window), and the CompositorAnimationsId key is unique within
// a particular CompositorAnimationStorage instance.
//
// Each layer which has animations gets a CompositorAnimationsId key, and reuses
// that key during its lifetime. Likewise, in layers-free webrender, a display
// item that is animated (e.g. nsDisplayTransform) gets a CompositorAnimationsId
// key and reuses that key (it persists the key via the frame user-data
// mechanism).
class CompositorAnimationStorage final {
  typedef nsClassHashtable<nsUint64HashKey, AnimatedValue> AnimatedValueTable;
  typedef std::unordered_map<uint64_t, std::unique_ptr<AnimationStorageData>>
      AnimationsTable;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorAnimationStorage)
 public:
  explicit CompositorAnimationStorage(CompositorBridgeParent* aCompositorBridge)
      : mLock("CompositorAnimationStorage::mLock"),
        mCompositorBridge(aCompositorBridge) {}

  OMTAValue GetOMTAValue(const uint64_t& aId) const;

  /**
   * Collect all animations in this class as WebRender type properties.
   */
  WrAnimations CollectWebRenderAnimations() const;

  /**
   * Set the animations based on the unique id
   */
  void SetAnimations(uint64_t aId, const LayersId& aLayersId,
                     const AnimationArray& aAnimations,
                     const TimeStamp& aPreviousSampleTime);

  /**
   * Sample animation based the given timestamps and store them in this
   * CompositorAnimationStorage. The animated values after sampling will be
   * stored in CompositorAnimationStorage as well.
   *
   * Returns true if there is any animation.
   * Note that even if there are only in-delay phase animations (i.e. not
   * visually effective), this function returns true to ensure we composite
   * again on the next tick.
   *
   * Note: This is called only by WebRender.
   */
  bool SampleAnimations(const OMTAController* aOMTAController,
                        TimeStamp aPreviousFrameTime,
                        TimeStamp aCurrentFrameTime);

  bool HasAnimations() const;

  /**
   * Clear AnimatedValues and Animations data
   */
  void ClearById(const uint64_t& aId);

  /**
   * Return the animated value if a given id can map to its animated value
   */
  AnimatedValue* GetAnimatedValue(const uint64_t& aId) const;

 private:
  ~CompositorAnimationStorage() = default;

  /**
   * Set the animation transform based on the unique id and also
   * set up |aFrameTransform| and |aData| for OMTA testing.
   * If |aPreviousValue| is not null, the animation transform replaces the value
   * in the |aPreviousValue|.
   * NOTE: |aPreviousValue| should be the value for the |aId|.
   */
  void SetAnimatedValue(uint64_t aId, AnimatedValue* aPreviousValue,
                        const gfx::Matrix4x4& aFrameTransform,
                        const TransformData& aData,
                        SampledAnimationArray&& aValue);

  /**
   * Similar to above but for opacity.
   */
  void SetAnimatedValue(uint64_t aId, AnimatedValue* aPreviousValue,
                        float aOpacity);

  /**
   * Similar to above but for color.
   */
  void SetAnimatedValue(uint64_t aId, AnimatedValue* aPreviousValue,
                        nscolor aColor);

  using JankedAnimationMap =
      std::unordered_map<LayersId, nsTArray<uint64_t>, LayersId::HashFn>;

  /*
   * Store the animated values from |aAnimationValues|.
   */
  void StoreAnimatedValue(
      NonCustomCSSPropertyId aProperty, uint64_t aId,
      const std::unique_ptr<AnimationStorageData>& aAnimationStorageData,
      SampledAnimationArray&& aAnimationValues,
      const MutexAutoLock& aProofOfMapLock,
      const RefPtr<APZSampler>& aApzSampler, AnimatedValue* aAnimatedValueEntry,
      JankedAnimationMap& aJankedAnimationMap);

 private:
  AnimatedValueTable mAnimatedValues;
  AnimationsTable mAnimations;
  std::unordered_set<uint64_t> mNewAnimations;
  mutable Mutex mLock MOZ_UNANNOTATED;
  // CompositorBridgeParent owns this CompositorAnimationStorage instance.
  CompositorBridgeParent* MOZ_NON_OWNING_REF mCompositorBridge;
};

}  // namespace layers
}  // namespace mozilla

#endif  // mozilla_layers_CompositorAnimationStorage_h
