/*
 * Copyright 2007 Sascha Weinreuter
 *
 * 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.intellij.plugins.xsltDebugger;

import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import org.intellij.plugins.xsltDebugger.impl.XsltDebugProcess;
import org.intellij.plugins.xsltDebugger.rt.engine.Debugger;
import org.intellij.plugins.xsltDebugger.rt.engine.OutputEventQueue;
import org.intellij.plugins.xsltDebugger.rt.engine.remote.RemoteDebuggerClient;
import org.intellij.plugins.xsltDebugger.ui.StructureTabComponent;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.rmi.ConnectException;
import java.rmi.NotBoundException;

/**
 * Establishes the debugger-connection to the started XSLT-process, starts the debugger-session
 * and attaches the debugger-UI.
 */
class DebuggerConnector implements Runnable {

  private final Project myProject;
  private final ProcessHandler myProcess;
  private final int myPort;
  private final String myAccessToken;

  DebuggerConnector(Project project, ProcessHandler process, int port, String accessToken) {
    myProject = project;
    myProcess = process;
    myPort = port;
    myAccessToken = accessToken;
  }

  @Override
  public void run() {
    final Debugger client = connect();
    if (client == null) {
      // client will be null if the process terminated prematurely for some reason. no need for an error message
      if (!myProcess.isProcessTerminated()) {
        myProcess.notifyTextAvailable("Failed to connect to debugged process. Terminating.\n", ProcessOutputTypes.SYSTEM);
        myProcess.destroyProcess();
      }
      return;
    }

    final XsltDebuggerSession session = XsltDebuggerSession.create(myProject, myProcess, client);

    final XsltDebugProcess dbgp = XsltDebugProcess.getInstance(myProcess);
    assert dbgp != null;
    dbgp.init(client);

    session.addListener(new XsltDebuggerSession.Listener() {
      @Override
      public void debuggerSuspended() {
        final OutputEventQueue queue = client.getEventQueue();
        StructureTabComponent.getInstance(myProcess).getEventModel().update(queue.getEvents());
      }

      @Override
      public void debuggerResumed() {
      }

      @Override
      public void debuggerStopped() {
        try {
          final OutputEventQueue queue = client.getEventQueue();
          StructureTabComponent.getInstance(myProcess).getEventModel().finalUpdate(queue.getEvents());
        } catch (Exception e) {
          // can fail when debugger is manually terminated
        }
      }
    });

    session.start();
  }

  private @Nullable Debugger connect() {
    Throwable lastException = null;
    for (int i = 0; i < 10; i++) {
      if (myProcess.isProcessTerminated()) return null;

      try {
        final Debugger realClient = EDTGuard.create(new RemoteDebuggerClient(myPort, myAccessToken), myProcess);
        myProcess.notifyTextAvailable("Connected to XSLT debugger on port " + myPort + "\n", ProcessOutputTypes.SYSTEM);
        return realClient;
      } catch (ConnectException e) {
        lastException = e;
        try {
          Thread.sleep(500);
        } catch (InterruptedException e1) {
          break;
        }
      } catch (NotBoundException e) {
        lastException = e;
        try {
          Thread.sleep(200);
        } catch (InterruptedException e1) {
          break;
        }
      } catch (IOException e) {
        lastException = e;
        break;
      }
    }

    Logger.getInstance(getClass().getName()).info("Could not connect to debugger", lastException);

    if (lastException.getMessage() != null) {
      myProcess.notifyTextAvailable("Connection error: " + lastException.getMessage() + "\n", ProcessOutputTypes.SYSTEM);
    }

    return null;
  }
}
