package io.termd.core.readline;

import io.termd.core.TestBase;
import io.termd.core.tty.TtyEvent;
import io.termd.core.util.Vector;
import org.junit.Test;

import java.util.Collections;
import java.util.LinkedList;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * @author <a href="mailto:[email protected]">Julien Viet</a>
 */
public class ReadlineTest extends TestBase {

  @Test
  public void testPrompt() {
    TestTerm term = new TestTerm(this);
    term.readline(event -> testComplete());
    term.assertScreen("% ");
    term.assertAt(0, 2);
  }

  @Test
  public void testEnter() {
    TestTerm term = new TestTerm(this);
    term.readline(event -> testComplete());
    term.read('\r');
    term.assertScreen("% ");
    term.assertAt(1, 0);
    await();
  }

  @Test
  public void cancel() {
    TestTerm term = new TestTerm(this);
    assertFalse(term.readline.cancel());
    term.readline(s -> {
      assertNull(s);
      testComplete();
    });
    assertTrue(term.readline.cancel());
    assertFalse(term.readline.cancel());
    await();
  }

  @Test
  public void testInsertChar() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read('A');
    term.assertScreen("% A");
    term.assertAt(0, 3);
  }

  @Test
  public void testInsertCharEnter() throws Exception {
    TestTerm term = new TestTerm(this);
    Supplier<String> line = term.readlineComplete();
    term.read('A');
    term.read('\r');
    term.assertScreen("% A");
    term.assertAt(1, 0);
    assertEquals("A", line.get());
  }

  @Test
  public void testEscapeCR() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read('\\');
    term.assertScreen("% \\");
    term.read('\r');
    term.assertScreen(
        "% \\",
        "> "
    );
    term.assertAt(1, 2);
  }

  @Test
  public void testBackwardDeleteChar() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read('A');
    term.read(BACKWARD_DELETE_KEY);
    term.assertScreen("% ");
    term.assertAt(0, 2);
  }

  @Test
  public void testBackwardDelete() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read(BACKWARD_DELETE_KEY);
    term.assertScreen(
        "% "
    );
    term.assertAt(0, 2);
  }

  @Test
  public void testBackwardDeleteLastChar() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read('A');
    term.read('B');
    term.read(8);
    term.assertScreen(
        "% A"
    );
    term.assertAt(0, 3);
  }

  @Test
  public void testBackwardCharBackwardDeleteChar() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read('A');
    term.read('B');
    term.read(BACKWARD_KEY);
    term.read(BACKWARD_DELETE_KEY);
    term.assertScreen(
        "% B"
    );
    term.assertAt(0, 2);
  }

  @Test
  public void testBackwardDeleteEscape() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read('\\');
    term.assertScreen("% \\");
    term.read(BACKWARD_DELETE_KEY);
    term.assertScreen(
        "% "
    );
    term.assertAt(0, 2);
  }

  @Test
  public void testBackwardChar() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read(BACKWARD_KEY);
    term.assertScreen("% ");
    term.assertAt(0, 2);
  }

  @Test
  public void testInsertCharBackwardChar() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read('A');
    term.read(BACKWARD_KEY);
    term.assertScreen("% A");
    term.assertAt(0, 2);
  }

  @Test
  public void testForwardChar() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read(FORWARD_KEY);
    term.assertScreen("% ");
    term.assertAt(0, 2);
  }

  @Test
  public void testInsertCharForwardChar() {
    TestTerm term = new TestTerm(this);
    term.readlineFail();
    term.read('A');
    term.read(BACKWARD_KEY);
    term.read(FORWARD_KEY);
    term.assertScreen("% A");
    term.assertAt(0, 3);
  }

  @Test
  public void testQuotedMultiline() {
    TestTerm term = new TestTerm(this);
    Supplier<String> a = term.readlineComplete();
    term.read('A');
    term.read('"');
    term.read('\r');
    assertNull(a.get());
    term.assertScreen(
        "% A\"",
        "> ");
    term.read('B');
    term.read('\r');
    term.assertScreen(
        "% A\"",
        "> B",
        "> ");
    assertNull(a.get());
    term.read('C');
    term.read('"');
    term.read('\r');
    term.assertScreen(
        "% A\"",
        "> B",
        "> C\"");
    term.assertAt(3, 0);
    assertEquals("A\"\nB\nC\"", a.get());
  }

  @Test
  public void testPreserveOriginalHandlers() {
    TestTerm term = new TestTerm(this);
    Consumer<int[]> readHandler = buf -> {};
    Consumer<Vector> sizeHandler = size -> {};
    term.readHandler = readHandler;
    term.sizeHandler = sizeHandler;
    term.readlineComplete();
    assertFalse(term.readHandler == readHandler);
    assertFalse(term.sizeHandler == sizeHandler);
    term.read('\r');
    assertEquals(term.readHandler, readHandler);
    assertEquals(term.sizeHandler, sizeHandler);
  }

  @Test
  public void testBuffering() {
    TestTerm term = new TestTerm(this);
    Supplier<String> line = term.readlineComplete();
    term.read('h', 'e', 'l', 'l', 'o', '\r', 'b', 'y', 'e', '\r');
    assertEquals("hello", line.get());
    term.assertScreen("% hello");
    term.assertAt(1, 0);
    line = term.readlineComplete();
    term.executeTasks();
    assertEquals("bye", line.get());
    term.assertScreen("% hello", "% bye");
    term.assertAt(2, 0);
  }

  @Test
  public void testHistory() {
    TestTerm term = new TestTerm(this);
    term.readlineComplete();
    term.read('0', '\r');
    term.readlineComplete();
    term.read('1', '\r');
    term.readlineComplete();
    term.read('2', '\r');
    term.assertScreen("% 0", "% 1", "% 2");
    term.readlineComplete();
    term.read('3');
    term.assertScreen("% 0", "% 1", "% 2", "% 3");
    term.read(UP_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 2");
    term.read(UP_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 1");
    term.read(UP_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 0");
    term.read(UP_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 0");
    term.read(DOWN_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 1");
    term.read('_');
    term.assertScreen("% 0", "% 1", "% 2", "% 1_");
    term.read(DOWN_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 2");
    term.read(UP_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 1");
    term.read(DOWN_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 2");
    term.read(DOWN_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 3");
    term.read(DOWN_KEY);
    term.assertScreen("% 0", "% 1", "% 2", "% 3");
  }

  @Test
  public void testEndOfLine() {
    TestTerm term = new TestTerm(this);
    term.readlineComplete();
    term.read('a', 'b', 'c', 'd');
    term.read(CTRL_A_KEY);
    term.assertScreen("% abcd");
    term.assertAt(0, 2);
    term.read(CTRL_A_KEY);
    term.assertScreen("% abcd");
    term.assertAt(0, 2);
  }

  @Test
  public void testBeginningOfLine() {
    TestTerm term = new TestTerm(this);
    term.readlineComplete();
    term.read('a', 'b', 'c', 'd');
    term.read(BACKWARD_KEY);
    term.read(BACKWARD_KEY);
    term.read(BACKWARD_KEY);
    term.read(BACKWARD_KEY);
    term.assertScreen("% abcd");
    term.assertAt(0, 2);
    term.read(CTRL_E_KEY);
    term.assertScreen("% abcd");
    term.assertAt(0, 6);
    term.assertScreen("% abcd");
    term.assertAt(0, 6);
  }

  @Test
  public void testResetDuringInteraction1() {
    TestTerm term = new TestTerm(this);
    Supplier<String> line = term.readlineComplete();
    term.read('a', 'b', 'c', 'd');
    term.read(CTRL_C_KEY);
    term.read('e');
    term.assertScreen("% abcd", "% e");
    term.assertAt(1, 3);
    term.read('\r');
    assertEquals("e", line.get());
  }

  @Test
  public void testResetDuringInteraction2() {
    TestTerm term = new TestTerm(this);
    Supplier<String> line = term.readlineComplete();
    term.read('a', 'b', 'c', 'd', '\\', '\r');
    term.read(CTRL_C_KEY);
    term.read('e');
    term.assertScreen("% abcd\\", "> ", "% e");
    term.assertAt(2, 3);
    term.read('\r');
    assertEquals("e", line.get());
  }

  @Test
  public void testIllegalChar() {
    TestTerm term = new TestTerm(this);
    Supplier<String> line = term.readlineComplete();
    assertEquals(0, term.getBellCount());
    term.read('a', 6, 'b', '\r');
    assertEquals(1, term.getBellCount());
    assertEquals("ab", line.get());
  }

  @Test
  public void testEventHandler() {
    TestTerm term = new TestTerm(this);
    LinkedList<TtyEvent> events = new LinkedList<>();
    BiConsumer<TtyEvent, Integer> handler = (event,cp) -> events.add(event);
    term.eventHandler = handler;
    Supplier<String> line = term.readlineComplete();
    term.read(CTRL_C_KEY);
    term.read('\r');
    assertEquals("", line.get());
    assertEquals(Collections.emptyList(), events);
    term.eventHandler.accept(TtyEvent.EOF, 4);
    assertEquals(Collections.singletonList(TtyEvent.EOF), events);
  }

  @Test
  public void testEOF() {
    TestTerm term = new TestTerm(this);
    LinkedList<String> lines = new LinkedList<>();
    term.readline(lines::add);
    term.read(TtyEvent.EOF.codePoint());
    assertEquals(Collections.singletonList(null), lines);
  }

  @Test
  public void testDeleteChar() {
    TestTerm term = new TestTerm(this);
    Supplier<String> line = term.readlineComplete();
    term.read('a', 'b', 'c');
    term.read(CTRL_D_KEY);
    term.assertScreen("% abc");
    term.assertAt(0, 5);
    term.read(BACKWARD_KEY);
    term.read(BACKWARD_KEY);
    term.read(CTRL_D_KEY);
    term.assertScreen("% ac");
    term.assertAt(0, 3);
    term.read('\r');
    assertEquals("ac", line.get());
  }

  @Test
  public void testKillLine() {
    TestTerm term = new TestTerm(this);
    Supplier<String> line = term.readlineComplete();
    term.setWidth(4);
    term.read('a', 'b', 'c', 'd', 'e');
    term.assertScreen("% ab", "cde");
    term.assertAt(1, 3);
    term.read(BACKWARD_KEY);
    term.read(BACKWARD_KEY);
    term.read(BACKWARD_KEY);
    term.read(BACKWARD_KEY);
    term.assertScreen("% ab", "cde");
    term.assertAt(0, 3);
    term.read(KILL_LINE);
    term.assertScreen("% a", "");
    term.assertAt(0, 3);
    term.read('\r');
    assertEquals("a", line.get());
  }

  @Test
  public void testBackwardWord() {
    TestTerm term = new TestTerm(this);
    term.readlineComplete();
    term.read('a', '0', '1', ' ', 'e', '9', '_', '_' ,'5', 'a');
    term.read(BACKWARD_KEY);
    term.assertAt(0, 11);
    term.read(BACKWARD_WORD_SEQ);
    term.assertAt(0, 10);
    term.read(BACKWARD_WORD_SEQ);
    term.assertAt(0, 6);
    term.read(BACKWARD_WORD_SEQ);
    term.assertAt(0, 2);
    term.read(BACKWARD_WORD_SEQ);
    term.assertAt(0, 2);
  }

  @Test
  public void testForwardWord() {
    TestTerm term = new TestTerm(this);
    term.readlineComplete();
    term.read('a', '0', '1', ' ', 'e', '9', '_', '_', '5', 'a');
    term.read(CTRL_A_KEY);
    term.assertAt(0, 2);
    term.read(FORWARD_KEY);
    term.assertAt(0, 3);
    term.read(FORWARD_WORD_SEQ);
    term.assertAt(0, 5);
    term.read(FORWARD_WORD_SEQ);
    term.assertAt(0, 8);
    term.read(FORWARD_WORD_SEQ);
    term.assertAt(0, 12);
    term.read(FORWARD_WORD_SEQ);
    term.assertAt(0, 12);
  }

  @Test
  public void testBackwardKillWord() {
    TestTerm term = new TestTerm(this);
    term.readlineComplete();
    term.read('a', '0', '1', ' ', 'e', '9', '_', '_', '5', 'a');
    term.read(BACKWARD_KEY);
    term.assertAt(0, 11);
    term.read(META_BACKSPACE);
    term.assertAt(0, 10);
    term.assertScreen("% a01 e9__a");
    term.read(META_BACKSPACE);
    term.assertAt(0, 6);
    term.assertScreen("% a01 a");
    term.read(META_BACKSPACE);
    term.assertAt(0, 2);
    term.assertScreen("% a");
    term.read(META_BACKSPACE);
    term.assertAt(0, 2);
    term.assertScreen("% a");
  }
}