package ibxm;

import java.io.*;
import javax.sound.sampled.*;

public class Player
{
    private Thread play_thread;
    private IBXM ibxm;
    private Module module;
    private int song_duration, play_position;
    private boolean running, loop;
    private byte[] output_buffer;
    private SourceDataLine output_line;

    /**
    	Simple command-line test player.
    */
    public static void main(String[] args) throws Exception
    {
        if (args.length < 1)
        {
            System.err.println("Usage: java ibxm.Player <module file>");
            System.exit(0);
        }

        FileInputStream file_input_stream = new FileInputStream(args[ 0 ]);
        Player player = new Player();
        player.set_module(Player.load_module(file_input_stream));
        file_input_stream.close();
        player.play();
    }

    /**
    	Decode the data in the specified InputStream into a Module instance.
    	@param input an InputStream containing the module file to be decoded.
    	@throws IllegalArgumentException if the data is not recognised as a module file.
    */
    public static Module load_module(InputStream input) throws IllegalArgumentException, IOException
    {
        DataInputStream data_input_stream = new DataInputStream(input);
        /* Check if data is in XM format.*/
        byte[] xm_header = new byte[ 60 ];
        data_input_stream.readFully(xm_header);

        if (FastTracker2.is_xm(xm_header))
        {
            return FastTracker2.load_xm(xm_header, data_input_stream);
        }

        /* Check if data is in ScreamTracker 3 format.*/
        byte[] s3m_header = new byte[ 96 ];
        System.arraycopy(xm_header, 0, s3m_header, 0, 60);
        data_input_stream.readFully(s3m_header, 60, 36);

        if (ScreamTracker3.is_s3m(s3m_header))
        {
            return ScreamTracker3.load_s3m(s3m_header, data_input_stream);
        }

        /* Check if data is in ProTracker format.*/
        byte[] mod_header = new byte[ 1084 ];
        System.arraycopy(s3m_header, 0, mod_header, 0, 96);
        data_input_stream.readFully(mod_header, 96, 988);
        return ProTracker.load_mod(mod_header, data_input_stream);
    }

    /**
    	Instantiate a new Player.
    */
    public Player() throws LineUnavailableException
    {
        ibxm = new IBXM(48000);
        set_loop(true);
        output_line = AudioSystem.getSourceDataLine(new AudioFormat(48000, 16, 2, true, true));
        output_buffer = new byte[ 1024 * 4 ];
    }

    /**
    	Set the Module instance to be played.
    */
    public void set_module(Module m)
    {
        if (m != null)
        {
            module = m;
        }

        stop();
        ibxm.set_module(module);
        song_duration = ibxm.calculate_song_duration();
    }

    /**
    	If loop is true, playback will continue indefinitely,
    	otherwise the module will play through once and stop.
    */
    public void set_loop(boolean loop)
    {
        this.loop = loop;
    }

    /**
    	Open the audio device and begin playback.
    	If a module is already playing it will be restarted.
    */
    public void play()
    {
        stop();
        play_thread = new Thread(new Driver());
        play_thread.start();
    }

    /**
    	Stop playback and close the audio device.
    */
    public void stop()
    {
        running = false;

        if (play_thread != null)
        {
            try
            {
                play_thread.join();
            }
            catch (InterruptedException ie) {}
        }
    }

    private class Driver implements Runnable
    {
        public void run()
        {
            if (running)
            {
                return;
            }

            try
            {
                output_line.open();
                output_line.start();
                play_position = 0;
                running = true;

                while (running)
                {
                    int frames = song_duration - play_position;

                    if (frames > 1024)
                    {
                        frames = 1024;
                    }

                    ibxm.get_audio(output_buffer, frames);
                    output_line.write(output_buffer, 0, frames * 4);
                    play_position += frames;

                    if (play_position >= song_duration)
                    {
                        play_position = 0;

                        if (!loop)
                        {
                            running = false;
                        }
                    }
                }

                output_line.drain();
                output_line.close();
            }
            catch (LineUnavailableException lue)
            {
                lue.printStackTrace();
            }
        }
    }
}
