/*
 * JVirus is a PacMan clone, written in Java.
 *
 * Please read "http://jvirus.sourceforge.net/jvirus_licence.txt" for copyrights.
 * 
 * The sourcecode is designed and created with
 * Sun J2SDK 1.3 and Microsoft Visual J++ 6.0
 *
 * JVirus homepage: http://jvirus.sourceforge.net
 *
 * autor: Slawa Weis
 * email: slawaweis@animatronik.net
 *
 */

package org.game.JVirus;

import java.io.*;
import java.util.*;

import javax.sound.midi.*;
import javax.sound.sampled.*;

import org.game.JVirus.res.*;

/**
 * Sound system of JVirus. Only static methods here.
 *
 * @see org.game.JVirus.res.Resource
 */
public final class Sound
{
 /**
  * Number of sound
  */
 public static final int FX_MOVE      = 0;
 /**
  * Number of sound
  */
 public static final int FX_EAT1      = 1;
 /**
  * Number of sound
  */
 public static final int FX_EAT2      = 2;
 /**
  * Number of sound
  */
 public static final int FX_WIN       = 3;
 /**
  * Number of sound
  */
 public static final int FX_GATE_OPEN = 4;
 /**
  * Number of sound
  */
 public static final int FX_FALL      = 5;
 /**
  * Number of sound
  */
 public static final int FX_LOSE      = 6;
 /**
  * Number of sound
  */
 public static final int FX_BEAM      = 7;

 /**
  * sound names list to load from disk
  */
 protected static final String fx_names [] = { "move", "eat_data", "eat_data2", "win", "gate_open", "fall", "lose", "beam" };

 /**
  * music list
  */
 protected static Sequence background_sound_array [] = null;
 /**
  * if the music track run to end it start again.
  * If the stop event coming from the programm and
  * the track is not run to end, then not start again.
  */
 protected static boolean ignoreStopEvent = false;
 /**
  * music volume
  */
 protected static int musicVolume = 50;
 /**
  * last played track
  */
 protected static int lastTrackNumber = 0;
 /**
  * midi Sequencer
  */
 protected static Sequencer midiSequencer = null;

 /**
  * wav list
  */
 protected static Clip fxs [] = null;
 /**
  * sound volume
  */
 protected static int fxVolume = 50;

 /**
  * hidden constructor
  */
 private Sound()
  {
  }

 /**
  * get music files count
  */
 public static int getBackgroundCount()
  {
  if(background_sound_array != null)
    return background_sound_array.length;
  else
    return -1;
  }

 /**
  * play a music files
  */
 public static void playBackground(int number)
  {
  if(midiSequencer != null)
    {
    if(midiSequencer.isRunning())
      {
      ignoreStopEvent = true;
      midiSequencer.stop();
      }
    try
       {
       if(number >= 0)
         {
         lastTrackNumber = number;
         midiSequencer.setMicrosecondPosition(0);
         midiSequencer.setSequence(background_sound_array[number]);
         midiSequencer.start();
         setBackgroundVolume(musicVolume);
         }
       }
    catch(InvalidMidiDataException e)
         {
         e.printStackTrace();
         }
    }
  }

 public static void playLastBackground()
  {
  playBackground(lastTrackNumber);
  }

 /**
  * if the music track run to end this listener start it again.
  */
 private static class BackgroundEndListener implements MetaEventListener
 {
  public void meta(MetaMessage meta)
   {
//   System.out.println(meta.getType() + " : " + ignoreStopEvent);
   if(47 == meta.getType() && !ignoreStopEvent)
     {
     ignoreStopEvent = false;
     midiSequencer.setMicrosecondPosition(0);
     midiSequencer.start();
     setBackgroundVolume(musicVolume);
     }
   }
 }

 /**
  * set the music volume
  */
 public static void setBackgroundVolume(int volume)
  {
  if(midiSequencer == null) return;

  if(volume < 0 || volume > 100)
    throw new IllegalArgumentException("volume must be between 0 and 100.");
  musicVolume = volume;
  double value = volume / 100.0;

  MidiChannel channels[] = ((Synthesizer)midiSequencer).getChannels();
  for(int i = 0; i < channels.length; i++)
     {
     channels[i].controlChange(7, (int)(value * 127.0));
     }
  }

 /**
  * set the music to mute
  */
 public static void setBackgroundMute(boolean b)
  {
//  midiSequencer.setTrackMute(0, b);
  if(midiSequencer != null)
    {
    MidiChannel channels[] = ((Synthesizer)midiSequencer).getChannels();
    for(int i = 0; i < channels.length; i++)
       {
       channels[i].setMute(b);
       }
    }
  }

 /**
  * stop music
  */
 public static void stopBackground()
  {
  if(midiSequencer != null && midiSequencer.isRunning())
    {
    ignoreStopEvent = true;
    midiSequencer.stop();
    }
  }

 /**
  * play a wav
  */
 public static void playFX(int number)
  {
  if(fxs != null && !fxs[number].isActive())
    {
    fxs[number].setFramePosition(0);
    fxs[number].start();
    }
  }

 /**
  * set sound volume
  */
 public static void setFXVolume(int volume)
  {
  if(fxs == null) return;

  if(volume < 0 || volume > 100)
    throw new IllegalArgumentException("volume must be between 0 and 100.");

  fxVolume = volume;
  double value = volume / 100.0;

  try
     {
     for(int i = 0; i < fxs.length; i++)
        {
        Clip clip = fxs[i];
        FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
        float dB = (float)(Math.log(value==0.0?0.0001:value)/Math.log(10.0)*20.0);
        gainControl.setValue(dB);
        }
     }
  catch(Exception e)
       {
       e.printStackTrace();
       }
  }

 /**
  * close the sound system
  */
 public static void closeSoundSystem()
  {
  try
     {
     if(midiSequencer != null)
       {
       if(midiSequencer.isRunning())
         {
         ignoreStopEvent = true;
         midiSequencer.stop();
         }
       if(midiSequencer.isOpen())
         midiSequencer.close();
       midiSequencer = null;
       System.out.println("Midi resources free.");
       }

     if(fxs != null)
       {
       for(int i = 0; i < fxs.length; i++)
          {
          fxs[i].stop();
          fxs[i].close();
          }
       fxs = null;
       System.out.println("Sound resources free.");
       }
     }
  catch(Exception e)
       {
       e.printStackTrace();
       }
  }

 /**
  * static initialiser
  */
 static
  {
  boolean sound_disabled = false;
  String jvirus_sound = System.getProperty("jvirus.sound");
  if(jvirus_sound != null && jvirus_sound.equals("disabled"))
    sound_disabled = true;

  // if -Djvirus.sound=disabled, don't create
  if(!sound_disabled)
    {
    initMidi();
    setBackgroundVolume(50);
    initFX();
    setFXVolume(80);
    }
  else
    System.out.println("Sound disabled.");
  }

 /**
  * init the music system
  */
 protected static void initMidi()
  {
  try
     {
     midiSequencer = MidiSystem.getSequencer(); 
     if(midiSequencer != null)
       {
       if(!midiSequencer.isOpen()) midiSequencer.open();
       }
     }
  catch(MidiUnavailableException e)
       {
       midiSequencer = null;
       System.out.println("Sound disabled.");
       e.printStackTrace();
       }

  if(midiSequencer == null) return;

  Vector vecBackground = new Vector();

  try
     {
     LineNumberReader lnr = new LineNumberReader(new InputStreamReader(Resource.class.getResourceAsStream("soundList.txt")));
     String line = null;
     while((line = lnr.readLine()) != null)
          {
          BufferedInputStream bis = new BufferedInputStream(Resource.class.getResourceAsStream(line), 20000);
          vecBackground.addElement(MidiSystem.getSequence(bis));
          }
     }
  catch(Exception e)
       {
       midiSequencer = null;
       System.out.println("Sound disabled.");
       e.printStackTrace();
       }

  if(midiSequencer == null) return;

  midiSequencer.addMetaEventListener(new BackgroundEndListener());

  background_sound_array = new Sequence[vecBackground.size()];
  for(int i = 0; i < vecBackground.size(); i++)
     background_sound_array[i] = (Sequence)vecBackground.elementAt(i);

  System.out.println("Sound enabled.");
  }

 /**
  * init the sound system
  */
 protected static void initFX()
  {
  fxs = new Clip[fx_names.length];

  try
     {
     for(int i = 0; i < fxs.length; i++)
        {
        BufferedInputStream bis = new BufferedInputStream(Resource.class.getResourceAsStream(fx_names[i] + ".wav"), 20000);
        AudioInputStream stream = AudioSystem.getAudioInputStream(bis);
        AudioFormat format = stream.getFormat();
        DataLine.Info info = new DataLine.Info(Clip.class, stream.getFormat(), ((int) stream.getFrameLength() * format.getFrameSize()));
        fxs[i] = (Clip)AudioSystem.getLine(info);
        fxs[i].open(stream);
        }
     }
  catch(Exception e)
       {
       fxs = null;
       e.printStackTrace();
       }
  }
}