// Copyright (C) 2013 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.client.diff;

import com.google.gerrit.client.diff.DiffInfo.Region;
import com.google.gerrit.client.patches.SkippedLine;
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
import com.google.gwt.core.client.JsArray;

import net.codemirror.lib.CodeMirror;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/** Collapses common regions with {@link SkipBar} for {@link SideBySide2}. */
class SkipManager {
  private final SideBySide2 host;
  private final CommentManager commentManager;
  private Set<SkipBar> skipBars;
  private SkipBar line0;

  SkipManager(SideBySide2 host, CommentManager commentManager) {
    this.host = host;
    this.commentManager = commentManager;
  }

  void render(int context, DiffInfo diff) {
    if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
      return;
    }

    JsArray<Region> regions = diff.content();
    List<SkippedLine> skips = new ArrayList<SkippedLine>();
    int lineA = 0, lineB = 0;
    for (int i = 0; i < regions.length(); i++) {
      Region current = regions.get(i);
      if (current.ab() != null || current.common()) {
        int len = (current.ab() != null ? current.ab() : current.b()).length();
        if (i == 0 && len > context + 1) {
          skips.add(new SkippedLine(0, 0, len - context));
        } else if (i == regions.length() - 1 && len > context + 1) {
          skips.add(new SkippedLine(lineA + context, lineB + context,
              len - context));
        } else if (len > 2 * context + 1) {
          skips.add(new SkippedLine(lineA + context, lineB + context,
              len - 2 * context));
        }
        lineA += len;
        lineB += len;
      } else {
        lineA += current.a() != null ? current.a().length() : 0;
        lineB += current.b() != null ? current.b().length() : 0;
      }
    }
    skips = commentManager.splitSkips(context, skips);

    if (!skips.isEmpty()) {
      CodeMirror cmA = host.getCmFromSide(DisplaySide.A);
      CodeMirror cmB = host.getCmFromSide(DisplaySide.B);

      skipBars = new HashSet<SkipBar>();
      for (SkippedLine skip : skips) {
        SkipBar barA = newSkipBar(cmA, DisplaySide.A, skip);
        SkipBar barB = newSkipBar(cmB, DisplaySide.B, skip);
        SkipBar.link(barA, barB);
        skipBars.add(barA);
        skipBars.add(barB);

        if (skip.getStartA() == 0 || skip.getStartB() == 0) {
          barA.upArrow.setVisible(false);
          barB.upArrow.setVisible(false);
          line0 = barB;
        } else if (skip.getStartA() + skip.getSize() == lineA
            || skip.getStartB() + skip.getSize() == lineB) {
          barA.downArrow.setVisible(false);
          barB.downArrow.setVisible(false);
        }
      }
    }
  }

  void ensureFirstLineIsVisible() {
    if (line0 != null) {
      line0.expandBefore(1);
      line0 = null;
    }
  }

  void removeAll() {
    if (skipBars != null) {
      for (SkipBar bar : skipBars) {
        bar.expandSideAll();
      }
      skipBars = null;
      line0 = null;
    }
  }

  void remove(SkipBar a, SkipBar b) {
    skipBars.remove(a);
    skipBars.remove(b);
    if (line0 == a || line0 == b) {
      line0 = null;
    }
    if (skipBars.isEmpty()) {
      skipBars = null;
    }
  }

  private SkipBar newSkipBar(CodeMirror cm, DisplaySide side, SkippedLine skip) {
    int start = side == DisplaySide.A ? skip.getStartA() : skip.getStartB();
    int end = start + skip.getSize() - 1;

    SkipBar bar = new SkipBar(this, cm);
    host.diffTable.add(bar);
    bar.collapse(start, end, true);
    return bar;
  }
}

@@ -2009,39 +2009,109 @@
 && len %3E context
- + 1) %7B
+) %7B%0A          len++; // Account for the trailing newline that is always added
 %0A          skips

Include trailing newline in last skip.