package at.oefai.aaa.animation;

import java.io.File;
import java.io.IOException;

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.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

/**
 * A thread for loading and playing an audio file, adapted from a sun tutorial (i think).
 * @author Stefan Rank
 */
public class AudioThread /*extends Thread*/ {
    private static final int EXTERNAL_BUFFER_SIZE = 128000;
    private final File file;
    // for loop playing
    private Clip clip = null;
    // for one shot playing (works better)
    private AudioInputStream audioInputStream = null;
    private SourceDataLine line = null;

    public AudioThread(final File pFile) {
        //super("AudioThread-" + file.getName());
        this.file = pFile;
    }




    public final void playAsynchronousLoops(final int loopCount) {
        if (this.clip == null) {
            this.createClip();
        }
        //if (! _clip.isActive() && ! _clip.isRunning()) {
            this.clip.loop(loopCount);
        //}
    }

    public final void playSynchronous() {
        this.run();
    }

    public final void playOnce() {
        this.run();
        if (this.line != null) {
            this.line.drain();
        }
        stopPlaying();
    }

    public final void stopPlaying() {
        if (this.clip != null) {
            this.clip.stop();
            this.clip.close();
            this.clip = null;
        }
        if (this.line != null) {
            // All data played. We can close the shop.
            this.line.close();
            this.line = null;
        }
        if (this.audioInputStream != null) {
            try {
                this.audioInputStream.close();
            } catch (IOException ex) { //dont care
            }
            this.audioInputStream = null;
        }
    }


    private void setupStreamAndLine() throws UnsupportedAudioFileException, IOException, LineUnavailableException {
        // We have to read in the sound file.
        this.audioInputStream = AudioSystem.getAudioInputStream(this.file);
        if (this.audioInputStream.markSupported()) {
            this.audioInputStream.mark(Integer.MAX_VALUE);
        }
        // format information  for a suitable output line for this audio file.
        AudioFormat audioFormat = this.audioInputStream.getFormat();
        // info object for getting a line
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
        if (this.line != null) {
            this.line.close();
        }
        this.line = (SourceDataLine) AudioSystem.getLine(info);
        this.line.open(audioFormat);
        this.line.start();
    }


    private void run() {
        // attention: following works only because of fast boolean evaluation
        if (this.audioInputStream == null || ! this.audioInputStream.markSupported()) {
            try {
                setupStreamAndLine();
            } catch (IOException ex) {
                ex.printStackTrace();
                return;
            } catch (UnsupportedAudioFileException e) {
                e.printStackTrace();
                return;
            } catch (LineUnavailableException e) {
                e.printStackTrace();
                return;
            }
        } else {
            try {
                this.audioInputStream.reset();
            } catch (IOException ex1) { //ignore
                ex1.printStackTrace();
                assert false : "reset didnt work";
            }
        }

        // read write data loop, -1 indicates end of file
        int nBytesRead = 0;
        byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];
        while (nBytesRead != -1) {
            try {
                nBytesRead = this.audioInputStream.read(abData, 0, abData.length);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (nBytesRead >= 0) {
                int nBytesWritten = this.line.write(abData, 0, nBytesRead);
            }
        }
        /*
          Wait until all data are played.
          This is only necessary because of the bug noted below.
          (If we do not wait, we would interrupt the playback by
          prematurely closing the line and exiting the VM.)
          Thanks to Margie Fitch for bringing me on the right
          path to this solution.
         */
        //_line.drain();
    }




    /*
     *The clip will be played nLoopCount + 1 times.
     */
    private void createClip() {
        AudioInputStream clipInputStream = null;
        try {
            clipInputStream = AudioSystem.getAudioInputStream(this.file);
        } catch (UnsupportedAudioFileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (clipInputStream != null) {
            AudioFormat format = clipInputStream.getFormat();
            DataLine.Info info = new DataLine.Info(Clip.class, format);
            try {
                this.clip = (Clip) AudioSystem.getLine(info);
                //_clip.addLineListener(this);
                this.clip.open(clipInputStream);
            } catch (LineUnavailableException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } else {
            System.out.println("AudioThread.<init>(): can't get data from file " + this.file.getName());
        }
    }

    protected final void finalize() throws java.lang.Throwable {
        this.stopPlaying();
        super.finalize();
    }



}
