/*
 * 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.producers;

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

import java.util.List;

import com.facebook.common.internal.Lists;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.imagepipeline.common.Priority;
import com.facebook.imagepipeline.request.ImageRequest;

/**
 * ProducerContext that allows the client to cancel an image request in-flight.
 */
@ThreadSafe
public class SettableProducerContext implements ProducerContext {

  private final ImageRequest mImageRequest;
  private final String mId;
  private final ProducerListener mProducerListener;
  private final Object mCallerContext;
  @GuardedBy("this")
  private final List<ProducerContextCallbacks> mCallbacks;
  @GuardedBy("this")
  private boolean mIsCancelled;
  @GuardedBy("this")
  private boolean mIsPrefetch;
  @GuardedBy("this")
  private Priority mPriority;
  @GuardedBy("this")
  private boolean mIsIntermediateResultExpected;

  public SettableProducerContext(
      ImageRequest imageRequest,
      String id,
      ProducerListener producerListener,
      Object callerContext,
      boolean isPrefetch,
      boolean isIntermediateResultExpected,
      Priority priority) {
    mImageRequest = Preconditions.checkNotNull(imageRequest);
    mId = Preconditions.checkNotNull(id);
    mProducerListener = Preconditions.checkNotNull(producerListener);
    mCallerContext = callerContext;
    mIsPrefetch = isPrefetch;
    mIsIntermediateResultExpected = isIntermediateResultExpected;
    mPriority = priority;
    mIsCancelled = false;
    mCallbacks = Lists.newArrayList();
  }

  @Override
  public ImageRequest getImageRequest() {
    return mImageRequest;
  }

  @Override
  public String getId() {
    return mId;
  }

  @Override
  public ProducerListener getListener() {
    return mProducerListener;
  }

  @Override
  public Object getCallerContext() {
    return mCallerContext;
  }

  @Override
  public synchronized boolean isPrefetch() {
    return mIsPrefetch;
  }

  @Override
  public synchronized Priority getPriority() {
    return mPriority;
  }

  @Override
  public synchronized boolean isIntermediateResultExpected() {
    return mIsIntermediateResultExpected;
  }

  @Override
  public void addCallbacks(ProducerContextCallbacks callbacks) {
    boolean cancelImmediately = false;
    synchronized (this) {
      mCallbacks.add(callbacks);
      if (mIsCancelled) {
        cancelImmediately = true;
      }
    }

    if (cancelImmediately) {
      callbacks.onCancellationRequested();
    }
  }

  /**
   * Cancels the request processing.
   */
  public void cancel() {
    List<ProducerContextCallbacks> callbacks = null;
    synchronized (this) {
      if (!mIsCancelled) {
        mIsCancelled = true;
        callbacks = Lists.newArrayList(mCallbacks);
      }
    }

    if (callbacks != null) {
      for (ProducerContextCallbacks callback : callbacks) {
        callback.onCancellationRequested();
      }
    }
  }

  /**
   * Set whether the request is a prefetch request or not.
   * @param isPrefetch
   */
  public void setIsPrefetch(boolean isPrefetch) {
    List<ProducerContextCallbacks> callbacks = null;
    synchronized (this) {
      if (mIsPrefetch != isPrefetch) {
        mIsPrefetch = isPrefetch;
        callbacks = Lists.newArrayList(mCallbacks);
      }
    }

    if (callbacks != null) {
      for (ProducerContextCallbacks callback : callbacks) {
        callback.onIsPrefetchChanged();
      }
    }
  }

  /**
   * Set whether intermediate result is expected or not
   * @param isIntermediateResultExpected
   */
  public void setIsIntermediateResultExpected(boolean isIntermediateResultExpected) {
    List<ProducerContextCallbacks> callbacks = null;
    synchronized (this) {
      if (mIsIntermediateResultExpected != isIntermediateResultExpected) {
        mIsIntermediateResultExpected = isIntermediateResultExpected;
        callbacks = Lists.newArrayList(mCallbacks);
      }
    }

    if (callbacks != null) {
      for (ProducerContextCallbacks callback : callbacks) {
        callback.onIsIntermediateResultExpectedChanged();
      }
    }
  }

  /**
   * Set the priority of the request
   * @param priority
   */
  public void setPriority(Priority priority) {
    List<ProducerContextCallbacks> callbacks = null;
    synchronized (this) {
      if (mPriority != priority) {
        mPriority = priority;
        callbacks = Lists.newArrayList(mCallbacks);
      }
    }

    if (callbacks != null) {
      for (ProducerContextCallbacks callback : callbacks) {
        callback.onPriorityChanged();
      }
    }
  }

  @VisibleForTesting
  synchronized boolean isCancelled() {
    return mIsCancelled;
  }
}