/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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 org.aesh.tty;

import org.aesh.readline.terminal.Key;
import org.aesh.terminal.Connection;
import org.aesh.terminal.tty.Capability;
import org.aesh.terminal.tty.Signal;
import org.aesh.terminal.tty.Size;
import org.aesh.util.Parser;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * @author <a href="mailto:stale.pedersen@jboss.org">Ståle W. Pedersen</a>
 */
public class TestConnection implements Connection {

    private Consumer<Size> sizeHandler;
    private Consumer<Signal> signalHandler;
    private Consumer<int[]> stdinHandler;
    private Consumer<int[]> stdOutHandler;
    private Consumer<Void> closeHandler;

    private StringBuilder bufferBuilder;
    private String out;
    private Size size;

    private CountDownLatch latch;
    private volatile boolean waiting = false;
    private volatile boolean reading = false;

    public TestConnection() {
        this(new Size(80, 20), true);
    }

    public TestConnection(boolean stripAnsiCodes) {
        this(new Size(80, 20), stripAnsiCodes);
    }

    public TestConnection(Size size) {
        this(size, true);
    }

    public TestConnection(Size size, boolean stripAnsiCodes) {
        bufferBuilder = new StringBuilder();
        stdOutHandler = ints -> {
            if(stripAnsiCodes)
                bufferBuilder.append(Parser.stripAwayAnsiCodes(Parser.fromCodePoints(ints)));
            else
                bufferBuilder.append(Parser.fromCodePoints(ints));
        };

        if(size == null)
            this.size = new Size(80, 20);
        else
            this.size = size;
    }

    public void clearOutputBuffer() {
        if(bufferBuilder.length() > 0)
            bufferBuilder.delete(0, bufferBuilder.length());
    }

    public String getOutputBuffer() {
        return bufferBuilder.toString();
    }

    public String getLine() {
        return out;
    }

    @Override
    public String terminalType() {
        return "fooTerm";
    }

    @Override
    public Size size() {
        return size;
    }

    @Override
    public Consumer<Size> getSizeHandler() {
        return sizeHandler;
    }

    @Override
    public void setSizeHandler(Consumer<Size> handler) {
        this.sizeHandler = handler;
    }

    @Override
    public Consumer<Signal> getSignalHandler() {
        return signalHandler;
    }

    @Override
    public void setSignalHandler(Consumer<Signal> handler) {
        signalHandler = handler;
    }

    @Override
    public Consumer<int[]> getStdinHandler() {
        return stdinHandler;
    }

    @Override
    public void setStdinHandler(Consumer<int[]> handler) {
        stdinHandler = handler;
    }

    @Override
    public Consumer<int[]> stdoutHandler() {
        return stdOutHandler;
    }

    @Override
    public void setCloseHandler(Consumer<Void> closeHandler) {
        this.closeHandler = closeHandler;
    }

    @Override
    public Consumer<Void> getCloseHandler() {
        return closeHandler;
    }

    @Override
    public void close() {
        reading = false;
        closeHandler.accept(null);
    }

    @Override
    public void openBlocking() {
        //we're not doing anything here, all input will come from the read(..) methods
        reading = true;
    }

    @Override
    public void openNonBlocking() {

    }

    @Override
    public void stopReading() {

    }

    private void doRead(int[] input) {
        if(reading) {
            if (waiting) {
                try {
                    latch.await();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(stdinHandler != null) {
                stdinHandler.accept(input);
            }
            else {
                try {
                    Thread.sleep(10);
                    doRead(input);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        else
            throw new RuntimeException("Got input when not reading: "+ Arrays.toString(input));
    }

    @Override
    public void suspend() {
        latch = new CountDownLatch(1);
        waiting = true;
     }

    @Override
    public boolean suspended() {
        return waiting;
    }

    @Override
    public void awake() {
        if(waiting) {
            waiting = false;
            latch.countDown();
        }
     }

    @Override
    public boolean put(Capability capability, Object... params) {
        return false;
    }

    @Override
    public Charset inputCharset() {
        return Charset.defaultCharset();
    }

    @Override
    public Charset outputCharset() {
        return Charset.defaultCharset();
    }

    public void assertLine(String expected) {
        assertEquals(expected, out);
    }

    public void assertBuffer(String expected) {
        assertEquals(expected, bufferBuilder.toString());
    }

    public void assertBufferEndsWith(String expected) {
        assertTrue(bufferBuilder.toString().endsWith(expected));
    }

     public void read(int... data) {
        doRead(data);
    }

    public void read(Key key) {
        doRead(key.getKeyValues());
    }

    public void read(String data) {
        doRead(Parser.toCodePoints(data));
    }

}

@@ -1135,52 +1135,8 @@
 ys;%0A
-import java.util.concurrent.CountDownLatch;%0A
 impo
@@ -1697,88 +1697,8 @@
 e;%0A%0A
-    private CountDownLatch latch;%0A    private volatile boolean waiting = false;%0A
     
@@ -4195,62 +4195,8 @@
  %7D%0A%0A
-    @Override%0A    public void stopReading() %7B%0A%0A    %7D%0A%0A
     
@@ -4256,232 +4256,8 @@
 ) %7B%0A
-            if (waiting) %7B%0A                try %7B%0A                    latch.await();%0A                %7D%0A                catch (InterruptedException e) %7B%0A                    e.printStackTrace();%0A                %7D%0A            %7D%0A
     
@@ -4733,339 +4733,8 @@
  %7D%0A%0A
-    @Override%0A    public void suspend() %7B%0A        latch = new CountDownLatch(1);%0A        waiting = true;%0A     %7D%0A%0A    @Override%0A    public boolean suspended() %7B%0A        return waiting;%0A    %7D%0A%0A    @Override%0A    public void awake() %7B%0A        if(waiting) %7B%0A            waiting = false;%0A            latch.countDown();%0A        %7D%0A     %7D%0A%0A
     

make sure to update tests as well