/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.webapp;

import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;

import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Log;

public class InstallHelper implements NativeEventListener {
    private static final String LOGTAG = "GeckoWebappInstallHelper";
    private static final String[] INSTALL_EVENT_NAMES = new String[] {"Webapps:Postinstall"};
    private final Context mContext;
    private final InstallCallback mCallback;
    private final ApkResources mApkResources;

    public static interface InstallCallback {
        // on the GeckoThread
        void installCompleted(InstallHelper installHelper, String event, NativeJSObject message);

        // on the GeckoBackgroundThread
        void installErrored(InstallHelper installHelper, Exception exception);
    }

    public InstallHelper(Context context, ApkResources apkResources, InstallCallback cb) {
        mContext = context;
        mCallback = cb;
        mApkResources = apkResources;
    }

    public void startInstall(String profileName) throws IOException {
        startInstall(profileName, null);
    }

    public void startInstall(final String profileName, final JSONObject message) throws IOException {
        ThreadUtils.postToBackgroundThread(new Runnable() {
            @Override
            public void run() {
                try {
                    install(profileName, message);
                } catch (IOException e) {
                    handleException(e);
                }
            }
        });
    }

    protected void handleException(Exception e) {
        if (mCallback != null) {
            mCallback.installErrored(this, e);
        } else {
            Log.e(LOGTAG, "mozApps.install failed", e);
        }
    }

    void install(String profileName, JSONObject message) throws IOException {
        if (message == null) {
            message = new JSONObject();
        }

        // we can change the profile to be in the app's area here
        GeckoProfile profile = GeckoProfile.get(mContext, profileName);

        try {
            message.put("apkPackageName", mApkResources.getPackageName());
            message.put("manifestURL", mApkResources.getManifestUrl());
            message.put("title", mApkResources.getAppName());
            message.put("manifest", new JSONObject(mApkResources.getManifest(mContext)));

            String appType = mApkResources.getWebappType();
            message.putOpt("type", appType);
            if ("packaged".equals(appType)) {
                message.putOpt("updateManifest", new JSONObject(mApkResources.getMiniManifest(mContext)));
            }

            message.putOpt("profilePath", profile.getDir());

            if (mApkResources.isPackaged()) {
                File zipFile = copyApplicationZipFile();
                message.putOpt("zipFilePath", Uri.fromFile(zipFile).toString());
            }
        } catch (JSONException e) {
            handleException(e);
            return;
        }

        registerGeckoListener();

        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Webapps:AutoInstall", message.toString()));
        calculateColor();
    }

    public File copyApplicationZipFile() throws IOException {
        if (!mApkResources.isPackaged()) {
            return null;
        }

        Uri uri = mApkResources.getZipFileUri();

        InputStream in = null;
        OutputStream out = null;
        File destPath = new File(mApkResources.getFileDirectory(), "application.zip");
        try {
            in = mContext.getContentResolver().openInputStream(uri);
            out = new FileOutputStream(destPath);
            byte[] buffer = new byte[1024];
            int read = 0;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            out.flush();
        } catch (IOException e) {
            throw e;
        } finally {
            close(in);
            close(out);
        }
        return destPath;
    }

    private static void close(Closeable close) {
        if (close == null) {
            return;
        }
        try {
            close.close();
        } catch (IOException e) {
            // NOP
        }
    }

    public void registerGeckoListener() {
        EventDispatcher.getInstance().registerGeckoThreadListener(this, INSTALL_EVENT_NAMES);
    }

    private void calculateColor() {
        ThreadUtils.assertOnBackgroundThread();
        Allocator slots = Allocator.getInstance(mContext);
        int index = slots.getIndexForApp(mApkResources.getPackageName());
        Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(mApkResources.getAppIcon());
        slots.updateColor(index, BitmapUtils.getDominantColor(bitmap));
    }

    @Override
    public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, INSTALL_EVENT_NAMES);

        if (mCallback != null) {
            mCallback.installCompleted(this, event, message);
        }
    }
}
