// Filename: openalAudioManager.h
// Created by:  Ben Buchwald <bb2@alumni.cmu.edu>
//
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////


#ifndef __OPENAL_AUDIO_MANAGER_H__
#define __OPENAL_AUDIO_MANAGER_H__

#include "pandabase.h"
#ifdef HAVE_OPENAL //[

#include "audioManager.h"
#include "plist.h"
#include "pmap.h"
#include "pset.h"
#include "movieAudioCursor.h"
#include "reMutex.h"

// OSX uses the OpenAL framework
#ifdef IS_OSX
  #include <OpenAL/al.h>
  #include <OpenAL/alc.h>
#else
  #include <AL/al.h>
  #include <AL/alc.h>
#endif

class OpenALAudioSound;

extern void al_audio_errcheck(const char *context);
extern void alc_audio_errcheck(const char *context,ALCdevice* device);

class EXPCL_OPENAL_AUDIO OpenALAudioManager : public AudioManager {
  class SoundData;
  
  friend class OpenALAudioSound;
  friend class OpenALSoundData;
 public:

  //Constructor and Destructor
  OpenALAudioManager();
  virtual ~OpenALAudioManager();

  virtual void shutdown();

  virtual bool is_valid();
          
  virtual PT(AudioSound) get_sound(const string&,     bool positional = false, int mode=SM_heuristic);
  virtual PT(AudioSound) get_sound(MovieAudio *sound, bool positional = false, int mode=SM_heuristic);
  
  virtual void uncache_sound(const string&);
  virtual void clear_cache();
  virtual void set_cache_limit(unsigned int count);
  virtual unsigned int get_cache_limit() const;
    
  virtual void set_volume(PN_stdfloat);
  virtual PN_stdfloat get_volume() const;
          
  void set_play_rate(PN_stdfloat play_rate);
  PN_stdfloat get_play_rate() const;

  virtual void set_active(bool);
  virtual bool get_active() const;

  // This controls the "set of ears" that listens to 3D spacialized sound
  // px, py, pz are position coordinates. Can be 0.0f to ignore.
  // vx, vy, vz are a velocity vector in UNITS PER SECOND.
  // fx, fy and fz are the respective components of a unit forward-vector
  // ux, uy and uz are the respective components of a unit up-vector
  // These changes will NOT be invoked until audio_3d_update() is called.
  virtual void audio_3d_set_listener_attributes(PN_stdfloat px, PN_stdfloat py, PN_stdfloat pz,
                                                PN_stdfloat vx, PN_stdfloat xy, PN_stdfloat xz, 
                                                PN_stdfloat fx, PN_stdfloat fy, PN_stdfloat fz,
                                                PN_stdfloat ux, PN_stdfloat uy, PN_stdfloat uz);

  virtual void audio_3d_get_listener_attributes(PN_stdfloat *px, PN_stdfloat *py, PN_stdfloat *pz,
                                                PN_stdfloat *vx, PN_stdfloat *vy, PN_stdfloat *vz,
                                                PN_stdfloat *fx, PN_stdfloat *fy, PN_stdfloat *fz,
                                                PN_stdfloat *ux, PN_stdfloat *uy, PN_stdfloat *uz);
          
  // Control the "relative distance factor" for 3D spacialized audio in units-per-foot. Default is 1.0
  // OpenAL has no distance factor but we use this as a scale
  // on the min/max distances of sounds to preserve FMOD compatibility.
  // Also, adjusts the speed of sound to compensate for unit difference.
  virtual void audio_3d_set_distance_factor(PN_stdfloat factor);
  virtual PN_stdfloat audio_3d_get_distance_factor() const;

  // Control the presence of the Doppler effect. Default is 1.0
  // Exaggerated Doppler, use >1.0
  // Diminshed Doppler, use <1.0
  virtual void audio_3d_set_doppler_factor(PN_stdfloat factor);
  virtual PN_stdfloat audio_3d_get_doppler_factor() const;

  // Exaggerate or diminish the effect of distance on sound. Default is 1.0
  // Faster drop off, use >1.0
  // Slower drop off, use <1.0
  virtual void audio_3d_set_drop_off_factor(PN_stdfloat factor);
  virtual PN_stdfloat audio_3d_get_drop_off_factor() const;

  virtual void set_concurrent_sound_limit(unsigned int limit = 0);
  virtual unsigned int get_concurrent_sound_limit() const;
  virtual void reduce_sounds_playing_to(unsigned int count);

  virtual void stop_all_sounds();

  virtual void update();

private:
  void make_current() const;

  bool can_use_audio(MovieAudioCursor *source);
  bool should_load_audio(MovieAudioCursor *source, int mode);
  
  SoundData *get_sound_data(MovieAudio *source, int mode);

  // Tell the manager that the sound dtor was called.
  void release_sound(OpenALAudioSound* audioSound);
  void increment_client_count(SoundData *sd);
  void decrement_client_count(SoundData *sd);
  void discard_excess_cache(int limit);
  
  void starting_sound(OpenALAudioSound* audio);
  void stopping_sound(OpenALAudioSound* audio);
  
  void cleanup();
  
private:
  // This global lock protects all access to OpenAL library interfaces.
  static ReMutex _lock;

  // An expiration queue is a list of SoundData
  // that are no longer being used.  They are kept
  // around for a little while, since it is common to
  // stop using a sound for a brief moment and then
  // quickly resume.
  
  typedef plist<void *> ExpirationQueue;
  ExpirationQueue _expiring_samples;
  ExpirationQueue _expiring_streams;

  // An AudioSound that uses a SoundData is called a "client"
  // of the SoundData.  The SoundData keeps track of how
  // many clients are using it.  When the number of clients
  // drops to zero, the SoundData is no longer in use.  The
  // expiration queue is a list of all SoundData that aren't
  // in use, in least-recently-used order.  If a SoundData
  // in the expiration queue gains a new client, it is removed
  // from the expiration queue.  When the number of sounds
  // in the expiration queue exceeds the cache limit, the
  // first sound in the expiration queue is purged.

  class SoundData {
  public:
    SoundData();
    ~SoundData();
    OpenALAudioManager*  _manager;
    PT(MovieAudio)       _movie;
    ALuint               _sample;
    PT(MovieAudioCursor) _stream;
    double               _length;
    int                  _rate;
    int                  _channels;
    int                  _client_count;
    ExpirationQueue::iterator _expire;
  };

  
  typedef phash_map<string, SoundData *> SampleCache;
  SampleCache _sample_cache;
  
  typedef phash_set<PT(OpenALAudioSound)> SoundsPlaying;
  SoundsPlaying _sounds_playing;

  typedef phash_set<OpenALAudioSound *> AllSounds;
  AllSounds _all_sounds;
  
  // State:
  int _cache_limit;
  PN_stdfloat _volume;
  PN_stdfloat _play_rate;
  bool _active;
  bool _cleanup_required;
  // keep a count for startup and shutdown:
  static int _active_managers;
  static bool _openal_active;
  unsigned int _concurrent_sound_limit;
  
  bool _is_valid;
  
  typedef pset<OpenALAudioManager *> Managers;
  static Managers *_managers;

  static ALCdevice* _device; 
  static ALCcontext* _context;

  // cache of openal sources, use only for playing sounds
  typedef pset<ALuint > SourceCache;
  static SourceCache *_al_sources;

  PN_stdfloat _distance_factor;
  PN_stdfloat _doppler_factor;
  PN_stdfloat _drop_off_factor;

  ALfloat _position[3];
  ALfloat _velocity[3];
  ALfloat _forward_up[6];

  ////////////////////////////////////////////////////////////
  //These are needed for Panda's Pointer System. DO NOT ERASE!
  ////////////////////////////////////////////////////////////

 public:
  static TypeHandle get_class_type() {
    return _type_handle;
  }
  static void init_type() {
    AudioManager::init_type();
    register_type(_type_handle, "OpenALAudioManager", AudioManager::get_class_type());
  }
  virtual TypeHandle get_type() const {
    return get_class_type();
  }
  virtual TypeHandle force_init_type() {
    init_type(); 
    return get_class_type();
  }

 private:
  static TypeHandle _type_handle;

  ////////////////////////////////////////////////////////////
  //DONE
  ////////////////////////////////////////////////////////////

};

EXPCL_OPENAL_AUDIO AudioManager *Create_OpenALAudioManager();


#endif //]

#endif /* __OPENAL_AUDIO_MANAGER_H__ */
