/*
 * File    : JavaSoundActiveMediaPlayer.java
 * Created : 16-jun-2004 15:59
 * By      : fbusquets
 *
 * JClic - Authoring and playing system for educational activities
 *
 * Copyright (C) 2000 - 2018 Francesc Busquets & Departament
 * d'Educacio de la Generalitat de Catalunya
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details (see the LICENSE file).
 */

package edu.xtec.jclic.media;

import edu.xtec.util.ExtendedByteArrayInputStream;
import edu.xtec.util.StreamIO;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.sound.midi.MidiSystem;
import javax.sound.sampled.*;
import javax.swing.Timer;

/** @author Francesc Busquets (fbusquets@xtec.cat) */
public class JavaSoundActiveMediaPlayer extends ActiveMediaPlayer {

  PseudoClip clip;
  Timer timer;

  boolean midi;
  ByteArrayInputStream midiIs;
  public static javax.sound.midi.Sequencer sequencer = null;

  /** Creates a new instance of JavaSoundActiveMediaPlayer */
  public JavaSoundActiveMediaPlayer(edu.xtec.jclic.media.MediaContent mc, edu.xtec.jclic.bags.MediaBag mb,
      edu.xtec.jclic.PlayStation ps) {
    super(mc, mb, ps);
    InputStream is;
    midi = false;
    midiIs = null;
    if (!useAudioBuffer) {
      try {
        switch (mc.mediaType) {
        case MediaContent.PLAY_MIDI:
          midi = true;
          is = mb.getInputStream(mc.mediaFileName);
          if (is instanceof ByteArrayInputStream)
            midiIs = (ByteArrayInputStream) is;
          else
            midiIs = new ExtendedByteArrayInputStream(StreamIO.readInputStream(is), mc.mediaFileName);
          break;

        case MediaContent.PLAY_AUDIO:
          if (mc.to > 0 || mc.from > 0)
            clip = ClipWrapper.getClipWrapper(mb, mc.mediaFileName);
          else
            clip = FalseClip.getFalseClip(mb, mc.mediaFileName);

          if (clip != null && !mc.loop && mc.to > Math.max(0, mc.from)) {
            timer = new Timer(mc.to - Math.max(0, mc.from), new ActionListener() {
              public void actionPerformed(ActionEvent ev) {
                if (clip != null && clip.isRunning())
                  clip.stop();
              }
            });
            timer.setCoalesce(false);
            timer.setRepeats(false);
          }
          break;

        default:
          break;
        }
      } catch (Exception ex) {
        System.err.println("Error reading media \"" + mc.mediaFileName + "\":\n" + ex);
      }
    }
  }

  public AudioBuffer createAudioBuffer(int seconds) throws Exception {
    return new JavaSoundAudioBuffer(mc.length);
  }

  public static void closeMidiSequencer() {
    if (sequencer != null) {
      if (sequencer.isRunning())
        sequencer.stop();
      if (sequencer.isOpen())
        sequencer.close();
    }
  }

  public void realize() {
    if (!useAudioBuffer) {
      try {
        if (midi) {
          if (sequencer == null)
            sequencer = MidiSystem.getSequencer();
        } else {
          if (clip != null && !clip.isOpen()) {
            clip.open();
          }
        }
      } catch (Exception e) {
        System.err.println("Error realizing media \"" + mc.mediaFileName + "\"\n" + e);
      }
    }
  }

  @Override
  protected void playNow(edu.xtec.jclic.boxes.ActiveBox setBx) {
    if (useAudioBuffer)
      super.playNow(setBx);
    else {
      try {
        if (midi) {
          if (sequencer == null)
            realize();
          if (sequencer != null && midiIs != null) {
            if (sequencer.isRunning())
              sequencer.stop();
            if (!sequencer.isOpen())
              sequencer.open();
            midiIs.reset();
            sequencer.setSequence(midiIs);
            sequencer.stop();
            setTimeRanges();
            sequencer.start();
          }
        } else if (clip != null) {
          if (!clip.isOpen())
            realize();
          setTimeRanges();
          attachVisualComponent();
          if (mc.loop) {
            clip.loop(Clip.LOOP_CONTINUOUSLY);
          } else {
            clip.start();
            if (timer != null)
              timer.start();
          }
        }
      } catch (Exception e) {
        System.err.println("Error playing media \"" + mc.mediaFileName + "\":\n" + e);
      }
    }
  }

  @Override
  public void stop() {
    super.stop();
    if (!useAudioBuffer) {
      try {
        if (midi && sequencer != null) {
          closeMidiSequencer();
        } else if (clip != null && clip.isActive()) {
          if (timer != null && timer.isRunning())
            timer.stop();
          clip.stop();
        }
      } catch (Exception e) {
        System.err.println("Error stopping media \"" + mc.mediaFileName + "\":\n" + e);
      }
    }
  }

  @Override
  public void clear() {
    super.clear();
    if (!useAudioBuffer) {
      try {
        if (midi) {
          if (sequencer != null) {
            sequencer.close();
            sequencer = null;
          }
          if (midiIs != null) {
            midiIs.close();
            midiIs = null;
          }
        } else if (clip != null) {
          stop();
          clip.close();
          destroyVisualComponent();
          clip = null;
          timer = null;
        }
      } catch (Exception e) {
        System.err.println("Error closing media \"" + mc.mediaFileName + "\":\n" + e);
      }
    }
  }

  protected void setTimeRanges() {
    if (useAudioBuffer)
      return;
    try {
      if (midi && sequencer != null) {
        sequencer.setTickPosition(0L);
      } else if (clip != null && clip.isOpen()) {
        if (mc.from >= 0 || mc.to >= 0) {
          int from = mc.from > 0 ? (int) ((clip.getFormat().getFrameRate() * mc.from) / 1000) : 0;
          int to = mc.to >= 0 ? (int) ((clip.getFormat().getFrameRate() * mc.to) / 1000) : -1;
          if (mc.loop)
            clip.setLoopPoints(from, to);
          clip.setFramePosition(from);
        } else {
          clip.setFramePosition(0);
        }
      }
    } catch (Exception e) {
      System.err.println("Error setting time ranges for \"" + mc.mediaFileName + "\":\n" + e);
    }
  }

  protected java.awt.Component getVisualComponent() {
    return null;
  }
}
