package code;



import com.sun.jna.Memory;
import com.sun.jna.Pointer;

import bass.BassLibrary;
import bass.BassLibrary.FloatValue;
import bass.BassLibrary.SYNCPROC;
import net.sourceforge.peers.media.AbstractSoundManager;

/**
 * Pengganti /AxisAudio/src/net/sourceforge/peers/javaxsound/JavaxSoundManager.java 
 * karena terbatas codec nya
 * @author rdkartono
 *
 */
public class BassSoundManager extends AbstractSoundManager {

	private BassSoundManagerListener bsml;
	private final int record_device, play_device;
	private final int init_freq = 48000;
	private final int samplingrate = 8000;
	private final int play_initflag = BassLibrary.Constant.BASS_DEVICE_16BITS | BassLibrary.Constant.BASS_DEVICE_MONO;
	private final int streamcreate_flag = 0;
	private final int recordstart_flag = 0;
	private final int read_size = 320;
	
	private int streamhandle, recordhandle;
	
	
	public BassSoundManager(int outputdevice , int inputdevice, BassSoundManagerListener listener) {
		this.bsml = listener;	
		this.record_device = inputdevice;
		this.play_device = outputdevice;
	}
	
	public void AddListener(BassSoundManagerListener listener) {
		this.bsml = listener;
	}
	
	public boolean IsInitialized() {
		return (streamhandle!=0) && (recordhandle!=0);
	}
	
	public void setInputChannelVolume(int value) {
		if (value<0) value = 0;
		if (value>100) value = 100;
		
		if (!BassLibrary.BASS.BASS_RecordSetInput(record_device, BassLibrary.Constant.BASS_INPUT_ON, value/100.0f)) {
			// gagal 
			logger_error("BASS_RecordSetInput error = "+BassLibrary.BASS.GetBassError("BASS_RecordSetInput"));
		}
	}
	
	public int getInputChannelVolume() {
		FloatValue vol = new FloatValue();
		int code = BassLibrary.BASS.BASS_RecordGetInput(record_device, vol);
		if (code==-1) {
			logger_error("BASS_RecordGetInput error = "+BassLibrary.BASS.GetBassError("BASS_RecordGetInput"));
			return 0;
		} else {
			int value = (int)(vol.value * 100);
			if (value<0) value = 0;
			if (value>100) value = 100;
			return value;
		}
	}
	
	public void setOutputChannelVolume(int value) {
		if (value<0) value = 0;
		if (value>100) value = 100;
		if (!BassLibrary.BASS.BASS_SetVolume(value/100.0f)) {
			logger_error("BASS_SetVolume error = "+BassLibrary.BASS.GetBassError("BASS_SetVolume"));
		}
	}
	
	public int getOutputChannelVolume() {
		float fl = BassLibrary.BASS.BASS_GetVolume() ;
		if (fl==-1) {
			logger_error("BASS_GetVolume error = "+BassLibrary.BASS.GetBassError("BASS_GetVolume"));
			return 0;
		} else {
			int value = (int)(fl * 100);
			if (value<0) value = 0;
			if (value>100) value = 100;
			return value;
		}
		
	}
	
	@Override
	public byte[] readData() {
		if (recordhandle!=0) {
			int rs = 0;
			Pointer buf = new Memory(read_size);
			while(true) {
				if (BassLibrary.BASS.BASS_ChannelIsActive(recordhandle)!=BassLibrary.Constant.BASS_ACTIVE_PLAYING) break;
				rs = BassLibrary.BASS.BASS_ChannelGetData(recordhandle, buf, read_size);
				if (rs<0) {
					// ada error
					if (bsml!=null) bsml.log_error("readData BASS_ChannelGetData error = "+BassLibrary.BASS.GetBassError("BASS_ChannelGetData"));
					return null;
				} else if (rs>0) {
					// ada hasil
					byte[] result = buf.getByteArray(0, rs);
					if (bsml!=null) bsml.ReadToRTP(rs, result);
					return result;
				} else {
					// nol
					try {
						Thread.sleep(2);
						continue;
					} catch (InterruptedException e) {
						return null;
					}
					
				}
			}
			
		} 
		return null;
		
	}
	
	
	
	

	@Override
	public void init() {
		if (streamhandle!=0 || recordhandle!=0) close();

		if (BassLibrary.BASS.BASS_GetVersion()!=0) {
			BassLibrary.BASS.BASS_Init(play_device, init_freq, play_initflag);
			BassLibrary.BASS.BASS_RecordInit(record_device);
			
			recordhandle = BassLibrary.BASS.BASS_RecordStart(samplingrate, 1, recordstart_flag,null, null);
			if (recordhandle!=0) {
				BassLibrary.BASS.BASS_ChannelSetSync(recordhandle, BassLibrary.Constant.BASS_SYNC_DEV_FAIL, 0, devfail, null);
				BassLibrary.BASS.BASS_ChannelSetSync(recordhandle, BassLibrary.Constant.BASS_SYNC_STALL, 0, bufferinginformation, null);
				
				if (BassLibrary.BASS.BASS_ChannelStart(recordhandle)) {
					logger_info("InputChannel Started");
				} else logger_error("BASS_ChannelStart recordhandle error = "+BassLibrary.BASS.GetBassError("BASS_ChannelStart"));
			} else logger_error("BASS_RecordStart error = "+BassLibrary.BASS.GetBassError("BASS_RecordStart"));

			
			streamhandle = BassLibrary.BASS.BASS_StreamCreate(samplingrate, 1, streamcreate_flag, BassLibrary.Constant.STREAMPROC_PUSH, null);
			if (streamhandle!=0) {
				BassLibrary.BASS.BASS_ChannelSetSync(streamhandle, BassLibrary.Constant.BASS_SYNC_DEV_FAIL, 0, devfail, null);
				BassLibrary.BASS.BASS_ChannelSetSync(streamhandle, BassLibrary.Constant.BASS_SYNC_STALL, 0, bufferinginformation, null);
				
				if (BassLibrary.BASS.BASS_ChannelStart(streamhandle)) {
					logger_info("OutputChannel Started");
				} else logger_error("BASS_ChannelStart streamhandle error = "+BassLibrary.BASS.GetBassError("BASS_ChannelStart"));
			} else logger_error("BASS_StreamCreate error = "+BassLibrary.BASS.GetBassError("BASS_StreamCreate"));

			if (IsInitialized()) {
				logger_info("init complete");
				
				new Thread() {
					public void run() {
						int inputvalue = 0;
						int outputvalue = 0;
						
						while(IsInitialized()) {
							inputvalue = BassLibrary.BASS.BASS_ChannelGetLevel(recordhandle);
							outputvalue = BassLibrary.BASS.BASS_ChannelGetLevel(streamhandle);
							if (inputvalue>=0) {
								inputvalue = (int) ((BassLibrary.Utils.LOWORD(inputvalue) * 1.0f)/32768); 
								if (bsml!=null) bsml.ChannelLevel(true, inputvalue);
							}
							if (outputvalue>=0) {
								outputvalue = (int) ((BassLibrary.Utils.LOWORD(outputvalue) * 1.0f)/32768);
								if (bsml!=null) bsml.ChannelLevel(false, outputvalue);
							}
							try {
								Thread.sleep(500);
							} catch (InterruptedException e) {
								logger_info("ChannelLevel thread interrupted");
							}
						}
					}
				}.start();
				
				if (bsml!=null) bsml.Opened();
				return;
			}
			
			logger_info("init failed");
			close();
			
		}

	}

	@Override
	public void close() {
		boolean somethingclosed = false;
		if (streamhandle!=0) {
			BassLibrary.BASS.BASS_StreamFree(streamhandle);
			streamhandle = 0;
			somethingclosed = true;
		}
		if (recordhandle!=0) {
			BassLibrary.BASS.BASS_ChannelStop(recordhandle);
			recordhandle = 0;
			somethingclosed = true;
		}
		
		if (somethingclosed) {
			if (bsml!=null) bsml.Closed();
		}
		
	}

	@Override
	public int writeData(byte[] buffer, int offset, int length) {
		if (streamhandle!=0) {
			Pointer buf = new Memory(length);
			buf.write(0, buffer, 0, length);
			int rs = BassLibrary.BASS.BASS_StreamPutData(streamhandle, buf, length);
			if (rs==-1) {
				if (bsml!=null) bsml.log_error("writeData BASS_StreamPutData error = "+BassLibrary.BASS.GetBassError("BASS_StreamPutData"));
			} else {
				
				if (bsml!=null) bsml.WriteFromRTP(length, buffer);
				return length;
			}
			
		} 
		return 0;
	}

	private void logger_error(String msg) {
		if (bsml!=null) bsml.log_error(msg);
		
	}
	
	private void logger_info(String msg) {
		if (bsml!=null) bsml.log_info(msg);
		
	}
	
	SYNCPROC devfail = new SYNCPROC() {

		@Override
		public void SYNCPROC(int handle, int channel, int data, Pointer user) {
			if (channel==streamhandle) {
				if (bsml!=null) bsml.DeviceFailure("OutputChannel stops unexpectedly");
			} else if (channel==recordhandle) {
				if (bsml!=null) bsml.DeviceFailure("InputChannel stops unexpectedly");
			}
		}
	};
	
	SYNCPROC bufferinginformation = new SYNCPROC() {

		@Override
		public void SYNCPROC(int handle, int channel, int data, Pointer user) {
			if (channel==streamhandle) {
				if (bsml!=null) bsml.BufferingInformation("OutputChannel "+ (data==0?"Stalled":"Resumed"));
			} else if (channel==recordhandle) {
				if (bsml!=null) bsml.BufferingInformation("InputChannel "+ (data==0?"Stalled":"Resumed"));
			}
		}
		
	};
}
