/*
 * 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.ByLine.Line;
import com.intellij.diff.comparison.iterables.DiffIterableUtil;
import com.intellij.diff.comparison.iterables.FairDiffIterable;
import com.intellij.diff.util.Range;
import com.intellij.openapi.progress.ProgressIndicator;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;

import java.util.List;

import static com.intellij.diff.comparison.TrimUtil.expand;
import static com.intellij.diff.comparison.iterables.DiffIterableUtil.diff;
import static com.intellij.diff.comparison.iterables.DiffIterableUtil.fair;

/*
 * Base class for two-step diff algorithms.
 * Given matching between some sub-sequences of base sequences - build matching on whole base sequences
 */
abstract class ChangeCorrector {
  private final int myLength1;
  private final int myLength2;
  @NotNull private final FairDiffIterable myChanges;

  @NotNull protected final ProgressIndicator myIndicator;

  @NotNull protected final DiffIterableUtil.ChangeBuilder myBuilder;

  ChangeCorrector(int length1,
                         int length2,
                         @NotNull FairDiffIterable changes,
                         @NotNull ProgressIndicator indicator) {
    myLength1 = length1;
    myLength2 = length2;
    myChanges = changes;
    myIndicator = indicator;

    myBuilder = new DiffIterableUtil.ChangeBuilder(length1, length2);
  }

  @NotNull
  public FairDiffIterable build() {
    execute();
    return fair(myBuilder.finish());
  }

  protected void execute() {
    int last1 = 0;
    int last2 = 0;

    for (Range ch : myChanges.iterateUnchanged()) {
      int count = ch.end1 - ch.start1;
      for (int i = 0; i < count; i++) {
        int index1 = getOriginalIndex1(ch.start1 + i);
        int index2 = getOriginalIndex2(ch.start2 + i);

        matchGap(last1, index1, last2, index2);
        myBuilder.markEqual(index1, index2);

        last1 = index1 + 1;
        last2 = index2 + 1;
      }
    }
    matchGap(last1, myLength1, last2, myLength2);
  }

  // match elements in range [start1 - end1) -> [start2 - end2)
  protected abstract void matchGap(int start1, int end1, int start2, int end2);

  protected abstract int getOriginalIndex1(int index);

  protected abstract int getOriginalIndex2(int index);

  //
  // Implementations
  //

  public static class DefaultCharChangeCorrector extends ChangeCorrector {
    @NotNull private final ByChar.CharOffsets myChars1;
    @NotNull private final ByChar.CharOffsets myChars2;
    @NotNull private final CharSequence myText1;
    @NotNull private final CharSequence myText2;

    public DefaultCharChangeCorrector(@NotNull ByChar.CharOffsets chars1,
                                      @NotNull ByChar.CharOffsets chars2,
                                      @NotNull CharSequence text1,
                                      @NotNull CharSequence text2,
                                      @NotNull FairDiffIterable changes,
                                      @NotNull ProgressIndicator indicator) {
      super(text1.length(), text2.length(), changes, indicator);
      myChars1 = chars1;
      myChars2 = chars2;
      myText1 = text1;
      myText2 = text2;
    }

    @Override
    protected void matchGap(int start1, int end1, int start2, int end2) {
      Range expand = expand(myText1, myText2, start1, start2, end1, end2);

      CharSequence inner1 = myText1.subSequence(expand.start1, expand.end1);
      CharSequence inner2 = myText2.subSequence(expand.start2, expand.end2);
      FairDiffIterable innerChanges = ByChar.compare(inner1, inner2, myIndicator);

      myBuilder.markEqual(start1, start2, expand.start1, expand.start2);

      for (Range chunk : innerChanges.iterateUnchanged()) {
        myBuilder.markEqual(expand.start1 + chunk.start1, expand.start2 + chunk.start2, chunk.end1 - chunk.start1);
      }

      myBuilder.markEqual(expand.end1, expand.end2, end1, end2);
    }

    @Override
    protected int getOriginalIndex1(int index) {
      return myChars1.offsets[index];
    }

    @Override
    protected int getOriginalIndex2(int index) {
      return myChars2.offsets[index];
    }
  }

  public static class SmartLineChangeCorrector extends ChangeCorrector {
    @NotNull private final TIntArrayList myIndexes1;
    @NotNull private final TIntArrayList myIndexes2;
    @NotNull private final List<? extends Line> myLines1;
    @NotNull private final List<? extends Line> myLines2;

    public SmartLineChangeCorrector(@NotNull TIntArrayList indexes1,
                                    @NotNull TIntArrayList indexes2,
                                    @NotNull List<? extends Line> lines1,
                                    @NotNull List<? extends Line> lines2,
                                    @NotNull FairDiffIterable changes,
                                    @NotNull ProgressIndicator indicator) {
      super(lines1.size(), lines2.size(), changes, indicator);
      myIndexes1 = indexes1;
      myIndexes2 = indexes2;
      myLines1 = lines1;
      myLines2 = lines2;
    }

    @Override
    protected void matchGap(int start1, int end1, int start2, int end2) {
      Range expand = expand(myLines1, myLines2, start1, start2, end1, end2);

      List<? extends Line> inner1 = myLines1.subList(expand.start1, expand.end1);
      List<? extends Line> inner2 = myLines2.subList(expand.start2, expand.end2);
      FairDiffIterable innerChanges = diff(inner1, inner2, myIndicator);

      myBuilder.markEqual(start1, start2, expand.start1, expand.start2);

      for (Range chunk : innerChanges.iterateUnchanged()) {
        myBuilder.markEqual(expand.start1 + chunk.start1, expand.start2 + chunk.start2, chunk.end1 - chunk.start1);
      }

      myBuilder.markEqual(expand.end1, expand.end2, end1, end2);
    }

    @Override
    protected int getOriginalIndex1(int index) {
      return myIndexes1.get(index);
    }

    @Override
    protected int getOriginalIndex2(int index) {
      return myIndexes2.get(index);
    }
  }
}
