/*
 *****************************************************************************
 *                                                                           *
 *              IMPINJ CONFIDENTIAL AND PROPRIETARY                          *
 *                                                                           *
 * This source code is the sole property of Impinj, Inc. Reproduction or     *
 * utilization of this source code in whole or in part is forbidden without  *
 * the prior written consent of Impinj, Inc.                                 *
 *                                                                           *
 * (c) Copyright Impinj, Inc. 2014. All rights reserved.                     *
 *                                                                           *
 *****************************************************************************/

package com.impinj.stp;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class DebugStreamGrabber implements Runnable {
    private java.io.InputStream inputStreamFromReader;
    private java.io.OutputStream outputStream;
    private String readerName;
    private StreamType streamType;
    private Thread pumperThread;
    
    public enum StreamType { FDD, RDD };

    /**
     * Initiates a debug stream capture from the given reader
     * 
     * @param readerName Name of the reader to which to connect.  This can be
     * a dns name or an ip address.
     * 
     * @param streamType Indicates whether its RDD or FDD
     * 
     * @throws java.net.UnknownHostException IP address is not correct
     * @throws java.io.IOException TCP/IP communication failure
     */
    public DebugStreamGrabber(String readerName, StreamType streamType)
            throws java.net.UnknownHostException, java.io.IOException {
        this.readerName = readerName;
        this.streamType = streamType;
        inputStreamFromReader = createConnection();
        outputStream = null;

        pumperThread = new Thread(this);
        pumperThread.setDaemon(true);
    }

    /**
     * Kicks off the thread that streams data from the reader to the
     * outputStream.
     * 
     * @param outputFileName The stream to which to send the data.
     * @throws java.io.IOException Error while outputting to file
     */
    public void startPumperThread(String outputFileName) throws Exception {
        this.outputStream = new FileOutputStream(outputFileName);
        pumperThread.start();
    }

    public void Close() throws InterruptedException {
        close(2000);
    }

    /**
     * Closes the connection and joins the pumper thread.
     * 
     * The connection is closed by initiating a second connection to the reader
     * with the same parameters.  This results in the reader abandoning the 
     * previous connection, thus causing the pipes to flush.
     * 
     * @param flushTime Extra time to allow data to get flushed through the 
     * system.
     * 
     * @throws InterruptedException Thread interrupted during close.
     */
    public void close(int flushTime)
            throws InterruptedException {
        
        Thread.sleep(flushTime);

        // This is a little strange that in order to close, we have to initiate
        // a new connection, but that's the cleanest way to shut down the stream
        // because the reader will abandon the existing connection when a new
        // one is established.
        try {
            createConnection();
        } catch (java.net.UnknownHostException uhe) {
            System.out.print(uhe.toString());
        } catch (java.io.IOException ioe) {
            System.out.print(ioe.toString());
        }

        pumperThread.join(1000);
    }

    /**
     * This is purely just the pumping of bytes from the reader to the output
     * stream 
     */
    public void run() {
        final int bufferSize = 0x1000;
        byte[] byteBuffer = new byte[bufferSize];
        try {
            for (;;) {
                int numRead = inputStreamFromReader.read(byteBuffer);
                if (-1 == numRead) {
                    break;
                }
                outputStream.write(byteBuffer, 0, numRead);
            }

        } catch (java.io.IOException ioe) {
            System.out.printf(ioe.toString());
        } finally {
            try {
                outputStream.close();
            } catch (java.io.IOException ioe) {
                // There is nothing intelligent to do to recover here...
                System.out.printf(ioe.toString());
            }
        }
    }

    /**
     * Establishes a connection with the reader and returns the input stream
     * from the reader.
     * 
     * Even though the connection is http like, it's slightly broken from the
     * point of view of standard http handlers, so we manually deal with the 
     * headers and such here.  The returned InputStream is queued up at the 
     * beginning of real data after the headers have been eaten off.
     * 
     * @throws java.net.UnknownHostException IP address is not correct
     * @throws java.io.IOException TCP/IP communication failure
     * @return InputStream rdd or fdd input stream 
     */
    protected final InputStream createConnection() throws UnknownHostException, IOException {
        Socket s = new Socket(readerName, 80);
        OutputStream outputStream = s.getOutputStream();
        InputStream inputStream = s.getInputStream();

        String target = "unknown";
        switch (streamType) {
            case FDD:
                target = "fpga-debug";
                break;
            case RDD:
                target = "rdd-stream";
                break;
        }

        StringBuilder request = new StringBuilder();
        request.append(String.format("GET /cgi-bin/%s HTTP/1.1\r\n", target));
        request.append("User-Agent: DebugStreamGrabber\r\n");
        request.append("Accept: */*\r\n");
        request.append("Authorization: Basic cm9vdDppbXBpbmo=\r\n");
        request.append(String.format("Host: %s\r\n", readerName));
        request.append("Connection: Keep-Alive\r\n");
        request.append("\r\n");

        outputStream.write(request.toString().getBytes("UTF-8"));

        // I'm going to be horribly inefficient here, but I don't care since
        // this is not something that happens often. I'm looking for the pattern
        // \r\n\r\n
        
        byte[] searchBytes = "\r\n\r\n".getBytes("UTF-8");
        int searchIndex = 0;
        for(;;) {
            int currByte = inputStream.read();
            if (searchBytes[searchIndex] == currByte){
                searchIndex++;
            } else {
                searchIndex = 0;
            }

            if (searchIndex == 4) {
                break;
            }
        }

        // At this point, I've chewed off the header and the rest of the stream
        // is the data that should get captured to the file.
        return inputStream;
    }
}
