package jp.sourceforge.nicoro;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

import static jp.sourceforge.nicoro.Log.LOG_TAG;

public class StreamAudioPlayer {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG && false;
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;
	
	/**
	 * 
	 * AVCODEC_MAX_AUDIO_FRAME_SIZE以上でなければならない
	 */
	private static final int AUDIO_BUFFER_BYTE_SIZE = 192000 + 0;
	
    private static final int DUMMY_DATA_SIZE = 256 * 1024;
	
	private AudioTrackCustom mAudioTrack;
    private int mAudioSampleByteSize;
	private short[] mAudioBufferSecond;
	private int mAudioBufferSecondOffset;
	
	private int mWrittenBufferByteSize;
	private int mWrittenDeviceByteSize;
    
	private int mLastPlaybackHeadPosition;
	
	private int mDummyDataByteSize;
    
	private volatile boolean mIsFinish;
	
	private static class AudioTrackCustom extends AudioTrack {
		public AudioTrackCustom(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) {
			super(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);
		}
		
		public int getNativeFrameCountPublic() {
			int count = getNativeFrameCount();
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf().append("getNativeFrameCount()=")
						.append(count).toString());
			}
			return count;
		}
	}
    
    public StreamAudioPlayer() {
    	
    }
    
    public void prepareStart() {
    	if (mAudioBufferSecond == null) {
    		mAudioBufferSecond = new short[getAudioBufferByteSize()/2*2];
    	}
    	mAudioBufferSecondOffset = 0;
    	mIsFinish = false;
    }
    public void finish() {
    	mIsFinish = true;
    }
    
    public void release() {
		AudioTrack audioTrack = mAudioTrack;
		if (audioTrack != null) {
			audioTrack.release();
		}
    }
    public void releaseAll() {
    	release();
    	mAudioTrack = null;
    	mAudioBufferSecond = null;
    }

    public void startByFFmpeg(int sample_rate, int channels, int sample_fmt) {
    	startCommon();
    	int sampleRateInHz = sample_rate;
    	mAudioSampleByteSize = 1;
    	int channelConfig;
    	switch (channels) {
    	case 1:
    		channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    		mAudioSampleByteSize *= 1;
    		break;
    	case 2:
			channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
			mAudioSampleByteSize *= 2;
			break;
		default:
			channelConfig = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
    		break;
    	}
    	int audioFormat;
    	switch (sample_fmt) {
    	case 0:		// SAMPLE_FMT_U8
    		audioFormat = AudioFormat.ENCODING_PCM_8BIT;
    		mAudioSampleByteSize *= 1;
    		break;
    	case 1:		// SAMPLE_FMT_S16
    		audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    		mAudioSampleByteSize *= 2;
    		break;
    	default:
    		audioFormat = AudioFormat.ENCODING_INVALID;
    		break;
    	}
    	
    	release();
    	int bufferSize = AUDIO_BUFFER_BYTE_SIZE;
        mAudioTrack = new AudioTrackCustom(AudioManager.STREAM_MUSIC,
        		sampleRateInHz,
        		channelConfig,
        		audioFormat,
        		bufferSize,
        		AudioTrack.MODE_STREAM);
        mAudioTrack.play();
        
        // ダミーデータ書き込み
        writeDummyData(bufferSize);
    }
    
    public void startBySwf(int sampleRateInHz, int channels) {
    	startCommon();
    	mAudioSampleByteSize = 2;
    	
    	int channelConfig;
    	switch (channels) {
    	case 1:
    		channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    		mAudioSampleByteSize *= 1;
    		break;
    	case 2:
			channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
			mAudioSampleByteSize *= 2;
			break;
		default:
			channelConfig = AudioFormat.CHANNEL_CONFIGURATION_INVALID;
    		break;
    	}
    	int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    	
    	release();
//    	int bufferSize = AUDIO_BUFFER_BYTE_SIZE;
    	int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        mAudioTrack = new AudioTrackCustom(AudioManager.STREAM_MUSIC,
        		sampleRateInHz,
        		channelConfig,
        		audioFormat,
        		bufferSize,
        		AudioTrack.MODE_STREAM);
        mAudioTrack.play();
        
        // TODO ダミーデータ書き込み：どうも再生時間の調整の効果無し？
//        writeDummyData(bufferSize);
        writeDummyData(mAudioTrack.getNativeFrameCountPublic() * mAudioSampleByteSize);
//        writeDummyData(DUMMY_DATA_SIZE);
//        writeDummyData(5 * sampleRateInHz * mAudioSampleByteSize);
//        writeDummyData(1 * sampleRateInHz * mAudioSampleByteSize);
//        writeDummyData(AUDIO_BUFFER_BYTE_SIZE);
    }
    
    private void startCommon() {
    	mWrittenBufferByteSize = 0;
    	mWrittenDeviceByteSize = 0;
    }
    
    private void writeDummyData(int size) {
    	mDummyDataByteSize = size;
//        byte[] dummy = new byte[DUMMY_DATA_SIZE];
//        mAudioTrack.write(dummy, 0, DUMMY_DATA_SIZE);
        byte[] dummy = new byte[size];
        int written = mAudioTrack.write(dummy, 0, size);
        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, Log.buf().append("writeDummyData() size=")
        			.append(size).append(" written=").append(written).toString());
        }
        mAudioTrack.flush();
    }
    public boolean isInDummyData() {
		if (mAudioTrack != null) {
//			final int dummyFrame = DUMMY_DATA_SIZE / mAudioSampleByteSize;
			final int dummyFrame = mDummyDataByteSize / mAudioSampleByteSize;
			return (mAudioTrack.getPlaybackHeadPosition() < dummyFrame);
		}
		return false;
    }
    public boolean canPlay() {
    	return mAudioTrack != null;
    }
	
	public void pause() {
		if (mAudioTrack != null) {
			mLastPlaybackHeadPosition = mAudioTrack.getPlaybackHeadPosition();
			mAudioTrack.pause();
		}
    }
	public void restart() {
		if (mAudioTrack != null) {
			mAudioTrack.play();
			mAudioTrack.setPlaybackHeadPosition(mLastPlaybackHeadPosition);
		}
    }
	public void waitRealStart() {
		while (!mIsFinish) {
			if (mAudioTrack != null && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
				break;
			}
			
			try {
				Thread.sleep(10L);
			} catch (InterruptedException e) {
			}
		}

		while (!mIsFinish) {
			// TODO: とりあえずAudioTrack開始までポーリング
//			if (mAudioSampleByteSize > 0) {
//				final int dummyFrame = DUMMY_DATA_SIZE / mAudioSampleByteSize;
				final int dummyFrame = mDummyDataByteSize / mAudioSampleByteSize;
	//				final int dummyFrame = 1;
				int playbackHeadPosition = mAudioTrack.getPlaybackHeadPosition();
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, Log.buf().append("PlayerThread wait: AudioTrack: getPlaybackHeadPosition=")
							.append(playbackHeadPosition).toString());
				}
				if (playbackHeadPosition >= dummyFrame) {
					break;
				}
//			}
			
			try {
				Thread.sleep(1L);
			} catch (InterruptedException e) {
			}
		}
		
//		// TODO 適当に追加待ち
//		try {
//			Thread.sleep(5000L);
//		} catch (InterruptedException e) {
//		}
    }
	public void waitPlayEnd() {
		while (!mIsFinish) {
			AudioTrack audioTrack = mAudioTrack;
			if (audioTrack == null) {
				break;
			}
			if (audioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
				break;
			}
			try {
				Thread.sleep(33L);
			} catch (InterruptedException e) {
			}
		}
    }
	public int getAudioBufferByteSize() {
//			int audioBufferByteSize = AUDIO_BUFFER_BYTE_SIZE;
		int audioBufferByteSize = mAudioTrack.getSampleRate() * mAudioSampleByteSize;
		if (audioBufferByteSize < AUDIO_BUFFER_BYTE_SIZE) {
			audioBufferByteSize = AUDIO_BUFFER_BYTE_SIZE;
		}
		return audioBufferByteSize;
    }
	
	public int getRemainBufferByteSize() {
		if (mAudioBufferSecond == null) {
			// 終了済み
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf()
						.append("getRemainBufferByteSize(): mAudioBufferSecond is null alreday")
						.toString());
			}
			return 0;
		}
		return (mAudioBufferSecond.length - mAudioBufferSecondOffset) * 2;
	}
	
	public void writeBuffer(short[] buffer, int sizeInShorts) {
		assert sizeInShorts >= 0;
		assert sizeInShorts <= buffer.length;
		if (mAudioBufferSecond == null) {
			// 終了済み
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf()
						.append("writeBuffer(): mAudioBufferSecond is null alreday")
						.toString());
			}
			return;
		}
		if (mAudioBufferSecond.length - mAudioBufferSecondOffset < sizeInShorts) {
			// TODO 空きが足らないのでデバイスに流す→一時停止でデバイスは止まっているのにデコーダが動き続けるのが悪い？
			flushBufferToAudioTrack();
		}
		System.arraycopy(buffer, 0, mAudioBufferSecond, mAudioBufferSecondOffset, sizeInShorts);
		mAudioBufferSecondOffset += sizeInShorts;
		mWrittenBufferByteSize += sizeInShorts * 2;
	}
	public void flushBufferToAudioTrack() {
		if (mAudioBufferSecond == null) {
			// 終了済み
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf()
						.append("flushBufferToAudioTrack(): mAudioBufferSecond is null alreday")
						.toString());
			}
			return;
		}
    	int writtenSize = mAudioTrack.write(mAudioBufferSecond, 0, mAudioBufferSecondOffset);
    	if (writtenSize < 0) {
    		Log.d(LOG_TAG, Log.buf().append("AudioTrack write NG: ")
    				.append(writtenSize).toString());
    		return;
    	}
    	if (DEBUG_LOGV) {
    		Log.v(LOG_TAG, Log.buf().append("writtenSize=")
    				.append(writtenSize).toString());
    	}
		mWrittenDeviceByteSize += writtenSize * 2;
    	if (writtenSize == mAudioBufferSecondOffset) {
    		mAudioBufferSecondOffset = 0;
    	} else {
    		assert writtenSize < mAudioBufferSecondOffset;
    		System.arraycopy(mAudioBufferSecond, writtenSize,
    				mAudioBufferSecond, 0,
    				mAudioBufferSecondOffset - writtenSize);
    		mAudioBufferSecondOffset -= writtenSize;
    	}
	}
	public void flushAudioTrack() {
		mAudioTrack.flush();
    }
	
	public int getCachedSize() {
		return mAudioBufferSecondOffset;
	}
	public boolean hasEnoughCache() {
		if (mAudioBufferSecond == null) {
			// 終了済み
			if (DEBUG_LOGD) {
				Log.d(LOG_TAG, Log.buf()
						.append("hasEnoughCache(): mAudioBufferSecond is null alreday")
						.toString());
			}
			return false;
		}
		int enough = mAudioTrack.getSampleRate() * mAudioSampleByteSize * 25 / 100;
		if (enough > mAudioBufferSecond.length * 1 / 2) {
			enough = mAudioBufferSecond.length * 1 / 2;
		}
		return mAudioBufferSecondOffset > enough;
	}
	public int getAudioSampleByteSize() {
		return mAudioSampleByteSize;
	}
	public int getSampleRate() {
		if (mAudioTrack != null) {
			return mAudioTrack.getSampleRate();
		} else {
			return 0;
		}
	}
	
	public void getCurrentPosition(Rational rational) {
		if (mAudioTrack != null) {
			final int dummyFrame = mDummyDataByteSize / mAudioSampleByteSize;
			int pos = (mAudioTrack.getPlaybackHeadPosition() - dummyFrame);
			if (pos >= 0) {
				rational.num = pos;
			} else {
				rational.num = 0;
			}
			rational.den = mAudioTrack.getSampleRate();
		} else {
			rational.num = 0;
			rational.den = 1;
		}
	}
	
	public int getRemainDataSizeOnDeviceMs() {
		if (mAudioTrack != null) {
			final int dummyFrame = mDummyDataByteSize / mAudioSampleByteSize;
			int currentFrame = (mAudioTrack.getPlaybackHeadPosition() - dummyFrame);
			int remainFrame = mWrittenDeviceByteSize / mAudioSampleByteSize - currentFrame;
			assert remainFrame >= 0;
			return (int) ((long) remainFrame * 1000L / mAudioTrack.getSampleRate());
		} else {
			return 0;
		}
	}
	
	public void clearBuffer() {
    	mAudioBufferSecondOffset = 0;
		if (mAudioTrack != null) {
	    	final int lastState = mAudioTrack.getPlayState();
	    	mAudioTrack.stop();
	    	mAudioTrack.play();
	    	if (lastState == AudioTrack.PLAYSTATE_PAUSED) {
	    		mAudioTrack.pause();
	    	}
		}
	}
}
