/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.test.tcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Various tests used to validate the behaviour of Java Sockets.
 *
 * The tests were for experimentation and don't have any assertions in them - JUnit
 * provided a convenient framework to explore this behaviour.  These tests shouldn't be
 * run with a normal build since they don't have any assertions and don't validate any
 * results.
 *
 * NOTE:  This class may be deleted in the future
 */
@Ignore(value = "Tests validating Java Socket behaviours")
public class JavaSocketTests {
    Logger log = LoggerFactory.getLogger(this.getClass());

    Socket clientSocket;
    ServerSocket serverSocket;

    int messageCount = 10;

    @Before
    public void setUp() throws Exception {
        serverSocket = new ServerSocket(0);
    }

    @After
    public void tearDown() throws Exception {
        if (null != clientSocket) {
            clientSocket.close();
        }

        if (null != serverSocket) {
            serverSocket.close();
        }
    }

    @Test
    public void testSocketReadOnClosedConnection() throws Exception {
        final Thread acceptThread = new Thread() {
            Logger log = LoggerFactory.getLogger("acceptThread");

            @Override
            public void run() {
                boolean running = true;
                try {
                    Socket echoSocket = serverSocket.accept();

                    log.info("Accepted connection: {}", echoSocket.getInetAddress());

                    echoSocket.setSoTimeout(2000);

                    while (echoSocket.isConnected() && !echoSocket.isClosed()) {
                        StringBuilder responseBuilder = new StringBuilder(500);
                        InputStream reader = echoSocket.getInputStream();
                        OutputStream writer = echoSocket.getOutputStream();

                        do {
                            int readByte = -1;
                            int available = -1;
                            try {
                                available = reader.available();
                                log.info("InputStream.available returned {}", available);
                                readByte = reader.read();
                                log.trace("Processing byte: {}", readByte);
                                switch (readByte) {
                                case -1:
                                    if (echoSocket.isConnected() && !echoSocket.isClosed()) {
                                        log.info("Available returned {}", reader.available());
                                        log.warn("Socket claims to still be open, but END_OF_STREAM received - closing echoSocket");
                                        try {
                                            echoSocket.close();
                                        } catch (Exception ex) {
                                            log.warn("Exception encountered closing echoSocket after END_OF_STREAM received", ex);
                                        }
                                    }
                                    running = false;
                                    break;
                                case 10:
                                    log.info("Complete Message - Sending Response");
                                    byte[] response = responseBuilder.toString().getBytes();
                                    responseBuilder.setLength(0);
                                    writer.write(response, 0, response.length);
                                    writer.write('\n');
                                    break;
                                default:
                                    responseBuilder.append((char) readByte);
                                }
                            } catch (SocketTimeoutException timeoutEx) {
                                log.info("Timeout reading data - available returned {}", available);
                            }
                        } while (echoSocket.isConnected() && !echoSocket.isClosed());
                    }

                } catch (IOException ioEx) {
                    log.error("IOException in run method", ioEx);
                } finally {
                    try {
                        serverSocket.close();
                    } catch (IOException ioEx) {
                        log.error("Exception encountered closing server socket", ioEx);
                    }
                }


                log.info("Finished processing connection");
            }

        };

        acceptThread.start();

        clientSocket = new Socket();
        clientSocket.setSoTimeout(1000);
        clientSocket.connect(serverSocket.getLocalSocketAddress(), 10000);
        clientSocket.setTcpNoDelay(true);
        log.info("Begining message send loop ");
        byte[] message = "Hello World".getBytes();
        BufferedReader reader;
        for (int i = 1; i <= messageCount; ++i) {
            reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            OutputStream writer = clientSocket.getOutputStream();
            log.info("Sending payload");
            writer.write(message, 0, message.length);
            writer.flush();
            log.info("Sending terminator");
            writer.write('\n');
            writer.flush();
            log.info("Received Response #{}: {}", i, reader.readLine());
            Thread.sleep(1000);
        }

        log.info("Message send loop complete - closing connection");
        // Javadoc for Socket says closing the InputStream will close the connection
        clientSocket.getInputStream().close();
        if (!clientSocket.isClosed()) {
            log.warn("Closing input stream didn't close socket");
            clientSocket.close();
        }
        log.info("Sleeping ...");
        Thread.sleep(5000);

    }
}
