package co.apptailor.Worker;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.devsupport.DevSupportManager;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import co.apptailor.Worker.core.BaseReactPackage;
import co.apptailor.Worker.core.ReactContextBuilder;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okio.Okio;
import okio.Sink;


public class WorkerModule extends ReactContextBaseJavaModule implements LifecycleEventListener {

    private String TAG = "WorkerManager";
    private HashMap<Integer, JSWorker> workers;

    private ReactPackage additionalWorkerPackages[];

    public WorkerModule(final ReactApplicationContext reactContext, ReactPackage additionalWorkerPackages[]) {
        super(reactContext);
        workers = new HashMap<>();
        this.additionalWorkerPackages = additionalWorkerPackages;
        reactContext.addLifecycleEventListener(this);
    }

    @Override
    public String getName() {
        return "WorkerManager";
    }

    @ReactMethod
    public void startWorker(final String jsFileName, final Promise promise) {
        Log.d(TAG, "Starting web worker - " + jsFileName);

        String jsFileSlug = jsFileName.contains("/") ? jsFileName.replaceAll("/", "_") : jsFileName;

        JSBundleLoader bundleLoader = getDevSupportManager().getDevSupportEnabled()
                ? createDevBundleLoader(jsFileName, jsFileSlug)
                : createReleaseBundleLoader(jsFileName, jsFileSlug);

        try {
            ArrayList<ReactPackage> workerPackages = new ArrayList<ReactPackage>(Arrays.asList(additionalWorkerPackages));
            workerPackages.add(0, new BaseReactPackage(getReactInstanceManager()));

            ReactContextBuilder workerContextBuilder = new ReactContextBuilder(getReactApplicationContext())
                    .setJSBundleLoader(bundleLoader)
                    .setDevSupportManager(getDevSupportManager())
                    .setReactPackages(workerPackages);

            JSWorker worker = new JSWorker(jsFileSlug);
            worker.runFromContext(
                    getReactApplicationContext(),
                    workerContextBuilder
            );
            workers.put(worker.getWorkerId(), worker);
            promise.resolve(worker.getWorkerId());
        } catch (Exception e) {
            promise.reject(e);
            getDevSupportManager().handleException(e);
        }
    }

    @ReactMethod
    public void stopWorker(final int workerId) {
        final JSWorker worker = workers.get(workerId);
        if (worker == null) {
            Log.d(TAG, "Cannot stop worker - worker is null for id " + workerId);
            return;
        }

        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                worker.terminate();
                workers.remove(workerId);
            }
        });
    }

    @ReactMethod
    public void postWorkerMessage(int workerId, String message) {
        JSWorker worker = workers.get(workerId);
        if (worker == null) {
            Log.d(TAG, "Cannot post message to worker - worker is null for id " + workerId);
            return;
        }

        worker.postMessage(message);
    }

    @ReactMethod
    public void startService() {
//        Activity activity = getCurrentActivity();
//        if (activity == null) { return; }
//        Intent intent = new Intent(activity, JSService.class);
//        activity.startService(intent);
    }

    @Override
    public void onHostResume() {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                for (int workerId : workers.keySet()) {
                    workers.get(workerId).onHostResume();
                }
            }
        });
    }

    @Override
    public void onHostPause() {
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                for (int workerId : workers.keySet()) {
                    workers.get(workerId).onHostPause();
                }
            }
        });
    }

    @Override
    public void onHostDestroy() {
        Log.d(TAG, "onHostDestroy - Clean JS Workers");

        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                for (int workerId : workers.keySet()) {
                    workers.get(workerId).terminate();
                }
            }
        });
    }

    @Override
    public void onCatalystInstanceDestroy() {
        super.onCatalystInstanceDestroy();
        onHostDestroy();
    }

    /*
     *  Helper methods
     */

    private JSBundleLoader createDevBundleLoader(String jsFileName, String jsFileSlug) {
        String bundleUrl = bundleUrlForFile(jsFileName);
        String bundleOut = getReactApplicationContext().getFilesDir().getAbsolutePath() + "/" + jsFileSlug;

        Log.d(TAG, "createDevBundleLoader - download web worker to - " + bundleOut);
        downloadScriptToFileSync(bundleUrl, bundleOut);

        return JSBundleLoader.createCachedBundleFromNetworkLoader(bundleUrl, bundleOut);
    }

    private JSBundleLoader createReleaseBundleLoader(String jsFileName, String jsFileSlug) {
        Log.d(TAG, "createReleaseBundleLoader - reading file from assets");
        return JSBundleLoader.createFileLoader(getReactApplicationContext(), "assets://workers/" + jsFileSlug + ".bundle");
    }

    private ReactInstanceManager getReactInstanceManager() {
        ReactApplication reactApplication = (ReactApplication)getCurrentActivity().getApplication();
        return reactApplication.getReactNativeHost().getReactInstanceManager();
    }

    private DevSupportManager getDevSupportManager() {
        return getReactInstanceManager().getDevSupportManager();
    }

    private String bundleUrlForFile(final String fileName) {
        // http://localhost:8081/index.android.bundle?platform=android&dev=true&hot=false&minify=false
        String sourceUrl = getDevSupportManager().getSourceUrl().replace("http://", "");
        return  "http://"
                + sourceUrl.split("/")[0]
                + "/"
                + fileName
                + ".bundle?platform=android&dev=true&hot=false&minify=false";
    }

    private void downloadScriptToFileSync(String bundleUrl, String bundleOut) {
        OkHttpClient client = new OkHttpClient();
        final File out = new File(bundleOut);

        Request request = new Request.Builder()
                .url(bundleUrl)
                .build();

        try {
            Response response = client.newCall(request).execute();
            if (!response.isSuccessful()) {
                throw new RuntimeException("Error downloading web worker script - " + response.toString());
            }

            Sink output = Okio.sink(out);
            Okio.buffer(response.body().source()).readAll(output);
        } catch (IOException e) {
            throw new RuntimeException("Exception downloading web worker script to file", e);
        }
    }
}