package Audio;



import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import peers.media.AbstractSoundManager;

/**
 * Pengganti /AxisAudio/src/net/sourceforge/peers/javaxsound/JavaxSoundManager.java 
 * karena terbatas codec nya
 * @author rdkartono
 *
 */
@SuppressWarnings({"unused", "FieldCanBeLocal", "BusyWait", "ConditionalBreakInInfiniteLoop"})
public class BassSoundManager extends AbstractSoundManager {
	private final Bass BASS = Bass.Instance;
	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 = Bass.BASS_DEVICE_16BITS | Bass.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 (!BASS.BASS_RecordSetInput(record_device, Bass.BASS_INPUT_ON, value/100.0f)) {
			// gagal 
			logger_error("BASS_RecordSetInput error = "+BASS.GetBassError("BASS_RecordSetInput"));
		}
	}
	
	public int getInputChannelVolume() {
		Bass.FloatValue vol = new Bass.FloatValue();
		int code = BASS.BASS_RecordGetInput(record_device, vol);
		if (code==-1) {
			logger_error("BASS_RecordGetInput error = "+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 (!BASS.BASS_SetVolume(value/100.0f)) {
			logger_error("BASS_SetVolume error = "+BASS.GetBassError("BASS_SetVolume"));
		}
	}
	
	public int getOutputChannelVolume() {
		float fl = BASS.BASS_GetVolume() ;
		if (fl==-1) {
			logger_error("BASS_GetVolume error = "+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;
			Pointer buf = new Memory(read_size);
			while(true) {
				if (BASS.BASS_ChannelIsActive(recordhandle)!=Bass.BASS_ACTIVE_PLAYING) break;
				rs = BASS.BASS_ChannelGetData(recordhandle, buf, read_size);
				if (rs<0) {
					// ada error
					if (bsml!=null) bsml.log_error("readData BASS_ChannelGetData error = "+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);
					} catch (InterruptedException e) {
						return null;
					}
					
				}
			}
			
		} 
		return null;
		
	}
	
	
	
	

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

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

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

			if (IsInitialized()) {
				logger_info("init complete");
				
				new Thread(() -> {
                    int inputvalue;
                    int outputvalue;

                    while(IsInitialized()) {
                        inputvalue = BASS.BASS_ChannelGetLevel(recordhandle);
                        outputvalue = BASS.BASS_ChannelGetLevel(streamhandle);
                        if (inputvalue>=0) {
                            inputvalue = (int) ((Bass.Utils.LOWORD(inputvalue) * 1.0f)/32768);
                            if (bsml!=null) bsml.ChannelLevel(true, inputvalue);
                        }
                        if (outputvalue>=0) {
                            outputvalue = (int) ((Bass.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) {
			BASS.BASS_StreamFree(streamhandle);
			streamhandle = 0;
			somethingclosed = true;
		}
		if (recordhandle!=0) {
			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 = BASS.BASS_StreamPutData(streamhandle, buf, length);
			if (rs==-1) {
				if (bsml!=null) bsml.log_error("writeData BASS_StreamPutData error = "+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);
		
	}
	
	Bass.SYNCPROC devfail = new Bass.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");
			}
		}
	};
	
	Bass.SYNCPROC bufferinginformation = new Bass.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"));
			}
		}
		
	};
}
