/*
 * Copyright (c) 2016. Saiy Ltd. All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package ai.saiy.android.cognitive.emotion.provider.beyondverbal.http;

import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Pair;

import com.android.volley.AuthFailureError;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.ServerError;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.RequestFuture;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import ai.saiy.android.cognitive.emotion.provider.beyondverbal.containers.StartResponse;
import ai.saiy.android.utils.MyLog;

/**
 * Class to get a recording identifier that will be used for emotion analysis request. This identifier
 * should be stored for future use, as it can be used to retrieve meta-data regarding the recording
 * in the future.
 * <p/>
 * This is a synchronous request, as the identifier will be required immediately.
 * <p/>
 * Created by [email protected] on 08/06/2016.
 */
public class BVStartRequest {

    private static final boolean DEBUG = MyLog.DEBUG;
    private static final String CLS_NAME = BVStartRequest.class.getSimpleName();

    private static final String START_URL = "https://apiv4.beyondverbal.com/v4/recording/start";

    private static final String AUTHORIZATION = "Authorization";
    private static final String BEARER_ = "Bearer ";
    private static final String ENCODING = "UTF-8";
    private static final String CHARSET = "Accept-Charset";

    private static final long THREAD_TIMEOUT = 7L;

    private final Context mContext;
    private final String token;

    /**
     * Constructor
     *
     * @param mContext the application context
     * @param token    the access token
     */
    public BVStartRequest(@NonNull final Context mContext, @NonNull final String token) {
        this.token = token;
        this.mContext = mContext.getApplicationContext();
    }

    /**
     * Method to get a recording identifier.
     *
     * @return an {@link Pair} of which the first parameter will denote success and the second an
     * {@link StartResponse} object, containing the recording id.
     * <p>
     * If the request was unsuccessful, the second parameter may be null.
     */
    public Pair<Boolean, StartResponse> getId(@NonNull final JSONObject body) {
        if (DEBUG) {
            MyLog.i(CLS_NAME, "getId");
        }

        final RequestFuture<JSONObject> future = RequestFuture.newFuture();
        final RequestQueue queue = Volley.newRequestQueue(mContext);
        queue.start();

        final JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.POST, START_URL,
                body, future, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(final VolleyError error) {
                if (DEBUG) {
                    MyLog.w(CLS_NAME, "onErrorResponse: " + error.toString());
                    BVStartRequest.this.verboseError(error);
                }
                queue.stop();
            }
        }) {

            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                final Map<String, String> params = new HashMap<>();
                params.put(CHARSET, ENCODING);
                params.put(AUTHORIZATION, BEARER_ + token);
                return params;
            }
        };

        jsonObjReq.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS * 2,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        queue.add(jsonObjReq);

        JSONObject response = null;

        try {
            response = future.get(THREAD_TIMEOUT, TimeUnit.SECONDS);
        } catch (final InterruptedException e) {
            if (DEBUG) {
                MyLog.w(CLS_NAME, "execute: InterruptedException");
                e.printStackTrace();
            }
        } catch (ExecutionException e) {
            if (DEBUG) {
                MyLog.w(CLS_NAME, "execute: ExecutionException");
                e.printStackTrace();
            }
        } catch (TimeoutException e) {
            if (DEBUG) {
                MyLog.w(CLS_NAME, "execute: TimeoutException");
                e.printStackTrace();
            }
        } finally {
            queue.stop();
        }

        if (response != null) {
            if (DEBUG) {
                MyLog.i(CLS_NAME, "response: " + response);
            }

            final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
            final StartResponse startResponse = gson.fromJson(response.toString(), StartResponse.class);

            if (DEBUG) {
                MyLog.i(CLS_NAME, "onResponse: getStatus: " + startResponse.getStatus());
            }

            if (startResponse.isSuccessful()) {
                if (DEBUG) {
                    MyLog.i(CLS_NAME, "onResponse: getRecordingId: " + startResponse.getRecordingId());
                }
            } else {
                if (DEBUG) {
                    MyLog.i(CLS_NAME, "onResponse: getReason: " + startResponse.getReason());
                }
            }

            return new Pair<>(true, startResponse);

        } else {
            if (DEBUG) {
                MyLog.w(CLS_NAME, "response: failed");
            }
            return new Pair<>(false, null);
        }
    }

    /**
     * Used for debugging only to view verbose error information
     *
     * @param error the {@link VolleyError}
     */
    private void verboseError(@NonNull final VolleyError error) {

        final NetworkResponse response = error.networkResponse;

        if (response != null && error instanceof ServerError) {

            try {
                final String result = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
                MyLog.i(CLS_NAME, "result: " + result);
            } catch (final UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }
}