package fr.Skydust.JdrBot.stock;

import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import fr.Skydust.JdrBot.enums.MessageType;
import fr.Skydust.JdrBot.utils.Utils;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.utils.FileUpload;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.CompressionMethod;
import ws.schild.jave.Encoder;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;

public class RecordState {
	public boolean isRecording;
	private ByteArrayOutputStream recordData;
	public boolean isProcessing;

	public void addBytes(byte[] bytes) {
		try {
			this.recordData.write(bytes);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public ByteArrayOutputStream getRecordData() {
		return recordData;
	}

	public void newRecord() {
		recordData = new ByteArrayOutputStream();
	}

	public void cancelRecord(Guild g) {
		isRecording = false;

		// Called from cancelRecord that checks if "isRecording" so it can't be processing
		try {
			if (recordData != null) {
				recordData.close();
				recordData = null;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void endRecord(Guild g, TextChannel tc) {
		isRecording = false;
		isProcessing = true;
		
		System.out.println("["+new Date()+"] Le serveur ID "+g.getId()+"("+g.getName()+") vient de terminer un enregistrement");

		new Thread(() -> {
			try {
				processRecording(tc);
			} catch (Exception e1) {
				e1.printStackTrace();
				System.out.println("["+new Date()+"] Une erreur est survenue pendant l'enregistrement sur le serveur ID "+g.getId()+"("+g.getName()+")");

				Utils.sendMessage(tc, MessageType.ERROR, "Une erreur est survenue pendant l'enregistrement.");

				try {
					recordData.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			isProcessing = false;
		}).start();
	}

    public void processRecording(TextChannel tc) throws IOException, IllegalArgumentException, ws.schild.jave.EncoderException {
		//BigEndian, 16 bit signed, 44100hz
		String OriginalName = new Date().toString().replaceAll(":", "-");

        File wavFile = new File(OriginalName+".wav");
        File mp3File = new File(OriginalName+".mp3");

        //PCM to Wave
		rawToWave(recordData, wavFile);

		recordData.close();
		recordData = null;
        
        //Wave to MP3
        Encoder encoder = new Encoder();
        AudioAttributes audio   = new AudioAttributes();
        audio.setCodec("libmp3lame");
        audio.setBitRate(128000);
        audio.setChannels(2);
        EncodingAttributes ea = new EncodingAttributes();
        ea.setAudioAttributes(audio);
        ea.setOutputFormat("mp3");

		encoder.encode(new MultimediaObject(wavFile),mp3File,ea);
		wavFile.delete();
        
        //MP3 to Zip
        ZipParameters parameters = new ZipParameters();
        parameters.setCompressionMethod(CompressionMethod.DEFLATE);
        parameters.setCompressionLevel(CompressionLevel.NORMAL);
        ZipFile zipFile;
		zipFile = new ZipFile(OriginalName+".zip");

		List<File> filesToAdd = new ArrayList<File>();
		filesToAdd.add(mp3File);

		zipFile.createSplitZipFile(filesToAdd, parameters, true, 8283750);
		mp3File.delete();

		//Send Files through Discord
		if(zipFile.getSplitZipFiles().size() > 1) {
			tc.sendMessage("Envoi du fichier audio(Via plusieurs fichiers zip)...").queue();
			for(Object file : zipFile.getSplitZipFiles()) {
				File currentFile = new File(file+"");
				FileUpload fileUpload = FileUpload.fromData(currentFile, currentFile.getName());
				tc.sendFiles(fileUpload).queue(msg -> currentFile.delete());
			}
		} else {
			tc.sendMessage("Envoi du fichier audio(Via un fichier zip)...").queue();
			File currentFile = new File(zipFile.getSplitZipFiles().get(0)+"");
			FileUpload fileUpload = FileUpload.fromData(currentFile, currentFile.getName());
			tc.sendFiles(fileUpload).queue(msg -> currentFile.delete());
		}
    }

	/**
	 * Defines a default audio format used to record
	 */
	private AudioFormat getAudioFormat() {
		float sampleRate = 48000;
		int sampleSizeInBits = 16;
		int channels = 2;
		boolean signed = true;
		boolean bigEndian = true;
		return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
	}

	public void rawToWave(ByteArrayOutputStream input, File wavFile) throws IOException {
		// Transform output to input stream with pipes
		PipedInputStream pis = new PipedInputStream();
		final PipedOutputStream out = new PipedOutputStream(pis);
		new Thread(() -> {
			try {
				input.writeTo(out);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}).start();

		AudioFormat format = getAudioFormat();

		AudioInputStream audioInputStream = new AudioInputStream(pis, format, input.size() / format.getFrameSize());

		AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, wavFile);

		audioInputStream.close();
	}
}
