package org.fdroid.fdroid.localrepo.type;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.net.LocalHTTPD;
import org.fdroid.fdroid.net.WifiStateChangeService;

import java.io.IOException;
import java.net.BindException;
import java.util.Random;

import rx.Single;
import rx.SingleSubscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Func2;
import rx.schedulers.Schedulers;

public class WifiSwap extends SwapType {

    private static final String TAG = "WifiSwap";

    private Handler webServerThreadHandler;
    private LocalHTTPD localHttpd;
    private final BonjourBroadcast bonjourBroadcast;

    public WifiSwap(Context context) {
        super(context);
        bonjourBroadcast = new BonjourBroadcast(context);
    }

    protected String getBroadcastAction() {
        return SwapService.WIFI_STATE_CHANGE;
    }

    public BonjourBroadcast getBonjour() {
        return bonjourBroadcast;
    }

    @Override
    public void start() {
        Utils.debugLog(TAG, "Preparing swap webserver.");
        sendBroadcast(SwapService.EXTRA_STARTING);

        if (FDroidApp.ipAddressString == null) {
            Log.e(TAG, "Not starting swap webserver, because we don't seem to be connected to a network.");
            setConnected(false);
        }

        Single.zip(
                Single.create(getWebServerTask()),
                Single.create(getBonjourTask()),
                new Func2<Boolean, Boolean, Boolean>() {
                    @Override
                    public Boolean call(Boolean webServerTask, Boolean bonjourServiceTask) {
                        return bonjourServiceTask && webServerTask;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.newThread())
                .subscribe(new Action1<Boolean>() {
                        @Override
                        public void call(Boolean success) {
                            setConnected(success);
                        }
                    },
                    new Action1<Throwable>() {
                        @Override
                        public void call(Throwable throwable) {
                            setConnected(false);
                        }
                    });
    }

    /**
     * A task which starts the {@link WifiSwap#bonjourBroadcast} and then emits a `true` value at
     * the end.
     */
    private Single.OnSubscribe<Boolean> getBonjourTask() {
        return new Single.OnSubscribe<Boolean>() {
            @Override
            public void call(SingleSubscriber<? super Boolean> singleSubscriber) {
                bonjourBroadcast.start();

                // TODO: Be more intelligent about failures here so that we can invoke
                // singleSubscriber.onError() in the appropriate circumstances.
                singleSubscriber.onSuccess(true);
            }
        };
    }

    /**
     * Constructs a new {@link Thread} for the webserver to run on. If successful, it will also
     * populate the webServerThreadHandler property and bind it to that particular thread. This
     * allows messages to be sent to the webserver thread by posting messages to that handler.
     */
    private Single.OnSubscribe<Boolean> getWebServerTask() {
        return new Single.OnSubscribe<Boolean>() {
            @Override
            public void call(final SingleSubscriber<? super Boolean> singleSubscriber) {
                new Thread(new Runnable() {
                    // Tell Eclipse this is not a leak because of Looper use.
                    @SuppressLint("HandlerLeak")
                    @Override
                    public void run() {
                        localHttpd = new LocalHTTPD(
                            context,
                            FDroidApp.ipAddressString,
                            FDroidApp.port,
                            context.getFilesDir(),
                            Preferences.get().isLocalRepoHttpsEnabled());

                        Looper.prepare(); // must be run before creating a Handler
                        webServerThreadHandler = new Handler() {
                            @Override
                            public void handleMessage(Message msg) {
                                Log.i(TAG, "we've been asked to stop the webserver: " + msg.obj);
                                localHttpd.stop();
                                Looper looper = Looper.myLooper();
                                if (looper == null) {
                                    Log.e(TAG, "Looper.myLooper() was null for sum reason while shutting down the swap webserver.");
                                } else {
                                    looper.quit();
                                }
                            }
                        };
                        try {
                            Utils.debugLog(TAG, "Starting swap webserver...");
                            localHttpd.start();
                            Utils.debugLog(TAG, "Swap webserver started.");
                            singleSubscriber.onSuccess(true);
                        } catch (BindException e) {
                            int prev = FDroidApp.port;
                            FDroidApp.port = FDroidApp.port + new Random().nextInt(1111);
                            context.startService(new Intent(context, WifiStateChangeService.class));
                            singleSubscriber.onError(new Exception("port " + prev + " occupied, trying on " + FDroidApp.port + "!"));
                        } catch (IOException e) {
                            Log.e(TAG, "Could not start local repo HTTP server", e);
                            singleSubscriber.onError(e);
                        }
                        Looper.loop(); // start the message receiving loop
                    }
                }).start();
            }
        };
    }

    @Override
    public void stop() {
        sendBroadcast(SwapService.EXTRA_STOPPING);
        if (webServerThreadHandler == null) {
            Log.i(TAG, "null handler in stopWebServer");
        } else {
            Utils.debugLog(TAG, "Sending message to swap webserver to stop it.");
            Message msg = webServerThreadHandler.obtainMessage();
            msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop";
            webServerThreadHandler.sendMessage(msg);
        }

        // Stop the Bonjour stuff after asking the webserver to stop. This is not required in this
        // order, but it helps. In practice, the Bonjour stuff takes a second or two to stop. This
        // should give enough time for the message we posted above to reach the web server thread
        // and for the webserver to thus be stopped.
        bonjourBroadcast.stop();
        setConnected(false);
    }

}
