/**
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.waveprotocol.wave.client.scheduler;

import com.google.common.annotations.VisibleForTesting;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.client.impl.SchedulerImpl;

/**
 * A command that is idempotentally schedulable and cancellable.
 *
 * This class primarily exists to work around the absence of isScheduled() and
 * cancel() methods in GWT's scheduler interface.
 *
 */
public abstract class IdempotentFinally implements ScheduledCommand {
  private enum State {
    /** Not scheduled. */
    NONE,
    /** Scheduled, and will run doExecute() when invoked. */
    SCHEDULED_TO_RUN,
    /** Scheduled, but will do nothing when invoked. */
    SCHEDULED_TO_SKIP
  }

  private final Scheduler scheduler;
  private State state = State.NONE;

  @VisibleForTesting
  IdempotentFinally(Scheduler scheduler) {
    this.scheduler = scheduler;
  }

  protected IdempotentFinally() {
    this(SchedulerImpl.get());
  }

  /**
   * Schedules this command to run before the end of this event loop. If it is
   * already scheduled to run, does nothing. If it was previously cancelled, it
   * is uncancelled.
   */
  public final void schedule() {
    switch(state) {
      case NONE:
        scheduler.scheduleFinally(this);
        state = State.SCHEDULED_TO_RUN;
        break;
      case SCHEDULED_TO_SKIP:
        // Already scheduled.
        state = State.SCHEDULED_TO_RUN;
        break;
      case SCHEDULED_TO_RUN:
        // Do nothing.
        break;
    }
    assert state == State.SCHEDULED_TO_RUN;
  }

  /**
   * Cancels this command from running, if it is currently scheduled.
   */
  public final void cancel() {
    switch(state) {
      case NONE:
        // Do nothing.
        break;
      case SCHEDULED_TO_SKIP:
        // Do nothing.
        break;
      case SCHEDULED_TO_RUN:
        // Already scheduled.
        state = State.SCHEDULED_TO_SKIP;
        break;
    }
    assert state != State.SCHEDULED_TO_RUN;
  }

  @Override
  public final void execute() {
    switch (state) {
      case NONE:
        throw new IllegalStateException( "IdempotentFinally run when not scheduled");
      case SCHEDULED_TO_SKIP:
        state = State.NONE;
        // Skip doExecute();
        break;
      case SCHEDULED_TO_RUN:
        state = State.NONE;
        doExecute();
        break;
    }
  }

  /**
   * Runs this command. Subclasses must override this to provide their command
   * logic.
   */
  protected abstract void doExecute();
}
