/**
 * Created by G-Canvas Open Source Team.
 * Copyright (c) 2017, Alibaba, Inc. All rights reserved.
 *
 * This source code is licensed under the Apache Licence 2.0.
 * For the full copyright and license information, please view
 * the LICENSE file in the root directory of this source tree.
 */
package com.taobao.gcanvas.audio;

import android.app.Activity;
import android.content.Context;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.os.Handler;
import android.provider.Settings;

import com.taobao.gcanvas.GCanvasResult;
import com.taobao.gcanvas.GCanvasWebView;
//import com.taobao.gcanvas.GUtil;
import com.taobao.gcanvas.util.GLog;

import org.json.JSONArray;
import org.json.JSONException;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;


/**
 * This class called by CordovaActivity to play and record audio.
 * The file can be local or over a network using http.
 * <p>
 * Audio formats supported (tested): .mp3, .wav
 * <p>
 * Local audio files must reside in one of two places:
 * android_asset: 		file name must start with /android_asset/sound.mp3
 * sdcard:				file name is just sound.mp3
 */
public class GAudioHandler {

    public static String TAG = GAudioHandler.class.getName();

    private Activity activity;
    private GCanvasWebView webView;

    private HashMap<String, GAudioPlayer> players; // Audio player object
    private ArrayList<GAudioPlayer> pausedForPhone; // Audio players that were paused when phone call came in

    private VolumeObserver volumeObserver = null;

    public GAudioHandler() {
        this.players = new HashMap<String, GAudioPlayer>();
        this.pausedForPhone = new ArrayList<GAudioPlayer>();
    }

    public static String stripFileProtocol(String uriString) {
        if (uriString.startsWith("file://")) {
            uriString = uriString.substring(7);
        }
        return uriString;
    }

    public void initialize(Context context, GCanvasWebView webView) {
        if (context instanceof Activity) {
            activity = (Activity) context;
        }

        this.webView = webView;
    }

    public Activity getActivity() {
        return activity;
    }

    public GCanvasWebView getWebView() {
        return webView;
    }

    private HashMap<String, GAudioPlayer> getPlayers() {
        return players;
    }

    /**
     * Executes the request and returns PluginResult.
     *
     * @param action        The action to execute.
     * @param args          JSONArry of arguments for the plugin.
     * @param resultContext The callback context used when calling back into JavaScript.
     * @return A PluginResult object with a status and message.
     */
    public boolean execute(String action, JSONArray args, GCanvasResult resultContext) throws JSONException {
        if (action == null) {
            GLog.e("CANVAS", " ~ GAutioHandler ::: action is null");
            return false;
        }

        String result = "";

        if (action.equals("startRecordingAudio")) {
            startRecordingAudio(args.getString(0), stripFileProtocol(args.getString(1)), resultContext);
        } else if (action.equals("stopRecordingAudio")) {
            stopRecordingAudio(args.getString(0));
        } else if (action.equals("startPlayingAudio")) {
            startPlayingAudio(args.getString(0), stripFileProtocol(args.getString(1)), resultContext);
        } else if (action.equals("seekToAudio")) {
            seekToAudio(args.getString(0), args.getInt(1));
        } else if (action.equals("pausePlayingAudio")) {
            pausePlayingAudio(args.getString(0));
        } else if (action.equals("stopPlayingAudio")) {
            stopPlayingAudio(args.getString(0));
        } else if (action.equals("setVolume")) {
            try {
                this.setVolume(args.getString(0), Float.parseFloat(args.getString(1)));
            } catch (NumberFormatException nfe) {
                //no-op
            }
        } else if (action.equals("getCurrentPositionAudio")) {
            float f = this.getCurrentPositionAudio(args.getString(0));
            resultContext.success(f);
            return true;
        } else if (action.equals("getDurationAudio")) {
            float f = this.getDurationAudio(args.getString(0), args.getString(1), resultContext);
            resultContext.success(f);
            return true;
        } else if (action.equals("create")) {
            String id = args.getString(0);
            String src = stripFileProtocol(args.getString(1));
            GAudioPlayer audio = new GAudioPlayer(this, id, src, resultContext);
            this.players.put(id, audio);
        } else if (action.equals("release")) {
            boolean success = release(args.getString(0));
            resultContext.success(success);
            return true;
        } else if (action.equals("disable")) {
            onDestroy();
            return true;
        } else if (action.equals("setSourceAudio")) {
            String id = args.getString(0);
            String src = stripFileProtocol(args.getString(1));
            GAudioPlayer audio = this.players.get(id);
            if (audio == null || src.isEmpty()) {
                GLog.e("CANVAS", "audio's id error");
                return false;
            }
            audio.setSourceAudio(src);
        } else if (action.equals("startLoadingAudio")) {
            String id = args.getString(0);
            startLoadingAudio(id);
        } else { // Unrecognized action.
            return false;
        }

        resultContext.success(result);

        return true;
    }

    /**
     * Release the audio player instance to save memory.
     *
     * @param id The id of the audio player
     */
    private boolean release(String id) {
        if (!players.containsKey(id)) {
            return false;
        }

        GAudioPlayer audio = players.get(id);
        players.remove(id);
        audio.destroy();
        return true;
    }

    /**
     * Stop all audio players and recorders on navigate.
     */
    public void onReset() {
        onDestroy();
    }

    /**
     * Stop all audio players and recorders.
     */
    public void onDestroy() {
        for (GAudioPlayer audio : players.values()) {
            audio.destroy();
        }

        // Unregister volume observer
        if (volumeObserver != null) {
            activity.getContentResolver().unregisterContentObserver(volumeObserver);
        }

        this.players.clear();
    }

    /**
     * Called when a message is sent to plugin.
     *
     * @param id   The message id
     * @param data The message data
     * @return Object to stop propagation or null
     */
    public Object onMessage(String id, Object data) {
        // If phone message
        if (id.equals("telephone")) {
            // If phone ringing, then pause playing
            if ("ringing".equals(data) || "offhook".equals(data)) {

                // Get all audio players and pause them
                for (GAudioPlayer audio : this.players.values()) {
                    if (audio.getState() == GAudioPlayer.STATE.MEDIA_RUNNING.ordinal()) {
                        this.pausedForPhone.add(audio);
                        audio.pausePlaying();
                    }
                }
            }
            // If phone idle, then resume playing those players we paused
            else if ("idle".equals(data)) {
                for (GAudioPlayer audio : this.pausedForPhone) {
                    audio.startPlaying(null);
                }
                this.pausedForPhone.clear();
            }
        }
        return null;
    }

    /**
     * Start recording and save the specified file.
     *
     * @param id   The id of the audio player
     * @param file The name of the file
     */
    public void startRecordingAudio(String id, String file, GCanvasResult resultContext) {
        GAudioPlayer audio = this.players.get(id);
        if (audio == null) {
            audio = new GAudioPlayer(this, id, file, resultContext);
            this.players.put(id, audio);
        }
        audio.startRecording(file);
    }

    /**
     * Stop recording and save to the file specified when recording started.
     *
     * @param id The id of the audio player
     */
    public void stopRecordingAudio(String id) {
        GAudioPlayer audio = this.players.get(id);
        if (audio != null) {
            audio.stopRecording();
        }
    }

    /**
     * Start or resume playing audio file.
     *
     * @param id   The id of the audio player
     * @param file The name of the audio file.
     */
    public void startPlayingAudio(String id, String file, GCanvasResult resultContext) {
        GAudioPlayer audio = this.players.get(id);
        if (audio == null) {
            audio = new GAudioPlayer(this, id, file, resultContext);
            this.players.put(id, audio);
        }

        audio.startPlaying(file);

        // Mute the audio based on the device settings
        int mode = Settings.System.getInt(activity.getContentResolver(), Settings.System.MODE_RINGER, 2);
        if (mode != 2) {
            audio.setVolume(0);
        } else {
            audio.setVolume(1);
        }
    }

    /**
     * Seek to a location.
     *
     * @param id           The id of the audio player
     * @param milliseconds int: number of milliseconds to skip 1000 = 1 second
     */
    public void seekToAudio(String id, int milliseconds) {
        GAudioPlayer audio = this.players.get(id);
        if (audio != null) {
            audio.seekToPlaying(milliseconds);
        }
    }

    /**
     * Pause playing.
     *
     * @param id The id of the audio player
     */
    public void pausePlayingAudio(String id) {
        GAudioPlayer audio = this.players.get(id);
        if (audio != null) {
            audio.pausePlaying();
        }
    }

    /**
     * Stop playing the audio file.
     *
     * @param id The id of the audio player
     */
    public void stopPlayingAudio(String id) {
        GAudioPlayer audio = this.players.get(id);
        if (audio != null) {
            audio.stopPlaying();
        }
    }

    public void startLoadingAudio(String id) {
        GAudioPlayer audio = this.players.get(id);
        if (audio != null) {
            audio.loadingAudio();
        }
    }

    /**
     * Get current position of playback.
     *
     * @param id The id of the audio player
     * @return position in msec
     */
    public float getCurrentPositionAudio(String id) {
        GAudioPlayer audio = this.players.get(id);
        if (audio != null) {
            return (audio.getCurrentPosition() / 1000.0f);
        }
        return -1;
    }

    /**
     * Get the duration of the audio file.
     *
     * @param id   The id of the audio player
     * @param file The name of the audio file.
     * @return The duration in msec.
     */
    public float getDurationAudio(String id, String file, GCanvasResult resultContext) {
        // Get audio file
        GAudioPlayer audio = this.players.get(id);
        if (audio != null) {
            return (audio.getDuration(file));
        }

        // If not already open, then open the file
        else {
            audio = new GAudioPlayer(this, id, file, resultContext);
            this.players.put(id, audio);
            return (audio.getDuration(file));
        }
    }

    /**
     * Get the audio device to be used for playback.
     *
     * @return 1=earpiece, 2=speaker
     */
    @SuppressWarnings("deprecation")
    public int getAudioOutputDevice() {
        AudioManager audiMgr = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
        if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_EARPIECE) {
            return 1;
        } else if (audiMgr.getRouting(AudioManager.MODE_NORMAL) == AudioManager.ROUTE_SPEAKER) {
            return 2;
        } else {
            return -1;
        }
    }

    /**
     * Set the audio device to be used for playback.
     *
     * @param output 1=earpiece, 2=speaker
     */
    @SuppressWarnings("deprecation")
    public void setAudioOutputDevice(int output) {
        AudioManager audiMgr = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
        if (output == 2) {
            audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_SPEAKER, AudioManager.ROUTE_ALL);
        } else if (output == 1) {
            audiMgr.setRouting(AudioManager.MODE_NORMAL, AudioManager.ROUTE_EARPIECE, AudioManager.ROUTE_ALL);
        } else {
            GLog.w("AudioHandler setAudioOutputDevice Error: Unknown output device.");
        }
    }

    /**
     * Set the volume for an audio device
     *
     * @param id     The id of the audio player
     * @param volume Volume to adjust to 0.0f - 1.0f
     */
    public void setVolume(String id, float volume) {
        GAudioPlayer audio = this.players.get(id);
        if (audio != null) {
            audio.setVolume(volume);
        } else {
            GLog.w("AudioHandler setVolume Error: Unknown Audio Player " + id);
        }
    }

    private static class VolumeObserver extends ContentObserver {

        private final WeakReference<GAudioHandler> mGAudioHandlerRef;

        public VolumeObserver(GAudioHandler gAudioHandler, Handler handler) {
            super(handler);
            mGAudioHandlerRef = new WeakReference<GAudioHandler>(gAudioHandler);
        }

        @SuppressWarnings("deprecation")
        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            GAudioHandler audioHandler = mGAudioHandlerRef.get();
            if (audioHandler != null) {
                int mode = Settings.System.getInt(audioHandler.getActivity().getContentResolver(), Settings.System.MODE_RINGER, 2);
                if (mode != 2) { // mute or virbate
                    for (String i : audioHandler.getPlayers().keySet()) {
                        audioHandler.getPlayers().get(i).setVolume(0);
                    }
                } else {
                    // Volume values are raw scalars in range 0.0 to 1.0.
                    for (String i : audioHandler.getPlayers().keySet()) {
                        audioHandler.getPlayers().get(i).setVolume(1);
                    }
                }
            }
        }
    }

}
