// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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 com.google.collide.client.util;

import com.google.collide.client.util.UserActivityManager.UserActivityListener;
import com.google.collide.client.util.logging.Log;
import com.google.collide.shared.util.ListenerRegistrar.Remover;
import com.google.gwt.core.client.Duration;

/**
 * A scheduler that can incrementally run a task.
 *
 */
public class BasicIncrementalScheduler implements IncrementalScheduler {

  private final AsyncRunner runner = new AsyncRunner() {
    @Override
    public void run() {
      if (isPaused) {
        return;
      }

      try {
        double start = Duration.currentTimeMillis();
        boolean keepRunning = worker.run(currentWorkAmount);
        updateWorkAmount(Duration.currentTimeMillis() - start);
        if (keepRunning) {
          schedule();
        } else {
          clearWorker();
        }
      } catch (Throwable t) {
        Log.error(getClass(), "Could not run worker", t);
      }
    }
  };

  private Task worker;
  private boolean isPaused;
  private int currentWorkAmount;
  private final int targetExecutionMs;
  private int completedWorkAmount;
  private double totalTimeTaken;
  public Remover userActivityRemover;

  public BasicIncrementalScheduler(int targetExecutionMs, int workGuess) {
    this.targetExecutionMs = targetExecutionMs;
    currentWorkAmount = workGuess;
  }

  public BasicIncrementalScheduler(
      UserActivityManager userActivityManager, int targetExecutionMs, int workGuess) {
    this(targetExecutionMs, workGuess);

    userActivityRemover =
        userActivityManager.getUserActivityListenerRegistrar().add(new UserActivityListener() {
      @Override
      public void onUserActive() {
        pause();
      }

      @Override
      public void onUserIdle() {
        resume();
      }
    });
  }

  @Override
  public void schedule(Task worker) {
    cancel();
    this.worker = worker;

    if (!isPaused) {
      runner.run();
    }
  }

  @Override
  public void cancel() {
    runner.cancel();
    worker = null;
  }

  @Override
  public void pause() {
    isPaused = true;
  }

  /**
   * Schedules the worker to resume.  This will run asychronously.
   */
  @Override
  public void resume() {
    isPaused = false;

    if (worker != null) {
      launch();
    }
  }

  @Override
  public boolean isPaused() {
    return isPaused;
  }

  @Override
  public void teardown() {
    cancel();

    if (userActivityRemover != null) {
      userActivityRemover.remove();
    }
  }

  /**
   * Update the currentWorkAmount based upon the workTime it took to run the
   * last command so running the worker will take ~targetExecutionMs.
   *
   * @param workTime ms the last run took
   */
  private void updateWorkAmount(double workTime) {
    if (workTime <= 0) {
      currentWorkAmount *= 2;
    } else {
      totalTimeTaken += workTime;
      completedWorkAmount += currentWorkAmount;
      currentWorkAmount = (int) Math.ceil(targetExecutionMs * completedWorkAmount / totalTimeTaken);
    }
  }

  private void clearWorker() {
    worker = null;
  }

  @Override
  public boolean isBusy() {
    return worker != null;
  }

  /**
   * Queues the worker launch.
   */
  private void launch() {
    runner.schedule();
  }
}
