package com.felhr.usbserial;

import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Arrays;

public class SerialBuffer
{
    public static final int DEFAULT_READ_BUFFER_SIZE = 1000 * 16 * 1024;
    public static final int DEFAULT_WRITE_BUFFER_SIZE = 16 * 1024;
    private ByteBuffer readBuffer;
    private SynchronizedBuffer writeBuffer;
    private byte[] readBuffer_compatible; // Read buffer for android < 4.2
    private boolean debugging = false;

    public SerialBuffer(boolean version)
    {
        writeBuffer = new SynchronizedBuffer();
        if(version)
        {
            readBuffer = ByteBuffer.allocate(DEFAULT_READ_BUFFER_SIZE);

        }else
        {
            readBuffer_compatible = new byte[DEFAULT_READ_BUFFER_SIZE];
        }
    }

    /*
     * Print debug messages
     */
    public void debug(boolean value)
    {
        debugging = value;
    }

    public void putReadBuffer(ByteBuffer data)
    {
        synchronized(this)
        {
            try
            {
                readBuffer.put(data);
            }catch(BufferOverflowException e)
            {
                // TO-DO
            }
        }
    }

    public ByteBuffer getReadBuffer()
    {
        synchronized(this)
        {
            return readBuffer;
        }
    }


    public byte[] getDataReceived()
    {
        synchronized(this)
        {
            byte[] dst = new byte[readBuffer.position()];
            readBuffer.position(0);
            readBuffer.get(dst, 0, dst.length);
            if(debugging)
                UsbSerialDebugger.printReadLogGet(dst, true);
            return dst;
        }
    }

    public void clearReadBuffer()
    {
        synchronized(this)
        {
            readBuffer.clear();
        }
    }

    public byte[] getWriteBuffer()
    {
        return writeBuffer.get();
    }

    public void putWriteBuffer(byte[]data)
    {
        writeBuffer.put(data);
    }


    public void resetWriteBuffer()
    {
        writeBuffer.reset();
    }

    public byte[] getBufferCompatible()
    {
        return readBuffer_compatible;
    }

    public byte[] getDataReceivedCompatible(int numberBytes)
    {
        byte[] tempBuff = Arrays.copyOfRange(readBuffer_compatible, 0, numberBytes);
        return tempBuff;
    }

    private class SynchronizedBuffer
    {
        private byte[] buffer;
        private int position;

        public SynchronizedBuffer()
        {
            this.buffer = new byte[DEFAULT_WRITE_BUFFER_SIZE];
            position = -1;
        }

        public synchronized void put(byte[] src)
        {
            if(position == -1)
                position = 0;
            if(debugging)
                UsbSerialDebugger.printLogPut(src, true);
            if(position + src.length > DEFAULT_WRITE_BUFFER_SIZE - 1) //Checking bounds. Source data does not fit in buffer
            {
                if(position < DEFAULT_WRITE_BUFFER_SIZE)
                    System.arraycopy(src, 0, buffer, position, DEFAULT_WRITE_BUFFER_SIZE - position);
                position = DEFAULT_WRITE_BUFFER_SIZE;
                notify();
            }else // Source data fits in buffer
            {
                System.arraycopy(src, 0, buffer, position, src.length);
                position += src.length;
                notify();
            }
        }

        public synchronized byte[] get()
        {
            if(position == -1)
            {
                try
                {
                    wait();
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            byte[] dst =  Arrays.copyOfRange(buffer, 0, position);
            if(debugging)
                UsbSerialDebugger.printLogGet(dst, true);
            position = -1;
            return dst;
        }

        public synchronized void reset()
        {
            position = -1;
        }
    }

}
