package com.lody.virtual.client.stub;

import android.annotation.TargetApi;
import android.app.Service;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.lody.virtual.client.core.InvocationStubManager;
import com.lody.virtual.client.hook.proxies.am.ActivityManagerStub;
import com.lody.virtual.helper.utils.VLog;
import com.lody.virtual.helper.collection.SparseArray;
import com.lody.virtual.os.VUserHandle;

import java.util.Map;

import static com.lody.virtual.server.job.VJobSchedulerService.JobConfig;
import static com.lody.virtual.server.job.VJobSchedulerService.JobId;
import static com.lody.virtual.server.job.VJobSchedulerService.get;

/**
 * @author Lody
 *         <p>
 *         This service running on the Server process.
 */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class StubJob extends Service {

    private static final String TAG = StubJob.class.getSimpleName();
    private final SparseArray<JobSession> mJobSessions = new SparseArray<>();
    private JobScheduler mScheduler;
    private final IJobService mService = new IJobService.Stub() {

        @Override
        public void startJob(JobParameters jobParams) throws RemoteException {
            int jobId = jobParams.getJobId();
            IBinder binder = mirror.android.app.job.JobParameters.callback.get(jobParams);
            IJobCallback callback = IJobCallback.Stub.asInterface(binder);
            Map.Entry<JobId, JobConfig> entry = get().findJobByVirtualJobId(jobId);
            if (entry == null) {
                emptyCallback(callback, jobId);
                mScheduler.cancel(jobId);
            } else {
                JobId key = entry.getKey();
                JobConfig config = entry.getValue();
                synchronized (mJobSessions) {
                    JobSession session = mJobSessions.get(jobId);
                    if (session != null) {
                        // Job Session has exist.
                        emptyCallback(callback, jobId);
                    } else {
                        session = new JobSession(jobId, callback, jobParams);
                        mirror.android.app.job.JobParameters.callback.set(jobParams, session.asBinder());
                        mirror.android.app.job.JobParameters.jobId.set(jobParams, key.clientJobId);
                        Intent service = new Intent();
                        service.setComponent(new ComponentName(key.packageName, config.serviceName));
                        service.putExtra("_VA_|_user_id_", VUserHandle.getUserId(key.vuid));
                        boolean bound = false;
                        try {
                            bound = bindService(service, session, 0);
                        } catch (Throwable e) {
                            VLog.e(TAG, e);
                        }
                        if (bound) {
                            mJobSessions.put(jobId, session);
                        } else {
                            emptyCallback(callback, jobId);
                            mScheduler.cancel(jobId);
                            get().cancel(jobId);
                        }
                    }
                }
            }
        }

        @Override
        public void stopJob(JobParameters jobParams) throws RemoteException {
            int jobId = jobParams.getJobId();
            synchronized (mJobSessions) {
                JobSession session = mJobSessions.get(jobId);
                if (session != null) {
                    session.stopSession();
                }
            }
        }
    };

    /**
     * Make JobScheduler happy.
     */
    private void emptyCallback(IJobCallback callback, int jobId) {
        try {
            callback.acknowledgeStartMessage(jobId, false);
            callback.jobFinished(jobId, false);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        InvocationStubManager.getInstance().checkEnv(ActivityManagerStub.class);
        mScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);

        Log.d("Q_M", "StubJob-->onCreate");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mService.asBinder();
    }

    private final class JobSession extends IJobCallback.Stub implements ServiceConnection {

        private int jobId;
        private IJobCallback clientCallback;
        private JobParameters jobParams;
        private IJobService clientJobService;

        JobSession(int jobId, IJobCallback clientCallback, JobParameters jobParams) {
            this.jobId = jobId;
            this.clientCallback = clientCallback;
            this.jobParams = jobParams;
        }

        @Override
        public void acknowledgeStartMessage(int jobId, boolean ongoing) throws RemoteException {
            clientCallback.acknowledgeStartMessage(jobId, ongoing);
        }

        @Override
        public void acknowledgeStopMessage(int jobId, boolean reschedule) throws RemoteException {
            clientCallback.acknowledgeStopMessage(jobId, reschedule);
        }

        @Override
        public void jobFinished(int jobId, boolean reschedule) throws RemoteException {
            clientCallback.jobFinished(jobId, reschedule);
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            clientJobService = IJobService.Stub.asInterface(service);
            if (clientJobService == null) {
                emptyCallback(clientCallback, jobId);
                stopSession();
                return;
            }
            try {
                clientJobService.startJob(jobParams);
            } catch (RemoteException e) {
                forceFinishJob();
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }

        void forceFinishJob() {
            try {
                clientCallback.jobFinished(jobId, false);
            } catch (RemoteException e) {
                e.printStackTrace();
            } finally {
                stopSession();
            }
        }

        void stopSession() {
            if (clientJobService != null) {
                try {
                    clientJobService.stopJob(jobParams);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            mJobSessions.remove(jobId);
            unbindService(this);
        }
    }

}
