/* 
 * Copyright 2015 by AVM GmbH <info@avm.de>
 *
 * This software contains free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License ("License") as 
 * published by the Free Software Foundation  (version 3 of the License). 
 * This software is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the copy of the 
 * License you received along with this software for more details.
 */

package de.avm.android.fritzapp.util;

import java.nio.BufferOverflowException;

import de.avm.fundamentals.logger.FileLog;


public class AudioLoopback
{
	private static final String TAG = "AudioLoopback";
	private static final long LOG_INTERVAL = 2000L;
	private static final long START_DELAY = 1000L; // delay on start in ms
	
	private short[] mBuffer = null;
	private int mNextWrite = 0;
	private int mNextRead = -1;
	private int mTotalWritten = 0;
	private int mTotalRead = 0;
	private long mLogTimestamp = -1L;
	private long mDelayTimestamp = -1L;
	
	public synchronized void setBuffersize(int sizeInShorts)
	{
		FileLog.d(TAG, sizeInShorts + " shorts to be buffered");
		log(true);
		if (sizeInShorts < 1)
		{
			mBuffer = null;
		}
		else if (mBuffer == null)
		{
			mDelayTimestamp = System.currentTimeMillis();
			mBuffer = new short[sizeInShorts];
		}
		else if (mBuffer.length != sizeInShorts)
		{
			mBuffer = new short[sizeInShorts];
		}
		mNextWrite = 0;
		mNextRead = -1;
		mTotalRead = 0;
	}
	
	public synchronized int getTotalReadShorts()
	{
		return mTotalRead;
	}

	public synchronized void clear()
	{
		log(true);
		mNextWrite = 0;
		mNextRead = -1;
	}
	
	public synchronized int read(short[] audioData, int offsetInShorts, int sizeInShorts)
	{
		if ((mBuffer == null) || (sizeInShorts < 1))
		{
			log(false);
			return 0;
		}

		if (mDelayTimestamp >= 0)
		{
			if ((mDelayTimestamp + START_DELAY) > System.currentTimeMillis())
			{
				log(false);
				return 0;
			}
			mDelayTimestamp = -1L;
		}
		
		if (audioData == null)
			throw new IllegalArgumentException("Argument audioData must not be null.");
		if ((offsetInShorts < 0) || ((audioData.length - offsetInShorts) < sizeInShorts))
			throw new IndexOutOfBoundsException();
		
		int read = 0;
		for (; (read < sizeInShorts) && (mNextRead >= 0); read++)
		{
			audioData[offsetInShorts + read] = mBuffer[mNextRead++];
			if (mNextRead >= mBuffer.length) mNextRead = 0;
			if (mNextRead == mNextWrite) mNextRead = -1;
		}

		mTotalRead += read; 
		log(false);
		return read;
	}
	
	public synchronized int write(short[] audioData, int offsetInShorts, int sizeInShorts)
	{
		if ((audioData == null) || (sizeInShorts < 1))
		{
			log(false);
			return 0;
		}
		
		if (mBuffer == null)
			throw new IllegalStateException("Call setBuffersize before writing.");
		if ((offsetInShorts < 0) || ((audioData.length - offsetInShorts) < sizeInShorts))
			throw new IndexOutOfBoundsException();
		if (sizeInShorts > mBuffer.length)
			throw new BufferOverflowException();

		for (int ii = 0; ii < sizeInShorts; ii++)
		{
			if (mNextRead < 0)
				mNextRead = mNextWrite;
			else if ((mNextWrite == mNextRead) && (++mNextRead >= mBuffer.length)) 
				mNextRead = 0;
			mBuffer[mNextWrite++] = audioData[offsetInShorts + ii];
			if (mNextWrite >= mBuffer.length)
				mNextWrite = 0;
		}
		
		mTotalWritten += sizeInShorts; 
		log(false);
		return sizeInShorts;
	}
	
	private void log(boolean force)
	{
		if (mBuffer != null)
		{
			long time = System.currentTimeMillis();
			if (force || (mLogTimestamp < 0) || ((mLogTimestamp + LOG_INTERVAL) < time))
			{
				mLogTimestamp = time;

				if (mDelayTimestamp < 0)
				{
					int size = 0;
					if (mNextRead >= 0)
					{
						if (mNextWrite > mNextRead)
							size = mNextWrite - mNextRead;
						else if (mNextWrite < mNextRead)
							size =  mBuffer.length - mNextRead + mNextWrite;
						else
							size = mBuffer.length;
					}
					
					if (mTotalWritten < mTotalRead)
					{
						// int overflow
						mTotalWritten = size;
						mTotalRead = 0;
					}
					FileLog.d(TAG, "shorts written: " + mTotalWritten + ", read: " + mTotalRead +
							", left: " + size + ", lost: " + (mTotalWritten - mTotalRead - size));
				}
				else FileLog.d(TAG, "delayed, shorts written: " + mTotalWritten);
			}
		}
	}
}
