package com.jediterm.terminal.model;

import com.google.common.collect.Lists;
import com.jediterm.terminal.StyledTextConsumer;
import com.jediterm.terminal.TextStyle;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;

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

/**
 * Holds styled characters lines
 */
public class LinesBuffer {
  private static final Logger LOG = Logger.getLogger(LinesBuffer.class);

  private static final int BUFFER_MAX_LINES = 1000;

  private ArrayList<TerminalLine> myLines = Lists.newArrayList();

  public LinesBuffer() {
  }

  public synchronized String getLines() {
    final StringBuilder sb = new StringBuilder();

    boolean first = true;

    for (TerminalLine line : myLines) {
      if (!first) {
        sb.append("\n");
      }

      sb.append(line.getText());
      first = false;
    }

    return sb.toString();
  }


  public synchronized void addNewLine(@NotNull TextStyle style, @NotNull CharBuffer characters) {
    addNewLine(new TerminalLine.TextEntry(style, characters));
  }


  private synchronized void addNewLine(@NotNull TerminalLine.TextEntry entry) {
    addLine(new TerminalLine(entry));
  }

  private synchronized void addLine(@NotNull TerminalLine line) {
    if (myLines.size() > BUFFER_MAX_LINES) {
      removeTopLines(1);
    }

    myLines.add(line);
  }

  public synchronized int getLineCount() {
    return myLines.size();
  }

  public synchronized void removeTopLines(int count) {
    myLines = Lists.newArrayList(myLines.subList(count, myLines.size()));
  }

  public String getLineText(int row) {
    TerminalLine line = getLine(row);

    return line.getText();
  }

  public synchronized void insertLines(int y, int count, int lastLine) {
    LinesBuffer tail = new LinesBuffer();

    if (lastLine < getLineCount() - 1) {
      moveBottomLinesTo(getLineCount() - lastLine - 1, tail);
    }

    LinesBuffer head = new LinesBuffer();
    if (y > 0) {
      moveTopLinesTo(y, head);
    }

    for (int i = 0; i < count; i++) {
      head.addNewLine(TextStyle.EMPTY, CharBuffer.EMPTY);
    }

    head.moveBottomLinesTo(head.getLineCount(), this);

    removeBottomLines(count);

    tail.moveTopLinesTo(tail.getLineCount(), this);
  }

  public synchronized LinesBuffer deleteLines(int y, int count, int lastLine) {
    LinesBuffer tail = new LinesBuffer();

    if (lastLine < getLineCount() - 1) {
      moveBottomLinesTo(getLineCount() - lastLine - 1, tail);
    }

    LinesBuffer head = new LinesBuffer();
    if (y > 0) {
      moveTopLinesTo(y, head);
    }

    int toRemove = Math.min(count, getLineCount());

    LinesBuffer removed = new LinesBuffer();
    moveTopLinesTo(toRemove, removed);

    head.moveBottomLinesTo(head.getLineCount(), this);

    for (int i = 0; i < toRemove; i++) {
      addNewLine(TextStyle.EMPTY, CharBuffer.EMPTY);
    }

    tail.moveTopLinesTo(tail.getLineCount(), this);

    return removed;
  }

  public synchronized void writeString(int x, int y, String str, @NotNull TextStyle style) {
    TerminalLine line = getLine(y);

    line.writeString(x, str, style);
  }

  public synchronized void clearLines(int startRow, int endRow) {
    for (int i = startRow; i <= endRow; i++) {
      if (i < myLines.size()) {
        TerminalLine line = myLines.get(i);
        line.clear();
      }
      else {
        break;
      }
    }
  }

  public synchronized void clearAll() {
    myLines.clear();
  }

  public synchronized void deleteCharacters(int x, int y, int count) {
    TerminalLine line = getLine(y);
    line.deleteCharacters(x, count);
  }

  public synchronized void insertBlankCharacters(final int x, final int y, final int count, final int maxLen) {
    TerminalLine line = getLine(y);
    line.insertBlankCharacters(x, count, maxLen);
  }

  public synchronized void clearArea(int leftX, int topY, int rightX, int bottomY, @NotNull TextStyle style) {
    for (int y = topY; y < bottomY; y++) {
      TerminalLine line = getLine(y);
      line.clearArea(leftX, rightX, style);
    }
  }


  interface TextEntryProcessor {
    /**
     * @return true to remove entry
     */
    void process(int x, int y, @NotNull TerminalLine.TextEntry entry);
  }

  public synchronized void processLines(final int yStart,
                                        final int yCount,
                                        @NotNull final StyledTextConsumer consumer,
                                        final int startRow) {
    iterateLines(yStart, yCount, new TextEntryProcessor() {
      @Override
      public void process(int x, int y, @NotNull TerminalLine.TextEntry entry) {
        consumer.consume(x, y, entry.getStyle(), entry.getText(), startRow);
      }
    });
  }

  public synchronized void processLines(final int yStart, final int yCount, @NotNull final StyledTextConsumer consumer) {
    processLines(yStart, yCount, consumer, -getLineCount());
  }

  public synchronized void iterateLines(final int firstLine, final int count, @NotNull TextEntryProcessor processor) {
    for (int y = firstLine; y<Math.min(firstLine+count, myLines.size()); y++) {
      TerminalLine line = myLines.get(y);

      line.process(y, processor);
    }
  }

  public synchronized void moveTopLinesTo(int count, final @NotNull LinesBuffer buffer) {
    count = Math.min(count, getLineCount());
    buffer.addLines(myLines.subList(0, count));
    removeTopLines(count);
  }

  public synchronized void addLines(@NotNull List<TerminalLine> lines) {
    myLines.addAll(lines);
  }

  @NotNull
  public synchronized TerminalLine getLine(int row) {
    if (row >= getLineCount()) {
      for (int i = getLineCount(); i <= row; i++) {
        addLine(TerminalLine.createEmpty());
      }
    }

    return myLines.get(row);
  }

  public synchronized void moveBottomLinesTo(int count, final @NotNull LinesBuffer buffer) {
    count = Math.min(count, getLineCount());
    buffer.addLinesFirst(myLines.subList(getLineCount() - count, getLineCount()));

    removeBottomLines(count);
  }

  private synchronized void addLinesFirst(@NotNull List<TerminalLine> lines) {
    List<TerminalLine> list = Lists.newArrayList(lines);
    list.addAll(myLines);
    myLines = Lists.newArrayList(list);
  }

  private synchronized void removeBottomLines(int count) {
    myLines = Lists.newArrayList(myLines.subList(0, getLineCount() - count));
  }
}

@@ -1293,16 +1293,17 @@
 size() %3E
+=
  BUFFER_
@@ -5508,24 +5508,162 @@
 e%3E lines) %7B%0A
+    int count = myLines.size() + lines.size();%0A    if (count %3E= BUFFER_MAX_LINES) %7B%0A      removeTopLines(count - BUFFER_MAX_LINES);%0A    %7D%0A
     myLines.

Fix LinesBuffer growing indefinitely (cherry picked from commit 853a809)