/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.imagepipeline.image;

import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import java.util.ArrayList;
import java.util.List;

import android.graphics.Bitmap;

import com.facebook.common.internal.Preconditions;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.references.ResourceReleaser;
import com.facebook.imageutils.BitmapUtil;

/**
 * CloseableImage that contains array of Bitmaps and frame durations.
 */
@ThreadSafe
public class CloseableAnimatedBitmap extends CloseableBitmap {

  // bitmap frames
  @GuardedBy("this")
  private List<CloseableReference<Bitmap>> mBitmapReferences;
  private volatile List<Bitmap> mBitmaps;

  // frame durations
  private volatile List<Integer> mDurations;

  public CloseableAnimatedBitmap(
      List<CloseableReference<Bitmap>> bitmapReferences,
      List<Integer> durations) {
    Preconditions.checkNotNull(bitmapReferences);
    Preconditions.checkState(bitmapReferences.size() >= 1, "Need at least 1 frame!");
    mBitmapReferences = new ArrayList<>(bitmapReferences.size());
    mBitmaps = new ArrayList<>(bitmapReferences.size());
    for (CloseableReference<Bitmap> bitmapReference : bitmapReferences) {
      mBitmapReferences.add(bitmapReference.clone());
      mBitmaps.add(bitmapReference.get());
    }
    mDurations = Preconditions.checkNotNull(durations);
    Preconditions.checkState(mDurations.size() == mBitmaps.size(), "Arrays length mismatch!");
  }

  /**
   * Creates a new instance of a CloseableStaticBitmap.
   *
   * @param bitmaps the bitmap frames. This list must be immutable.
   * @param durations the frame durations, This list must be immutable.
   * @param resourceReleaser ResourceReleaser to release the bitmaps to
   */
  public CloseableAnimatedBitmap(
      List<Bitmap> bitmaps,
      List<Integer> durations,
      ResourceReleaser<Bitmap> resourceReleaser) {
    Preconditions.checkNotNull(bitmaps);
    Preconditions.checkState(bitmaps.size() >= 1, "Need at least 1 frame!");
    mBitmaps = new ArrayList<>(bitmaps.size());
    mBitmapReferences = new ArrayList<>(bitmaps.size());
    for (Bitmap bitmap : bitmaps) {
      mBitmapReferences.add(CloseableReference.of(bitmap, resourceReleaser));
      mBitmaps.add(bitmap);
    }
    mDurations = Preconditions.checkNotNull(durations);
    Preconditions.checkState(mDurations.size() == mBitmaps.size(), "Arrays length mismatch!");
  }

  /**
   * Releases the bitmaps to the pool.
   */
  @Override
  public void close() {
    List<CloseableReference<Bitmap>> bitmapReferences;
    synchronized (this) {
      if (mBitmapReferences == null) {
        return;
      }
      bitmapReferences = mBitmapReferences;
      mBitmapReferences = null;
      mBitmaps = null;
      mDurations = null;
    }
    CloseableReference.closeSafely(bitmapReferences);
  }

  /**
   * Returns whether this instance is closed.
   */
  @Override
  public synchronized boolean isClosed() {
    return mBitmaps == null;
  }

  /**
   * Gets the bitmap frames.
   *
   * @return bitmap frames
   */
  public List<Bitmap> getBitmaps() {
    return mBitmaps;
  }

  /**
   * Gets the frame durations.
   *
   * @return frame durations
   */
  public List<Integer> getDurations() {
    return mDurations;
  }

  /**
   * Gets the first frame.
   *
   * @return the first frame
   */
  @Override
  public Bitmap getUnderlyingBitmap() {
    List<Bitmap> bitmaps = mBitmaps;
    return (bitmaps != null) ? bitmaps.get(0) : null;
  }

  /**
   * @return size in bytes all bitmaps in sum
   */
  @Override
  public int getSizeInBytes() {
    List<Bitmap> bitmaps = mBitmaps;
    if (bitmaps == null || bitmaps.size() == 0) {
      return 0;
    } else {
      return BitmapUtil.getSizeInBytes(bitmaps.get(0)) * bitmaps.size();
    }
  }

  /**
   * @return width of the image
   */
  @Override
  public int getWidth() {
    List<Bitmap> bitmaps = mBitmaps;
    return (bitmaps == null) ? 0 : bitmaps.get(0).getWidth();
  }

  /**
   * @return height of the image
   */
  @Override
  public int getHeight() {
    List<Bitmap> bitmaps = mBitmaps;
    return (bitmaps == null) ? 0 : bitmaps.get(0).getHeight();
  }

}
