/*
 * Copyright 2000-2015 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.diff.comparison;

import com.intellij.diff.comparison.iterables.FairDiffIterable;
import com.intellij.diff.util.MergeRange;
import com.intellij.diff.util.Range;
import com.intellij.diff.util.Side;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.registry.Registry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ComparisonMergeUtil {
  @NotNull
  public static List<MergeRange> buildFair(@NotNull FairDiffIterable fragments1,
                                           @NotNull FairDiffIterable fragments2,
                                           @NotNull ProgressIndicator indicator) {
    assert fragments1.getLength1() == fragments2.getLength1();
    return new FairMergeBuilder().execute(fragments1, fragments2);
  }

  private static class FairMergeBuilder {
    @NotNull private final ChangeBuilder myChangesBuilder = new ChangeBuilder();

    @NotNull
    public List<MergeRange> execute(@NotNull FairDiffIterable fragments1,
                                    @NotNull FairDiffIterable fragments2) {
      PeekIterator<Range> unchanged1 = new PeekIterator<>(fragments1.unchanged());
      PeekIterator<Range> unchanged2 = new PeekIterator<>(fragments2.unchanged());

      while (!unchanged1.atEnd() && !unchanged2.atEnd()) {
        Side side = add(unchanged1.peek(), unchanged2.peek());
        side.select(unchanged1, unchanged2).advance();
      }
      return finish(fragments1, fragments2);
    }

    @NotNull
    private Side add(@NotNull Range range1, @NotNull Range range2) {
      int start1 = range1.start1;
      int end1 = range1.end1;

      int start2 = range2.start1;
      int end2 = range2.end1;

      if (end1 <= start2) return Side.LEFT;
      if (end2 <= start1) return Side.RIGHT;

      int startBase = Math.max(start1, start2);
      int endBase = Math.min(end1, end2);

      int startShift1 = startBase - start1;
      int endCut1 = end1 - endBase;
      int startShift2 = startBase - start2;
      int endCut2 = end2 - endBase;

      int startLeft = range1.start2 + startShift1;
      int endLeft = range1.end2 - endCut1;
      int startRight = range2.start2 + startShift2;
      int endRight = range2.end2 - endCut2;

      myChangesBuilder.markEqual(startLeft, startBase, startRight, endLeft, endBase, endRight);

      return Side.fromLeft(end1 <= end2);
    }

    @NotNull
    private List<MergeRange> finish(@NotNull FairDiffIterable fragments1, @NotNull FairDiffIterable fragments2) {
      int length1 = fragments1.getLength2();
      int length2 = fragments1.getLength1();
      int length3 = fragments2.getLength2();

      return myChangesBuilder.finish(length1, length2, length3);
    }
  }

  private static class ChangeBuilder {
    @NotNull private final List<MergeRange> myChanges = new ArrayList<>();

    private int myIndex1 = 0;
    private int myIndex2 = 0;
    private int myIndex3 = 0;

    private void addChange(int start1, int start2, int start3, int end1, int end2, int end3) {
      if (start1 == end1 && start2 == end2 && start3 == end3) return;
      myChanges.add(new MergeRange(start1, end1, start2, end2, start3, end3));
    }

    public void markEqual(int start1, int start2, int start3, int end1, int end2, int end3) {
      assert myIndex1 <= start1;
      assert myIndex2 <= start2;
      assert myIndex3 <= start3;
      assert start1 <= end1;
      assert start2 <= end2;
      assert start3 <= end3;

      addChange(myIndex1, myIndex2, myIndex3, start1, start2, start3);

      myIndex1 = end1;
      myIndex2 = end2;
      myIndex3 = end3;
    }

    @NotNull
    public List<MergeRange> finish(int length1, int length2, int length3) {
      assert myIndex1 <= length1;
      assert myIndex2 <= length2;
      assert myIndex3 <= length3;

      addChange(myIndex1, myIndex2, myIndex3, length1, length2, length3);

      return myChanges;
    }
  }

  private static class PeekIterator<T> {
    @NotNull private final Iterator<T> myIterator;
    private T myValue = null;

    public PeekIterator(@NotNull Iterator<T> iterator) {
      myIterator = iterator;
      advance();
    }

    public boolean atEnd() {
      return myValue == null;
    }

    public boolean hasNext() {
      return myIterator.hasNext();
    }

    public T peek() {
      return myValue;
    }

    public void advance() {
      myValue = myIterator.hasNext() ? myIterator.next() : null;
    }
  }

  @Nullable
  public static CharSequence tryResolveConflict(@NotNull CharSequence leftText,
                                                @NotNull CharSequence baseText,
                                                @NotNull CharSequence rightText) {
    if (Registry.is("diff.merge.resolve.conflict.action.use.greedy.approach")) {
      return MergeResolveUtil.tryGreedyResolve(leftText, baseText, rightText);
    }
    else {
      return MergeResolveUtil.tryResolve(leftText, baseText, rightText);
    }
  }
}