public void initComponent() {
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      myCacheRefreshTimer =
          UIUtil.createNamedTimer(
              "TaskManager refresh",
              myConfig.updateInterval * 60 * 1000,
              new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                  if (myConfig.updateEnabled && !myUpdating) {
                    updateIssues(null);
                  }
                }
              });
      myCacheRefreshTimer.setInitialDelay(0);
      StartupManager.getInstance(myProject)
          .registerPostStartupActivity(
              new Runnable() {
                public void run() {
                  myCacheRefreshTimer.start();
                }
              });
    }

    // make sure that the default task is exist
    LocalTask defaultTask = findTask(LocalTaskImpl.DEFAULT_TASK_ID);
    if (defaultTask == null) {
      defaultTask = createDefaultTask();
      addTask(defaultTask);
    }

    // search for active task
    LocalTask activeTask = null;
    final List<LocalTask> tasks = getLocalTasks();
    Collections.sort(tasks, TASK_UPDATE_COMPARATOR);
    for (LocalTask task : tasks) {
      if (activeTask == null) {
        if (task.isActive()) {
          activeTask = task;
        }
      } else {
        task.setActive(false);
      }
    }
    if (activeTask == null) {
      activeTask = defaultTask;
    }

    myActiveTask = activeTask;
    doActivate(myActiveTask, false);
    myDispatcher.getMulticaster().taskActivated(myActiveTask);
  }
 @Override
 public LocalTask put(String key, LocalTask task) {
   LocalTask result = super.put(key, task);
   if (size() > myConfig.taskHistoryLength) {
     ArrayList<LocalTask> list = new ArrayList<LocalTask>(values());
     Collections.sort(list, TASK_UPDATE_COMPARATOR);
     for (LocalTask oldest : list) {
       if (!oldest.isDefault()) {
         remove(oldest);
         break;
       }
     }
   }
   return result;
 }
/** @author Dmitry Avdeev */
@State(
    name = "TaskManager",
    storages = {@Storage(file = StoragePathMacros.WORKSPACE_FILE)})
public class TaskManagerImpl extends TaskManager
    implements ProjectComponent,
        PersistentStateComponent<TaskManagerImpl.Config>,
        ChangeListDecorator {

  private static final Logger LOG = Logger.getInstance("#com.intellij.tasks.impl.TaskManagerImpl");

  private static final DecimalFormat LOCAL_TASK_ID_FORMAT = new DecimalFormat("LOCAL-00000");
  public static final Comparator<Task> TASK_UPDATE_COMPARATOR =
      new Comparator<Task>() {
        public int compare(Task o1, Task o2) {
          int i = Comparing.compare(o2.getUpdated(), o1.getUpdated());
          return i == 0 ? Comparing.compare(o2.getCreated(), o1.getCreated()) : i;
        }
      };
  private static final Convertor<Task, String> KEY_CONVERTOR =
      new Convertor<Task, String>() {
        @Override
        public String convert(Task o) {
          return o.getId();
        }
      };
  static final String TASKS_NOTIFICATION_GROUP = "Task Group";

  private final Project myProject;

  private final WorkingContextManager myContextManager;

  private final Map<String, Task> myIssueCache =
      Collections.synchronizedMap(new LinkedHashMap<String, Task>());

  private final Map<String, LocalTask> myTasks =
      Collections.synchronizedMap(
          new LinkedHashMap<String, LocalTask>() {
            @Override
            public LocalTask put(String key, LocalTask task) {
              LocalTask result = super.put(key, task);
              if (size() > myConfig.taskHistoryLength) {
                ArrayList<LocalTask> list = new ArrayList<LocalTask>(values());
                Collections.sort(list, TASK_UPDATE_COMPARATOR);
                for (LocalTask oldest : list) {
                  if (!oldest.isDefault()) {
                    remove(oldest);
                    break;
                  }
                }
              }
              return result;
            }
          });

  @NotNull private LocalTask myActiveTask = createDefaultTask();
  private Timer myCacheRefreshTimer;

  private volatile boolean myUpdating;
  private final Config myConfig = new Config();
  private final ChangeListAdapter myChangeListListener;
  private final ChangeListManager myChangeListManager;

  private final List<TaskRepository> myRepositories = new ArrayList<TaskRepository>();
  private final EventDispatcher<TaskListener> myDispatcher =
      EventDispatcher.create(TaskListener.class);
  private Set<TaskRepository> myBadRepositories = new ConcurrentHashSet<TaskRepository>();

  public TaskManagerImpl(
      Project project,
      WorkingContextManager contextManager,
      final ChangeListManager changeListManager) {

    myProject = project;
    myContextManager = contextManager;
    myChangeListManager = changeListManager;

    myChangeListListener =
        new ChangeListAdapter() {
          @Override
          public void changeListRemoved(ChangeList list) {
            LocalTask task = getAssociatedTask((LocalChangeList) list);
            if (task != null) {
              for (ChangeListInfo info : task.getChangeLists()) {
                if (Comparing.equal(info.id, ((LocalChangeList) list).getId())) {
                  info.id = "";
                }
              }
            }
          }

          @Override
          public void defaultListChanged(ChangeList oldDefaultList, ChangeList newDefaultList) {
            final LocalTask associatedTask = getAssociatedTask((LocalChangeList) newDefaultList);
            if (associatedTask != null && !getActiveTask().equals(associatedTask)) {
              ApplicationManager.getApplication()
                  .invokeLater(
                      new Runnable() {
                        public void run() {
                          activateTask(associatedTask, true);
                        }
                      },
                      myProject.getDisposed());
            }
          }
        };
  }

  @Override
  public TaskRepository[] getAllRepositories() {
    return myRepositories.toArray(new TaskRepository[myRepositories.size()]);
  }

  public <T extends TaskRepository> void setRepositories(List<T> repositories) {

    Set<TaskRepository> set = new HashSet<TaskRepository>(myRepositories);
    set.removeAll(repositories);
    myBadRepositories.removeAll(set); // remove all changed reps
    myIssueCache.clear();

    myRepositories.clear();
    myRepositories.addAll(repositories);

    reps:
    for (T repository : repositories) {
      if (repository.isShared() && repository.getUrl() != null) {
        List<TaskProjectConfiguration.SharedServer> servers = getProjectConfiguration().servers;
        TaskRepositoryType type = repository.getRepositoryType();
        for (TaskProjectConfiguration.SharedServer server : servers) {
          if (repository.getUrl().equals(server.url) && type.getName().equals(server.type)) {
            continue reps;
          }
        }
        TaskProjectConfiguration.SharedServer server = new TaskProjectConfiguration.SharedServer();
        server.type = type.getName();
        server.url = repository.getUrl();
        servers.add(server);
      }
    }
  }

  @Override
  public void removeTask(LocalTask task) {
    if (task.isDefault()) return;
    if (myActiveTask.equals(task)) {
      activateTask(myTasks.get(LocalTaskImpl.DEFAULT_TASK_ID), true);
    }
    myTasks.remove(task.getId());
    myDispatcher.getMulticaster().taskRemoved(task);
    myContextManager.removeContext(task);
  }

  @Override
  public void addTaskListener(TaskListener listener) {
    myDispatcher.addListener(listener);
  }

  @Override
  public void removeTaskListener(TaskListener listener) {
    myDispatcher.removeListener(listener);
  }

  @NotNull
  @Override
  public LocalTask getActiveTask() {
    return myActiveTask;
  }

  @Nullable
  @Override
  public LocalTask findTask(String id) {
    return myTasks.get(id);
  }

  @NotNull
  @Override
  public List<Task> getIssues(@Nullable final String query) {
    return getIssues(query, true);
  }

  @Override
  public List<Task> getIssues(@Nullable final String query, final boolean forceRequest) {
    return getIssues(query, 50, 0, forceRequest, true, new EmptyProgressIndicator());
  }

  @Override
  public List<Task> getIssues(
      @Nullable String query,
      int max,
      long since,
      boolean forceRequest,
      final boolean withClosed,
      @NotNull final ProgressIndicator cancelled) {
    List<Task> tasks = getIssuesFromRepositories(query, max, since, forceRequest, cancelled);
    if (tasks == null) return getCachedIssues(withClosed);
    myIssueCache.putAll(ContainerUtil.newMapFromValues(tasks.iterator(), KEY_CONVERTOR));
    return ContainerUtil.filter(
        tasks,
        new Condition<Task>() {
          @Override
          public boolean value(final Task task) {
            return withClosed || !task.isClosed();
          }
        });
  }

  @Override
  public List<Task> getCachedIssues() {
    return getCachedIssues(true);
  }

  @Override
  public List<Task> getCachedIssues(final boolean withClosed) {
    return ContainerUtil.filter(
        myIssueCache.values(),
        new Condition<Task>() {
          @Override
          public boolean value(final Task task) {
            return withClosed || !task.isClosed();
          }
        });
  }

  @Nullable
  @Override
  public Task updateIssue(@NotNull String id) {
    for (TaskRepository repository : getAllRepositories()) {
      if (repository.extractId(id) == null) {
        continue;
      }
      try {
        Task issue = repository.findTask(id);
        if (issue != null) {
          LocalTask localTask = findTask(id);
          if (localTask != null) {
            localTask.updateFromIssue(issue);
            return localTask;
          }
          return issue;
        }
      } catch (Exception e) {
        LOG.info(e);
      }
    }
    return null;
  }

  @Override
  public List<LocalTask> getLocalTasks() {
    return getLocalTasks(true);
  }

  @Override
  public List<LocalTask> getLocalTasks(final boolean withClosed) {
    synchronized (myTasks) {
      return ContainerUtil.filter(
          myTasks.values(),
          new Condition<LocalTask>() {
            @Override
            public boolean value(final LocalTask task) {
              return withClosed || !isLocallyClosed(task);
            }
          });
    }
  }

  @Override
  public LocalTask addTask(Task issue) {
    LocalTaskImpl task =
        issue instanceof LocalTaskImpl ? (LocalTaskImpl) issue : new LocalTaskImpl(issue);
    addTask(task);
    return task;
  }

  @Override
  public LocalTaskImpl createLocalTask(@NotNull String summary) {
    return createTask(LOCAL_TASK_ID_FORMAT.format(myConfig.localTasksCounter++), summary);
  }

  private static LocalTaskImpl createTask(@NotNull String id, @NotNull String summary) {
    LocalTaskImpl task = new LocalTaskImpl(id, summary);
    Date date = new Date();
    task.setCreated(date);
    task.setUpdated(date);
    return task;
  }

  @Override
  public LocalTask activateTask(@NotNull final Task origin, boolean clearContext) {
    LocalTask activeTask = getActiveTask();
    if (origin.equals(activeTask)) return activeTask;

    saveActiveTask();

    if (clearContext) {
      myContextManager.clearContext();
    }
    myContextManager.restoreContext(origin);

    final LocalTask task = doActivate(origin, true);

    return restoreVcsContext(task);
  }

  private LocalTask restoreVcsContext(LocalTask task) {
    if (!isVcsEnabled()) return task;

    List<ChangeListInfo> changeLists = task.getChangeLists();
    if (!changeLists.isEmpty()) {
      ChangeListInfo info = changeLists.get(0);
      LocalChangeList changeList = myChangeListManager.getChangeList(info.id);
      if (changeList == null) {
        changeList = myChangeListManager.addChangeList(info.name, info.comment);
        info.id = changeList.getId();
      }
      myChangeListManager.setDefaultChangeList(changeList);
    }

    List<BranchInfo> branches = task.getBranches(false);
    VcsTaskHandler.TaskInfo info = fromBranches(branches);

    VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
    for (VcsTaskHandler handler : handlers) {
      handler.switchToTask(info);
    }
    return task;
  }

  private static VcsTaskHandler.TaskInfo fromBranches(List<BranchInfo> branches) {
    MultiMap<String, String> map = new MultiMap<String, String>();
    for (BranchInfo branch : branches) {
      map.putValue(branch.name, branch.repository);
    }
    return new VcsTaskHandler.TaskInfo(map);
  }

  public void createBranch(LocalTask task, LocalTask previousActive, String name) {
    VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
    for (VcsTaskHandler handler : handlers) {
      VcsTaskHandler.TaskInfo info = handler.getActiveTask();
      if (previousActive != null) {
        addBranches(previousActive, info, false);
      }
      addBranches(task, info, true);
      addBranches(task, handler.startNewTask(name), false);
    }
  }

  public void mergeBranch(LocalTask task) {
    VcsTaskHandler.TaskInfo original = fromBranches(task.getBranches(true));
    VcsTaskHandler.TaskInfo feature = fromBranches(task.getBranches(false));

    VcsTaskHandler[] handlers = VcsTaskHandler.getAllHandlers(myProject);
    for (VcsTaskHandler handler : handlers) {
      handler.closeTask(feature, original);
    }
  }

  private static void addBranches(LocalTask task, VcsTaskHandler.TaskInfo info, boolean original) {
    List<BranchInfo> branchInfos = BranchInfo.fromTaskInfo(info, original);
    for (BranchInfo branchInfo : branchInfos) {
      task.addBranch(branchInfo);
    }
  }

  private void saveActiveTask() {
    myContextManager.saveContext(myActiveTask);
    myActiveTask.setUpdated(new Date());
  }

  private LocalTask doActivate(Task origin, boolean explicitly) {
    final LocalTaskImpl task =
        origin instanceof LocalTaskImpl ? (LocalTaskImpl) origin : new LocalTaskImpl(origin);
    if (explicitly) {
      task.setUpdated(new Date());
    }
    myActiveTask.setActive(false);
    task.setActive(true);
    addTask(task);
    if (task.isIssue()) {
      StartupManager.getInstance(myProject)
          .runWhenProjectIsInitialized(
              new Runnable() {
                public void run() {
                  ProgressManager.getInstance()
                      .run(
                          new com.intellij.openapi.progress.Task.Backgroundable(
                              myProject, "Updating " + task.getId()) {

                            public void run(@NotNull ProgressIndicator indicator) {
                              updateIssue(task.getId());
                            }
                          });
                }
              });
    }
    LocalTask oldActiveTask = myActiveTask;
    boolean isChanged = !task.equals(oldActiveTask);
    myActiveTask = task;
    if (isChanged) {
      myDispatcher.getMulticaster().taskDeactivated(oldActiveTask);
      myDispatcher.getMulticaster().taskActivated(task);
    }
    return task;
  }

  private void addTask(LocalTaskImpl task) {
    myTasks.put(task.getId(), task);
    myDispatcher.getMulticaster().taskAdded(task);
  }

  @Override
  public boolean testConnection(final TaskRepository repository) {

    TestConnectionTask task =
        new TestConnectionTask("Test connection") {
          public void run(@NotNull ProgressIndicator indicator) {
            indicator.setText("Connecting to " + repository.getUrl() + "...");
            indicator.setFraction(0);
            indicator.setIndeterminate(true);
            try {
              myConnection = repository.createCancellableConnection();
              if (myConnection != null) {
                Future<Exception> future =
                    ApplicationManager.getApplication().executeOnPooledThread(myConnection);
                while (true) {
                  try {
                    myException = future.get(100, TimeUnit.MILLISECONDS);
                    return;
                  } catch (TimeoutException ignore) {
                    try {
                      indicator.checkCanceled();
                    } catch (ProcessCanceledException e) {
                      myException = e;
                      myConnection.cancel();
                      return;
                    }
                  } catch (Exception e) {
                    myException = e;
                    return;
                  }
                }
              } else {
                try {
                  repository.testConnection();
                } catch (Exception e) {
                  LOG.info(e);
                  myException = e;
                }
              }
            } catch (Exception e) {
              myException = e;
            }
          }
        };
    ProgressManager.getInstance().run(task);
    Exception e = task.myException;
    if (e == null) {
      myBadRepositories.remove(repository);
      Messages.showMessageDialog(
          myProject, "Connection is successful", "Connection", Messages.getInformationIcon());
    } else if (!(e instanceof ProcessCanceledException)) {
      String message = e.getMessage();
      if (e instanceof UnknownHostException) {
        message = "Unknown host: " + message;
      }
      if (message == null) {
        LOG.error(e);
        message = "Unknown error";
      }
      Messages.showErrorDialog(myProject, StringUtil.capitalize(message), "Error");
    }
    return e == null;
  }

  @SuppressWarnings({"unchecked"})
  @NotNull
  public Config getState() {
    myConfig.tasks =
        ContainerUtil.map(
            myTasks.values(),
            new Function<Task, LocalTaskImpl>() {
              public LocalTaskImpl fun(Task task) {
                return new LocalTaskImpl(task);
              }
            });
    myConfig.servers = XmlSerializer.serialize(getAllRepositories());
    return myConfig;
  }

  @SuppressWarnings({"unchecked"})
  public void loadState(Config config) {
    XmlSerializerUtil.copyBean(config, myConfig);
    myTasks.clear();
    for (LocalTaskImpl task : config.tasks) {
      addTask(task);
    }

    myRepositories.clear();
    Element element = config.servers;
    List<TaskRepository> repositories = loadRepositories(element);
    myRepositories.addAll(repositories);
  }

  public static ArrayList<TaskRepository> loadRepositories(Element element) {
    ArrayList<TaskRepository> repositories = new ArrayList<TaskRepository>();
    for (TaskRepositoryType repositoryType : TaskRepositoryType.getRepositoryTypes()) {
      for (Object o : element.getChildren()) {
        if (((Element) o).getName().equals(repositoryType.getName())) {
          try {
            @SuppressWarnings({"unchecked"})
            TaskRepository repository =
                (TaskRepository)
                    XmlSerializer.deserialize((Element) o, repositoryType.getRepositoryClass());
            if (repository != null) {
              repository.setRepositoryType(repositoryType);
              repositories.add(repository);
            }
          } catch (XmlSerializationException e) {
            // ignore
            LOG.error(e.getMessage());
          }
        }
      }
    }
    return repositories;
  }

  public void projectOpened() {

    TaskProjectConfiguration projectConfiguration = getProjectConfiguration();

    servers:
    for (TaskProjectConfiguration.SharedServer server : projectConfiguration.servers) {
      if (server.type == null || server.url == null) {
        continue;
      }
      for (TaskRepositoryType<?> repositoryType : TaskRepositoryType.getRepositoryTypes()) {
        if (repositoryType.getName().equals(server.type)) {
          for (TaskRepository repository : myRepositories) {
            if (!repositoryType.equals(repository.getRepositoryType())) {
              continue;
            }
            if (server.url.equals(repository.getUrl())) {
              continue servers;
            }
          }
          TaskRepository repository = repositoryType.createRepository();
          repository.setUrl(server.url);
          myRepositories.add(repository);
        }
      }
    }

    myContextManager.pack(200, 50);

    // make sure the task is associated with default changelist
    LocalTask defaultTask = findTask(LocalTaskImpl.DEFAULT_TASK_ID);
    LocalChangeList defaultList = myChangeListManager.findChangeList(LocalChangeList.DEFAULT_NAME);
    if (defaultList != null && defaultTask != null) {
      ChangeListInfo listInfo = new ChangeListInfo(defaultList);
      if (!defaultTask.getChangeLists().contains(listInfo)) {
        defaultTask.addChangelist(listInfo);
      }
    }

    // remove already not existing changelists from tasks changelists
    for (LocalTask localTask : getLocalTasks()) {
      for (Iterator<ChangeListInfo> iterator = localTask.getChangeLists().iterator();
          iterator.hasNext(); ) {
        final ChangeListInfo changeListInfo = iterator.next();
        if (myChangeListManager.getChangeList(changeListInfo.id) == null) {
          iterator.remove();
        }
      }
    }

    myChangeListManager.addChangeListListener(myChangeListListener);
  }

  private TaskProjectConfiguration getProjectConfiguration() {
    return ServiceManager.getService(myProject, TaskProjectConfiguration.class);
  }

  public void projectClosed() {}

  @NotNull
  public String getComponentName() {
    return "Task Manager";
  }

  public void initComponent() {
    if (!ApplicationManager.getApplication().isUnitTestMode()) {
      myCacheRefreshTimer =
          UIUtil.createNamedTimer(
              "TaskManager refresh",
              myConfig.updateInterval * 60 * 1000,
              new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                  if (myConfig.updateEnabled && !myUpdating) {
                    updateIssues(null);
                  }
                }
              });
      myCacheRefreshTimer.setInitialDelay(0);
      StartupManager.getInstance(myProject)
          .registerPostStartupActivity(
              new Runnable() {
                public void run() {
                  myCacheRefreshTimer.start();
                }
              });
    }

    // make sure that the default task is exist
    LocalTask defaultTask = findTask(LocalTaskImpl.DEFAULT_TASK_ID);
    if (defaultTask == null) {
      defaultTask = createDefaultTask();
      addTask(defaultTask);
    }

    // search for active task
    LocalTask activeTask = null;
    final List<LocalTask> tasks = getLocalTasks();
    Collections.sort(tasks, TASK_UPDATE_COMPARATOR);
    for (LocalTask task : tasks) {
      if (activeTask == null) {
        if (task.isActive()) {
          activeTask = task;
        }
      } else {
        task.setActive(false);
      }
    }
    if (activeTask == null) {
      activeTask = defaultTask;
    }

    myActiveTask = activeTask;
    doActivate(myActiveTask, false);
    myDispatcher.getMulticaster().taskActivated(myActiveTask);
  }

  private static LocalTaskImpl createDefaultTask() {
    return new LocalTaskImpl(LocalTaskImpl.DEFAULT_TASK_ID, "Default task");
  }

  public void disposeComponent() {
    if (myCacheRefreshTimer != null) {
      myCacheRefreshTimer.stop();
    }
    myChangeListManager.removeChangeListListener(myChangeListListener);
  }

  public void updateIssues(final @Nullable Runnable onComplete) {
    TaskRepository first =
        ContainerUtil.find(
            getAllRepositories(),
            new Condition<TaskRepository>() {
              public boolean value(TaskRepository repository) {
                return repository.isConfigured();
              }
            });
    if (first == null) {
      myIssueCache.clear();
      if (onComplete != null) {
        onComplete.run();
      }
      return;
    }
    myUpdating = true;
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      doUpdate(onComplete);
    } else {
      ApplicationManager.getApplication()
          .executeOnPooledThread(
              new Runnable() {
                public void run() {
                  doUpdate(onComplete);
                }
              });
    }
  }

  private void doUpdate(@Nullable Runnable onComplete) {
    try {
      List<Task> issues =
          getIssuesFromRepositories(
              null, myConfig.updateIssuesCount, 0, false, new EmptyProgressIndicator());
      if (issues == null) return;

      synchronized (myIssueCache) {
        myIssueCache.clear();
        for (Task issue : issues) {
          myIssueCache.put(issue.getId(), issue);
        }
      }
      // update local tasks
      synchronized (myTasks) {
        for (Map.Entry<String, LocalTask> entry : myTasks.entrySet()) {
          Task issue = myIssueCache.get(entry.getKey());
          if (issue != null) {
            entry.getValue().updateFromIssue(issue);
          }
        }
      }
    } finally {
      if (onComplete != null) {
        onComplete.run();
      }
      myUpdating = false;
    }
  }

  @Nullable
  private List<Task> getIssuesFromRepositories(
      @Nullable String request,
      int max,
      long since,
      boolean forceRequest,
      @NotNull final ProgressIndicator cancelled) {
    List<Task> issues = null;
    for (final TaskRepository repository : getAllRepositories()) {
      if (!repository.isConfigured() || (!forceRequest && myBadRepositories.contains(repository))) {
        continue;
      }
      try {
        Task[] tasks = repository.getIssues(request, max, since, cancelled);
        myBadRepositories.remove(repository);
        if (issues == null) issues = new ArrayList<Task>(tasks.length);
        if (!repository.isSupported(TaskRepository.NATIVE_SEARCH) && request != null) {
          List<Task> filteredTasks =
              TaskSearchSupport.filterTasks(request, ContainerUtil.list(tasks));
          ContainerUtil.addAll(issues, filteredTasks);
        } else {
          ContainerUtil.addAll(issues, tasks);
        }
      } catch (ProcessCanceledException ignored) {
        // OK
      } catch (Exception e) {
        String reason = "";
        // Fix to IDEA-111810
        if (e.getClass() == Exception.class) {
          // probably contains some message meaningful to end-user
          reason = e.getMessage();
        }
        //noinspection InstanceofCatchParameter
        if (e instanceof SocketTimeoutException) {
          LOG.warn("Socket timeout from " + repository);
        } else {
          LOG.warn("Cannot connect to " + repository, e);
        }
        myBadRepositories.add(repository);
        if (forceRequest) {
          notifyAboutConnectionFailure(repository, reason);
        }
      }
    }
    return issues;
  }

  private void notifyAboutConnectionFailure(final TaskRepository repository, String details) {
    Notifications.Bus.register(TASKS_NOTIFICATION_GROUP, NotificationDisplayType.BALLOON);
    String content = "<p><a href=\"\">Configure server...</a></p>";
    if (!StringUtil.isEmpty(details)) {
      content = "<p>" + details + "</p>" + content;
    }
    Notifications.Bus.notify(
        new Notification(
            TASKS_NOTIFICATION_GROUP,
            "Cannot connect to " + repository.getUrl(),
            content,
            NotificationType.WARNING,
            new NotificationListener() {
              public void hyperlinkUpdate(
                  @NotNull Notification notification, @NotNull HyperlinkEvent event) {
                TaskRepositoriesConfigurable configurable =
                    new TaskRepositoriesConfigurable(myProject);
                ShowSettingsUtil.getInstance().editConfigurable(myProject, configurable);
                if (!ArrayUtil.contains(repository, getAllRepositories())) {
                  notification.expire();
                }
              }
            }),
        myProject);
  }

  @Override
  public boolean isVcsEnabled() {
    return ProjectLevelVcsManager.getInstance(myProject).getAllActiveVcss().length > 0;
  }

  @Override
  public AbstractVcs getActiveVcs() {
    AbstractVcs[] vcss = ProjectLevelVcsManager.getInstance(myProject).getAllActiveVcss();
    if (vcss.length == 0) return null;
    for (AbstractVcs vcs : vcss) {
      if (vcs.getType() == VcsType.distributed) {
        return vcs;
      }
    }
    return vcss[0];
  }

  @Override
  public boolean isLocallyClosed(final LocalTask localTask) {
    if (isVcsEnabled()) {
      List<ChangeListInfo> lists = localTask.getChangeLists();
      if (lists.isEmpty()) return true;
      for (ChangeListInfo list : lists) {
        if (StringUtil.isEmpty(list.id)) {
          return true;
        }
      }
    }
    return false;
  }

  @Nullable
  @Override
  public LocalTask getAssociatedTask(LocalChangeList list) {
    for (LocalTask task : getLocalTasks()) {
      for (ChangeListInfo changeListInfo : task.getChangeLists()) {
        if (changeListInfo.id.equals(list.getId())) {
          return task;
        }
      }
    }
    return null;
  }

  @Override
  public void trackContext(LocalChangeList changeList) {
    ChangeListInfo changeListInfo = new ChangeListInfo(changeList);
    String changeListName = changeList.getName();
    LocalTaskImpl task = createLocalTask(changeListName);
    task.addChangelist(changeListInfo);
    addTask(task);
    if (changeList.isDefault()) {
      activateTask(task, false);
    }
  }

  @Override
  public void disassociateFromTask(LocalChangeList changeList) {
    ChangeListInfo changeListInfo = new ChangeListInfo(changeList);
    for (LocalTask localTask : getLocalTasks()) {
      if (localTask.getChangeLists().contains(changeListInfo)) {
        localTask.removeChangelist(changeListInfo);
      }
    }
  }

  public void decorateChangeList(
      LocalChangeList changeList,
      ColoredTreeCellRenderer cellRenderer,
      boolean selected,
      boolean expanded,
      boolean hasFocus) {
    LocalTask task = getAssociatedTask(changeList);
    if (task != null && task.isIssue()) {
      cellRenderer.setIcon(task.getIcon());
    }
  }

  public void createChangeList(LocalTask task, String name) {
    String comment = TaskUtil.getChangeListComment(task);
    createChangeList(task, name, comment);
  }

  private void createChangeList(LocalTask task, String name, @Nullable String comment) {
    LocalChangeList changeList = myChangeListManager.findChangeList(name);
    if (changeList == null) {
      changeList = myChangeListManager.addChangeList(name, comment);
    } else {
      final LocalTask associatedTask = getAssociatedTask(changeList);
      if (associatedTask != null) {
        associatedTask.removeChangelist(new ChangeListInfo(changeList));
      }
      changeList.setComment(comment);
    }
    task.addChangelist(new ChangeListInfo(changeList));
    myChangeListManager.setDefaultChangeList(changeList);
  }

  public String getChangelistName(Task task) {
    if (task.isIssue() && myConfig.changelistNameFormat != null) {
      return TaskUtil.formatTask(task, myConfig.changelistNameFormat);
    }
    return task.getSummary();
  }

  public String suggestBranchName(Task task) {
    if (task.isIssue() && StringUtil.isNotEmpty(task.getNumber())) {
      return task.getId().replace(' ', '-');
    } else {
      String summary = task.getSummary();
      List<String> words = StringUtil.getWordsIn(summary);
      String[] strings = ArrayUtil.toStringArray(words);
      return StringUtil.join(strings, 0, Math.min(2, strings.length), "-");
    }
  }

  @TestOnly
  public ChangeListAdapter getChangeListListener() {
    return myChangeListListener;
  }

  public static class Config {

    @Property(surroundWithTag = false)
    @AbstractCollection(surroundWithTag = false, elementTag = "task")
    public List<LocalTaskImpl> tasks = new ArrayList<LocalTaskImpl>();

    public int localTasksCounter = 1;

    public int taskHistoryLength = 50;

    public boolean updateEnabled = true;
    public int updateInterval = 20;
    public int updateIssuesCount = 100;

    // create task options
    public boolean clearContext = true;
    public boolean createChangelist = true;
    public boolean createBranch = true;

    // close task options
    public boolean closeIssue = true;
    public boolean commitChanges = true;
    public boolean mergeBranch = true;

    public boolean saveContextOnCommit = true;
    public boolean trackContextForNewChangelist = false;
    public boolean markAsInProgress = false;

    public String changelistNameFormat = "{id} {summary}";

    public boolean searchClosedTasks = false;

    @Tag("servers")
    public Element servers = new Element("servers");
  }

  private abstract class TestConnectionTask extends com.intellij.openapi.progress.Task.Modal {

    protected Exception myException;

    @Nullable protected TaskRepository.CancellableConnection myConnection;

    public TestConnectionTask(String title) {
      super(TaskManagerImpl.this.myProject, title, true);
    }

    @Override
    public void onCancel() {
      if (myConnection != null) {
        myConnection.cancel();
      }
    }
  }
}