/**
 * OpenAtlasForAndroid Project
 * The MIT License (MIT) Copyright (OpenAtlasForAndroid) 2015 Bunny Blue,achellies
 * <p>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * <p>
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 * <p>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author BunnyBlue
 **/
package com.openatlas.framework;

import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Process;

import com.openatlas.framework.bundlestorage.BundleArchive;
import com.openatlas.log.Logger;
import com.openatlas.log.LoggerFactory;
import com.openatlas.log.OpenAtlasMonitor;
import com.openatlas.runtime.ClassNotFoundInterceptorCallback;
import com.openatlas.runtime.RuntimeVariables;
import com.openatlas.util.BundleLock;
import com.openatlas.util.FileUtils;
import com.openatlas.util.OpenAtlasFileLock;
import com.openatlas.util.OpenAtlasUtils;
import com.openatlas.util.StringUtils;

import org.osgi.framework.AdminPermission;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.service.startlevel.StartLevel;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;

//import org.osgi.framework.ServiceReference;

public final class Framework {
    private static final AdminPermission ADMIN_PERMISSION = new AdminPermission();
    private static String BASEDIR = null;
    private static String BUNDLE_LOCATION = null;
    static int CLASSLOADER_BUFFER_SIZE = 0;
    static boolean DEBUG_BUNDLES = true;
    static boolean DEBUG_CLASSLOADING = true;
    static boolean DEBUG_PACKAGES = true;
    static boolean DEBUG_SERVICES = true;
    static final String FRAMEWORK_VERSION = "1.0.0";
    private static final String DOWN_GRADE_FILE = "down_grade_list";
    static int LOG_LEVEL;
    static String STORAGE_LOCATION;
    @SuppressWarnings("unused")
    private static boolean STRICT_STARTUP;
    static List<BundleListener> bundleListeners = new ArrayList<BundleListener>();
    static Map<String, Bundle> bundles = new ConcurrentHashMap<String, Bundle>();
    private static ClassNotFoundInterceptorCallback classNotFoundCallback;
    static List<FrameworkListener> frameworkListeners = new ArrayList<FrameworkListener>();
    static boolean frameworkStartupShutdown = false;
    static int initStartlevel = 1;
    static final Logger log = LoggerFactory.getInstance("Framework");
    static Properties properties;
    static boolean restart = false;
    static int startlevel = 0;
    static List<BundleListener> syncBundleListeners = new ArrayList<BundleListener>();
    static SystemBundle systemBundle;
    static ClassLoader systemClassLoader;
    static List<String> writeAheads = new ArrayList<String>();



    private static final class SystemBundle implements Bundle,  StartLevel {
        private final Dictionary<String, String> props;
        int state;

        class ShutdownThread extends Thread {
            final boolean restart;

            ShutdownThread(boolean restart) {
                this.restart = restart;
            }

            @Override
            public void run() {
                Framework.shutdown(this.restart);
            }
        }

        class UpdateLevelThread extends Thread {
            final int targetLevel;

            UpdateLevelThread(int i) {
                this.targetLevel = i;
            }

            @Override
            public void run() {
                List bundles = Framework.getBundles();
                SystemBundle.this.setLevel((Bundle[]) bundles.toArray(new Bundle[bundles.size()]), this.targetLevel, false);
                Framework.notifyFrameworkListeners(BundleEvent.UPDATED, Framework.systemBundle, null);
                Framework.storeMetadata();
            }
        }


        class RefreshBundlesThread extends Thread {
            final Bundle[] bundleArray;

            RefreshBundlesThread(Bundle[] bundleArr) {
                this.bundleArray = bundleArr;
            }

            @Override
            public void run() {
            }
        }

        SystemBundle() {
            this.props = new Hashtable<String, String>();
            this.props.put(Constants.BUNDLE_NAME, Constants.SYSTEM_BUNDLE_LOCATION);
            this.props.put(Constants.BUNDLE_VERSION, Framework.FRAMEWORK_VERSION);
            this.props.put(Constants.BUNDLE_VENDOR, "OpenAtlas");
        }

        @Override
        public long getBundleId() {
            return 0;
        }

        @Override
        public Dictionary<String, String> getHeaders() {
            return this.props;
        }

        @Override
        public String getLocation() {
            return Constants.SYSTEM_BUNDLE_LOCATION;
        }

//        @Override
//        public ServiceReference[] getRegisteredServices() {
//            return null;
//        }

        @Override
        public URL getResource(String name) {
            return getClass().getResource(name);
        }

//        @Override
//        public ServiceReference[] getServicesInUse() {
//            return null;
//        }

        @Override
        public int getState() {
            return this.state;
        }

        @Override
        public boolean hasPermission(Object permission) {
            return true;
        }

        @Override
        public void start() throws BundleException {
        }

        @Override
        public void stop() throws BundleException {
            shutdownThread(false);
        }

        @Override
        public void uninstall() throws BundleException {
            throw new BundleException("Cannot uninstall the System Bundle");
        }

        @Override
        public void update() throws BundleException {
            shutdownThread(true);
        }

        private void shutdownThread(boolean z) {
            new ShutdownThread(z).start();
        }

        @Override
        public void update(InputStream inputStream) throws BundleException {
            shutdownThread(true);
        }

        @Override
        public void update(File file) throws BundleException {
            shutdownThread(true);
        }

        @Override
        public int getBundleStartLevel(Bundle bundle) {
            if (bundle == this) {
                return 0;
            }
            BundleImpl bundleImpl = (BundleImpl) bundle;
            if (bundleImpl.state != BundleEvent.INSTALLED) {
                return bundleImpl.currentStartlevel;
            }
            throw new IllegalArgumentException("Bundle " + bundle + " has been uninstalled");
        }

        @Override
        public int getInitialBundleStartLevel() {
            return Framework.initStartlevel;
        }

        @Override
        public int getStartLevel() {
            return Framework.startlevel;
        }

        @Override
        public boolean isBundlePersistentlyStarted(Bundle bundle) {
            if (bundle == this) {
                return true;
            }
            BundleImpl bundleImpl = (BundleImpl) bundle;
            if (bundleImpl.state != BundleEvent.INSTALLED) {
                return bundleImpl.persistently;
            }
            throw new IllegalArgumentException("Bundle " + bundle + " has been uninstalled");
        }

        @Override
        public void setBundleStartLevel(Bundle bundle, int level) {
            if (bundle == this) {
                throw new IllegalArgumentException("Cannot set the start level for the system bundle.");
            }
            BundleImpl bundleImpl = (BundleImpl) bundle;
            if (bundleImpl.state == BundleEvent.INSTALLED) {
                throw new IllegalArgumentException("Bundle " + bundle + " has been uninstalled");
            } else if (level <= 0) {
                throw new IllegalArgumentException("Start level " + level + " is not Component valid level");
            } else {
                bundleImpl.currentStartlevel = level;
                bundleImpl.updateMetadata();
                if (level <= Framework.startlevel && bundle.getState() != BundleEvent.RESOLVED && bundleImpl.persistently) {
                    try {
                        bundleImpl.startBundle();
                    } catch (Throwable e) {
                        e.printStackTrace();
                        Framework.notifyFrameworkListeners(BundleEvent.STARTED, bundle, e);
                    }
                } else if (level <= Framework.startlevel) {
                } else {
                    if (bundle.getState() != BundleEvent.STOPPED || bundle.getState() != BundleEvent.STARTED) {
                        try {
                            bundleImpl.stopBundle();
                        } catch (Throwable e2) {
                            Framework.notifyFrameworkListeners(BundleEvent.STARTED, bundle, e2);
                        }
                    }
                }
            }
        }

        @Override
        public void setInitialBundleStartLevel(int level) {
            if (level <= 0) {
                throw new IllegalArgumentException("Start level " + level + " is not Component valid level");
            }
            Framework.initStartlevel = level;
        }

        @Override
        public void setStartLevel(int i) {
            if (i <= 0) {
                throw new IllegalArgumentException("Start level " + i + " is not Component valid level");
            }
            new UpdateLevelThread(i).start();
        }

        @SuppressLint({"UseSparseArrays"})
        private void setLevel(Bundle[] bundles, int startlevel, boolean z) {
            if (Framework.startlevel != startlevel) {
                int iStartlevelHigh = startlevel > Framework.startlevel ? 1 : 0;
                int levelDiff = iStartlevelHigh != 0 ? startlevel - Framework.startlevel : Framework.startlevel - startlevel;
                Map hashMap = new HashMap(0);
                for (Bundle mBundle:bundles){
                    if (mBundle != Framework.systemBundle && (z || ((BundleImpl)mBundle).persistently)) {
                        int mLevelDiff;
                        BundleImpl bundleImpl = (BundleImpl) mBundle;
                        if (iStartlevelHigh != 0) {
                            mLevelDiff = (bundleImpl.currentStartlevel - Framework.startlevel) - 1;
                        } else {
                            mLevelDiff = Framework.startlevel - bundleImpl.currentStartlevel;
                        }
                        if (mLevelDiff >= 0 && mLevelDiff < levelDiff) {
                            Framework.addValue(hashMap, Integer.valueOf(mLevelDiff), bundleImpl);
                        }
                    }
                }

                for (int j = 0; j < levelDiff; j++) {
                    if (iStartlevelHigh != 0) {
                        Framework.startlevel++;
                    } else {
                        Framework.startlevel--;
                    }
                    List list = (List) hashMap.get(Integer.valueOf(j));
                    if (list != null) {
                        BundleImpl[] bundleImplArr = (BundleImpl[]) list.toArray(new BundleImpl[list.size()]);
                        for (int i = 0; i < bundleImplArr.length; i++) {
                            if (iStartlevelHigh != 0) {
                                try {
                                    System.out.println("STARTING " + bundleImplArr[i].location);
                                    bundleImplArr[i].startBundle();
                                } catch (Throwable e) {
                                    e.printStackTrace();
                                    e.printStackTrace();
                                    Framework.notifyFrameworkListeners(FrameworkEvent.ERROR, Framework.systemBundle, e);
                                }
                            } else if (bundleImplArr[i].getState() != 1) {
                                System.out.println("STOPPING " + bundleImplArr[i].location);
                                try {
                                    bundleImplArr[(bundleImplArr.length - i) - 1].stopBundle();
                                } catch (BundleException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }
                Framework.startlevel = startlevel;
            }
        }

        //@Override
        public void refreshPackages(Bundle[] bundleArr) {
            new RefreshBundlesThread(bundleArr).start();
        }

        @Override
        public String toString() {
            return "SystemBundle";
        }
    }

    static BundleImpl installNewBundle(String location, File apkFile) throws BundleException {
        BundleImpl bundleImpl;
        File mBundleArchiveFile = null;
        try {
            BundleLock.WriteLock(location);
            bundleImpl = (BundleImpl) Framework.getBundle(location);
            if (bundleImpl != null) {
                BundleLock.WriteUnLock(location);
            } else {
                mBundleArchiveFile = new File(STORAGE_LOCATION, location);

                OpenAtlasFileLock.getInstance().LockExclusive(mBundleArchiveFile);
                if (mBundleArchiveFile.exists()) {
                    bundleImpl = restoreFromExistedBundle(location, mBundleArchiveFile);
                    if (bundleImpl != null) {
                        BundleLock.WriteUnLock(location);
                        if (mBundleArchiveFile != null) {
                            OpenAtlasFileLock.getInstance().unLock(mBundleArchiveFile);
                        }
                    }
                }
                bundleImpl = new BundleImpl(mBundleArchiveFile, location, null, apkFile, true);
                storeMetadata();
                BundleLock.WriteUnLock(location);
                if (mBundleArchiveFile != null) {
                    OpenAtlasFileLock.getInstance().unLock(mBundleArchiveFile);
                }
            }
        } catch (Throwable e) {

            e.printStackTrace();
            BundleLock.WriteUnLock(location);
            throw new BundleException(e.getMessage());
        }

        return bundleImpl;
    }

    static boolean restoreBundle(String[] packageNames) {

        try {
            for (String pkgName : packageNames) {
                File archiveFile = new File(STORAGE_LOCATION, pkgName);
                if (!archiveFile.exists() || !BundleArchive.downgradeRevision(archiveFile)) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    static BundleImpl installNewBundle(String location, InputStream archiveInputStream) throws BundleException {
        BundleImpl bundleImpl = null;
        File mBundleArchiveFile = null;
        try {
            BundleLock.WriteLock(location);
            bundleImpl = (BundleImpl) getBundle(location);
            if (bundleImpl != null) {
                BundleLock.WriteUnLock(location);

            } else {
                mBundleArchiveFile = new File(STORAGE_LOCATION, location);
                OpenAtlasFileLock.getInstance().LockExclusive(mBundleArchiveFile);
                if (mBundleArchiveFile.exists()) {
                    bundleImpl = restoreFromExistedBundle(location, mBundleArchiveFile);
                    if (bundleImpl != null) {
                        BundleLock.WriteUnLock(location);
                        if (location != null) {
                            OpenAtlasFileLock.getInstance().unLock(mBundleArchiveFile);
                        }
                    }
                }
                bundleImpl = new BundleImpl(mBundleArchiveFile, location, archiveInputStream, null, true);
                storeMetadata();
                BundleLock.WriteUnLock(location);
                if (mBundleArchiveFile != null) {
                    OpenAtlasFileLock.getInstance().unLock(mBundleArchiveFile);
                }

            }
        } catch (Throwable v0) {
            BundleLock.WriteUnLock(location);
        }

        return bundleImpl;
    }


    private Framework() {
    }

    static void startup(Properties properties) throws BundleException {
        if (properties == null) {
            properties = new Properties();
        }
        Framework.properties = properties;
        startup();
    }

    private static void startup() throws BundleException {
        int startlevel;
        frameworkStartupShutdown = true;
        System.out.println("---------------------------------------------------------");
        System.out.println("  OpenAtlas OSGi 1.0.0  Pre-Release on " + Build.MODEL + "/" + Build.CPU_ABI + "/"
                + VERSION.RELEASE +" SDK version "+Build.VERSION.SDK_INT+ " starting ...");
        System.out.println("---------------------------------------------------------");
        long currentTimeMillis = System.currentTimeMillis();
        initialize();
        Framework.launch();
        boolean init = getProperty("osgi.init", false);
        if (init) {
            startlevel = -1;
        } else {
            startlevel = restoreProfile();
            restart = true;
        }
        if (startlevel == -1) {
            restart = false;
            File file = new File(STORAGE_LOCATION);
            if (init && file.exists()) {
                System.out.println("Purging storage ...");
                try {
                    deleteDirectory(file);
                } catch (Throwable e) {
                    throw new RuntimeException("deleteDirectory failed", e);
                }
            }
            try {
                file.mkdirs();
                Integer.getInteger("osgi.maxLevel", Integer.valueOf(1)).intValue();
                initStartlevel = getProperty("osgi.startlevel.bundle", 1);
                startlevel = getProperty("osgi.startlevel.framework", 1);
            } catch (Throwable e) {
                throw new RuntimeException("mkdirs failed", e);
            }
        }

        notifyFrameworkListeners(FrameworkEvent.STARTING, systemBundle, null);
        systemBundle.setLevel(getBundles().toArray(new Bundle[bundles.size()]), startlevel, false);
        frameworkStartupShutdown = false;
        if (!restart) {
            try {
                storeProfile();
            } catch (Throwable e) {
                throw new RuntimeException("storeProfile failed", e);
            }
        }
        long currentTimeMillis2 = System.currentTimeMillis() - currentTimeMillis;
        System.out.println("---------------------------------------------------------");
        System.out.println("  Framework " + (restart ? "restarted" : "started") + " in " + currentTimeMillis2 + " milliseconds.");
        System.out.println("---------------------------------------------------------");
        System.out.flush();
        systemBundle.state = BundleEvent.RESOLVED;
        try {
            notifyFrameworkListeners(FrameworkEvent.STARTED, systemBundle, null);
        } catch (Throwable e) {
            throw new RuntimeException("notifyFrameworkListeners failed", e);
        }
    }

    public static ClassLoader getSystemClassLoader() {
        return systemClassLoader;
    }

    public static List<Bundle> getBundles() {
        List<Bundle> arrayList = new ArrayList<Bundle>(bundles.size());
        synchronized (bundles) {
            arrayList.addAll(bundles.values());
        }
        return arrayList;
    }

    public static Bundle getBundle(String location) {
        return bundles.get(location);
    }

    public static Bundle getBundle(long j) {
        return null;
    }

    static void shutdown(boolean restart) {
        System.out.println("---------------------------------------------------------");
        System.out.println("  OpenAtlas OSGi shutting down ...");
        System.out.println("  Bye !");
        System.out.println("---------------------------------------------------------");
        systemBundle.state = BundleEvent.UNINSTALLED;
        systemBundle.setLevel(getBundles().toArray(new Bundle[bundles.size()]), 0, true);
        bundles.clear();
        systemBundle.state = BundleEvent.INSTALLED;
        if (restart) {
            try {
                startup();
            } catch (Throwable th) {
                th.printStackTrace();
            }
        }
    }

    public static void initialize() {

        File filesDir = RuntimeVariables.androidApplication.getFilesDir();
        if (filesDir == null || !filesDir.exists()) {
            filesDir = RuntimeVariables.androidApplication.getFilesDir();
        }
        BASEDIR = properties.getProperty(OpenAtlasInternalConstant.OPENATLAS_BASEDIR, filesDir.getAbsolutePath());
        BUNDLE_LOCATION = properties.getProperty(OpenAtlasInternalConstant.OPENATLAS_BUNDLE_LOCATION, "file:" + BASEDIR);
        CLASSLOADER_BUFFER_SIZE = getProperty(OpenAtlasInternalConstant.OPENATLAS_CLASSLOADER_BUFFER_SIZE, 1024 * 10);
        LOG_LEVEL = getProperty(OpenAtlasInternalConstant.OPENATLAS_LOG_LEVEL, 6);
        DEBUG_BUNDLES = getProperty(OpenAtlasInternalConstant.OPENATLAS_DEBUG_BUNDLES, false);
        DEBUG_PACKAGES = getProperty(OpenAtlasInternalConstant.OPENATLAS_DEBUG_PACKAGES, false);
        DEBUG_SERVICES = getProperty(OpenAtlasInternalConstant.OPENATLAS_DEBUG_SERVICES, false);
        DEBUG_CLASSLOADING = getProperty(OpenAtlasInternalConstant.OPENATLAS_DEBUG_CLASSLOADING, false);
        if (getProperty(OpenAtlasInternalConstant.OPENATLAS_DEBUG, false)) {
            System.out.println("SETTING ALL DEBUG FLAGS");
            LOG_LEVEL = 3;
            DEBUG_BUNDLES = true;
            DEBUG_PACKAGES = true;
            DEBUG_SERVICES = true;
            DEBUG_CLASSLOADING = true;
        }
        STRICT_STARTUP = getProperty(OpenAtlasInternalConstant.OPENATLAS_STRICT_STARTUP, false);
        String property = properties.getProperty("org.osgi.framework.system.packages");
        if (property != null) {
            StringTokenizer stringTokenizer = new StringTokenizer(property, ",");
            int countTokens = stringTokenizer.countTokens();
            for (int i = 0; i < countTokens; i++) {
                BundleClassLoader.FRAMEWORK_PACKAGES.add(stringTokenizer.nextToken().trim());
            }
        }
        properties.put(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, System.getProperty("java.specification.name") + "/" + System.getProperty("java.specification.version"));
        String key = Constants.FRAMEWORK_OS_NAME;
         property = System.getProperty("os.name");
        if (property == null) {
            property = "undefined";
        }
        properties.put(key, property);

        key = Constants.FRAMEWORK_OS_VERSION;
        property = System.getProperty("os.version");
        if (property == null) {
            property = "undefined";
        }
        properties.put(key, property);

        key = Constants.FRAMEWORK_PROCESSOR;
        property = System.getProperty("os.arch");
        if (property == null) {
            property = "undefined";
        }
        properties.put(key, property);
        properties.put(Constants.FRAMEWORK_VERSION, FRAMEWORK_VERSION);
        properties.put(Constants.FRAMEWORK_VENDOR, "OpenAtlas");
        property = Locale.getDefault().getLanguage();

        key = Constants.FRAMEWORK_LANGUAGE;
        if (property == null) {
            property = "en";
        }
        properties.put(key,property);


    }

    private static void launch() {
        STORAGE_LOCATION = properties.getProperty(OpenAtlasInternalConstant.INSTALL_LOACTION, properties.getProperty("org.osgi.framework.dir", BASEDIR + File.separatorChar + "storage"))
                + File.separatorChar;
        systemBundle = new SystemBundle();
        systemBundle.state = BundleEvent.UPDATED;
    }

    public static boolean getProperty(String key, boolean defaultValue) {
        if (properties == null) {
            return defaultValue;
        }
        String value = (String) properties.get(key);
        return value != null ? Boolean.valueOf(value).booleanValue() : defaultValue;
    }

    public static int getProperty(String key, int defaultValue) {
        if (properties == null) {
            return defaultValue;
        }
        String value = (String) properties.get(key);
        return value != null ? Integer.parseInt(value) : defaultValue;
    }

    public static String getProperty(String key) {
        if (properties == null) {
            return null;
        }
        return (String) properties.get(key);
    }

    public static String getProperty(String key, String defaultValue) {
        return properties == null ? defaultValue : (String) properties.get(key);
    }

    protected static void warning(String message) throws RuntimeException {
        if (getProperty(OpenAtlasInternalConstant.OPENATLAS_STRICT_STARTUP, false)) {
            throw new RuntimeException(message);
        }
        System.err.println("WARNING: " + message);
    }

    private static void storeProfile() {
        BundleImpl[] bundleImplArr = getBundles().toArray(new BundleImpl[bundles.size()]);
        for (BundleImpl updateMetadata : bundleImplArr) {
            updateMetadata.updateMetadata();
        }
        storeMetadata();
    }

    static void storeMetadata() {

        try {
            File metaFile = new File(STORAGE_LOCATION, "meta");

            DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(metaFile));
            dataOutputStream.writeInt(startlevel);
            String join = StringUtils.join(writeAheads.toArray(), ",");
            if (join == null) {
                join = "";
            }
            dataOutputStream.writeUTF(join);
            dataOutputStream.flush();
            dataOutputStream.close();

        } catch (IOException e) {
            OpenAtlasMonitor.getInstance().trace(Integer.valueOf(OpenAtlasMonitor.WRITE_META_FAIL), "", "", "storeMetadata failed ", e);
            log.error("Could not save meta data.", e);
        }
    }


    private static int restoreProfile() {
        try {
            System.out.println("Restoring profile");
            File meta = new File(STORAGE_LOCATION, "meta");
            if (meta.exists()) {
                DataInputStream dataInputStream = new DataInputStream(new FileInputStream(meta));
                int readInt = dataInputStream.readInt();
                String[] split = StringUtils.split(dataInputStream.readUTF(), ",");
                if (split != null) {
                    writeAheads.addAll(Arrays.asList(split));
                }
                dataInputStream.close();
                if (!getProperty(OpenAtlasInternalConstant.OPENATLAS_AUTO_LOAD, true)) {
                    return readInt;
                }
                File storageLocation = new File(STORAGE_LOCATION);
                mergeWalsDir(new File(STORAGE_LOCATION, "wal"), storageLocation);
                MergeWirteAheads(storageLocation);
                File[] listFiles = storageLocation.listFiles(new FilenameFilter() {
                    @Override
                    public boolean accept(File file, String str) {
                        return !str.matches("^[0-9]*");
                    }
                });
                int i = 0;
                while (i < listFiles.length) {
                    if (listFiles[i].isDirectory() && new File(listFiles[i], "meta").exists()) {
                        try {
                            System.out.println("RESTORED BUNDLE " + new BundleImpl(listFiles[i]).location);
                        } catch (Exception e) {
                            log.error(e.getMessage(), e.getCause());
                        }
                    }
                    i++;
                }
                return readInt;
            }
            System.out.println("Profile not found, performing clean start ...");
            return -1;
        } catch (Exception e2) {
            e2.printStackTrace();
            return 0;
        }
    }

    private static void mergeWalsDir(File walFile, File storageLocation) {
        if (writeAheads != null && writeAheads.size() > 0) {
            for (int i = 0; i < writeAheads.size(); i++) {
                if (writeAheads.get(i) != null) {
                    File mHeadDir = new File(walFile, writeAheads.get(i));
                    if (mHeadDir != null) {
                        try {
                            if (mHeadDir.exists()) {
                                File[] mHeadFiles = mHeadDir.listFiles();
                                if (mHeadFiles != null) {
                                    for (File mHeadFile : mHeadFiles) {
                                        if (mHeadFile.isDirectory()) {
                                            File targetFile = new File(storageLocation, mHeadFile.getName());
                                            if (targetFile.exists()) {
                                                File[] reversionList = mHeadFile.listFiles(new FilenameFilter() {
                                                    @Override
                                                    public boolean accept(File file, String str) {
                                                        return str.startsWith(BundleArchive.REVISION_DIRECTORY);
                                                    }
                                                });
                                                if (reversionList != null) {
                                                    for (File mFile : reversionList) {
                                                        if (new File(mFile, "meta").exists()) {
                                                            mFile.renameTo(new File(targetFile, mFile.getName()));
                                                        }
                                                    }
                                                }
                                            } else {
                                                mHeadFile.renameTo(targetFile);
                                            }
                                        }
                                    }
                                }
                            }
                        } catch (Throwable e) {
                            log.error("Error while merge wal dir", e);
                        }
                    }
                    writeAheads.set(i, null);
                }
            }
        }
        if (walFile.exists()) {
            walFile.delete();
        }
    }

    public static void deleteDirectory(File mDirectory) {
        File[] listFiles = mDirectory.listFiles();
        for (int i = 0; i < listFiles.length; i++) {
            if (listFiles[i].isDirectory()) {
                deleteDirectory(listFiles[i]);
            } else {
                listFiles[i].delete();
            }
        }
        mDirectory.delete();
    }


    static BundleImpl installNewBundle(String bundleName) throws BundleException {
        try {
            String location = bundleName.indexOf(":") > -1 ? bundleName : BUNDLE_LOCATION + File.separatorChar + bundleName;
            return installNewBundle(location, new URL(location).openConnection().getInputStream());
        } catch (Throwable e) {
            throw new BundleException("Cannot retrieve bundle from " + bundleName, e);
        }
    }

    private static BundleImpl restoreFromExistedBundle(String location, File file) {

        try {
            return new BundleImpl(file);
        } catch (Throwable e) {
            OpenAtlasMonitor.getInstance().trace(Integer.valueOf(-1), "", "", "restore bundle failed " + location + e);
            log.error("restore bundle failed" + location, e);
            return null;
        }
    }

    static void installOrUpdate(String[] locations, File[] archiveFiles) throws BundleException {
        if (locations == null || archiveFiles == null || locations.length != archiveFiles.length) {
            throw new IllegalArgumentException("locations and files must not be null and must be same length");
        }
        String valueOf = String.valueOf(System.currentTimeMillis());
        File file = new File(new File(STORAGE_LOCATION, "wal"), valueOf);
        file.mkdirs();
        int i = 0;
        while (i < locations.length) {
            if (!(locations[i] == null || archiveFiles[i] == null)) {
                try {
                    BundleLock.WriteLock(locations[i]);
                    Bundle bundle = getBundle(locations[i]);
                    if (bundle != null) {
                        bundle.update(archiveFiles[i]);
                    } else {
                        BundleImpl bundleImpl = new BundleImpl(new File(file, locations[i]), locations[i],  null, archiveFiles[i], false);
                    }
                    BundleLock.WriteUnLock(locations[i]);
                } catch (Throwable th) {
                    BundleLock.WriteUnLock(locations[i]);
                }
            }
            i++;
        }
        writeAheads.add(valueOf);
        storeMetadata();
    }


    static void notifyBundleListeners(int event, Bundle bundle) {

        if (!syncBundleListeners.isEmpty() || !bundleListeners.isEmpty()) {
            BundleEvent bundleEvent = new BundleEvent(event, bundle);
            BundleListener[] bundleListenerArr = syncBundleListeners.toArray(new BundleListener[syncBundleListeners.size()]);
            for (BundleListener bundleChanged : bundleListenerArr) {
                bundleChanged.bundleChanged(bundleEvent);
            }
            if (!bundleListeners.isEmpty()) {
                bundleListenerArr = bundleListeners.toArray(new BundleListener[bundleListeners.size()]);
                for (BundleListener bundleListener : bundleListenerArr) {
                    bundleListener.bundleChanged(bundleEvent);
                }

            }
        }
    }

    static void addFrameworkListener(FrameworkListener frameworkListener) {
        frameworkListeners.add(frameworkListener);
    }

    static void removeFrameworkListener(FrameworkListener frameworkListener) {
        frameworkListeners.remove(frameworkListener);
    }

    private static void restoreBundles() throws IOException {
        File file = new File(STORAGE_LOCATION, DOWN_GRADE_FILE);
        for (String pkg : FileUtils.getStrings(file)) {

            File locationFolder = new File(STORAGE_LOCATION, pkg);
            if (locationFolder.exists()) {
                String[] list = locationFolder.list();
                String version = null;
                if (list != null) {
                    for (String string : list) {
                        if (string.startsWith("version")
                                || Long.parseLong(StringUtils.substringAfter(string, ".")) <= 0) {
                            version = string;
                        }
                    }

                }
                if (version == null) {
                    FileUtils.deleteFile(locationFolder.getAbsolutePath());
                } else {
                    File tmp = new File(locationFolder, version);
                    if (tmp.exists()) {
                        FileUtils.deleteFile(tmp.getAbsolutePath());
                    }
                }
            }
        }
        if (file.exists()) {
            FileUtils.deleteFile(file.getAbsolutePath());
        }
    }

    private static void MergeWirteAheads(File storageLocation) {
        try {
            File wal = new File(STORAGE_LOCATION, "wal");
            String curProcessName = OpenAtlasUtils.getProcessNameByPID(Process.myPid());
            log.debug("restoreProfile in process " + curProcessName);
            String packageName = RuntimeVariables.androidApplication.getPackageName();
            if (curProcessName != null && packageName != null && curProcessName.equals(packageName)) {
                mergeWalsDir(wal, storageLocation);
            }
        } catch (Throwable th) {
            if (Build.MODEL == null || !Build.MODEL.equals("HTC 802w")) {
                log.error(th.getMessage(), th.getCause());
                return;
            }
           
        }
    }

    static void addBundleListener(BundleListener bundleListener) {
        bundleListeners.add(bundleListener);
    }

    static void removeBundleListener(BundleListener bundleListener) {
        bundleListeners.remove(bundleListener);
    }

    static void notifyFrameworkListeners(int event, Bundle bundle, Throwable th) {
        if (!frameworkListeners.isEmpty()) {
            FrameworkEvent frameworkEvent = new FrameworkEvent(event, bundle, th);
            FrameworkListener[] frameworkListenerArr = frameworkListeners.toArray(new FrameworkListener[frameworkListeners.size()]);
            for (FrameworkListener frameworkListener : frameworkListenerArr) {
                frameworkListener.frameworkEvent(frameworkEvent);
            }
        }
    }



    static void clearBundleTrace(BundleImpl bundleImpl) {

        if (bundleImpl.registeredFrameworkListeners != null) {
            frameworkListeners.removeAll(bundleImpl.registeredFrameworkListeners);
            bundleImpl.registeredFrameworkListeners = null;
        }

        if (bundleImpl.registeredBundleListeners != null) {
            bundleListeners.removeAll(bundleImpl.registeredBundleListeners);
            syncBundleListeners.removeAll(bundleImpl.registeredBundleListeners);
            bundleImpl.registeredBundleListeners = null;
        }

    }

    static void addValue(Map map, Object key, Object value) {
        List list = (List) map.get(key);
        if (list == null) {
            list = new ArrayList();
        }
        list.add(value);
        map.put(key, list);
    }

    static void removeValue(Map map, Object[] objArr, Object obj) {
        for (int i = 0; i < objArr.length; i++) {
            List list = (List) map.get(objArr[i]);
            if (list != null) {
                list.remove(obj);
                if (list.isEmpty()) {
                    map.remove(objArr[i]);
                } else {
                    map.put(objArr[i], list);
                }
            }
        }
    }





    public static boolean isFrameworkStartupShutdown() {
        return frameworkStartupShutdown;
    }

    public static ClassNotFoundInterceptorCallback getClassNotFoundCallback() {
        return classNotFoundCallback;
    }

    public static void setClassNotFoundCallback(ClassNotFoundInterceptorCallback classNotFoundInterceptorCallback) {
        classNotFoundCallback = classNotFoundInterceptorCallback;
    }


}