package Audio;

import java.util.ArrayList;
import java.util.List;

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

import Audio.Bass.BASS_DEVICEINFO;
import Audio.Bass.RECORDPROC;
import lombok.Getter;

@SuppressWarnings("unused")
public abstract class basicfunctions {
	
	abstract public boolean FileConvert(String sourcefilename, int targetsamplingrate, String targetfilename);
	abstract public int OpenFileForStreaming(int playdevice, String sourcefilename, int targetsamplingrate);
	abstract public int OpenInputForStreaming(int recdevice, int targetsamplingrate);
	//used in BASS_Init
	protected final int devinit_freq;
	protected final int devinit_flag;
	private final Bass BASS = Bass.Instance;
	private final BassMix BASSMIX = BassMix.Instance;
		
	public basicfunctions(int init_freq, int init_flag) {
		this.devinit_freq = init_freq;
		this.devinit_flag = init_flag;
	}
	
	/**
	 * Stop/Close Input For Streaming
	 * @param rechandle Recording Handle
	 * @return true if Input can be stopped
	 */
	public boolean CloseInputForStreaming(int rechandle) {
		return BASS.BASS_ChannelFree(rechandle);
	}
	
	/**
	 * BASS_Init dengan devinit_freq dan devinit_flag
	 * @param device playback device start from 0=no sound, 1=first real device
	 * @return true if playback device inited or already inited
	 */
	private boolean BassInit(int device) {
		if (!BASS.BASS_Init(device, devinit_freq, devinit_flag)) {
			// ada gagal init, cek dulu
			int err = BASS.BASS_ErrorGetCode();
            // gak boleh gagal
            //raise_log(BASS.GetBassError("BASS_Init", err));
            return err == Bass.BASS_ERROR_ALREADY;
		} 
		// sampe sini sukses
		return true;
	}
	
	
	/**
	 * BASS_RecordInit
	 * @param device recorder device, start from 0=first real device
	 * @return true if recording device inited or already inited
	 */
	private boolean RecordInit(int device) {
		if (!BASS.BASS_RecordInit(device)) {
			// ada gagal init, cek dulu
			int err = BASS.BASS_ErrorGetCode();
            // gak boleh gagal
            //raise_log(BASS.GetBassError("BASS_RecordInit", err));
            return err == Bass.BASS_ERROR_ALREADY;
		}
		return true;
	}
	
	/**
	 * OpenInput helper
	 * @author rdkartono
	 *
	 */
	protected class OpenInput{
		@Getter private final int recorderdev;
		@Getter private final int samplingrate;
		@Getter private boolean success = false;
		@Getter private final String[] errorlist;
		@Getter private int recordhandle=0;
		private final RECORDPROC recproc;
		private @Getter BASS_DEVICEINFO devinfo;
		
		/**
		 * Open Input 
		 * @param dev 0 = real device
		 * @param samplingrate sampling rate
		 * @param proc RECORDPROC to receive PCM data
		 */
		public OpenInput(int dev, int samplingrate, RECORDPROC proc) {
			this.recorderdev = dev;
			this.samplingrate = samplingrate;
			this.recproc = proc;
			errorlist = OpenSequence();
			
		}
		
		private String[] OpenSequence() {
			
			List<String> error = new ArrayList<>();
			if (RecordInit(recorderdev)) {
				devinfo = new BASS_DEVICEINFO();
				if (!BASS.BASS_RecordGetDeviceInfo(recorderdev, devinfo)) devinfo = null;
				Pointer user = null;
				if (devinfo!=null) {
					user = new Memory(devinfo.name.length()+1);
					user.setString(0, devinfo.name);
				}
				
				recordhandle = BASS.BASS_RecordStart(samplingrate, 1, Bass.BASS_RECORD_PAUSE, recproc, user);
				if (recordhandle!=0) {
					success = true;
				} else error.add(BASS.GetBassError("BASS_RecordStart"));
			} else error.add(BASS.GetBassError("BASS_RecordInit"));
			
			return error.toArray(new String[0]);
		}
		
		public Pointer PointerToDeviceName() {
			if (devinfo==null)
				return null;
			else {
				Pointer pp = new Memory(devinfo.name.length()+1);
				pp.setString(0, devinfo.name);
				return pp;
			}
		}
		
		public String getDeviceName() {
			return devinfo==null ? "" : devinfo.name;
		}
		
		/**
		 * Start Recording
		 * @return error messages if available
		 */
		public String[] StartRecord() {
			List<String> error = new ArrayList<>();
			if (recordhandle!=0) {
				if (BASS.BASS_ChannelStart(recordhandle)) {
					// nothing...
				} else error.add(BASS.GetBassError("BASS_ChannelStart"));
			} else error.add("RecordHandle is zero");
			
			return error.toArray(new String[0]);
		}
		
		/**
		 * Stop Recording
		 * @return error messages if available
		 */
		public String[] StopRecord() {
			List<String> error = new ArrayList<>();
			if (recordhandle!=0) {
				if (BASS.BASS_ChannelFree(recordhandle)) {
					// nothing...
				} else error.add(BASS.GetBassError("BASS_ChannelStop"));
				recordhandle = 0;
			} else error.add("RecordHandle is zero");
			
			return error.toArray(new String[0]);
		}
	}
	
	/**
	 * OpenFile helper
	 * @author rdkartono
	 *
	 */
    @Getter
	protected class OpenFile{
		private final int playbackdev;
		private final int samplingrate;
		private final String filename;
		private final boolean decodingonly;
		private boolean success = false;
		private int filehandle;
		private int mixerhandle;
		private int mixerflag;
		private final String[] errorlist;
		private long ByteSize;
		private double Duration;
		private BASS_DEVICEINFO devinfo;
		
		/**
		 * Create OpenFile object
		 * @param filename source file name
		 * @param playbackdev 0 =no sound, 1 = real device
		 * @param samplingrate sampling rate
		 * @param decodingonly if true, mixer will have Decoded flag
		 */
		public OpenFile(String filename, int playbackdev, int samplingrate, boolean decodingonly) {
			this.playbackdev = playbackdev;
			this.samplingrate = samplingrate;
			this.filename = filename;
			this.decodingonly = decodingonly;
			this.errorlist = OpenSequence();
			if (success) {
				ByteSize = BASS.BASS_ChannelGetLength(filehandle, Bass.BASS_POS_BYTE);
				Duration = BASS.BASS_ChannelBytes2Seconds(filehandle, ByteSize);
				
			}
			devinfo = new BASS_DEVICEINFO();
			if (!BASS.BASS_GetDeviceInfo(playbackdev, devinfo)) {
				devinfo = null;
			} 
	
		}
		
		public Pointer PointerToDeviceName() {
			if (devinfo==null)
				return null;
			else {
				Pointer pp = new Memory(devinfo.name.length()+1);
				pp.setString(0, devinfo.name);
				return pp;
			}
		}
		
		public String getDeviceName() {
			return devinfo==null ? "" : devinfo.name;
		}
		
		/**
		 * Create OpenFile Object
		 * @param filename source file anme
		 * @param playbackdev 0 = no sound, 1 = real device
		 * @param samplingrate samplingrate
		 */
		public OpenFile(String filename, int playbackdev, int samplingrate) {
			this(filename, playbackdev, samplingrate, false);
		}
		
		private String[] OpenSequence() {
			
			List<String> error = new ArrayList<>();
			
			if (BassInit(playbackdev)) {
				
				filehandle = BASS.BASS_StreamCreateFile(false,filename, 0, 0, Bass.BASS_STREAM_DECODE);
				if (filehandle!=0) {
					mixerflag = decodingonly ? Bass.BASS_STREAM_DECODE : 0;
					
					mixerhandle = BASSMIX.BASS_Mixer_StreamCreate(samplingrate, 1, mixerflag);
					if (mixerhandle!=0) {
						if (BASSMIX.BASS_Mixer_StreamAddChannel(mixerhandle, filehandle, Bass.BASS_STREAM_AUTOFREE)) {
							success = true;
						} else error.add(BASS.GetBassError("BASS_Mixer_StreamAddChannel"));
					} else error.add(BASS.GetBassError("BASS_Mixer_StreamCreate"));
				} else error.add(BASS.GetBassError("BASS_StreamCreateFile"));
			} else error.add(BASS.GetBassError("Bass_Init"));
			
			return error.toArray(new String[0]);
		}
		
		/**
		 * Start Playback
		 * @return true if it can be started
		 */
		public boolean StartPlayback() {
			if (filehandle!=0) {
				// file sudah kebuka
				if (mixerhandle!=0) {
					// mixer sudah kebuka
					if (success) {
						// sudah plugged in 
						if (!decodingonly) {
							// bukan channel decoding
							return BASS.BASS_ChannelStart(filehandle);
						}
					}
					
				}
			}
			return false;
		}
		
		/**
		 * Close Opened handles
		 * @return array of string containing error messages
		 */
		public String[] CloseHandles() {
			List<String> error = new ArrayList<>();
			
			if (filehandle!=0) {
				if (success) {
					// sudah plug ke mixer
					if (!BASSMIX.BASS_Mixer_ChannelRemove(filehandle)) {
						error.add(BASS.GetBassError("BASS_Mixer_ChannelRemove"));
					}
				}
				if (!BASS.BASS_ChannelFree(filehandle)) {
					error.add(BASS.GetBassError("BASS_ChannelFree"));
				}
				
				filehandle = 0;
			}
			if (mixerhandle!=0) {
				if (!BASS.BASS_ChannelFree(mixerhandle)) {
					error.add(BASS.GetBassError("BASS_ChannelFree"));
				}
				mixerhandle = 0;
			}
			
			return error.toArray(new String[0]);
		}
	}
}
