// Copyright (c) 2010 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.nodeclipse.debug.model;

import org.chromium.debug.core.ChromiumDebugPlugin;
import org.chromium.debug.core.model.BreakpointSynchronizer;
import org.chromium.debug.core.model.ConnectedTargetData;
import org.chromium.debug.core.model.DebugTargetImpl;
import org.chromium.debug.core.model.LaunchParams;
import org.chromium.debug.core.model.Messages;
import org.chromium.debug.core.util.ProgressUtil;
import org.chromium.debug.core.util.ProgressUtil.MonitorWrapper;
import org.chromium.debug.core.util.ProgressUtil.Stage;
import org.chromium.sdk.CallbackSemaphore;
import org.chromium.sdk.RelayOk;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.osgi.util.NLS;

/**
 * Does several things we need on debug session start. This procedure works asynchronously while
 * user may already start his debug activity.
 */
class LaunchInitializationProcedure {
  static void startAsync(VProjectWorkspaceBridge workspaceBridge,
      BreakpointSynchronizer.Direction presetDirection) {
    final LaunchInitializationProcedure procedure =
        new LaunchInitializationProcedure(workspaceBridge, presetDirection);

    ILaunch launch = workspaceBridge.getConnectedTargetData().getDebugTarget().getLaunch();
    final String jobName = NLS.bind(Messages.LaunchInitializationProcedure_JOB_NAME,
        launch.getLaunchConfiguration().getName());
    Job job = new Job(jobName) {
      @Override
      protected IStatus run(IProgressMonitor monitor) {
        return procedure.execute(monitor);
      }
    };
    job.schedule();
  }

  private final VProjectWorkspaceBridge workspaceBridge;
  private final BreakpointSynchronizer.Direction presetDirection;

  private LaunchInitializationProcedure(VProjectWorkspaceBridge workspaceBridge,
      BreakpointSynchronizer.Direction presetDirection) {
    this.workspaceBridge = workspaceBridge;
    this.presetDirection = presetDirection;
  }

  /**
   * A list of tasks for progress monitor.
   */
  private interface WorkPlan {
    Stage PREINIT = new Stage(Messages.LaunchInitializationProcedure_UPDATE_DEBUGGER_STATE, 0.1f);
    Stage SET_OPTIONS = new Stage(Messages.LaunchInitializationProcedure_SET_OPTIONS, 1f);
    Stage LOAD_SCRIPTS = new Stage(Messages.LaunchInitializationProcedure_LOAD_SCRIPTS, 1f);
    Stage SYNCHRONIZE_BREAKPOINTS =
        new Stage(Messages.LaunchInitializationProcedure_SYNCHRONIZE_BREAKPOINTS, 1f);

    boolean IS_INITIZALIZED = ProgressUtil.layoutProgressPlan(PREINIT, SET_OPTIONS, LOAD_SCRIPTS,
        SYNCHRONIZE_BREAKPOINTS);
  }

  private IStatus execute(IProgressMonitor monitor) {
    ConnectedTargetData connectedTargetData = workspaceBridge.getConnectedTargetData();

    MonitorWrapper monitorWrapper = new MonitorWrapper(monitor, ""); //$NON-NLS-1$

    monitorWrapper.beginTask();
    try {
      WorkPlan.PREINIT.start(monitorWrapper);
      // Nothing here right now.
      WorkPlan.PREINIT.finish(monitorWrapper);
      checkIsCanceled(monitorWrapper);
      WorkPlan.SET_OPTIONS.start(monitorWrapper);
      // Not implemented yet
      WorkPlan.SET_OPTIONS.finish(monitorWrapper);
      checkIsCanceled(monitorWrapper);
      WorkPlan.LOAD_SCRIPTS.start(monitorWrapper);
      workspaceBridge.reloadScriptsAtStart();
      WorkPlan.LOAD_SCRIPTS.finish(monitorWrapper);
      checkIsCanceled(monitorWrapper);
      synchronizeBreakpoints(
          WorkPlan.SYNCHRONIZE_BREAKPOINTS.createSubMonitorWrapper(monitorWrapper));

    } finally {
      monitorWrapper.done();
    }
    return Status.OK_STATUS;
  }

  /**
   * A list of tasks for breakpoint synchronization subprocess.
   */
  private interface BreakpointsWorkPlan {
    Stage ANALYZE = new Stage(null, 1f);
    Stage REMOTE_CHANGES = new Stage(null, 1f);

    boolean IS_LAYOUTED = ProgressUtil.layoutProgressPlan(ANALYZE, REMOTE_CHANGES);
  }

  private void synchronizeBreakpoints(MonitorWrapper monitor) {
    monitor.beginTask();
    try {
      BreakpointsWorkPlan.ANALYZE.start(monitor);

      DebugTargetImpl debugTarget = workspaceBridge.getConnectedTargetData().getDebugTarget();
      ILaunchConfiguration launchConfiguration = debugTarget.getLaunch().getLaunchConfiguration();

      BreakpointSynchronizer.Direction direction;
      if (presetDirection == null) {
        try {
          direction = LaunchParams.readBreakpointSyncDirection(launchConfiguration);
        } catch (CoreException e) {
          ChromiumDebugPlugin.log(
              new Exception("Failed to read breakpoint synchronization direction " + //$NON-NLS-1$
                  "from launch configuration " + launchConfiguration.getName(), e)); //$NON-NLS-1$
          direction = null;
        }
      } else {
        direction = presetDirection;
      }

      if (direction == null) {
        return;
      }
      final CallbackSemaphore callbackSemaphore = new CallbackSemaphore();
      BreakpointSynchronizer.Callback callback = new BreakpointSynchronizer.Callback() {
        public void onDone(IStatus status) {
          callbackSemaphore.callbackDone(null);
        }
      };
      workspaceBridge.getBreakpointSynchronizer().syncBreakpoints(direction, callback);
      RelayOk relayOk = SYNCHRONIZER_MUST_RELAY_OK;
      checkIsCanceled(monitor);

      BreakpointsWorkPlan.ANALYZE.finish(monitor);

      BreakpointsWorkPlan.REMOTE_CHANGES.start(monitor);
      callbackSemaphore.tryAcquireDefault(relayOk);
      BreakpointsWorkPlan.REMOTE_CHANGES.finish(monitor);

    } finally {
      monitor.done();
    }
  }

  private static void checkIsCanceled(MonitorWrapper monitor) {
    if (monitor.isCanceled()) {
      throw new OperationCanceledException();
    }
  }

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