/**
 * Copyright 2014 The PlayN Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package playn.robovm;

import java.io.File;

import org.robovm.apple.avfoundation.AVAudioPlayer;
import org.robovm.apple.avfoundation.AVAudioSession;
import org.robovm.apple.foundation.NSError;
import org.robovm.apple.foundation.NSURL;

import playn.core.AudioImpl;
import playn.core.Sound;
import static playn.robovm.OpenAL.*;

public class RoboAudio extends AudioImpl {

  private final RoboPlatform platform;
  private final AVAudioSession session;
  private final long oalDevice;
  private final long oalContext;

  private final int[] sources;
  private final RoboSoundOAL[] active;
  private final int[] started;

  public RoboAudio(RoboPlatform platform, int numSources) {
    super(platform);
    this.platform = platform;

    session = AVAudioSession.sharedInstance();
    session.setActive(true, null); // TODO: options?

    oalDevice = alcOpenDevice(null);
    if (oalDevice != 0) {
      oalContext = alcCreateContext(oalDevice, null);
      alcMakeContextCurrent(oalContext);
    } else {
      platform.log().warn("Unable to open OpenAL device. Disabling OAL sound.");
      oalContext = 0;
    }

    // obtain our desired number of sources
    sources = new int[numSources];
    alGenSources(numSources, sources);
    active = new RoboSoundOAL[sources.length];
    started = new int[sources.length];

    // TODO: this should use AVAudioSessionInterruptionNotification

    // clear and restore our OAL context on audio session interruption
    // AudioSession.add_Interrupted(new EventHandler(new EventHandler.Method() {
    //   public void Invoke(Object sender, EventArgs event) {
    //     // not needed?: session.setActive(false, null);
    //     OpenAL.alcMakeContextCurrent(0);
    //   }
    // }));
    // AudioSession.add_Resumed(new EventHandler(new EventHandler.Method() {
    //   public void Invoke(Object sender, EventArgs event) {
    //     OpenAL.alcMakeContextCurrent(alcContext);
    //     // not needed?: session.setActive(true, null);
    //   }
    // }));
  }

  public Sound createSound(File path, boolean isMusic) {
    // if the file is meant to be music, or if it's not uncompressed CAFF, we need to use
    // AVAudioPlayer; if it's uncompressed CAFF, we can use OpenAL
    return (isMusic || !path.getName().endsWith(".caf")) ?
      createAVAP(new NSURL(path)) : createOAL(path);
  }

  Sound createAVAP(final NSURL url) {
    final RoboSoundAVAP sound = new RoboSoundAVAP();
    platform.invokeAsync(new Runnable() {
      public void run () {
        NSError.NSErrorPtr error = new NSError.NSErrorPtr();
        AVAudioPlayer player = new AVAudioPlayer(url, error);
        if (error.get() == null) {
          dispatchLoaded(sound, player);
        } else {
          String errstr = error.get().toString();
          platform.log().warn("Error loading sound [" + url + ", " + errstr + "]");
          dispatchLoadError(sound, new Exception(errstr));
        }
      }
    });
    return sound;
  }

  Sound createOAL(final File assetPath) {
    final RoboSoundOAL sound = new RoboSoundOAL(this);
    platform.invokeAsync(new Runnable() {
      public void run () {
        int bufferId = 0;
        try {
          bufferId = alGenBuffer();
          CAFLoader.load(assetPath, bufferId);
          dispatchLoaded(sound, bufferId);
        } catch (Throwable t) {
          if (bufferId != 0)
            alDeleteBuffer(bufferId);
          dispatchLoadError(sound, t);
        }
      }
    });
    return sound;
  }

  boolean isPlaying(int sourceIdx, RoboSoundOAL sound) {
    if (active[sourceIdx] != sound)
      return false;
    int[] result = new int[1];
    alGetSourcei(sources[sourceIdx], AL_SOURCE_STATE, result);
    return (result[0] == AL_PLAYING);
  }

  int play(RoboSoundOAL sound, float volume, boolean looping) {
    // find a source that's not currently playing
    int sourceIdx = -1, eldestIdx = 0;
    for (int ii = 0; ii < sources.length; ii++) {
      if (!isPlaying(ii, active[ii])) {
        sourceIdx = ii;
        break;
      } else if (started[ii] < started[eldestIdx]) {
        eldestIdx = ii;
      }
    }
    // if all of our sources are playing, stop the oldest source and steal it
    if (sourceIdx < 0) {
      stop(eldestIdx, active[eldestIdx]);
      sourceIdx = eldestIdx;
    }
    // prepare the source to play this sound's buffer
    int sourceId = sources[sourceIdx];
    alSourcei(sourceId, AL_BUFFER, sound.bufferId());
    alSourcef(sourceId, AL_GAIN, volume);
    alSourcei(sourceId, AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
    alSourcePlay(sourceId);
    active[sourceIdx] = sound;
    started[sourceIdx] = platform.tick();
    return sourceIdx;
  }

  void stop(int sourceIdx, RoboSoundOAL sound) {
    if (active[sourceIdx] == sound) {
      alSourceStop(sources[sourceIdx]);
    }
  }

  void setLooping(int sourceIdx, RoboSoundOAL sound, boolean looping) {
    if (active[sourceIdx] == sound) {
      alSourcei(sources[sourceIdx], AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
    }
  }

  void setVolume(int sourceIdx, RoboSoundOAL sound, float volume) {
    if (active[sourceIdx] == sound) {
      // OpenAL uses gain between 0 and 1, rather than raw db-based gain
      alSourcef(sources[sourceIdx], AL_GAIN, volume);
    }
  }

  void terminate() {
    // TODO: ?
    // if (actx.get_IsProcessing()) actx.Suspend();
    // actx.Dispose();
  }
}

@@ -1346,17 +1346,20 @@
 Session.
-s
+getS
 haredIns

Synchronize robovm API updates