package tv.twitch.sdk;

import android.content.Context;

import tv.twitch.volley.AuthFailureError;
import tv.twitch.volley.DefaultRetryPolicy;
import tv.twitch.volley.NetworkResponse;
import tv.twitch.volley.Request;
import tv.twitch.volley.Response;
import tv.twitch.volley.VolleyError;
import tv.twitch.volley.toolbox.HttpHeaderParser;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.HashMap;
import java.util.Map;

import tv.twitch.CoreErrorCode;
import tv.twitch.ErrorCode;
import tv.twitch.HttpParameter;
import tv.twitch.HttpRequestResult;
import tv.twitch.api.DownloadManager;
import tv.twitch.util.LocaleUtil;

import tv.twitch.twiglib.Logger;

/**
 * Adapted by loohill on 05/02/18
 * Last sync: 05/03/18
 * Source: twitch-apps/twitch-android/blob/master/Twitch/src/main/java/tv/twitch/android/sdk/SDKHttpRequestProvider.java
 * Changes:
 *** Removed all codes for test and logger;
 *** Added our own logger;
 */

public class SDKHttpRequestProvider implements tv.twitch.IHttpRequestProvider {

    private final static String TAG = "Twitch_Http";

    private class GenericRequest extends Request<String> {

        private boolean mDone = false;
        private HashMap<String, String> mHeaders = new HashMap<>();

        private byte[] mBody;
        private HttpRequestResult mResult = null;

        public GenericRequest(int method, String url,
                              HttpParameter[] headerParams,
                              byte[] body,
                              int timeOutInSecs,
                              HttpRequestResult result) {
            super(method, url, null);

            // Do not perform automatic retries
            setRetryPolicy(new DefaultRetryPolicy(timeOutInSecs * 1000, 0, 1));

            mResult = result;
            mBody = body;

            if (headerParams != null) {
                for (HttpParameter header : headerParams) {

                    // The underlying httpurlconnection automatically encodes/decodes gzip for us,
                    // and setting it ourselves leads to the response being still gzipped.
                    // https://gist.github.com/premnirmal/8526542 see FrancoSabadini's response.
                    if (!header.name.equalsIgnoreCase("Accept-Encoding") || !header.value.equalsIgnoreCase("gzip")) {
                        mHeaders.put(header.name, header.value);
                    }
                }
            }

            mHeaders.put("Accept-Language", LocaleUtil.create().getApiLanguageCodeFromLocale());
        }

        public boolean isDone() {
            return mDone;
        }

        private void responseToResult(NetworkResponse response, String body) {
            mResult.statusCode = response.statusCode;
            if (response.headers != null) {
                mResult.headers = new HttpParameter[response.headers.size()];
                int i = 0;
                for (Map.Entry<String, String> entry : response.headers.entrySet()) {
                    mResult.headers[i] = new HttpParameter();
                    mResult.headers[i].name = entry.getKey();
                    mResult.headers[i].value = entry.getValue();
                    i++;
                }
            } else {
                mResult.headers = new HttpParameter[0];
            }
            mResult.response = body;

        }

        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            return mHeaders;
        }

        @Override
        public byte[] getBody() throws AuthFailureError {
            return mBody;
        }

        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            String parsed;
            if (mUtf8Charset != null) {
                parsed = new String(response.data, mUtf8Charset);
            } else {
                try {
                    parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
                } catch (UnsupportedEncodingException e) {
                    parsed = new String(response.data);
                }
            }
            responseToResult(response, parsed);
            return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
        }

        @Override
        protected void deliverResponse(String response) {
            mDone = true;
        }

        @Override
        public void deliverError(VolleyError error) {
            String parsed;
            if (error.networkResponse != null) {
                if (mUtf8Charset != null) {
                    parsed = new String(error.networkResponse.data, mUtf8Charset);
                } else {
                    try {
                        parsed = new String(error.networkResponse.data, HttpHeaderParser.parseCharset(error.networkResponse.headers));
                    } catch (UnsupportedEncodingException e) {
                        parsed = new String(error.networkResponse.data);
                    }
                }
                responseToResult(error.networkResponse, parsed);
            }
            mDone = true;
        }
    }

    private Context mContext;
    private DownloadManager mDownloadManager;
    private Charset mUtf8Charset;

    public SDKHttpRequestProvider(Context context) {
        mContext = context;
        mDownloadManager = DownloadManager.getInstance();
        try {
            mUtf8Charset = Charset.forName("UTF-8");
        } catch (IllegalCharsetNameException|UnsupportedCharsetException e) {
            mUtf8Charset = null;
        }
    }

    @Override
    public ErrorCode sendHttpRequest(String url,
                                     HttpParameter[] headerParams,
                                     byte[] requestBody,
                                     String httpReqType,
                                     int timeOutInSecs,
                                     HttpRequestResult result) {
        Logger.d(TAG, "SDK HTTP REQUEST STARTED: " + url);

        if (mDownloadManager == null || !mDownloadManager.isActive()) {
            return CoreErrorCode.TTV_EC_REQUEST_ABORTED;
        }

        // Prepare the parameters
        int requestType;
        switch (httpReqType.toUpperCase()) {
            case "PUT":
                requestType = Request.Method.PUT;
                break;
            case "POST":
                requestType = Request.Method.POST;
                break;
            case "DELETE":
                requestType = Request.Method.DELETE;
                break;
            case "GET":
            default:
                requestType = Request.Method.GET;
                break;
        }

        // Make the request
        result.statusCode = 0;
        GenericRequest request = new GenericRequest(requestType, url, headerParams, requestBody, timeOutInSecs, result);
        mDownloadManager.scheduleGlobalRequest(request);

        // Wait for the response
        ErrorCode ec;
        try {
            while (!request.isDone()) {
                if (mDownloadManager == null || !mDownloadManager.isActive()) {
                    // HACK: if activity stops, enqueued requests are not acted upon
                    return CoreErrorCode.TTV_EC_REQUEST_ABORTED;
                }
                Thread.sleep(200);
            }

            if (result.statusCode != 0) {
                Logger.d(TAG, "SDK HTTP SUCCESS REQUEST: " + url);
                ec = CoreErrorCode.TTV_EC_SUCCESS;
            } else {
                Logger.d(TAG, "SDK HTTP FAIL REQUEST: " + url);
                ec = CoreErrorCode.TTV_EC_API_REQUEST_FAILED;
            }

        } catch (InterruptedException e) {
            Logger.d(TAG, "SDK HTTP FAIL REQUEST: " + url);
            result.statusCode = 0;
            result.headers = new HttpParameter[0];
            result.response = "";
            ec = CoreErrorCode.TTV_EC_API_REQUEST_FAILED;
        }

        return ec;
    }
}
