/*
 * Copyright 2011 JBoss, by Red Hat, 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.jboss.errai.common.client.api.tasks;


import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.user.client.Timer;
import org.jboss.errai.common.client.util.TimeUnit;

/**
 * The client-side implementation of {@link TaskManager}.
 */
public class ClientTaskManager implements TaskManager {
  @Override
  public void execute(final Runnable task) {
    GWT.runAsync(new RunAsyncCallback() {
      @Override
      public void onFailure(final Throwable reason) {
        GWT.log("failed async execution", reason);
      }

      @Override
      public void onSuccess() {
        task.run();
      }
    });
  }

  @Override
  public AsyncTask scheduleRepeating(final TimeUnit unit, final int interval, final Runnable userTask) {
    final TaskManagerTimer timer = new TaskManagerTimer(userTask);
    timer.scheduleRepeating((int) unit.toMillis(interval));
    return timer.asyncTask;
  }

  @Override
  public AsyncTask schedule(final TimeUnit unit, final int interval, final Runnable userTask) {
    final TaskManagerTimer timer = new TaskManagerTimer(userTask);
    timer.schedule((int) unit.toMillis(interval));
    return timer.asyncTask;
  }

  /**
   * An AsyncTask implementation that is meant to be created and used by the
   * {@link TaskManagerTimer}.
   *
   * @author Jonathan Fuerth <jfuerth@gmail.com>
   */
  private static class ClientAsyncTask implements Runnable, AsyncTask {
    private final Runnable task;
    private final TaskManagerTimer timer;
    private Runnable exitHandler;

    /**
     * True if and only if the task will never run again.
     */
    private boolean finished;
    private boolean isCancelled;

    /**
     * Don't call this. Use {@link #create(Runnable, org.jboss.errai.common.client.api.tasks.ClientTaskManager.TaskManagerTimer)}.
     *
     * @param task
     * @param timer
     */
    private ClientAsyncTask(final Runnable task, final TaskManagerTimer timer) {
      this.task = task;
      this.timer = timer;
    }

    @Override
    public void run() {
      try {
        task.run();
      } catch (Throwable t) {
        GWT.log("Async Task Execution Failed. Future executions (if any) are cancelled.", t);
        timer.cancel();
      }
    }

    /**
     * Creates a new async task for the client, injecting the reference into
     * {@code task} if it is an instance of {@link HasAsyncTaskRef}.
     *
     * @param task The code to execute. Not null.
     * @param timer The timer that will execute class. Not null.
     * @return A new AsyncTask that relates to {@code task}.
     */
    public static ClientAsyncTask create(final Runnable task, final TaskManagerTimer timer) {
      final ClientAsyncTask t = new ClientAsyncTask(task, timer);
      if (task instanceof HasAsyncTaskRef) {
        ((HasAsyncTaskRef) task).setAsyncTask(t);
      }
      return t;
    }

    @Override
    public void cancel(final boolean interrupt) {
      timer.cancel();
      finishUp();
    }

    @Override
    public void setExitHandler(final Runnable runnable) {
      if (exitHandler != null) {
        throw new IllegalStateException("Exit handler is already set to " + exitHandler);
      }
      this.exitHandler = runnable;
      if (isFinished()) {
        exitHandler.run();
      }
    }

    @Override
    public boolean isCancelled() {
      return timer.isCancelled();
    }

    private boolean isFinished() {
      return finished;
    }

    public void finishUp() {
      if (finished) {
        throw new IllegalStateException("Already finished");
      }
      finished = true;
      if (exitHandler != null) {
        exitHandler.run();
      }
    }
  }

  private enum SchedulingMode { ONE_TIME, REPEATING }

  /**
   * A GWT Timer implementation that has a public flag indicating if
   * {@link #cancel()})} has been called.
   * <p>
   * This Timer class only allows a single call to either {@link #schedule(int)}
   * or {@link #scheduleRepeating(int)}. Further attempts to schedule the call
   * will result in {@link IllegalStateException}.
   *
   * @author Jonathan Fuerth <jfuerth@gmail.com>
   */
  private final class TaskManagerTimer extends Timer {

    private SchedulingMode mode;
    private final ClientAsyncTask asyncTask;

    TaskManagerTimer(final Runnable userTask) {
      asyncTask = ClientAsyncTask.create(userTask, this);
    }

    @Override
    public void schedule(final int delayMillis) {
      if (mode != null) {
        throw new IllegalStateException("This timer has already been scheduled.");
      }
      mode = SchedulingMode.ONE_TIME;
      super.schedule(delayMillis);
    }

    @Override
    public void scheduleRepeating(final int periodMillis) {
      if (mode != null) {
        throw new IllegalStateException("This timer has already been scheduled.");
      }
      mode = SchedulingMode.REPEATING;
      super.scheduleRepeating(periodMillis);
    }

    @Override
    public void run() {
      asyncTask.run();
      if (mode == SchedulingMode.ONE_TIME) {
        asyncTask.finishUp();
      }
    }

    @Override
    public void cancel() {
      super.cancel();
      asyncTask.isCancelled = true;
    }

    public boolean isCancelled() {
      return asyncTask.isCancelled;
    }
  }

  @Override
  public void requestStop() {
  }
}
