package mara.mybox.tools;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.Port;
import javax.sound.sampled.SourceDataLine;
import javazoom.jl.player.Player;
import static mara.mybox.value.AppVariables.logger;
import mara.mybox.value.CommonValues;

/**
 * @Author Mara
 * @CreateDate 2018-7-12
 * @Description
 * @License Apache License Version 2.0
 */
public class SoundTools {

    public static synchronized Clip playback(String name, float addVolume) {
        File file = new File(name);
        if (file.isFile() && file.exists()) {
            return playback(file, addVolume);
        }
        try {
            URL url = new URL(name);
            return playback(url, addVolume);
        } catch (Exception e) {
            return null;
        }

    }

    public static synchronized Clip playback(File file, float addVolume) {
        try {
            try ( AudioInputStream in = AudioSystem.getAudioInputStream(file)) {
                return playback(in, addVolume);
            }
        } catch (Exception e) {
            logger.debug(e.toString());
            return null;
        }
    }

    public static synchronized Clip playback(URL url, float addVolume) {
        try {
            try ( AudioInputStream in = AudioSystem.getAudioInputStream(url)) {
                return playback(in, addVolume);
            }
        } catch (Exception e) {
            logger.debug(e.toString());
            return null;
        }
    }

    // This can work for both mp3 and wav
    public static synchronized Clip playback(AudioInputStream in, float addVolume) {
        try {
            AudioFormat baseFormat = in.getFormat();
            AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                    baseFormat.getSampleRate(),
                    16,
                    baseFormat.getChannels(),
                    baseFormat.getChannels() * 2,
                    baseFormat.getSampleRate(),
                    false);
            AudioInputStream ain = AudioSystem.getAudioInputStream(decodedFormat, in);
            DataLine.Info info = new DataLine.Info(Clip.class, decodedFormat);

            // https://stackoverflow.com/questions/25564980/java-use-a-clip-and-a-try-with-resources-block-which-results-with-no-sound
            final Clip clip = (Clip) AudioSystem.getLine(info);
            clip.open(ain);
            // Input stream must be closed, or else some thread is still running when application is exited.
            in.close();
            ain.close();
            FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
            gainControl.setValue(addVolume); // Add volume by decibels.
            return clip;

        } catch (Exception e) {
            logger.debug(e.toString());
            return null;
        }
    }

    public static FloatControl getControl(File file) {
        try {
            try ( AudioInputStream in = AudioSystem.getAudioInputStream(file)) {
                return getControl(in);
            }
        } catch (Exception e) {
            logger.debug(e.toString());
            return null;
        }
    }

    public static FloatControl getControl(URL url) {
        try {
            try ( AudioInputStream in = AudioSystem.getAudioInputStream(url)) {
                return getControl(in);
            }
        } catch (Exception e) {
            logger.debug(e.toString());
            return null;
        }
    }

    public static FloatControl getControl(AudioInputStream in) {
        try {

            AudioFormat baseFormat = in.getFormat();
            AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                    baseFormat.getSampleRate(),
                    16,
                    baseFormat.getChannels(),
                    baseFormat.getChannels() * 2,
                    baseFormat.getSampleRate(),
                    false);
            AudioInputStream ain = AudioSystem.getAudioInputStream(decodedFormat, in);
            DataLine.Info info = new DataLine.Info(Clip.class, decodedFormat);
            try ( Clip clip = (Clip) AudioSystem.getLine(info)) {
                clip.open(ain);
                FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
                return gainControl;
            }
        } catch (Exception e) {
            logger.debug(e.toString());
            return null;
        }
    }

    public static void playBigAudio(File file) {

        try {
            if (file.getName().endsWith(".mp3")) {
                getMp3Player(file);
                return;
            }

            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
            AudioFormat aif = audioInputStream.getFormat();
            //System.out.println(aif);
            final SourceDataLine sdl;
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, aif);
            sdl = (SourceDataLine) AudioSystem.getLine(info);
            if (!AudioSystem.isLineSupported(info)) {
                System.out.print("no support for " + aif.toString());
            }

            FloatControl fc = (FloatControl) sdl.getControl(FloatControl.Type.MASTER_GAIN);
            //value可以用来设置音量,从0-2.0
            double value = 2;
            float dB = (float) (Math.log(value == 0.0 ? 0.0001 : value) / Math.log(10.0) * 20.0);
            fc.setValue(dB);
            int nByte = 0;
            byte[] buffer = new byte[CommonValues.IOBufferLength];
            while (nByte != -1) {
                nByte = audioInputStream.read(buffer, 0, CommonValues.IOBufferLength);
                sdl.write(buffer, 0, nByte);
            }
            sdl.stop();
        } catch (Exception e) {

        }
    }

    public static Clip playSmallAudio(File file) {

        try {

            AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
            AudioFormat aif = audioInputStream.getFormat();
            //System.out.println(aif);
            DataLine.Info info = new DataLine.Info(Clip.class, aif);
            try ( Clip clip = (Clip) AudioSystem.getLine(info)) {
                clip.addLineListener(new LineListener() {
                    @Override
                    public void update(LineEvent e) {
                        if (e.getType() == LineEvent.Type.STOP) {
                            synchronized (clip) {
                                clip.notify();
                            }
                        }
                    }
                });
                clip.open(audioInputStream);

                FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
                gainControl.getValue();
                double value = 2;
                float dB = (float) (Math.log(value == 0.0 ? 0.0001 : value) / Math.log(10.0) * 20.0);
                gainControl.setValue(dB);
                gainControl.setValue(-10.0f); // Reduce volume by 10 decibels.
                clip.start();
                synchronized (clip) {
                    clip.wait();
                }
                return clip;
            }
        } catch (Exception e) {
            return null;
        }
    }

    public static Player getMp3Player(File file) {
        try {
            BufferedInputStream buffer = new BufferedInputStream(new FileInputStream(file));
            Player player = new Player(buffer);
//            player.play();
            return player;
        } catch (Exception e) {
            return null;
        }
    }

    // http://www.javazoom.net/mp3spi/documents.html
    public static void testPlay(String filename) {
        try {
            File file = new File(filename);
            AudioInputStream in = AudioSystem.getAudioInputStream(file);
            AudioInputStream din = null;
            AudioFormat baseFormat = in.getFormat();
            AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                    baseFormat.getSampleRate(),
                    16,
                    baseFormat.getChannels(),
                    baseFormat.getChannels() * 2,
                    baseFormat.getSampleRate(),
                    false);
            din = AudioSystem.getAudioInputStream(decodedFormat, in);
            // Play now.
            rawplay(decodedFormat, din);
            in.close();
        } catch (Exception e) {
            //Handle exception.
        }
    }

    public static void rawplay(AudioFormat targetFormat, AudioInputStream din)
            throws IOException, LineUnavailableException {
        byte[] data = new byte[CommonValues.IOBufferLength];
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, targetFormat);
        SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
        line.open(targetFormat);

        if (line != null) {
            // Start
            FloatControl vol = (FloatControl) line.getControl(FloatControl.Type.MASTER_GAIN);
            logger.debug(vol.getValue() + vol.getUnits());
            line.start();
            int nBytesRead = 0, nBytesWritten = 0;
            while (nBytesRead != -1) {
                nBytesRead = din.read(data, 0, data.length);
                if (nBytesRead != -1) {
                    nBytesWritten = line.write(data, 0, nBytesRead);
                }
            }
            // Stop
            line.drain();
            line.stop();
            line.close();
            din.close();
        }
    }

    public static SourceDataLine getLine(AudioFormat audioFormat) throws
            LineUnavailableException {
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
        SourceDataLine res = (SourceDataLine) AudioSystem.getLine(info);
        res.open(audioFormat);
        return res;
    }

    public void setGain(float ctrl) {
        try {
            Mixer.Info[] infos = AudioSystem.getMixerInfo();
            for (Mixer.Info info : infos) {
                Mixer mixer = AudioSystem.getMixer(info);
                if (mixer.isLineSupported(Port.Info.SPEAKER)) {
                    try ( Port port = (Port) mixer.getLine(Port.Info.SPEAKER)) {
                        port.open();
                        if (port.isControlSupported(FloatControl.Type.VOLUME)) {
                            FloatControl volume = (FloatControl) port.getControl(FloatControl.Type.VOLUME);
                            volume.setValue(ctrl);
                        }
                    }
                }
            }
        } catch (Exception e) {

        }
    }

}