package com.brackeen.javagamebook.sound;

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

public class MidiPlayer implements MetaEventListener {

    // Zdarzenie meta Midi
    public static final int END_OF_TRACK_MESSAGE = 47;

    private Sequencer sequencer;
    private boolean loop;
    private boolean paused;

    /**
        Tworzy nowy obiekt MidiPlayer.
    */
    public MidiPlayer() {
        try {
            sequencer = MidiSystem.getSequencer();
            sequencer.open();
            sequencer.addMetaEventListener(this);
        }
        catch ( MidiUnavailableException ex) {
            sequencer = null;
        }
    }

    /**
        Ładowanie sekwencji z systemu plików. Zwraca null gdy wystąpi błąd.
    */
    public Sequence getSequence(String filename) {
        try {
            return getSequence(new FileInputStream(filename));
        }
        catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }

    /**
        Ładowanie sekwencji ze strumienia wejściowego. 
        Zwraca null gdy wystąpi błąd.
    */
    public Sequence getSequence(InputStream is) {
        try {
            if (!is.markSupported()) {
                is = new BufferedInputStream(is);
            }
            Sequence s = MidiSystem.getSequence(is);
            is.close();
            return s;
        }
        catch (InvalidMidiDataException ex) {
            ex.printStackTrace();
            return null;
        }
        catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }

    /**
        Odtwarza sekwencję, ewentualnie ją zapętlając. Metoda natychmiast 
        kończy działanie. Jeżeli sekwencja jest nieprawidłowa, 
        nie jest odtwarzana.
    */
    public void play(Sequence sequence, boolean loop) {
        if (sequencer != null && sequence != null && sequencer.isOpen()) {
            try {
                sequencer.setSequence(sequence);
                sequencer.start();
                this.loop = loop;
            }
            catch (InvalidMidiDataException ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
        Metoda wywoływana przez system dźwiękowy w przypadku zajścia 
        zdarzenia meta. W tym przypadku, gdy zdarzeniem jes koniec ścieżki,
        i jest włączone zapętlanie, sekwencja jest uruchamiana od początku.
    */
    public void meta(MetaMessage event) {
        if (event.getType() == END_OF_TRACK_MESSAGE) {
            if (sequencer != null && sequencer.isOpen() && loop) {
                sequencer.start();
            }
        }
    }

     /**
       Zatrzymuje sekwencer i ustawia jego pozycję na 0.
    */
    public void stop() {
         if (sequencer != null && sequencer.isOpen()) {
             sequencer.stop();
             sequencer.setMicrosecondPosition(0);
         }
    }

     /**
        Zamyka sekwencer.
    */
    public void close() {
         if (sequencer != null && sequencer.isOpen()) {
             sequencer.close();
         }
    }

    /**
        Pobiera sekwencer.
    */
    public Sequencer getSequencer() {
        return sequencer;
    }

    /**
        Włącza pauzę. Muzyka może nie zatrzymać się natychmiast.
    */
    public void setPaused(boolean paused) {
        if (this.paused != paused && sequencer != null && sequencer.isOpen()) {
            this.paused = paused;
            if (paused) {
                sequencer.stop();
            }
            else {
                sequencer.start();
            }
        }
    }

    /**
        Zwraca stan pauzy.
    */
    public boolean isPaused() {
        return paused;
    }
}
