/*
 * Copyright (C) 2014 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.android.server.audio;

import android.app.PendingIntent;
import android.content.ComponentName;
import android.media.AudioManager;
import android.media.IRemoteControlClient;
import android.media.IRemoteVolumeObserver;
import android.media.RemoteControlClient;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Log;

import java.io.PrintWriter;

/**
 * @hide
 * Class to handle all the information about a media player, encapsulating information
 * about its use RemoteControlClient, playback type and volume... The lifecycle of each
 * instance is managed by android.media.MediaFocusControl, from its addition to the player stack
 * stack to its release.
 */
class PlayerRecord implements DeathRecipient {

    // on purpose not using this classe's name, as it will only be used from MediaFocusControl
    private static final String TAG = "MediaFocusControl";
    private static final boolean DEBUG = false;

    /**
     * A global counter for RemoteControlClient identifiers
     */
    private static int sLastRccId = 0;

    public static MediaFocusControl sController;

    /**
     * The target for the ACTION_MEDIA_BUTTON events.
     * Always non null. //FIXME verify
     */
    final private PendingIntent mMediaIntent;
    /**
     * The registered media button event receiver.
     */
    final private ComponentName mReceiverComponent;

    private int mRccId = -1;

    /**
     * A non-null token implies this record tracks a "live" player whose death is being monitored.
     */
    private IBinder mToken;
    private String mCallingPackageName;
    private int mCallingUid;
    /**
     * Provides access to the information to display on the remote control.
     * May be null (when a media button event receiver is registered,
     *     but no remote control client has been registered) */
    private IRemoteControlClient mRcClient;
    private RcClientDeathHandler mRcClientDeathHandler;
    /**
     * Information only used for non-local playback
     */
    //FIXME private?
    public int mPlaybackType;
    public int mPlaybackVolume;
    public int mPlaybackVolumeMax;
    public int mPlaybackVolumeHandling;
    public int mPlaybackStream;
    public RccPlaybackState mPlaybackState;
    public IRemoteVolumeObserver mRemoteVolumeObs;


    protected static class RccPlaybackState {
        public int mState;
        public long mPositionMs;
        public float mSpeed;

        public RccPlaybackState(int state, long positionMs, float speed) {
            mState = state;
            mPositionMs = positionMs;
            mSpeed = speed;
        }

        public void reset() {
            mState = RemoteControlClient.PLAYSTATE_STOPPED;
            mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
            mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
        }

        @Override
        public String toString() {
            return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
        }

        private String posToString() {
            if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
                return "PLAYBACK_POSITION_INVALID";
            } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
                return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
            } else {
                return (String.valueOf(mPositionMs) + "ms");
            }
        }

        private String stateToString() {
            switch (mState) {
                case RemoteControlClient.PLAYSTATE_NONE:
                    return "PLAYSTATE_NONE";
                case RemoteControlClient.PLAYSTATE_STOPPED:
                    return "PLAYSTATE_STOPPED";
                case RemoteControlClient.PLAYSTATE_PAUSED:
                    return "PLAYSTATE_PAUSED";
                case RemoteControlClient.PLAYSTATE_PLAYING:
                    return "PLAYSTATE_PLAYING";
                case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
                    return "PLAYSTATE_FAST_FORWARDING";
                case RemoteControlClient.PLAYSTATE_REWINDING:
                    return "PLAYSTATE_REWINDING";
                case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
                    return "PLAYSTATE_SKIPPING_FORWARDS";
                case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
                    return "PLAYSTATE_SKIPPING_BACKWARDS";
                case RemoteControlClient.PLAYSTATE_BUFFERING:
                    return "PLAYSTATE_BUFFERING";
                case RemoteControlClient.PLAYSTATE_ERROR:
                    return "PLAYSTATE_ERROR";
                default:
                    return "[invalid playstate]";
            }
        }
    }


    /**
     * Inner class to monitor remote control client deaths, and remove the client for the
     * remote control stack if necessary.
     */
    private class RcClientDeathHandler implements IBinder.DeathRecipient {
        final private IBinder mCb; // To be notified of client's death
        //FIXME needed?
        final private PendingIntent mMediaIntent;

        RcClientDeathHandler(IBinder cb, PendingIntent pi) {
            mCb = cb;
            mMediaIntent = pi;
        }

        public void binderDied() {
            Log.w(TAG, "  RemoteControlClient died");
            // remote control client died, make sure the displays don't use it anymore
            //  by setting its remote control client to null
            sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
            // the dead client was maybe handling remote playback, the controller should reevaluate
            sController.postReevaluateRemote();
        }

        public IBinder getBinder() {
            return mCb;
        }
    }


    protected static class RemotePlaybackState {
        int mRccId;
        int mVolume;
        int mVolumeMax;
        int mVolumeHandling;

        protected RemotePlaybackState(int id, int vol, int volMax) {
            mRccId = id;
            mVolume = vol;
            mVolumeMax = volMax;
            mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
        }
    }


    void dump(PrintWriter pw, boolean registrationInfo) {
        if (registrationInfo) {
            pw.println("  pi: " + mMediaIntent +
                    " -- pack: " + mCallingPackageName +
                    "  -- ercvr: " + mReceiverComponent +
                    "  -- client: " + mRcClient +
                    "  -- uid: " + mCallingUid +
                    "  -- type: " + mPlaybackType +
                    "  state: " + mPlaybackState);
        } else {
            // emphasis on state
            pw.println("  uid: " + mCallingUid +
                    "  -- id: " + mRccId +
                    "  -- type: " + mPlaybackType +
                    "  -- state: " + mPlaybackState +
                    "  -- vol handling: " + mPlaybackVolumeHandling +
                    "  -- vol: " + mPlaybackVolume +
                    "  -- volMax: " + mPlaybackVolumeMax +
                    "  -- volObs: " + mRemoteVolumeObs);
        }
    }


    static protected void setMediaFocusControl(MediaFocusControl mfc) {
        sController = mfc;
    }

    /** precondition: mediaIntent != null */
    protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)
    {
        mMediaIntent = mediaIntent;
        mReceiverComponent = eventReceiver;
        mToken = token;
        mCallingUid = -1;
        mRcClient = null;
        mRccId = ++sLastRccId;
        mPlaybackState = new RccPlaybackState(
                RemoteControlClient.PLAYSTATE_STOPPED,
                RemoteControlClient.PLAYBACK_POSITION_INVALID,
                RemoteControlClient.PLAYBACK_SPEED_1X);

        resetPlaybackInfo();
        if (mToken != null) {
            try {
                mToken.linkToDeath(this, 0);
            } catch (RemoteException e) {
                sController.unregisterMediaButtonIntentAsync(mMediaIntent);
            }
        }
    }

    //---------------------------------------------
    // Accessors
    protected int getRccId() {
        return mRccId;
    }

    protected IRemoteControlClient getRcc() {
        return mRcClient;
    }

    protected ComponentName getMediaButtonReceiver() {
        return mReceiverComponent;
    }

    protected PendingIntent getMediaButtonIntent() {
        return mMediaIntent;
    }

    protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) {
        if (mToken != null) {
            return mMediaIntent.equals(pi);
        } else {
            if (mReceiverComponent != null) {
                return mReceiverComponent.equals(pi.getIntent().getComponent());
            } else {
                return false;
            }
        }
    }

    protected boolean isPlaybackActive() {
        return MediaFocusControl.isPlaystateActive(mPlaybackState.mState);
    }

    //---------------------------------------------
    // Modify the records stored in the instance
    protected void resetControllerInfoForRcc(IRemoteControlClient rcClient,
            String callingPackageName, int uid) {
        // already had a remote control client?
        if (mRcClientDeathHandler != null) {
            // stop monitoring the old client's death
            unlinkToRcClientDeath();
        }
        // save the new remote control client
        mRcClient = rcClient;
        mCallingPackageName = callingPackageName;
        mCallingUid = uid;
        if (rcClient == null) {
            // here mcse.mRcClientDeathHandler is null;
            resetPlaybackInfo();
        } else {
            IBinder b = mRcClient.asBinder();
            RcClientDeathHandler rcdh =
                    new RcClientDeathHandler(b, mMediaIntent);
            try {
                b.linkToDeath(rcdh, 0);
            } catch (RemoteException e) {
                // remote control client is DOA, disqualify it
                Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
                mRcClient = null;
            }
            mRcClientDeathHandler = rcdh;
        }
    }

    protected void resetControllerInfoForNoRcc() {
        // stop monitoring the RCC death
        unlinkToRcClientDeath();
        // reset the RCC-related fields
        mRcClient = null;
        mCallingPackageName = null;
    }

    public void resetPlaybackInfo() {
        mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
        mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
        mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
        mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
        mPlaybackStream = AudioManager.STREAM_MUSIC;
        mPlaybackState.reset();
        mRemoteVolumeObs = null;
    }

    //---------------------------------------------
    public void unlinkToRcClientDeath() {
        if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
            try {
                mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
                mRcClientDeathHandler = null;
            } catch (java.util.NoSuchElementException e) {
                // not much we can do here
                Log.e(TAG, "Error in unlinkToRcClientDeath()", e);
            }
        }
    }

    // FIXME rename to "release"? (as in FocusRequester class)
    public void destroy() {
        unlinkToRcClientDeath();
        if (mToken != null) {
            mToken.unlinkToDeath(this, 0);
            mToken = null;
        }
    }

    @Override
    public void binderDied() {
        sController.unregisterMediaButtonIntentAsync(mMediaIntent);
    }

    @Override
    protected void finalize() throws Throwable {
        destroy(); // unlink exception handled inside method
        super.finalize();
    }
}
