package com.airbnb.epoxy;

import java.util.Collections;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.AdapterListUpdateCallback;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView.Adapter;

/**
 * Wraps the result of {@link AsyncEpoxyDiffer#submitList(List)}.
 */
public class DiffResult {
  @NonNull final List<? extends EpoxyModel<?>> previousModels;
  @NonNull final List<? extends EpoxyModel<?>> newModels;

  /**
   * If this is non null it means the full differ ran and the result is contained
   * in this object. If it is null, it means that either the old list or the new list was empty, so
   * we can simply add all or clear all items and skipped running the full diffing.
   */
  @Nullable final DiffUtil.DiffResult differResult;

  /** No changes were made to the models. */
  static DiffResult noOp(@Nullable List<? extends EpoxyModel<?>> models) {
    if (models == null) {
      models = Collections.emptyList();
    }
    return new DiffResult(models, models, null);
  }

  /** The previous list was empty and the given non empty list was inserted. */
  static DiffResult inserted(@NonNull List<? extends EpoxyModel<?>> newModels) {
    //noinspection unchecked
    return new DiffResult(Collections.EMPTY_LIST, newModels, null);
  }

  /** The previous list was non empty and the new list is empty. */
  static DiffResult clear(@NonNull List<? extends EpoxyModel<?>> previousModels) {
    //noinspection unchecked
    return new DiffResult(previousModels, Collections.EMPTY_LIST, null);
  }

  /**
   * The previous and new models are both non empty and a full differ pass was run on them.
   * There may be no changes, however.
   */
  static DiffResult diff(
      @NonNull List<? extends EpoxyModel<?>> previousModels,
      @NonNull List<? extends EpoxyModel<?>> newModels,
      @NonNull DiffUtil.DiffResult differResult
  ) {
    return new DiffResult(previousModels, newModels, differResult);
  }

  private DiffResult(
      @NonNull List<? extends EpoxyModel<?>> previousModels,
      @NonNull List<? extends EpoxyModel<?>> newModels,
      @Nullable DiffUtil.DiffResult differResult
  ) {
    this.previousModels = previousModels;
    this.newModels = newModels;
    this.differResult = differResult;
  }

  public void dispatchTo(Adapter adapter) {
    dispatchTo(new AdapterListUpdateCallback(adapter));
  }

  public void dispatchTo(ListUpdateCallback callback) {
    if (differResult != null) {
      differResult.dispatchUpdatesTo(callback);
    } else if (newModels.isEmpty() && !previousModels.isEmpty()) {
      callback.onRemoved(0, previousModels.size());
    } else if (!newModels.isEmpty() && previousModels.isEmpty()) {
      callback.onInserted(0, newModels.size());
    }

    // Else nothing changed!
  }
}