/*******************************************************************************
 * Copyright (c) 2011, 2021 SAP AG and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Lazar Kirchev, SAP AG - initial contribution
 ******************************************************************************/

package org.eclipse.equinox.console.telnet;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.equinox.console.common.ConsoleInputStream;
import org.eclipse.equinox.console.common.ConsoleOutputStream;
import org.eclipse.equinox.console.common.KEYS;
import org.eclipse.equinox.console.common.terminal.ANSITerminalTypeMappings;
import org.eclipse.equinox.console.common.terminal.SCOTerminalTypeMappings;
import org.eclipse.equinox.console.common.terminal.TerminalTypeMappings;
import org.eclipse.equinox.console.common.terminal.VT100TerminalTypeMappings;
import org.eclipse.equinox.console.common.terminal.VT220TerminalTypeMappings;
import org.eclipse.equinox.console.common.terminal.VT320TerminalTypeMappings;
import org.junit.Test;

public class TelnetInputScannerTests {

	private static final int IAC = 255;

	private static final int DO = 253;
	
	private static final int DO_NOT = 254;

	private static final int TTYPE = 24;

	private static final int WILL = 251;
	
	private static final int WILL_NOT = 252;

	private static final int SB = 250;

	private static final int SE = 240;
	
	private static final int EL = 248;

	private static final int SEND = 1;

	private static final int IS = 0;

	protected static final byte ESC = 27;

	@Test
	public void testScan() throws Exception {
		ConsoleInputStream in = new ConsoleInputStream();
		ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
		ConsoleOutputStream out = new ConsoleOutputStream(byteOut);
		Callback callback = mock(Callback.class);
		TelnetInputScanner scanner = new TelnetInputScanner(in, out, callback);
		try {
			scanner.scan((byte) SE);
			scanner.scan((byte) EL);
			scanner.scan((byte) SB);
			scanner.scan((byte) WILL);
			scanner.scan((byte) WILL_NOT);
			scanner.scan((byte) DO);
			scanner.scan((byte) DO_NOT);
			scanner.scan((byte) 'a');
			scanner.scan((byte) 'b');
			scanner.scan((byte) 'c');
		} catch (IOException e) {
			System.out.println("Error while scanning: " + e.getMessage());
			e.printStackTrace();
			throw e;
		}

		String output = byteOut.toString();
		assertEquals("Output incorrect. Expected abc, but read " + output, "abc", output);
	}

	@Test
	public void testScanESC() throws Exception {
		ConsoleInputStream in = new ConsoleInputStream();
		ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
		ConsoleOutputStream out = new ConsoleOutputStream(byteOut);
		Callback callback = mock(Callback.class);
		TelnetInputScanner scanner = new TelnetInputScanner(in, out, callback);

		try {
			scanner.scan((byte) 'a');
			scanner.scan(ESC);
			scanner.scan((byte) 'b');
		} catch (IOException e) {
			System.out.println("Error while scanning: " + e.getMessage());
			e.printStackTrace();
			throw e;
		}

		String output = byteOut.toString();
		assertTrue("Output incorrect. Expected ab, but read " + output, output.equals("ab"));
	}

	@Test
	public void testTTNegotiations() throws Exception {
		Map<byte[], TerminalTypeMappings> ttMappings = new HashMap<>();
		ttMappings.put(new byte[] { 'A', 'N', 'S', 'I' }, new ANSITerminalTypeMappings());
		ttMappings.put(new byte[] { 'V', 'T', '1', '0', '0' }, new VT100TerminalTypeMappings());
		ttMappings.put(new byte[] { 'V', 'T', '2', '2', '0' }, new VT220TerminalTypeMappings());
		ttMappings.put(new byte[] { 'X', 'T', 'E', 'R', 'M' }, new VT220TerminalTypeMappings());
		ttMappings.put(new byte[] { 'V', 'T', '3', '2', '0' }, new VT320TerminalTypeMappings());
		ttMappings.put(new byte[] { 'S', 'C', 'O' }, new SCOTerminalTypeMappings());

		for (byte[] ttype : ttMappings.keySet()) {
			testTerminalTypeNegotiation(ttype, ttMappings.get(ttype));
		}
	}

	private void testTerminalTypeNegotiation(byte[] terminalType, TerminalTypeMappings mappings) throws Exception {
		PipedInputStream clientIn = new PipedInputStream();
		PipedOutputStream serverOut = new PipedOutputStream(clientIn);

		byte[] requestNegotiation = { (byte) IAC, (byte) DO, (byte) TTYPE };

		TestCallback testCallback = new TestCallback();
		TelnetOutputStream out = new TelnetOutputStream(serverOut);
		TelnetInputScanner scanner = new TelnetInputScanner(new ConsoleInputStream(), out, testCallback);
		out.write(requestNegotiation);
		out.flush();

		int read = clientIn.read();
		assertEquals("Unexpected input ", IAC, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", DO, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", TTYPE, read);

		scanner.scan(IAC);
		scanner.scan(WILL);
		scanner.scan(TTYPE);

		read = clientIn.read();
		assertEquals("Unexpected input ", IAC, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", SB, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", TTYPE, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", SEND, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", IAC, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", SE, read);

		scanner.scan(IAC);
		scanner.scan(SB);
		scanner.scan(TTYPE);
		scanner.scan(IS);
		scanner.scan('A');
		scanner.scan('B');
		scanner.scan('C');
		scanner.scan('D');
		scanner.scan('E');
		scanner.scan(IAC);
		scanner.scan(SE);

		read = clientIn.read();
		assertEquals("Unexpected input ", IAC, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", SB, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", TTYPE, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", SEND, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", IAC, read);
		read = clientIn.read();
		assertEquals("Unexpected input ", SE, read);

		scanner.scan(IAC);
		scanner.scan(SB);
		scanner.scan(TTYPE);
		scanner.scan(IS);
		for (byte symbol : terminalType) {
			scanner.scan(symbol);
		}
		scanner.scan(IAC);
		scanner.scan(SE);

		assertEquals("Incorrect BACKSPACE: ", mappings.getBackspace(), scanner.getBackspace());
		assertEquals("Incorrect DELL: ", mappings.getDel(), scanner.getDel());

		Map<String, KEYS> currentEscapesToKey = scanner.getCurrentEscapesToKey();
		Map<String, KEYS> expectedEscapesToKey = mappings.getEscapesToKey();
		for (String escape : expectedEscapesToKey.keySet()) {
			KEYS key = expectedEscapesToKey.get(escape);
			assertEquals("Incorrect " + key.name(), key, currentEscapesToKey.get(escape));
		}

		assertTrue("Callback not called ", testCallback.getState());
	}

	class TestCallback implements Callback {

		private boolean isCalled = false;

		@Override
		public void finished() {
			isCalled = true;
		}

		public boolean getState() {
			return isCalled;
		}
	}

}
