package com.theta.view;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * Input stream for motion JPEG data
 */
public class MJpegInputStream extends DataInputStream {
    private final byte[] SOI_MARKER = {(byte) 0xFF, (byte) 0xD8};
    private final byte[] EOF_MARKER = {(byte) 0xFF, (byte) 0xD9};
    private final static String CONTENT_LENGTH = "Content-Length";
    private final static int HEADER_MAX_LENGTH = 100;
    private final static int FRAME_MAX_LENGTH = 40000 + HEADER_MAX_LENGTH;

    /**
     * Constructor
     * @param inputStream Input stream for receiving data
     */
    public MJpegInputStream(InputStream inputStream) {
        super(new BufferedInputStream(inputStream, FRAME_MAX_LENGTH));
    }

    /**
     * Acquire end position of specified character string
     * @param dataInputStream Input stream for receiving data
     * @param sequence Specified character string
     * @return End position of specified character string
     * @throws IOException
     */
    private int getEndOfSequence(DataInputStream dataInputStream, byte[] sequence) throws IOException {
        int sequenceIndex = 0;
        byte readByteData;
        for(int index = 0; index < FRAME_MAX_LENGTH; index++) {
            readByteData = (byte) dataInputStream.readUnsignedByte();
            if(readByteData == sequence[sequenceIndex]) {
                sequenceIndex++;
                if(sequenceIndex == sequence.length) {
                    return index + 1;
                }
            } else {
                sequenceIndex = 0;
            }
        }
        return -1;
    }

    /**
     * Acquire start position of specified character string
     * @param dataInputStream Input stream for receiving data
     * @param sequence Specified character string
     * @return Start position of specified character string
     * @throws IOException
     */
    private int getStartOfSequence(DataInputStream dataInputStream, byte[] sequence) throws IOException {
        int endIndex = getEndOfSequence(dataInputStream, sequence);
        return (endIndex < 0) ? (-1) : (endIndex - sequence.length);
    }

    /**
     * Acquire data length from header
     * @param headerByteData Header data
     * @return Data length
     * @throws IOException
     * @throws NumberFormatException
     */
    private int parseContentLength(byte[] headerByteData) throws IOException, NumberFormatException {
        ByteArrayInputStream bais = new ByteArrayInputStream(headerByteData);
        Properties properties = new Properties();
        properties.load(bais);
        return Integer.parseInt(properties.getProperty(CONTENT_LENGTH));
    }

    /**
     * Acquire image data for 1 frame
     * @return Image data for 1 frame
     * @throws IOException
     */
    public Bitmap readMJpegFrame() throws IOException {
        mark(FRAME_MAX_LENGTH);
        int headerLength = getStartOfSequence(this, SOI_MARKER);
        int contentLength;
        reset();

        byte[] headerData = new byte[headerLength];
        readFully(headerData);
        try {
            contentLength = parseContentLength(headerData);
        } catch (NumberFormatException e) {
            e.getStackTrace();
            contentLength = getEndOfSequence(this, EOF_MARKER);
        }
        reset();

        byte[] frameData = new byte[contentLength];
        skipBytes(headerLength);
        readFully(frameData);

        return BitmapFactory.decodeStream(new ByteArrayInputStream(frameData));
    }
}