// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.sdk.util;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import org.chromium.sdk.RelayOk;
import org.chromium.sdk.SyncCallback;
import org.chromium.sdk.util.AsyncFuture.Callback;

/**
 * A class that provides an {@link AsyncFuture} for a group operation: one that consists of any
 * number of sub-operations and completes once all of them have completed.
 * The class does not care about nature of sub-operations, it only keeps their number. User
 * registers new sub-operations ({@link #addSubOperation()}) and reports on their completion
 * ({@link #subOperationDone} and ({@link #subOperationDoneSync}). One sub-operation is registered
 * by default. Once all sub-operations completed,
 * the main operation is considered done and the future returns the group result. Groups result is
 * a list of sub-operation results.
 *
 * @param <T> type of sub-operation result
 */
public class AsyncFutureMerger<T> {
  private final AtomicReference<AsyncFuture<List<T>>> futureRef;
  private AsyncFuture.Callback<List<T>> callback;
  private SyncCallback syncCallback;

  private final List<T> resultBuffer = new ArrayList<T>();
  private int subOperationCounter = 1;
  private int subOperationSyncCounter = 1;
  private RuntimeException savedException = null;

  public AsyncFutureMerger() {
    futureRef = new AtomicReference<AsyncFuture<List<T>>>();
    AsyncFuture.initializeReference(futureRef, new AsyncFuture.Operation<List<T>>() {
      @Override
      public RelayOk start(Callback<List<T>> callback, SyncCallback syncCallback) {
        AsyncFutureMerger.this.callback = callback;
        AsyncFutureMerger.this.syncCallback = syncCallback;
        return SOMEONE_CARES_ABOUT_RELAY_OK;
      }
    });
  }

  /**
   * Registers a new sub-operation.
   * This method is not thread-safe. User must synchronize access himself.
   */
  public void addSubOperation() {
    assert !futureRef.get().isDone();

    subOperationCounter++;
    subOperationSyncCounter++;
  }

  /**
   * Registers a sub-operation completion and saves its result.
   * This call is not mandatory and user may fail to make it; this won't cause waiting thread
   * to block forever.
   * This method is not thread-safe. User must synchronize access himself.
   */
  public void subOperationDone(T result) {
    resultBuffer.add(result);
    subOperationCounter--;
    if (subOperationCounter == 0) {
      callback.done(resultBuffer);
    }
  }

  /**
   * Additionally registers a sub-operation completion. This method should be called only after
   * {@link #subOperationDone} and this call is mandatory and must be made even in case of
   * sub-operation failure; this method is typically called form finally section of
   * the top-level procedure.
   * Other threads may block infinitely unless this method is being called a proper number of times.
   * This method is not thread-safe. User must synchronize access himself.
   * @param exception optional value of a failure that prevented corresponding call
   *     to {@link #subOperationDone}
   */
  public void subOperationDoneSync(RuntimeException exception) {
    if (exception != null && savedException == null) {
      savedException = exception;
    }
    subOperationSyncCounter--;
    if (subOperationSyncCounter == 0) {
      syncCallback.callbackDone(savedException);
    }
  }

  /**
   * Returns {@link AsyncFuture} that provides the group result.
   * This method is thread-safe.
   */
  public AsyncFuture<List<T>> getFuture() {
    return futureRef.get();
  }

  private static final RelayOk SOMEONE_CARES_ABOUT_RELAY_OK = new RelayOk() {};
}