package com.airbnb.lottie.animation.content;

import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.RectF;
import androidx.annotation.Nullable;

import com.airbnb.lottie.LottieDrawable;
import com.airbnb.lottie.LottieProperty;
import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;
import com.airbnb.lottie.animation.keyframe.TransformKeyframeAnimation;
import com.airbnb.lottie.model.KeyPath;
import com.airbnb.lottie.model.content.Repeater;
import com.airbnb.lottie.model.layer.BaseLayer;
import com.airbnb.lottie.utils.MiscUtils;
import com.airbnb.lottie.value.LottieValueCallback;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;

public class RepeaterContent implements DrawingContent, PathContent, GreedyContent,
    BaseKeyframeAnimation.AnimationListener, KeyPathElementContent {
  private final Matrix matrix = new Matrix();
  private final Path path = new Path();

  private final LottieDrawable lottieDrawable;
  private final BaseLayer layer;
  private final String name;
  private final boolean hidden;
  private final BaseKeyframeAnimation<Float, Float> copies;
  private final BaseKeyframeAnimation<Float, Float> offset;
  private final TransformKeyframeAnimation transform;
  private ContentGroup contentGroup;


  public RepeaterContent(LottieDrawable lottieDrawable, BaseLayer layer, Repeater repeater) {
    this.lottieDrawable = lottieDrawable;
    this.layer = layer;
    name = repeater.getName();
    this.hidden = repeater.isHidden();
    copies = repeater.getCopies().createAnimation();
    layer.addAnimation(copies);
    copies.addUpdateListener(this);

    offset = repeater.getOffset().createAnimation();
    layer.addAnimation(offset);
    offset.addUpdateListener(this);

    transform = repeater.getTransform().createAnimation();
    transform.addAnimationsToLayer(layer);
    transform.addListener(this);
  }

  @Override public void absorbContent(ListIterator<Content> contentsIter) {
    // This check prevents a repeater from getting added twice.
    // This can happen in the following situation:
    //    RECTANGLE
    //    REPEATER 1
    //    FILL
    //    REPEATER 2
    // In this case, the expected structure would be:
    //     REPEATER 2
    //        REPEATER 1
    //            RECTANGLE
    //        FILL
    // Without this check, REPEATER 1 will try and absorb contents once it is already inside of
    // REPEATER 2.
    if (contentGroup != null) {
      return;
    }
    // Fast forward the iterator until after this content.
    //noinspection StatementWithEmptyBody
    while (contentsIter.hasPrevious() && contentsIter.previous() != this) {}
    List<Content> contents = new ArrayList<>();
    while (contentsIter.hasPrevious()) {
      contents.add(contentsIter.previous());
      contentsIter.remove();
    }
    Collections.reverse(contents);
    contentGroup = new ContentGroup(lottieDrawable, layer, "Repeater", hidden, contents, null);
  }

  @Override public String getName() {
    return name;
  }

  @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
    contentGroup.setContents(contentsBefore, contentsAfter);
  }

  @Override public Path getPath() {
    Path contentPath = contentGroup.getPath();
    path.reset();
    float copies = this.copies.getValue();
    float offset = this.offset.getValue();
    for (int i = (int) copies - 1; i >= 0; i--) {
      matrix.set(transform.getMatrixForRepeater(i + offset));
      path.addPath(contentPath, matrix);
    }
    return path;
  }

  @Override public void draw(Canvas canvas, Matrix parentMatrix, int alpha) {
    float copies = this.copies.getValue();
    float offset = this.offset.getValue();
    //noinspection ConstantConditions
    float startOpacity = this.transform.getStartOpacity().getValue() / 100f;
    //noinspection ConstantConditions
    float endOpacity = this.transform.getEndOpacity().getValue() / 100f;
    for (int i = (int) copies - 1; i >= 0; i--) {
      matrix.set(parentMatrix);
      matrix.preConcat(transform.getMatrixForRepeater(i + offset));
      float newAlpha = alpha * MiscUtils.lerp(startOpacity, endOpacity, i / copies);
      contentGroup.draw(canvas, matrix, (int) newAlpha);
    }
  }

  @Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) {
    contentGroup.getBounds(outBounds, parentMatrix, applyParents);
  }

  @Override public void onValueChanged() {
    lottieDrawable.invalidateSelf();
  }

  @Override public void resolveKeyPath(
      KeyPath keyPath, int depth, List<KeyPath> accumulator, KeyPath currentPartialKeyPath) {
    MiscUtils.resolveKeyPath(keyPath, depth, accumulator, currentPartialKeyPath, this);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> callback) {
    if (transform.applyValueCallback(property, callback)) {
      return;
    }

    if (property == LottieProperty.REPEATER_COPIES) {
      copies.setValueCallback((LottieValueCallback<Float>) callback);
    } else if (property == LottieProperty.REPEATER_OFFSET) {
      offset.setValueCallback((LottieValueCallback<Float>) callback);
    }
  }
}
