package com.topjohnwu.magisk;

import android.app.job.JobService;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;

import com.topjohnwu.magisk.dummy.DummyProvider;
import com.topjohnwu.magisk.dummy.DummyReceiver;
import com.topjohnwu.magisk.dummy.DummyService;
import com.topjohnwu.magisk.utils.DynamicClassLoader;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import io.michaelrocks.paranoid.Obfuscate;

// Wrap the actual classloader as we only want to resolve classname
// mapping when loading from platform (via LoadedApk.mClassLoader)
@Obfuscate
class AppClassLoader extends ClassLoader {

    final Map<String, String> mapping = new HashMap<>();

    AppClassLoader(File apk) {
        super(new DynamicClassLoader(apk));
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        String clz = mapping.get(name);
        name = clz != null ? clz : name;
        return super.loadClass(name, resolve);
    }

    void updateComponentMap(PackageInfo stub, PackageInfo app) {
        {
            var src = stub.activities;
            var dest = app.activities;

            final ActivityInfo sa;
            final ActivityInfo da;
            final ActivityInfo sb;
            final ActivityInfo db;
            if (src[0].exported) {
                sa = src[0];
                sb = src[1];
            } else {
                sa = src[1];
                sb = src[0];
            }
            if (dest[0].exported) {
                da = dest[0];
                db = dest[1];
            } else {
                da = dest[1];
                db = dest[0];
            }
            mapping.put(sa.name, da.name);
            mapping.put(sb.name, db.name);
        }

        {
            var src = stub.services;
            var dest = app.services;

            final ServiceInfo sa;
            final ServiceInfo da;
            final ServiceInfo sb;
            final ServiceInfo db;
            if (JobService.PERMISSION_BIND.equals(src[0].permission)) {
                sa = src[0];
                sb = src[1];
            } else {
                sa = src[1];
                sb = src[0];
            }
            if (JobService.PERMISSION_BIND.equals(dest[0].permission)) {
                da = dest[0];
                db = dest[1];
            } else {
                da = dest[1];
                db = dest[0];
            }
            mapping.put(sa.name, da.name);
            mapping.put(sb.name, db.name);
        }

        {
            var src = stub.receivers;
            var dest = app.receivers;
            mapping.put(src[0].name, dest[0].name);
        }

        {
            var src = stub.providers;
            var dest = app.providers;
            mapping.put(src[0].name, dest[0].name);
        }
    }
}

class StubClassLoader extends ClassLoader {

    private final Map<String, Class<?>> mapping = new HashMap<>();

    StubClassLoader(PackageInfo info) {
        super(StubClassLoader.class.getClassLoader());
        for (var c : info.activities) {
            mapping.put(c.name, DownloadActivity.class);
        }
        for (var c : info.services) {
            mapping.put(c.name, DummyService.class);
        }
        for (var c : info.providers) {
            mapping.put(c.name, DummyProvider.class);
        }
        for (var c : info.receivers) {
            mapping.put(c.name, DummyReceiver.class);
        }
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> clz = mapping.get(name);
        return clz == null ? super.loadClass(name, resolve) : clz;
    }
}

class DelegateClassLoader extends ClassLoader {

    // The active classloader
    static ClassLoader cl = DelegateClassLoader.class.getClassLoader();

    DelegateClassLoader() {
        super(null);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        return cl.loadClass(name);
    }
}
