package zielu.gittoolbox.status;
import com.google.common.collect.Maps;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.util.messages.MessageBusConnection;
import git4idea.repo.GitRepository;
import git4idea.util.GitUIUtil;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.event.HyperlinkEvent;
import jodd.util.StringBand;
import org.jetbrains.annotations.NotNull;
import zielu.gittoolbox.GitToolBoxConfig;
import zielu.gittoolbox.ResBundle;
import zielu.gittoolbox.cache.PerRepoInfoCache;
import zielu.gittoolbox.cache.PerRepoStatusCacheListener;
import zielu.gittoolbox.cache.RepoInfo;
import zielu.gittoolbox.compat.Notifier;
import zielu.gittoolbox.ui.StatusMessages;
import zielu.gittoolbox.ui.UpdateProject;
import zielu.gittoolbox.util.GtUtil;
import zielu.gittoolbox.util.Html;
public class BehindTracker extends AbstractProjectComponent {
private final Logger LOG = Logger.getInstance(getClass());
private final AtomicBoolean myActive = new AtomicBoolean();
private final Map<GitRepository, RepoInfo> myState = new ConcurrentHashMap<GitRepository, RepoInfo>();
private final NotificationListener updateProjectListener;
private MessageBusConnection myConnection;
public BehindTracker(Project project) {
super(project);
updateProjectListener = new NotificationListener.Adapter() {
@Override
protected void hyperlinkActivated(@NotNull Notification notification, @NotNull HyperlinkEvent hyperlinkEvent) {
UpdateProject.create(myProject).execute();
}
};
}
public static BehindTracker getInstance(@NotNull Project project) {
return project.getComponent(BehindTracker.class);
}
@Override
public void initComponent() {
myConnection = myProject.getMessageBus().connect();
myConnection.subscribe(PerRepoInfoCache.CACHE_CHANGE, new PerRepoStatusCacheListener() {
@Override
public void stateChanged(@NotNull RepoInfo info,
@NotNull GitRepository repository) {
if (LOG.isDebugEnabled()) {
LOG.debug("State changed [" + GtUtil.name(repository) + "]: " + info);
}
onRepoChange(info, repository);
}
});
}
private void onRepoChange(@NotNull RepoInfo info, @NotNull GitRepository repository) {
if (myActive.get()) {
onStateChange(repository, info);
}
}
private Optional<BehindMessage> prepareMessage() {
Map<GitRepository, RevListCount> statuses = Maps.newHashMap();
for (Entry<GitRepository, RepoInfo> entry : myState.entrySet()) {
RepoInfo value = entry.getValue();
if (value.count != null) {
if (value.count.isNotZeroBehind()) {
statuses.put(entry.getKey(), value.count.behind);
}
}
}
if (!statuses.isEmpty()) {
boolean manyReposInProject = myState.size() > 1;
BehindMessage message = new BehindMessage(StatusMessages.getInstance()
.prepareBehindMessage(statuses, manyReposInProject), statuses.size() > 1);
return Optional.of(message);
} else {
return Optional.empty();
}
}
private void showNotification(@NotNull ChangeType changeType) {
Optional<BehindMessage> messageOption = prepareMessage();
if (messageOption.isPresent() && myActive.get()) {
BehindMessage message = messageOption.get();
StringBand finalMessage = new StringBand(GitUIUtil.bold(changeType.title()))
.append(" (").append(Html.link("update", ResBundle.getString("update.project")))
.append(")").append(Html.br).append(message.text);
Notifier.getInstance(myProject).notifySuccess(finalMessage.toString(), updateProjectListener);
}
}
private boolean isNotificationEnabled() {
return GitToolBoxConfig.getInstance().behindTracker;
}
private boolean isRemoteHashChanged(RepoInfo previous, RepoInfo current) {
return !previous.status.sameRemoteHash(current.status);
}
private boolean isBranchSwitched(RepoInfo previous, RepoInfo current) {
return !previous.status.sameBranch(current.status);
}
private void onStateChange(@NotNull GitRepository repository, @NotNull RepoInfo info) {
RepoInfo previousInfo = myState.put(repository, info);
if (LOG.isDebugEnabled()) {
LOG.debug("Info update [" + GtUtil.name(repository) + "]: " + previousInfo + " > " + info);
}
ChangeType type = ChangeType.none;
if (previousInfo != null) {
if (previousInfo.status.sameRemoteBranch(info.status)) {
if (isBranchSwitched(previousInfo, info)) {
type = ChangeType.switched;
} else if (isRemoteHashChanged(previousInfo, info)) {
type = ChangeType.fetched;
}
} else {
type = ChangeType.switched;
}
}
if (type.isVisible() && isNotificationEnabled()) {
showNotification(type);
}
}
@Override
public void disposeComponent() {
myState.clear();
}
@Override
public void projectOpened() {
myActive.compareAndSet(false, true);
}
@Override
public void projectClosed() {
if (myActive.compareAndSet(true, false)) {
myConnection.disconnect();
myState.clear();
}
}
private static class BehindMessage {
public final String text;
public final boolean manyRepos;
private BehindMessage(String text, boolean manyRepos) {
this.text = text;
this.manyRepos = manyRepos;
}
}
private enum ChangeType {
none(false, "NONE"),
hidden(false, "HIDDEN"),
fetched(true, ResBundle.getString("message.fetch.done")),
switched(true, ResBundle.getString("message.switched"));
private final boolean myVisible;
private final String myTitle;
ChangeType(boolean visible, String title) {
myVisible = visible;
this.myTitle = title;
}
boolean isVisible() {
return myVisible;
}
String title() {
return myTitle;
}
}
}