package com.hokolinks.utils.networking;

import android.content.Context;
import android.util.Log;

import com.hokolinks.model.Device;
import com.hokolinks.utils.Utils;
import com.hokolinks.utils.lifecycle.ApplicationLifecycle;
import com.hokolinks.utils.lifecycle.ApplicationLifecycleCallback;
import com.hokolinks.utils.log.HokoLog;
import com.hokolinks.utils.networking.async.HttpRequest;
import com.hokolinks.utils.networking.async.HttpRequestCallback;

import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

/**
 * The Networking class is a wrapper around HokoHttpRequests in order to process them in a
 * serial queue, where requests can be retried in case of failure. The flushes are triggered by
 * a timer to avoid clogging the device's network. This class will also handle the persistence of
 * HokoHttpRequests in order to avoid losing any data on application closes/crashes and network
 * problems. It will also only try to flush in case the device recognizes it has internet
 * connectivity.
 */
public class Networking {

    // Filename to save the http tasks to storage
    private static final String HTTP_TASKS_FILENAME = "http_tasks";

    // Configuration of the Networking
    private static final int FLUSH_TIMER_INTERVAL = 30000; // in millis
    private static final int HTTP_TASKS_NUMBER_OF_RETRIES = 1;

    // Static class to avoid duplication of Networking instances
    private static Networking sInstance;
    final private List<HttpRequest> mHttpTasks;
    private Context mContext;
    private Timer mTimer;

    /**
     * Private constructor, will try to load the tasks from file to resume them as soon as possible.
     *
     * @param context A context object.
     */
    @SuppressWarnings("unchecked")
    private Networking(Context context) {
        mContext = context;
        List<HttpRequest> httpTasks;
        try {
            httpTasks = (List<HttpRequest>)
                    Utils.loadFromFile(HTTP_TASKS_FILENAME, context);
        } catch (ClassCastException e) {
            httpTasks = new ArrayList<>();
        }
        if (httpTasks == null) {
            httpTasks = new ArrayList<>();
        }
        mHttpTasks = httpTasks;
        flush();
        registerActivityLifecycleCallbacks();
    }

    /**
     * This function will setup the Networking static instance, resuming pending http requests
     * and starting normal functionality.
     *
     * @param context A context object.
     */
    public static void setupNetworking(Context context) {
        if (sInstance == null) {
            sInstance = new Networking(context);
        }
    }

    /**
     * Returns the static Networking instance.
     *
     * @return The static Networking instance.
     */
    public static Networking getNetworking() {
        return sInstance;
    }

    public Context getContext() {
        return mContext;
    }

    //Tasks

    /**
     * Executes the pending http request tasks in case the device has internet connectivity,
     * otherwise it will just reset the timer.
     */
    private void flush() {
        if (mHttpTasks.size() > 0 && Device.hasInternetConnectivity(mContext)) {
            stopFlushTimer();
            executeTasks();
        } else {
            startFlushTimer();
        }
    }

    /**
     * Adds an http request to the queue, will only add it in case it has not surpassed the maximum
     * number of retries.
     *
     * @param httpRequest A HttpRequest object.
     */
    public void addRequest(HttpRequest httpRequest) {
        if (httpRequest.getNumberOfRetries() < HTTP_TASKS_NUMBER_OF_RETRIES) {
            HokoLog.d("Adding request to queue");
            synchronized (mHttpTasks) {
                mHttpTasks.add(httpRequest);
            }
        }
        saveTasks();
    }

    private void removeRequest(HttpRequest httpRequest) {
        synchronized (mHttpTasks) {
            mHttpTasks.remove(httpRequest);
        }
    }

    /**
     * Executes the HttpRequests in a serial queue. Will only start the next request in case the
     * previous one has finished. This will handle incrementing the number of retries in case of
     * failure and re-adding them to the http request queue.
     */
    private void executeTasks() {
        ExecutorService service = Executors.newFixedThreadPool(1);
        synchronized (mHttpTasks) {
            for (final HttpRequest httpRequest : mHttpTasks) {
                service.execute(httpRequest.toRunnable(new HttpRequestCallback() {
                    @Override
                    public void onSuccess(JSONObject jsonObject) {
                        HokoLog.d("Success " + jsonObject.toString());
                        removeRequest(httpRequest);
                        saveTasks();
                    }

                    @Override
                    public void onFailure(Exception e) {
                        httpRequest.incrementNumberOfRetries();
                        removeRequest(httpRequest);
                        addRequest(httpRequest);
                    }
                }));
            }
        }
        service.execute(new Runnable() {
            @Override
            public void run() {
                startFlushTimer();
            }
        });
    }

    /**
     * Saves all the current http requests to file, guaranteeing persistence.
     */
    private void saveTasks() {
        Utils.saveToFile(mHttpTasks, HTTP_TASKS_FILENAME, mContext);
    }

    //Timer

    /**
     * Starts the flush timer in case it is stopped, will do nothing otherwise.
     */
    private synchronized void startFlushTimer() {
        if (mTimer != null) {
            return;
        }
        mTimer = new Timer();

        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                stopFlushTimer();
                flush();
            }
        }, FLUSH_TIMER_INTERVAL);
    }

    /**
     * Stops the flush timer.
     */
    private synchronized void stopFlushTimer() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }
    }

    // Application Lifecycle

    /**
     * Registers activity lifecycle callbacks to know when the application is in background and
     * foreground, stopping the timer or flushing.
     */
    private void registerActivityLifecycleCallbacks() {
        ApplicationLifecycle.registerApplicationLifecycleCallback(mContext,
                new ApplicationLifecycleCallback() {
                    @Override
                    public void onResume() {
                        flush();
                    }

                    @Override
                    public void onPause() {
                        stopFlushTimer();
                    }
                });
    }


}

@@ -5650,32 +5650,72 @@
 d saveTasks() %7B%0A
+        synchronized (mHttpTasks) %7B%0A    
         Utils.sa
@@ -5763,24 +5763,34 @@
  mContext);%0A
+        %7D%0A
     %7D%0A%0A    /

Save HTTP tasks to file in a thread safe way