// Copyright (c) 2009 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.debug.ui.launcher;

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

import org.chromium.debug.core.ChromiumDebugPlugin;
import org.chromium.debug.core.model.BreakpointSynchronizer.Direction;
import org.chromium.debug.core.model.ConnectionLoggerImpl;
import org.chromium.debug.core.model.ConsolePseudoProcess;
import org.chromium.debug.core.model.DebugTargetImpl;
import org.chromium.debug.core.model.IPredefinedSourceWrapProvider;
import org.chromium.debug.core.model.JavascriptVmEmbedder;
import org.chromium.debug.core.model.LaunchParams;
import org.chromium.debug.core.model.NamedConnectionLoggerFactory;
import org.chromium.debug.core.model.SourceWrapSupport;
import org.chromium.debug.core.model.VProjectWorkspaceBridge;
import org.chromium.debug.core.model.WorkspaceBridge;
import org.chromium.debug.ui.PluginUtil;
import org.chromium.sdk.ConnectionLogger;
import org.chromium.sdk.util.Destructable;
import org.chromium.sdk.util.DestructingGuard;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.ILaunchConfigurationDelegate;

/**
 * A launch configuration delegate for the JavaScript debugging.
 */
public abstract class LaunchTypeBase implements ILaunchConfigurationDelegate {

  public void launch(ILaunchConfiguration config, String mode, final ILaunch launch,
      IProgressMonitor monitor) throws CoreException {
    if (!mode.equals(ILaunchManager.DEBUG_MODE)) {
      // Chromium JavaScript launch is only supported for debugging.
      return;
    }


    String host = config.getAttribute(LaunchParams.CHROMIUM_DEBUG_HOST,
        PluginVariablesUtil.getValue(PluginVariablesUtil.DEFAULT_HOST));

    int port = config.getAttribute(LaunchParams.CHROMIUM_DEBUG_PORT,
        PluginVariablesUtil.getValueAsInt(PluginVariablesUtil.DEFAULT_PORT));

    if (host == null && port == -1) {
      throw new RuntimeException("Missing parameters in launch config");
    }

    boolean addNetworkConsole = config.getAttribute(LaunchParams.ADD_NETWORK_CONSOLE, false);

    SourceWrapSupport sourceWrapSupport = createSourceWrapSupportFromConfig(config);

    JavascriptVmEmbedder.ConnectionToRemote remoteServer =
        createConnectionToRemote(host, port, launch, addNetworkConsole);
    try {

      final String projectNameBase = config.getName();

      DestructingGuard destructingGuard = new DestructingGuard();
      try {
        Destructable lauchDestructor = new Destructable() {
          public void destruct() {
            if (!launch.hasChildren()) {
              DebugPlugin.getDefault().getLaunchManager().removeLaunch(launch);
            }
          }
        };

        destructingGuard.addValue(lauchDestructor);

        WorkspaceBridge.Factory bridgeFactory =
            new VProjectWorkspaceBridge.FactoryImpl(projectNameBase);

        final DebugTargetImpl target =
            new DebugTargetImpl(launch, bridgeFactory, sourceWrapSupport, getPresetSyncDirection());

        Destructable targetDestructor = new Destructable() {
          public void destruct() {
            terminateTarget(target);
          }
        };
        destructingGuard.addValue(targetDestructor);

        launch.addDebugTarget(target);

        boolean attached = DebugTargetImpl.attach(target, remoteServer, destructingGuard,
            OPENING_VIEW_ATTACH_CALLBACK, monitor);
        if (!attached) {
          // Cancel pressed.
          return;
        }

        launch.addDebugTarget(target);
        monitor.done();

        // All OK
        destructingGuard.discharge();
      } finally {
        destructingGuard.doFinally();
      }

    } finally {
      remoteServer.disposeConnection();
    }
  }

  protected abstract JavascriptVmEmbedder.ConnectionToRemote createConnectionToRemote(String host,
      int port, ILaunch launch, boolean addConsoleLogger) throws CoreException;

  protected abstract Direction getPresetSyncDirection();

  protected static void terminateTarget(DebugTargetImpl target) {
    try {
      target.terminate();
    } catch (DebugException e) {
      ChromiumDebugPlugin.log(e);
    }
  }

  static ConnectionLogger createConsoleAndLogger(final ILaunch launch,
      final boolean addLaunchToManager, final String title) {
    final ConsolePseudoProcess.Retransmitter consoleRetransmitter =
        new ConsolePseudoProcess.Retransmitter();

    // This controller is responsible for creating ConsolePseudoProcess only on
    // logStarted call. Before this ConnectionLoggerImpl with all it fields should stay
    // garbage-collectible, because connection may not even start.
    ConnectionLoggerImpl.LogLifecycleListener consoleController =
        new ConnectionLoggerImpl.LogLifecycleListener() {
      private final AtomicBoolean alreadyStarted = new AtomicBoolean(false);

      public void logClosed() {
        consoleRetransmitter.processClosed();
      }

      public void logStarted(ConnectionLoggerImpl connectionLogger) {
        boolean res = alreadyStarted.compareAndSet(false, true);
        if (!res) {
          throw new IllegalStateException();
        }
        ConsolePseudoProcess consolePseudoProcess = new ConsolePseudoProcess(launch, title,
            consoleRetransmitter, connectionLogger.getConnectionTerminate());
        consoleRetransmitter.startFlushing();
        if (addLaunchToManager) {
          // Active the launch (again if it has already been removed)
          DebugPlugin.getDefault().getLaunchManager().addLaunch(launch);
       }
      }
    };

    return new ConnectionLoggerImpl(consoleRetransmitter, consoleController);
  }

  protected static SourceWrapSupport createSourceWrapSupportFromConfig(ILaunchConfiguration config)
      throws CoreException {
    List<IPredefinedSourceWrapProvider.Entry> entries =
        LaunchParams.PredefinedSourceWrapperIds.resolveEntries(config);
    List<SourceWrapSupport.Wrapper> wrappers =
        new ArrayList<SourceWrapSupport.Wrapper>(entries.size());
    for (IPredefinedSourceWrapProvider.Entry en : entries) {
      wrappers.add(en.getWrapper());
    }
    return new SourceWrapSupport(wrappers);
  }

  static final NamedConnectionLoggerFactory NO_CONNECTION_LOGGER_FACTORY =
      new NamedConnectionLoggerFactory() {
    public ConnectionLogger createLogger(String title) {
      return null;
    }
  };

  protected static final Runnable OPENING_VIEW_ATTACH_CALLBACK = new Runnable() {
    public void run() {
      PluginUtil.openProjectExplorerView();
    }
  };
}