package zielu.gittoolbox.blame;

import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import zielu.gittoolbox.revision.RevisionDataProvider;
import zielu.gittoolbox.revision.RevisionInfo;
import zielu.gittoolbox.revision.RevisionService;

class BlameAnnotationImpl implements BlameAnnotation {
  private final RevisionDataProvider provider;
  private final RevisionService revisionService;
  private final Map<VcsRevisionNumber, RevisionInfo> revisions;
  private final VcsRevisionNumber[] lineRevisions;

  BlameAnnotationImpl(@NotNull RevisionDataProvider provider, @NotNull RevisionService revisionService) {
    this.provider = provider;
    this.revisionService = revisionService;
    lineRevisions = new VcsRevisionNumber[provider.getLineCount()];
    revisions = new HashMap<>(provider.getLineCount());
  }

  @NotNull
  @Override
  public RevisionInfo getBlame(int lineIndex) {
    VcsRevisionNumber revisionNumber = getLineRevisionNumber(lineIndex);
    if (VcsRevisionNumber.NULL.equals(revisionNumber)) {
      return RevisionInfo.NULL;
    } else {
      RevisionInfo revisionInfo = revisions.get(revisionNumber);
      if (revisionInfo == null) {
        revisionInfo = loadRevision(lineIndex);
      }
      return revisionInfo;
    }
  }

  @NotNull
  private VcsRevisionNumber getLineRevisionNumber(int lineIndex) {
    if (lineIndex < 0 || lineIndex >= lineRevisions.length) {
      return VcsRevisionNumber.NULL;
    }
    VcsRevisionNumber revisionNumber = lineRevisions[lineIndex];
    if (revisionNumber == null) {
      VcsRevisionNumber lineRevisionNumber = provider.getRevisionNumber(lineIndex);
      lineRevisions[lineIndex] = lineRevisionNumber;
      revisionNumber = lineRevisionNumber;
    }
    return revisionNumber;
  }

  @NotNull
  private RevisionInfo loadRevision(int lineNumber) {
    RevisionInfo revisionInfo = revisionService.getForLine(provider, lineNumber);
    revisions.put(revisionInfo.getRevisionNumber(), revisionInfo);
    return revisionInfo;
  }

  @Override
  public boolean isChanged(@NotNull VcsRevisionNumber revision) {
    return !Objects.equals(provider.getBaseRevision(), revision);
  }

  @Nullable
  @Override
  public VirtualFile getVirtualFile() {
    return provider.getFile();
  }

  @Override
  public boolean updateRevision(@NotNull RevisionInfo revisionInfo) {
    if (revisions.containsKey(revisionInfo.getRevisionNumber())) {
      revisions.put(revisionInfo.getRevisionNumber(), revisionInfo);
      return true;
    }
    return false;
  }

  @Override
  public String toString() {
    return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
        .append("file", getVirtualFile())
        .append("baseRevision", provider.getBaseRevision())
        .toString();
  }
}