package com.intellij.debugger.impl;

import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerManagerEx;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.ui.breakpoints.BreakpointManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.util.ui.MessageCategory;
import com.sun.jdi.ReferenceType;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 * User: lex
 * Date: Mar 10, 2004
 * Time: 7:12:57 PM
 * To change this template use File | Settings | File Templates.
 */
class ReloadClassesWorker {
  private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.impl.ReloadClassesWorker");
  /**
   * number of clasess that will be reloaded in one go. 
   * Such restriction is needed to deal with big number of classes being reloaded
   */
  private static final int CLASSES_CHUNK_SIZE = 100; 
  private final DebuggerSession  myDebuggerSession;
  private final HotSwapProgress  myProgress;

  public ReloadClassesWorker(DebuggerSession session, HotSwapProgress progress) {
    myDebuggerSession = session;
    myProgress = progress;
  }

  private DebugProcessImpl getDebugProcess() {
    return myDebuggerSession.getProcess();
  }

  private Project getProject() {
    return myDebuggerSession.getProject();
  }

  private void processException(Throwable e) {
    if (e.getMessage() != null) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, e.getMessage());
    }

    if (e instanceof ProcessCanceledException) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.INFORMATION, DebuggerBundle.message("error.operation.canceled"));
      return;
    }

    if (e instanceof UnsupportedOperationException) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.operation.not.supported.by.vm"));
    }
    else if (e instanceof NoClassDefFoundError) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.class.def.not.found", e.getLocalizedMessage()));
    }
    else if (e instanceof VerifyError) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.verification.error", e.getLocalizedMessage()));
    }
    else if (e instanceof UnsupportedClassVersionError) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.unsupported.class.version", e.getLocalizedMessage()));
    }
    else if (e instanceof ClassFormatError) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.class.format.error", e.getLocalizedMessage()));
    }
    else if (e instanceof ClassCircularityError) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.class.circularity.error", e.getLocalizedMessage()));
    }
    else {
      myProgress.addMessage(
        myDebuggerSession, MessageCategory.ERROR,
        DebuggerBundle.message("error.exception.while.reloading", e.getClass().getName(), e.getLocalizedMessage())
      );
    }
  }

  public void reloadClasses(final Map<String, HotSwapFile> modifiedClasses) {
    if(modifiedClasses == null || modifiedClasses.size() == 0) {
      myProgress.addMessage(myDebuggerSession, MessageCategory.INFORMATION, DebuggerBundle.message("status.hotswap.loaded.classes.up.to.date"));
      return;
    }

    final VirtualMachineProxyImpl virtualMachineProxy = getDebugProcess().getVirtualMachineProxy();
    if(virtualMachineProxy == null) {
      return;
    }

    virtualMachineProxy.suspend();
           
    final Project project = getDebugProcess().getProject();
    try {
      final Map<ReferenceType, byte[]> redefineMap = new java.util.HashMap<ReferenceType,byte[]>();
      int processedClassesCount = 0;
      final IOException[] _ex = new IOException[] {null};
      for (final String qualifiedName : modifiedClasses.keySet()) {
        processedClassesCount++;
        if (qualifiedName != null) {
          myProgress.setText(qualifiedName);
          myProgress.setFraction(processedClassesCount / (double)modifiedClasses.size());
        }
        _ex[0] = null;
        final HotSwapFile fileDescr = modifiedClasses.get(qualifiedName);
        final byte[] buffer = ApplicationManager.getApplication().runReadAction(new Computable<byte[]>() {
          public byte[] compute() {
            try {
              return fileDescr.file.contentsToByteArray();
            }
            catch (IOException e) {
              _ex[0] = e;
              return null;
            }
          }
        });
        if (buffer != null) {
          final List<ReferenceType> classes = virtualMachineProxy.classesByName(qualifiedName);
          for (final ReferenceType reference : classes) {
            redefineMap.put(reference, buffer);
          }
        }
        else {
          reportProblem(qualifiedName, _ex[0]);
        }
        if (redefineMap.size() >= CLASSES_CHUNK_SIZE) {
          // reload this portion of clasess and clear the map to free memory
          try {
            virtualMachineProxy.redefineClasses(redefineMap);
          }
          finally {
            redefineMap.clear();
          }
        }
      }
      if (redefineMap.size() > 0) {
        virtualMachineProxy.redefineClasses(redefineMap);
      }
      myProgress.setFraction(1);
            
      myProgress.addMessage(myDebuggerSession, MessageCategory.INFORMATION, DebuggerBundle.message("status.classes.reloaded", modifiedClasses.size()));
      if (LOG.isDebugEnabled()) {
        LOG.debug("classes reloaded");
      }
    }
    catch (Throwable e) {
      processException(e);
    }

    //noinspection SSBasedInspection
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        if (project.isDisposed()) {
          return;
        }
        final BreakpointManager breakpointManager = (DebuggerManagerEx.getInstanceEx(project)).getBreakpointManager();
        breakpointManager.reloadBreakpoints();
        breakpointManager.updateAllRequests();
        if (LOG.isDebugEnabled()) {
          LOG.debug("requests updated");
          LOG.debug("time stamp set");
        }
        myDebuggerSession.refresh(false);

        getDebugProcess().getManagerThread().schedule(new DebuggerCommandImpl() {
              protected void action() throws Exception {
                try {
                  virtualMachineProxy.resume();
                }
                catch (Exception e) {
                  processException(e);
                }
              }
            });
      }
    });
  }

  private void reportProblem(final String qualifiedName, @Nullable Exception ex) {
    String reason = null;     
    if (ex != null)  {
      reason = ex.getLocalizedMessage();
    }
    if (reason == null || reason.length() == 0) {
      reason = DebuggerBundle.message("error.io.error");
    }
    final StringBuilder buf = StringBuilderSpinAllocator.alloc();
    try {
      buf.append(qualifiedName).append(" : ").append(reason);
      myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, buf.toString());
    }
    finally {
      StringBuilderSpinAllocator.dispose(buf);
    }
  }
}

@@ -4106,55 +4106,8 @@
  %7D%0A%0A
-    virtualMachineProxy.suspend();%0A           %0A
     
@@ -4158,24 +4158,252 @@
 tProject();%0A
+    final BreakpointManager breakpointManager = (DebuggerManagerEx.getInstanceEx(project)).getBreakpointManager();%0A    breakpointManager.disableBreakpoints(getDebugProcess());%0A    %0A    virtualMachineProxy.suspend();%0A           %0A
     try %7B%0A  

possible fix for hotswap hangups