package org.lsposed.lspatch.service;

import android.content.Context;
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;

import org.lsposed.lspatch.loader.util.FileUtils;
import org.lsposed.lspatch.share.Constants;
import org.lsposed.lspatch.util.ModuleLoader;
import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.service.ILSPApplicationService;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipFile;

public class LocalApplicationService extends ILSPApplicationService.Stub {

    private static final String TAG = "LSPatch";

    private final List<Module> modules = new ArrayList<>();

    public LocalApplicationService(Context context) {
        try {
            Log.i(TAG, "LocalApplicationService: init context=" + context
                    + " package=" + context.getPackageName()
                    + " packageResourcePath=" + context.getPackageResourcePath()
                    + " cacheDir=" + context.getCacheDir()
                    + " classLoader=" + context.getClassLoader());
            var moduleAssetNames = context.getAssets().list("lspatch/modules");
            Log.i(TAG, "LocalApplicationService: module assets=" + java.util.Arrays.toString(moduleAssetNames));
            for (var name : moduleAssetNames) {
                String packageName = name.substring(0, name.length() - 4);
                String modulePath = context.getFilesDir() + "/lspatch/modules/" + packageName + "/";
                String cacheApkPath;
                try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
                    var moduleEntry = sourceFile.getEntry(Constants.EMBEDDED_MODULES_ASSET_PATH + name);
                    Log.i(TAG, "LocalApplicationService: module asset name=" + name
                            + " package=" + packageName
                            + " entry=" + moduleEntry
                            + " size=" + (moduleEntry == null ? -1 : moduleEntry.getSize())
                            + " crc=" + (moduleEntry == null ? -1 : moduleEntry.getCrc()));
                    cacheApkPath = modulePath + moduleEntry.getCrc() + ".apk";
                }

                if (!Files.exists(Paths.get(cacheApkPath))) {
                    Log.i(TAG, "Extract module apk: " + packageName);
                    FileUtils.deleteFolderIfExists(Paths.get(modulePath));
                    Files.createDirectories(Paths.get(modulePath));
                    try (var is = context.getAssets().open("lspatch/modules/" + name)) {
                        Files.copy(is, Paths.get(cacheApkPath));
                    }
                    Log.i(TAG, "LocalApplicationService: module extracted package=" + packageName
                            + " path=" + cacheApkPath
                            + " size=" + Files.size(Paths.get(cacheApkPath)));
                } else {
                    Log.i(TAG, "LocalApplicationService: module cache exists package=" + packageName
                            + " path=" + cacheApkPath
                            + " size=" + Files.size(Paths.get(cacheApkPath)));
                }

                var module = new Module();
                module.apkPath = cacheApkPath;
                module.packageName = packageName;
                module.file = ModuleLoader.loadModule(cacheApkPath);
                Log.i(TAG, "LocalApplicationService: module loaded package=" + packageName
                        + " apkPath=" + cacheApkPath
                        + " preloaded=" + (module.file != null)
                        + " classNames=" + (module.file == null ? null : module.file.moduleClassNames)
                        + " nativeNames=" + (module.file == null ? null : module.file.moduleLibraryNames)
                        + " dexCount=" + (module.file == null || module.file.preLoadedDexes == null ? -1 : module.file.preLoadedDexes.size()));
                modules.add(module);
            }
            Log.i(TAG, "LocalApplicationService: init done modules=" + modules.size());
        } catch (IOException e) {
            Log.e(TAG, "Error when initializing LocalApplicationServiceClient", e);
        }
    }

    @Override
    public boolean isLogMuted() throws RemoteException {
        return false;
    }

    @Override
    public List<Module> getLegacyModulesList() {
        Log.i(TAG, "LocalApplicationService: getLegacyModulesList modules=" + modules.size());
        return modules;
    }

    @Override
    public List<Module> getModulesList() {
        return new ArrayList<>();
    }

    @Override
    public String getPrefsPath(String packageName) {
        return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/").getAbsolutePath();
    }

    @Override
    public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) {
        return null;
    }

    @Override
    public IBinder asBinder() {
        return this;
    }
}
