// Copyright (C) 2009 The Android Open Source Project
//
// 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.google.gerrit.prettify.common;

import org.eclipse.jgit.diff.Edit;

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

public class EditList {
  private final List<Edit> edits;
  private final int context;
  private final int aSize;
  private final int bSize;

  public EditList(final List<Edit> edits, final int contextLines,
      final int aSize, final int bSize) {
    this.edits = edits;
    this.context = contextLines;
    this.aSize = aSize;
    this.bSize = bSize;
  }

  public List<Edit> getEdits() {
    return edits;
  }

  public Iterable<Hunk> getHunks() {
    return new Iterable<Hunk>() {
      public Iterator<Hunk> iterator() {
        return new Iterator<Hunk>() {
          private int curIdx;

          public boolean hasNext() {
            return curIdx < edits.size();
          }

          public Hunk next() {
            final int c = curIdx;
            final int e = findCombinedEnd(c);
            curIdx = e + 1;
            return new Hunk(c, e);
          }

          public void remove() {
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }

  private int findCombinedEnd(final int i) {
    int end = i + 1;
    while (end < edits.size() && (combineA(end) || combineB(end)))
      end++;
    return end - 1;
  }

  private boolean combineA(final int i) {
    final Edit s = edits.get(i);
    final Edit e = edits.get(i - 1);
    // + 1 to prevent '... skipping 1 common line ...' messages.
    return s.getBeginA() - e.getEndA() <= 2 * context + 1;
  }

  private boolean combineB(final int i) {
    final int s = edits.get(i).getBeginB();
    final int e = edits.get(i - 1).getEndB();
    // + 1 to prevent '... skipping 1 common line ...' messages.
    return s - e <= 2 * context + 1;
  }

  public class Hunk {
    private int curIdx;
    private Edit curEdit;
    private final int endIdx;
    private final Edit endEdit;

    private int aCur;
    private int bCur;
    private final int aEnd;
    private final int bEnd;

    private Hunk(final int ci, final int ei) {
      curIdx = ci;
      endIdx = ei;
      curEdit = edits.get(curIdx);
      endEdit = edits.get(endIdx);

      aCur = Math.max(0, curEdit.getBeginA() - context);
      bCur = Math.max(0, curEdit.getBeginB() - context);
      aEnd = Math.min(aSize, endEdit.getEndA() + context);
      bEnd = Math.min(bSize, endEdit.getEndB() + context);
    }

    public int getCurA() {
      return aCur;
    }

    public int getCurB() {
      return bCur;
    }

    public Edit getCurEdit() {
      return curEdit;
    }

    public int getEndA() {
      return aEnd;
    }

    public int getEndB() {
      return bEnd;
    }

    public void incA() {
      aCur++;
    }

    public void incB() {
      bCur++;
    }

    public void incBoth() {
      incA();
      incB();
    }

    public boolean isStartOfFile() {
      return aCur == 0 && bCur == 0;
    }

    public boolean isContextLine() {
      return !isModifiedLine();
    }

    public boolean isDeletedA() {
      return curEdit.getBeginA() <= aCur && aCur < curEdit.getEndA();
    }

    public boolean isInsertedB() {
      return curEdit.getBeginB() <= bCur && bCur < curEdit.getEndB();
    }

    public boolean isModifiedLine() {
      return isDeletedA() || isInsertedB();
    }

    public boolean next() {
      if (!in(curEdit)) {
        if (curIdx < endIdx) {
          curEdit = edits.get(++curIdx);
        }
      }
      return aCur < aEnd || bCur < bEnd;
    }

    private boolean in(final Edit edit) {
      return aCur < edit.getEndA() || bCur < edit.getEndB();
    }
  }
}

@@ -3644,32 +3644,65 @@
 ) %7B%0A      return
+ aCur %3C aEnd && (bCur %3E= bSize %7C%7C
  curEdit.getBegi
@@ -3741,16 +3741,17 @@
 etEndA()
+)
 ;%0A    %7D%0A
@@ -3790,32 +3790,65 @@
 ) %7B%0A      return
+ bCur %3C bEnd && (aCur %3E= aSize %7C%7C
  curEdit.getBegi
@@ -3879,32 +3879,33 @@
 urEdit.getEndB()
+)
 ;%0A    %7D%0A%0A    pub

Fix EditList behavior for new case.