/*
 * Copyright (C) 2015 Zemin Liu
 *
 * 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 zemin.notification;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;

import android.support.v4.app.NotificationCompat;

/**
 * Status-bar notification.
 */
public class NotificationRemote extends NotificationHandler {

    public static final String SIMPLE_NAME = "Remote";
    public static boolean DBG;

    public static final String ACTION_CANCEL =
        "notification.intent.action.notificationremote.cancel";

    public static final String ACTION_CONTENT =
        "notification.intent.action.notificationremote.content";

    public static final String ACTION_ACTION =
        "notification.intent.action.notificationremote.action";

    public static final String KEY_ENTRY_ID = "key_entry_id";
    public static final String KEY_ACTION_ID = "key_action_id";

    private static int sID = 0;

    private NotificationRemoteCallback mCallback;
    private NotificationManager mManager;
    private NotificationCompat.Builder mBuilder;
    private Receiver mReceiver;
    private IntentFilter mFilter;
    private boolean mListening;

    /* package */ NotificationRemote(Context context, Looper looper) {
        super(context, NotificationDelegater.REMOTE, looper);

        mManager = (NotificationManager)
            context.getSystemService(Context.NOTIFICATION_SERVICE);

        mReceiver = new Receiver();
    }

    /**
     * Set callback.
     *
     * @param cb
     */
    public void setCallback(NotificationRemoteCallback cb) {
        if (mCallback != cb) {
            stopListening();
            mCallback = cb;
        }
    }

    /**
     * Add a new Intent action for BroadcastReceiver {@link #Receiver}.
     *
     * @param action
     */
    public void addAction(String action) {
        getFilter().addAction(action);
    }

    /**
     * Whether the given action is included in the filter.
     *
     * @param action
     */
    public boolean hasAction(String action) {
        return getFilter().hasAction(action);
    }

    /**
     * Generate Id for {@link android.app.PendingIntent}.
     *
     * @return int
     */
    public int genIdForPendingIntent() {
        return sID++;
    }

    /**
     * Get builder for {@link android.app.Notification}.
     *
     * @return NotificationCompat#Builder
     */
    public NotificationCompat.Builder getStatusBarNotificationBuilder() {
        if (mBuilder == null) {
            mBuilder = new NotificationCompat.Builder(mContext);
        }
        return mBuilder;
    }

    /**
     * Create an PendingIntent to execute when the notification is explicitly dismissed by the user.
     *
     * @see android.app.Notification#setDeleteIntent(PendingIntent)
     *
     * @param entry
     * @return PendingIntent
     */
    public PendingIntent getDeleteIntent(NotificationEntry entry) {
        Intent intent = new Intent(ACTION_CANCEL);
        intent.putExtra(KEY_ENTRY_ID, entry.ID);
        return PendingIntent.getBroadcast(mContext, genIdForPendingIntent(), intent, 0);
    }

    /**
     * Create an PendingIntent to execute when the notification is clicked by the user.
     *
     * @see android.app.Notification#setContentIntent(PendingIntent)
     *
     * @param entry
     * @return PedningIntent
     */
    public PendingIntent getContentIntent(NotificationEntry entry) {
        Intent intent = new Intent(ACTION_CONTENT);
        intent.putExtra(KEY_ENTRY_ID, entry.ID);
        return PendingIntent.getBroadcast(mContext, genIdForPendingIntent(), intent, 0);
    }

    /**
     * Create an PendingIntent to be fired when the notification action is invoked.
     *
     * @see android.app.Notification#addAction(int, CharSequence, PendingIntent)
     *
     * @param entry
     * @param act
     * @return PendingIntent
     */
    public PendingIntent getActionIntent(NotificationEntry entry, NotificationEntry.Action act) {
        Intent intent = new Intent(ACTION_ACTION);
        intent.putExtra(KEY_ENTRY_ID, entry.ID);
        intent.putExtra(KEY_ACTION_ID, entry.mActions.indexOf(act));
        return PendingIntent.getBroadcast(mContext, genIdForPendingIntent(), intent, 0);
    }

    @Override
    protected void onCancel(NotificationEntry entry) {
        mManager.cancel(entry.ID);
        onCancelFinished(entry);
    }

    @Override
    protected void onCancelAll() {
        mManager.cancelAll();
        onCancelAllFinished();
    }

    @Override
    protected void onArrival(NotificationEntry entry) {

        if (mCallback == null) {
            if (DBG) Log.v(TAG, "set default NotificationRemoteCallback");
            mCallback = new NotificationRemoteCallback();
        }

        if (!mListening) {
            mCallback.onRemoteSetup(this);
            startListening();
        }

        Notification n = mCallback.makeStatusBarNotification(this, entry, entry.layoutId);
        if (n == null) {
            Log.e(TAG, "failed to send remote notification. {null Notification}");
            onSendIgnored(entry);
            return;
        }

        mManager.notify(entry.tag, entry.ID, n);

        if (entry.useSystemEffect) {
            cancelEffect(entry);
        }
        onSendFinished(entry);
    }

    @Override
    protected void onUpdate(NotificationEntry entry) {
        onArrival(entry);
    }

    private void onCanceledRemotely(NotificationEntry entry) {
        reportCanceled(entry);
    }

    private void startListening() {
        if (!mListening) {
            IntentFilter filter = getFilter();
            filter.addAction(ACTION_CANCEL);
            filter.addAction(ACTION_CONTENT);
            filter.addAction(ACTION_ACTION);
            mContext.registerReceiver(mReceiver, filter, null, this);
            mListening = true;
        }
    }

    private void stopListening() {
        if (mListening) {
            mListening = false;
            mFilter = null;
            mContext.unregisterReceiver(mReceiver);
        }
    }

    private IntentFilter getFilter() {
        if (mFilter == null) {
            mFilter = new IntentFilter();
        }
        return mFilter;
    }

    private class Receiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {

            final String action = intent.getAction();
            final int entryId = intent.getIntExtra(KEY_ENTRY_ID, -1);
            final NotificationEntry entry = getNotification(entryId);
            if (DBG) Log.d(TAG, "onReceive - " + entryId + ", " + action);

            if (ACTION_CANCEL.equals(action)) {
                onCanceledRemotely(entry);
                mCallback.onCancelRemote(NotificationRemote.this, entry);
            } else if (ACTION_CONTENT.equals(action)) {
                entry.executeContentAction(mContext);
                mCallback.onClickRemote(NotificationRemote.this, entry);
                if (entry.autoCancel) {
                    onCanceledRemotely(entry);
                }
            } else if (ACTION_ACTION.equals(action)) {
                final int actionId = intent.getIntExtra(KEY_ACTION_ID, -1);
                final NotificationEntry.Action act = entry.mActions.get(actionId);
                mCallback.onClickRemoteAction(NotificationRemote.this, entry, act);
                act.execute(mContext);
            } else {
                mCallback.onReceive(NotificationRemote.this, entry, intent, action);
            }
        }
    }

    @Override public String toSimpleString() { return SIMPLE_NAME; }
    @Override public String toString() { return SIMPLE_NAME; }
}