/**
 * Copyright (c) 2014 Baidu, Inc. All Rights Reserved.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.baidu.android.gporter.proxy.service;

import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;

import com.baidu.android.gporter.GPTComponentInfo;
import com.baidu.android.gporter.ProxyEnvironment;
import com.baidu.android.gporter.proxy.ProxyUtil;
import com.baidu.android.gporter.stat.ExceptionConstants;
import com.baidu.android.gporter.stat.ReportManger;
import com.baidu.android.gporter.util.Constants;
import com.baidu.android.gporter.util.JavaCalls;
import com.baidu.android.gporter.util.Util;

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

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URISyntaxException;
import java.util.HashMap;

/**
 * Service代理,承载所有插件Service
 *
 * @author liuhaitao
 * @since 2014年12月3日
 */
public class ServiceProxy extends Service {

    /**
     * DEBUG 开关
     */
    public static final boolean DEBUG = true & Constants.DEBUG;
    /**
     * TAG
     */
    public static final String TAG = "ServiceProxy";

    /**
     * 停止Service的方法
     */
    private static final String METHOD_STOPSERVICETOKEN = "stopServiceToken";
    /**
     * 改变Service前后台的方法
     */
    private static final String METHOD_SETSERVICEFOREGROUND = "setServiceForeground";
    /**
     * 存储Services的SP文件
     */
    private static final String SP_FILENAME = "com.baidu.android.gpt.Services.";
    /**
     * 存储Services的SP KEY
     */
    private static final String KEY_SERVICES = "runing_services";
    /**
     * Json字符串Key:包名
     */
    private static final String JKEY_PKG = "package_name";
    /**
     * Json字符串Key:类名
     */
    private static final String JKEY_CLASS_NAME = "class_name";
    /**
     * Json字符串Key:上一次Start的Intent
     */
    private static final String JKEY_LAST_INTENT = "last_intent";

    /**
     * ServiceProxy实例
     */
    protected static ServiceProxy sInstance = null;

    /**
     * 保存启动的Service的信息
     *
     * @author liuhaitao
     * @since 2014年12月3日
     */
    static class ServiceRecord {

        /**
         * Service对应的ComponentName
         */
        ComponentName name;
        /**
         * 运行的Service对象
         */
        Service service;
        /**
         * 是否前台进程运行
         */
        boolean isForeground; // is service currently in foreground mode?
        /**
         * 前台运行的通知
         */
        int foregroundId; // Notification ID of last foreground req.
        /**
         * 被杀死时是否停止
         */
        boolean stopIfKilled;
        /**
         * 最后一个start ID
         */
        int lastStartId;
        /**
         * 最后一个Intent
         */
        Intent lastIntent;
    }

    /**
     * 记录当前启动的Service的HashMap
     */
    private HashMap<String, ServiceRecord> mServices = new HashMap<String, ServiceProxy.ServiceRecord>();
    /**
     * IActivityManager的桩
     */
    private Object mIActivityManagerProxy = null;

    /**
     * Handler
     */
    private Handler mHandler;

    /**
     * 从Intent获取插件的Service信息
     *
     * @param intent intent
     * @return 插件
     */
    public static ComponentName getTargetComponent(Intent intent) {
        if (intent == null) {
            return null;
        }

        GPTComponentInfo info = GPTComponentInfo.parseFromIntent(intent);
        if (info == null) {
            return null;
        }

        String packageName = info.packageName;
        String targetService = info.className;

        if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(targetService)) {
            return null;
        }

        return new ComponentName(packageName, targetService);
    }

    /**
     * 获取参数中对应类型的参数
     *
     * @param <T> 参数类型
     * @author liuhaitao
     * @since 2014年12月3日
     */
    private static class ArgumentParser<T> {

        /**
         * 获取参数方法
         *
         * @param args   所有参数
         * @param prefer 参数所在的位置
         * @return 参数
         */
        @SuppressWarnings("unchecked")
        T get(Object[] args, int prefer) {
            if (args == null) {
                return null;
            }

            T ret = null;

            int pos = 0;
            for (Object arg : args) {
                try {
                    ret = (T) arg;
                    if (pos == prefer) {
                        break;
                    }
                } catch (ClassCastException e) {
                    if (DEBUG) {
                        e.printStackTrace();
                    }

                }
                pos++;
            }

            return ret;

        }
    }

    /**
     * 从其他Context 来stopService
     *
     * @param cpName 插件Component
     * @return 参考系统1表示stop成功,-1表示没查记录,可能已经被stop了,0表示没找到service
     */
    public static int stopServiceExternal(ComponentName cpName) {
        if (DEBUG) {
            Log.i(TAG, "--- stopServiceExternal : " + cpName);
        }
        if (sInstance != null) {
            return sInstance.stopService(cpName);
        } else {
            if (DEBUG) {
                Log.i(TAG, "--- stopServiceExternal ret=0: " + cpName);
            }
            return 0;
        }
    }

    /**
     * stop插件Service
     *
     * @param cpName 插件ComponentName
     * @return 参考系统1表示stop成功,-1表示没查记录,可能已经被stop了
     */
    public int stopService(ComponentName cpName) {
        ServiceRecord sr = mServices.get(cpName.toString());
        if (sr == null) {
            if (DEBUG) {
                Log.d(TAG, "### Proxy stopService ret = 0, cp=" + cpName);
            }
            return 0;
        }

        sr.service.onDestroy();
        updateServicesToSp();

        mServices.remove(cpName.toString());
        if (mServices.isEmpty()) {
            stopSelf();
        }

        if (DEBUG) {
            Log.d(TAG, "### Proxy stopService ret = 1, cp=" + cpName);
        }

        return 1;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        sInstance = this;

        if (DEBUG) {
            Log.i(TAG, "---  ServiceProxy start!!!");
        }

        // 初始化工具
        try {
            Class<?> iActivityManagerClazz = Class.forName("android.app.ActivityManagerNative");
            mIActivityManagerProxy = Proxy.newProxyInstance(iActivityManagerClazz.getClassLoader(),
                    iActivityManagerClazz.getInterfaces(),
                    new InvocationHandler() {

                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                            if (DEBUG) {
                                Log.d(TAG, "--- ServiceProxy am method called : " + method.toGenericString());
                            }

                            Object result = null;

                            String methodName = method.getName();
                            if (METHOD_STOPSERVICETOKEN.equals(methodName)) {
                                result = false;

                                Integer startId = 0;
                                final ComponentName cpName = new ArgumentParser<ComponentName>().get(args, 0);
                                if (DEBUG) {
                                    Log.d(TAG, "--- METHOD_STOPSERVICETOKEN : " + cpName.toString());
                                }
                                startId = new ArgumentParser<Integer>().get(args, 2); // SUPPRESS CHECKSTYLE
                                if (cpName != null && startId != null) {
                                    final ServiceRecord sr = mServices.get(cpName.toString());
                                    if (sr != null && (sr.lastStartId == startId.intValue() || startId == -1)) {
                                        result = true;
                                        mHandler.post(new Runnable() {

                                            @Override
                                            public void run() {
                                                stopService(cpName);
                                            }
                                        });

                                    }
                                }
                            } else if (METHOD_SETSERVICEFOREGROUND.equals(methodName)) {
                                ComponentName cpName = null;
                                Integer id = null;
                                Notification notification = null;
                                Boolean removeNotification = null;
                                cpName = new ArgumentParser<ComponentName>().get(args, 0);
                                if (DEBUG) {
                                    Log.d(TAG, "--- METHOD_SETSERVICEFOREGROUND : " + cpName.toString());
                                }
                                id = new ArgumentParser<Integer>().get(args, 2);
                                notification = new ArgumentParser<Notification>().get(args, 3);
                                if (Build.VERSION.SDK_INT >= 24) {
                                    int removeNotificationFlag = new ArgumentParser<Integer>().get(args, 4);
                                    if ((removeNotificationFlag & 1) != 0) {
                                        removeNotification = true;
                                    }
                                } else {
                                    removeNotification = new ArgumentParser<Boolean>().get(args, 4);
                                }

                                if (cpName != null && id != null && removeNotification != null) {
                                    ServiceRecord sr = mServices.get(cpName.toString());

                                    if (sr != null) {

                                        NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                                        if (id.intValue() != 0) {

                                            if (notification == null) {
                                                throw new IllegalArgumentException("null notification");
                                            }

                                            nm.notify(id.intValue(), notification);
                                            sr.foregroundId = id.intValue();
                                            sr.isForeground = true;
                                        } else {
                                            nm.cancel(sr.foregroundId);
                                            sr.foregroundId = 0;
                                            sr.isForeground = false;
                                        }
                                    }
                                }
                            }

                            return result;
                        }
                    });
        } catch (ClassNotFoundException e) {
            if (DEBUG) {
                Log.e(TAG, "### Get am failed, can not support service!!! msg=" + e.getMessage());
            }
        }

        if (mIActivityManagerProxy == null) {

            // 容错,不能支持Service了
            stopSelf();
            return;
        }

        mHandler = new Handler();

        // 从SharedPref读取原来运行的Service信息
        SharedPreferences sp = getSharedPreferences(SP_FILENAME + getClass().getSimpleName(), Context.MODE_PRIVATE);
        String jsonStr = sp.getString(KEY_SERVICES, null);

        if (jsonStr != null) {
            try {
                JSONArray jarr = new JSONArray(jsonStr);
                for (int i = 0; i < jarr.length(); i++) {
                    JSONObject jo = jarr.getJSONObject(i);

                    String packageName = jo.getString(JKEY_PKG);
                    String className = jo.getString(JKEY_CLASS_NAME);
                    Intent srvIntent = null;
                    if (jo.has(JKEY_LAST_INTENT)) {
                        try {
                            srvIntent = Intent.parseUri(jo.getString(JKEY_LAST_INTENT), 0);
                        } catch (URISyntaxException e) {
                            if (DEBUG) {
                                e.printStackTrace();
                            }
                        }
                    }

                    // 创建默认intent
                    if (srvIntent == null) {
                        srvIntent = new Intent();
                    }

                    srvIntent.setComponent(new ComponentName(packageName, className));
                    if (DEBUG) {
                        Log.i(TAG, "--- Schedule restart service by intent : " + srvIntent.toUri(0));
                        Log.i(TAG, "--- packageName=" + packageName + ", className=" + className + ", srvIntent="
                                + srvIntent);
                    }

                    ProxyEnvironment.enterProxy(getApplicationContext(), srvIntent, true, true);
                }
            } catch (JSONException e) {
                if (DEBUG) {
                    Log.w(TAG, "### Parse json fail : " + jsonStr);
                }
            }
        }

    }

    /**
     * 加载插件Service
     *
     * @param intent        传递给Service的Intent
     * @param componentName 插件Service对应的ComponentName
     * @return service信息
     */
    public ServiceRecord loadTarget(Intent intent, ComponentName componentName, boolean fromBind) {

        if (DEBUG) {
            Log.i(TAG, "--- Try to create service : " + componentName);
        }

        ServiceRecord sr = null;

        // 插件Service没有运行,创建插件Service
        if (!ProxyEnvironment.isEnterProxy(componentName.getPackageName())) {
            GPTComponentInfo info = GPTComponentInfo.parseFromIntent(intent);
            if (info != null && !info.reschedule) {
                Intent intentCpy = new Intent(intent);
                intentCpy.setComponent(componentName);
                ProxyEnvironment.enterProxy(this, intentCpy, true, true);
            }
        } else {
            Service service = null;
            try {
                service = (Service) ProxyEnvironment.getInstance(componentName.getPackageName()).getDexClassLoader()
                        .loadClass(componentName.getClassName()).newInstance();
                Application app = ProxyEnvironment.getInstance(componentName.getPackageName()).getApplication();
                JavaCalls.invokeMethod(service, "attach",
                        new Class<?>[]{Context.class, Class.forName("android.app.ActivityThread"), String.class,
                                IBinder.class, Application.class, Object.class},
                        new Object[]{app, null, componentName.getClassName(), null, app, mIActivityManagerProxy});

                // OpPackageName调用的地方一般都是aidl接口传递包名给system server,所以要用宿主的包名
                Object baseContext = service.getBaseContext();
                JavaCalls.setField(baseContext, "mOpPackageName", ProxyEnvironment
                        .getInstance(service.getPackageName())
                        .getHostPackageName());

                ProxyUtil.replaceSystemServices(app);
            } catch (InstantiationException e) {
                if (DEBUG) {
                    e.printStackTrace();
                }
                ReportManger.getInstance().onException(this, componentName.getPackageName(),
                        Util.getCallStack(e), ExceptionConstants.TJ_78730014);
            } catch (IllegalAccessException e) {
                if (DEBUG) {
                    e.printStackTrace();
                }
                ReportManger.getInstance().onException(this, componentName.getPackageName(),
                        Util.getCallStack(e), ExceptionConstants.TJ_78730014);
            } catch (ClassNotFoundException e) {
                if (DEBUG) {
                    e.printStackTrace();
                }
                ReportManger.getInstance().onException(this, componentName.getPackageName(),
                        Util.getCallStack(e), ExceptionConstants.TJ_78730014);
            } catch (SecurityException e) {
                if (DEBUG) {
                    e.printStackTrace();
                }
                ReportManger.getInstance().onException(this, componentName.getPackageName(),
                        Util.getCallStack(e), ExceptionConstants.TJ_78730014);
            } catch (IllegalArgumentException e) {
                if (DEBUG) {
                    e.printStackTrace();
                }
                ReportManger.getInstance().onException(this, componentName.getPackageName(),
                        Util.getCallStack(e), ExceptionConstants.TJ_78730014);
            } catch (RuntimeException e) {
                if (DEBUG) {
                    e.printStackTrace();
                }
                ReportManger.getInstance().onException(this, componentName.getPackageName(),
                        Util.getCallStack(e), ExceptionConstants.TJ_78730014);
            } catch (java.lang.VerifyError e) {
                if (DEBUG) {
                    e.printStackTrace();
                }
                ReportManger.getInstance().onException(this, componentName.getPackageName(),
                        Util.getCallStack(e), ExceptionConstants.TJ_78730014);
            }

            if (service != null) {
                sr = new ServiceRecord();
                sr.service = service;
                sr.name = componentName;

                // 因为bind的方式如果放到队列中存储。ubind后,如果不从队列中删除。进程再次启动后会通过 startService 的方式启动。
                // 然后 如果此进程意外终止,系统会 schedule service 重启。
                if (!fromBind) {
                    mServices.put(componentName.toString(), sr);
                }
                service.onCreate();

                if (DEBUG) {
                    Log.i(TAG, "--- Create service success : " + componentName);
                }

            } else {
                if (DEBUG) {
                    Log.e(TAG, "### Create service fail! cp=" + componentName);
                }
            }
        }

        return sr;
    }

    @Override
    public IBinder onBind(Intent intent) {
        if (mIActivityManagerProxy == null) {
            // 容错,不能支持Service了
            return null;
        }

        ComponentName target = getTargetComponent(intent);
        if (target == null) {
            if (mServices.isEmpty()) {
                stopSelf();
            }
            return null;
        }

        // 获取SR
        ServiceRecord sr = mServices.get(target.toString());
        if (sr == null) {
            sr = loadTarget(intent, target, true);
        }

        // SR还是空的,可能是load失败了
        if (sr == null) {
            if (mServices.isEmpty()) {
                stopSelf();
            }
            return null;
        }

        updateServicesToSp();

        return sr.service.onBind(intent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (DEBUG) {
            Log.d(TAG, "onStartCommand(Intent intent, int flags, int startId) : intent="
                    + ((intent == null) ? "null." : intent.toString())
                    + "; flags=" + flags + "; startId=" + startId);
        }

        if (mIActivityManagerProxy == null) {
            // 容错,不能支持Service了
            return 0;
        }

        if (intent == null) {
            return START_STICKY;
        }

        // 调用super,返回值用super的
        int ret = super.onStartCommand(intent, flags, startId);

        // 调用插件的onStartCommand方法
        int targetRet = 0;
        ComponentName target = getTargetComponent(intent);
        if (target == null) {
            if (mServices.isEmpty()) {
                stopSelf();
            }
            return ret;
        }

        // 插件SDK不能支持百度PushService,暂时先屏蔽了。
        if (TextUtils.equals(target.getClassName(), "com.baidu.android.pushservice.PushService")) {
            return ret;
        }

        // 获取SR
        ServiceRecord sr = mServices.get(target.toString());
        if (sr == null) {
            sr = loadTarget(intent, target, false);
        }

        // SR还是空的,可能是load失败了
        if (sr == null) {
            if (mServices.isEmpty()) {
                stopSelf();
            }
            return ret;
        }

        // 解决andorid 5.0 service get Serializable extra 找不到class的问题。
        intent.setExtrasClassLoader(ProxyEnvironment.getInstance(target.getPackageName()).getDexClassLoader());

        targetRet = sr.service.onStartCommand(intent, flags, startId);

        // 处理插件返回的ret
        switch (targetRet) {
            case Service.START_STICKY_COMPATIBILITY:
            case Service.START_STICKY: {
                sr.stopIfKilled = false;
                break;
            }
            case Service.START_NOT_STICKY: {
                if (sr.lastStartId == startId) {
                    sr.stopIfKilled = true;
                }
                break;
            }
            case Service.START_REDELIVER_INTENT: {
                sr.lastIntent = new Intent(intent);
                sr.stopIfKilled = false;

                // 更新Intent
                updateServicesToSp();
                break;
            }
            default:
                throw new IllegalArgumentException("Unknown service start result: " + targetRet);
        }

        updateServicesToSp();

        return ret;
    }

    @Override
    public void onDestroy() {

        if (DEBUG) {
            Log.i(TAG, "--- ServiceProxy onDestroy()!");
        }
        sInstance = null;

        // 停止所有还未停止运行的插件Service
        for (ServiceRecord sr : mServices.values()) {
            sr.service.onDestroy();
        }
        mServices.clear();

        updateServicesToSp();
        super.onDestroy();

    }

    /**
     * 把当前运行的Service信息更新到SharedPreferences里。
     */
    public void updateServicesToSp() {
        SharedPreferences sp = getSharedPreferences(SP_FILENAME + getClass().getSimpleName(), Context.MODE_PRIVATE);
        Editor edit = sp.edit();
        if (mServices.isEmpty()) {
            edit.putString(KEY_SERVICES, null);
        } else {
            JSONArray jarr = new JSONArray();
            for (String name : mServices.keySet()) {
                ServiceRecord sr = mServices.get(name);
                if (sr.stopIfKilled) {

                    // service不用重启的话,用不着存下来
                    continue;
                }
                JSONObject jo = new JSONObject();
                try {
                    jo.put(JKEY_PKG, sr.name.getPackageName());
                    jo.put(JKEY_CLASS_NAME, sr.name.getClassName());
                    if (sr.lastIntent != null) {
                        jo.put(JKEY_LAST_INTENT, sr.lastIntent.toUri(0));
                    }
                    jarr.put(jo);
                } catch (JSONException e) {
                    if (DEBUG) {
                        e.printStackTrace();
                    }
                    continue;
                }
            }
            edit.putString(KEY_SERVICES, jarr.toString());
        }
        edit.commit();
    }
}