/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * 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.intellij.util.concurrency;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;

/**
 * This is the 3rd version of SwingWorker (also known as
 * SwingWorker 3), an abstract class that you subclass to
 * perform GUI-related work in a dedicated thread.  For
 * instructions on using this class, see:
 *
 * http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html
 *
 * Note that the API changed slightly in the 3rd version:
 * You must now invoke start() on the SwingWorker after
 * creating it.
 */
public abstract class SwingWorker {
  private static final Logger LOG = Logger.getInstance("#com.intellij.util.concurrency.SwingWorker");
  private Object value;
  // see getValue(), setValue()

  private final ModalityState myModalityState;

  /**
   * Class to maintain reference to current worker thread
   * under separate synchronization control.
   */
  private static class ThreadVar {
    private Thread myThread;

    ThreadVar(Thread t) {
      myThread = t;
    }

    synchronized Thread get() {
      return myThread;
    }

    synchronized void clear() {
      myThread = null;
    }
  }

  private final ThreadVar myThreadVar;
  /**
   * Get the value produced by the worker thread, or null if it
   * hasn't been constructed yet.
   */

  protected synchronized Object getValue() {
    return value;
  }

  /**
   * Set the value produced by worker thread
   */

  private synchronized void setValue(Object x) {
    value = x;
  }

  /**
   * Compute the value to be returned by the <code>get</code> method.
   */

  public abstract Object construct();

  /**
   * Called on the event dispatching thread (not on the worker thread)
   * after the <code>construct</code> method has returned.
   */

  public void finished() {
  }

  /**
   * Called in the worker thread in case a RuntimeException or Error occurred
   * if the <code>construct</code> method has thrown an uncaught Throwable.
   */
  public void onThrowable() {
  }

  /**
   * A new method that interrupts the worker thread.  Call this method
   * to force the worker to stop what it's doing.
   */

  public void interrupt() {
    Thread t = myThreadVar.get();
    if (t != null){
      t.interrupt();
    }
    myThreadVar.clear();
  }

  /**
   * Return the value created by the <code>construct</code> method.
   * Returns null if either the constructing thread or the current
   * thread was interrupted before a value was produced.
   *
   * @return the value created by the <code>construct</code> method
   */

  public Object get() {
    while(true){
      Thread t = myThreadVar.get();
      if (t == null){
        return getValue();
      }
      try{
        t.join();
      }
      catch(InterruptedException e){
        Thread.currentThread().interrupt();
        // propagate
        return null;
      }
    }
  }

  /**
   * Start a thread that will call the <code>construct</code> method
   * and then exit.
   */

  public SwingWorker() {
    myModalityState = ModalityState.current();

    final Runnable doFinished = () -> finished();

    Runnable doConstruct = new Runnable() {
      public void run() {
        try{
          setValue(construct());
          if (LOG.isDebugEnabled()) {
            LOG.debug("construct() terminated");
          }
        }
        catch (Throwable e) {
          LOG.error(e);
          onThrowable();
          throw new RuntimeException(e);
        }
        finally{
          myThreadVar.clear();
        }
        if (LOG.isDebugEnabled()) {
          LOG.debug("invoking 'finished' action");
        }
        ApplicationManager.getApplication().invokeLater(doFinished, myModalityState);
      }
    };

    final Thread workerThread = new Thread(doConstruct, "SwingWorker work thread");
    workerThread.setPriority(Thread.NORM_PRIORITY);
    myThreadVar = new ThreadVar(workerThread);
  }

  /**
   * Start the worker thread.
   */

  public void start() {
    Thread t = myThreadVar.get();
    if (t != null){
      t.start();
    }
  }
}
