/**
 * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
 * This file is part of CSipSimple.
 *
 *  CSipSimple is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  If you own a pjsip commercial license you can also redistribute it
 *  and/or modify it under the terms of the GNU Lesser General Public License
 *  as an android library.
 *
 *  CSipSimple is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.voiceblue.phone.utils;

import android.Manifest.permission;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.text.TextUtils;

import com.voiceblue.phone.api.SipManager;
import com.voiceblue.phone.api.SipProfile;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CallHandlerPlugin {

    private static final String THIS_FILE = "CallHandlerPlugin";
    public static final String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";

    private OnLoadListener listener;
    private PendingIntent pendingIntent = null;
    private Bitmap icon = null;
    private String nextExclude = null;
    private String label = null;
    private long accountId = SipProfile.INVALID_ID;
    private final Context context;

    private static Map<String, String> AVAILABLE_HANDLERS = null;

    private final static String VIRTUAL_ACC_MAX_ENTRIES = "maxVirtualAcc";
    private final static String VIRTUAL_ACC_PREFIX = "vAcc_";

    public CallHandlerPlugin(Context ctxt) {
        context = ctxt;
    }
    private static Handler sThreadHandler = null;

    /**
     * Load plugin from a given plugin component name 
     * @param componentName Fully qualified component name to call
     * @param number Optional number to call
     * @param l Listener to fire on load completion
     */
    public void loadFrom(final String componentName, String number, OnLoadListener l) {
        listener = l;
        ComponentName cn = ComponentName.unflattenFromString(componentName);

        Intent it = new Intent(SipManager.ACTION_GET_PHONE_HANDLERS);
        it.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
        it.setComponent(cn);

        context.sendOrderedBroadcast(it, permission.PROCESS_OUTGOING_CALLS,
                new BroadcastReceiver() {
                    @Override
                    public void onReceive(Context context, Intent intent) {
                        Bundle resolvedInfos = getResultExtras(true);
                        fillWith(componentName, resolvedInfos);
                        if (listener != null) {
                            listener.onLoad(CallHandlerPlugin.this);
                        }
                    }
                }, sThreadHandler, Activity.RESULT_OK, null, null);

    }

    /**
     * Load plugin from a given account id.
     * @param accountId Fake (< -1) account id to load plugin from
     * @param number Optional number to call
     * @param l Listener to fire on load completion
     */
    public void loadFrom(final Long accountId, String number, OnLoadListener l) {
        Map<String, String> callHandlers = getAvailableCallHandlers(context);
        for (String packageName : callHandlers.keySet()) {
            if (accountId == getAccountIdForCallHandler(context, packageName)) {
                loadFrom(packageName, number, l);
                return;
            }
        }

    }

    public void fillWith(String packageName, Bundle resolvedInfos) {

        pendingIntent = (PendingIntent) resolvedInfos.getParcelable(EXTRA_REMOTE_INTENT_TOKEN);
        icon = (Bitmap) resolvedInfos.getParcelable(Intent.EXTRA_SHORTCUT_ICON);
        nextExclude = resolvedInfos.getString(Intent.EXTRA_PHONE_NUMBER);
        label = resolvedInfos.getString(Intent.EXTRA_TITLE);
        if(TextUtils.isEmpty(label)) {
            if(AVAILABLE_HANDLERS != null && AVAILABLE_HANDLERS.containsKey(packageName)) {
                label = AVAILABLE_HANDLERS.get(packageName);
            }
        }

        accountId = getAccountIdForCallHandler(context, packageName);
    }

    /**
     * Retrieve internal id of call handler as saved in databases It should be
     * some negative < SipProfile.INVALID_ID number
     * 
     * @param ctxt Application context
     * @param packageName name of the call handler package
     * @return the id of this call handler in databases
     */
    public static Long getAccountIdForCallHandler(Context ctxt, String packageName) {
        SharedPreferences prefs = ctxt.getSharedPreferences("handlerCache", Context.MODE_PRIVATE);

        long accountId = SipProfile.INVALID_ID;
        try {
            accountId = prefs.getLong(VIRTUAL_ACC_PREFIX + packageName, SipProfile.INVALID_ID);
        } catch (Exception e) {
            Log.e(THIS_FILE, "Can't retrieve call handler cache id - reset");
        }
        if (accountId == SipProfile.INVALID_ID) {
            // We never seen this one, add a new entry for account id
            int maxAcc = prefs.getInt(VIRTUAL_ACC_MAX_ENTRIES, 0x0);
            int currentEntry = maxAcc + 1;
            accountId = SipProfile.INVALID_ID - (long) currentEntry;
            Editor edt = prefs.edit();
            edt.putLong(VIRTUAL_ACC_PREFIX + packageName, accountId);
            edt.putInt(VIRTUAL_ACC_MAX_ENTRIES, currentEntry);
            edt.commit();
        }
        return accountId;
    }

    /**
     * Retrieve outgoing call handlers available as plugin for csipsimple Also
     * contains stock call handler if available
     * 
     * @param ctxt context of application
     * @return A map of package name => Fancy name of call handler
     */
    public static Map<String, String> getAvailableCallHandlers(Context ctxt) {

        if (AVAILABLE_HANDLERS == null) {
            AVAILABLE_HANDLERS = new HashMap<String, String>();

            PackageManager packageManager = ctxt.getPackageManager();
            Intent it = new Intent(SipManager.ACTION_GET_PHONE_HANDLERS);

            List<ResolveInfo> availables = packageManager.queryBroadcastReceivers(it, 0);
            for (ResolveInfo resInfo : availables) {
                ActivityInfo actInfos = resInfo.activityInfo;
                Log.d(THIS_FILE, "Found call handler " + actInfos.packageName + " " + actInfos.name);
                if (packageManager.checkPermission(permission.PROCESS_OUTGOING_CALLS,
                        actInfos.packageName) == PackageManager.PERMISSION_GRANTED) {
                    String packagedActivityName = (new ComponentName(actInfos.packageName,
                            actInfos.name)).flattenToString();
                    AVAILABLE_HANDLERS.put(packagedActivityName,
                            (String) resInfo.loadLabel(packageManager));
                }
            }
        }

        return AVAILABLE_HANDLERS;
    }

    /**
     * Reset cache of outgoing call handlers
     */
    public static void clearAvailableCallHandlers() {
        AVAILABLE_HANDLERS = null;
    }

    /**
     * Interface for listener about load state of remote call handler plugin
     */
    public interface OnLoadListener {
        /**
         * Fired when call handler has been loaded
         * 
         * @param ch the call handler object that has been loaded
         */
        void onLoad(CallHandlerPlugin ch);
    }

    /**
     * Get the display label name for this call handler
     * 
     * @return A string to display to represent this call handler
     */
    public String getLabel() {
        return label;
    }

    /**
     * Get the icon bitmap for this call handler
     * 
     * @return the bitmap icon representing the call handler
     */
    public Bitmap getIcon() {
        return icon;
    }

    /**
     * Get the icon drawable for this call handler
     * 
     * @return the drawable icon representing the call handler
     */
    public Drawable getIconDrawable() {
        if (icon != null) {
            //return new BitmapDrawable(icon);
            return new BitmapDrawable(context.getResources(), icon);
        }
        return null;
    }

    /**
     * The pending intent to fire when user select this call handler This is
     * only populated once loadFromXXX has been launched and called back
     * 
     * @return the intent to fire
     */
    public PendingIntent getIntent() {
        return pendingIntent;
    }

    /**
     * The number to exclude from treatment in next run of outgoing call if any.
     * This is useful if the call handler also launch a make call intent to
     * ignore this intent from processing This is only populated once
     * loadFromXXX has been launched and called back
     * 
     * @return The phone number to ignore or null if none to ignore
     */
    public String getNextExcludeTelNumber() {
        return nextExclude;
    }

    /**
     * Get the call id as stored in db for this call handler Should be <
     * SipProfile.INVALID_ID
     * 
     * @return the sip account id
     */
    public long getAccountId() {
        return accountId;
    }

    /**
     * Build a fake sip profile object for this plugin call handler It will
     * contain id for this callhandler, display name and icon
     * 
     * @return the SipProfile equivalent object for this CallHandler
     */
    public SipProfile getFakeProfile() {
        SipProfile profile = new SipProfile();
        profile.id = accountId;
        profile.display_name = label;
        profile.icon = icon;
        return profile;

    }

    public static void initHandler() {
        if(sThreadHandler == null) {
            HandlerThread thread = new HandlerThread("CallHandlerPluginWorker");
            thread.start();
            sThreadHandler = new Handler(thread.getLooper());
        }
    }

}
