/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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 com.googlecode.android_scripting.facade.media;

import android.app.Service;
import android.media.MediaPlayer;
import android.net.Uri;

import com.googlecode.android_scripting.facade.EventFacade;
import com.googlecode.android_scripting.facade.FacadeManager;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcDefault;
import com.googlecode.android_scripting.rpc.RpcParameter;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

/**
 * This facade exposes basic mediaPlayer functionality. <br>
 * <br>
 * <b>Usage Notes:</b><br>
 * mediaPlayerFacade maintains a list of media streams, identified by a user supplied tag. If the
 * tag is null or blank, this tag defaults to "default"<br>
 * Basic operation is: mediaPlayOpen("file:///sdcard/MP3/sample.mp3","mytag",true)<br>
 * This will look for a media file at /sdcard/MP3/sample.mp3. Other urls should work. If the file
 * exists and is playable, this will return a true otherwise it will return a false. <br>
 * If play=true, then the media file will play immediately, otherwise it will wait for a
 * {@link #mediaPlayStart mediaPlayerStart} command. <br>
 * When done with the resource, use {@link #mediaPlayClose mediaPlayClose} <br>
 * You can get information about the loaded media with {@link #mediaPlayInfo mediaPlayInfo} This
 * returns a map with the following elements:
 * <ul>
 * <li>"tag" - user supplied tag identifying this mediaPlayer.
 * <li>"loaded" - true if loaded, false if not. If false, no other elements are returned.
 * <li>"duration" - length of the media in milliseconds.
 * <li>"position" - current position of playback in milliseconds. Controlled by
 * {@link #mediaPlaySeek mediaPlaySeek}
 * <li>"isplaying" - shows whether media is playing. Controlled by {@link #mediaPlayPause
 * mediaPlayPause} and {@link #mediaPlayStart mediaPlayStart}
 * <li>"url" - the url used to open this media.
 * <li>"looping" - whether media will loop. Controlled by {@link #mediaPlaySetLooping
 * mediaPlaySetLooping}
 * </ul>
 * <br>
 * You can use {@link #mediaPlayList mediaPlayList} to get a list of the loaded tags. <br>
 * {@link #mediaIsPlaying mediaIsPlaying} will return true if the media is playing.<br>
 * <b>Events:</b><br>
 * A playing media will throw a <b>"media"</b> event on completion. NB: In remote mode, a media file
 * will continue playing after the script has finished unless an explicit {@link #mediaPlayClose
 * mediaPlayClose} event is called.
 *
 */

public class MediaPlayerFacade extends RpcReceiver implements MediaPlayer.OnCompletionListener {

    private final Service mService;
    static private final Map<String, MediaPlayer> mPlayers = new Hashtable<String, MediaPlayer>();
    static private final Map<String, String> mUrls = new Hashtable<String, String>();

    private final EventFacade mEventFacade;

    public MediaPlayerFacade(FacadeManager manager) {
        super(manager);
        mService = manager.getService();
        mEventFacade = manager.getReceiver(EventFacade.class);
    }

    private String getDefault(String tag) {
        return (tag == null || tag.equals("")) ? "default" : tag;
    }

    private MediaPlayer getPlayer(String tag) {
        tag = getDefault(tag);
        return mPlayers.get(tag);
    }

    private String getUrl(String tag) {
        tag = getDefault(tag);
        return mUrls.get(tag);
    }

    private void putMp(String tag, MediaPlayer player, String url) {
        tag = getDefault(tag);
        mPlayers.put(tag, player);
        mUrls.put(tag, url);
    }

    private void removeMp(String tag) {
        tag = getDefault(tag);
        MediaPlayer player = mPlayers.get(tag);
        if (player != null) {
            player.stop();
            player.release();
        }
        mPlayers.remove(tag);
        mUrls.remove(tag);
    }

    @Rpc(description = "Open a media file", returns = "true if play successful")
    public synchronized boolean mediaPlayOpen(@RpcParameter(name = "url",
                                                            description = "url of media resource")
    String url, @RpcParameter(name = "tag", description = "string identifying resource")
    @RpcDefault(value = "default")
    String tag, @RpcParameter(name = "play", description = "start playing immediately")
    @RpcDefault(value = "true")
    Boolean play) {
        removeMp(tag);
        MediaPlayer player = getPlayer(tag);
        player = MediaPlayer.create(mService, Uri.parse(url));
        if (player != null) {
            putMp(tag, player, url);
            player.setOnCompletionListener(this);
            if (play) {
                player.start();
            }
        }
        return player != null;
    }

    @Rpc(description = "pause playing media file", returns = "true if successful")
    public synchronized boolean mediaPlayPause(
            @RpcParameter(name = "tag", description = "string identifying resource")
            @RpcDefault(value = "default")
            String tag) {
        MediaPlayer player = getPlayer(tag);
        if (player == null) {
            return false;
        }
        player.pause();
        return true;
    }

    @Rpc(description = "Start playing media file.", returns = "true if successful")
    public synchronized boolean mediaPlayStart(
            @RpcParameter(name = "tag", description = "string identifying resource")
            @RpcDefault(value = "default")
            String tag) {
        MediaPlayer player = getPlayer(tag);
        if (player == null) {
            return false;
        }
        player.start();
        return mediaIsPlaying(tag);
    }

    @Rpc(description = "Stop playing media file.", returns = "true if successful")
    public synchronized boolean mediaPlayStop(
            @RpcParameter(name = "tag", description = "string identifying resource")
            @RpcDefault(value = "default")
            String tag) {
        MediaPlayer player = getPlayer(tag);
        if (player == null) {
            return false;
        }
        player.stop();
        return !mediaIsPlaying(tag) && player.getCurrentPosition() == 0;
    }

    @Rpc(description = "Stop all players.")
    public synchronized void mediaPlayStopAll() {
        for (MediaPlayer p : mPlayers.values()) {
            p.stop();
        }
    }

    @Rpc(description = "Seek To Position", returns = "New Position (in ms)")
    public synchronized int mediaPlaySeek(@RpcParameter(name = "msec",
                                                        description = "Position in millseconds")
    Integer msec, @RpcParameter(name = "tag", description = "string identifying resource")
    @RpcDefault(value = "default")
    String tag) {
        MediaPlayer player = getPlayer(tag);
        if (player == null) {
            return 0;
        }
        player.seekTo(msec);
        return player.getCurrentPosition();
    }

    @Rpc(description = "Close media file", returns = "true if successful")
    public synchronized boolean mediaPlayClose(
            @RpcParameter(name = "tag", description = "string identifying resource")
            @RpcDefault(value = "default")
            String tag) throws Exception {
        if (!mPlayers.containsKey(tag)) {
            return false;
        }
        removeMp(tag);
        return true;
    }

    @Rpc(description = "Checks if media file is playing.", returns = "true if playing")
    public synchronized boolean mediaIsPlaying(
            @RpcParameter(name = "tag", description = "string identifying resource")
            @RpcDefault(value = "default")
            String tag) {
        MediaPlayer player = getPlayer(tag);
        return (player == null) ? false : player.isPlaying();
    }

    @Rpc(description = "Information on current media", returns = "Media Information")
    public synchronized Map<String, Object> mediaPlayGetInfo(
            @RpcParameter(name = "tag", description = "string identifying resource")
            @RpcDefault(value = "default")
            String tag) {
        Map<String, Object> result = new HashMap<String, Object>();
        MediaPlayer player = getPlayer(tag);
        result.put("tag", getDefault(tag));
        if (player == null) {
            result.put("loaded", false);
        } else {
            result.put("loaded", true);
            result.put("duration", player.getDuration());
            result.put("position", player.getCurrentPosition());
            result.put("isplaying", player.isPlaying());
            result.put("url", getUrl(tag));
            result.put("looping", player.isLooping());
        }
        return result;
    }

    @Rpc(description = "Lists currently loaded media", returns = "List of Media Tags")
    public Set<String> mediaPlayList() {
        return mPlayers.keySet();
    }

    @Rpc(description = "Set Looping", returns = "True if successful")
    public synchronized boolean mediaPlaySetLooping(@RpcParameter(name = "enabled")
    @RpcDefault(value = "true")
    Boolean enabled, @RpcParameter(name = "tag", description = "string identifying resource")
    @RpcDefault(value = "default")
    String tag) {
        MediaPlayer player = getPlayer(tag);
        if (player == null) {
            return false;
        }
        player.setLooping(enabled);
        return true;
    }

    @Rpc(description = "Checks if media file is playing.", returns = "true if playing")
    public synchronized void mediaSetNext(
            @RpcParameter(name = "tag", description = "string identifying resource")
            @RpcDefault(value = "default")
            String tag,
            @RpcParameter(name = "next", description = "tag of the next track to play.")
            String next) {
        MediaPlayer player = getPlayer(tag);
        MediaPlayer nPlayer = getPlayer(next);
        if (player == null) {
            throw new NullPointerException("Non-existent player tag " + tag);
        }
        if (nPlayer == null) {
            throw new NullPointerException("Non-existent player tag " + next);
        }
        player.setNextMediaPlayer(nPlayer);
    }

    @Override
    public synchronized void shutdown() {
        for (String key : mPlayers.keySet()) {
            MediaPlayer player = mPlayers.get(key);
            if (player != null) {
                player.stop();
                player.release();
                player = null;
            }
        }
        mPlayers.clear();
        mUrls.clear();
    }

    @Override
    public void onCompletion(MediaPlayer player) {
        String tag = getTag(player);
        if (tag != null) {
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("action", "complete");
            data.put("tag", tag);
            mEventFacade.postEvent("media", data);
        }
    }

    private String getTag(MediaPlayer player) {
        for (Entry<String, MediaPlayer> m : mPlayers.entrySet()) {
            if (m.getValue() == player) {
                return m.getKey();
            }
        }
        return null;
    }
}
