package com.seatel.mobilehall.data.network;

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

import com.android.volley.AuthFailureError;
import com.android.volley.Cache;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Network;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.BasicNetwork;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.HurlStack;
import com.seatel.mobilehall.BuildConfig;

import java.util.HashMap;
import java.util.Map;

public abstract class CoreRequest<T> {
    protected static final String PROTOCOL_CHARSET = "utf-8";
    private static RequestQueue sRequestQueue;
    private Context mContext;
    private int mTimeOut = 30000;
    private Response.ErrorListener mErrorListener;
    private Response.Listener<T> mOnResponseListener;
    private Request mRequest;
    private NetworkResponse mNetworkResponse;
    private VolleyError mVolleyError;

    public CoreRequest(Context context) {
        this.mContext = context;
    }

    public Context getContext() {
        return mContext;
    }

    public void setContext(Context context) {
        this.mContext = context;
    }

    public NetworkResponse getNetworkResponse() {
        return mNetworkResponse;
    }

    public void setNetworkResponse(NetworkResponse networkResponse) {
        this.mNetworkResponse = networkResponse;
    }

    public VolleyError getVolleyError() {
        return mVolleyError;
    }

    public void setVolleyError(VolleyError volleyError) {
        this.mVolleyError = volleyError;
    }

    public Response.Listener getOnResponseListener() {
        return mOnResponseListener;
    }

    public CoreRequest<T> setOnResponseListener(Response.Listener onResponseListener) {
        this.mOnResponseListener = onResponseListener;
        return this;
    }

    public Response.ErrorListener getOnErrorListener() {
        return mErrorListener;
    }

    public CoreRequest<T> setOnErrorListener(Response.ErrorListener onErrorListener) {
        this.mErrorListener = onErrorListener;
        return this;
    }

    public static RequestQueue getRequestQueue(Context context) {
        if (sRequestQueue == null) {
            Cache cache = new DiskBasedCache(context.getCacheDir(), 10 * 1024 * 1024);
            Network network = new BasicNetwork(new HurlStack());
            sRequestQueue = new RequestQueue(cache, network);
            // Don't forget to start the volley request queue
            sRequestQueue.start();
        }
        return sRequestQueue;
    }

    public void execute(Response.Listener onResponseListener) {
        setOnResponseListener(onResponseListener);
        execute();
    }

    public void execute() {
        if (getContext() != null) {
            mRequest = onMakeRequest();
            if (mRequest != null) {
                getRequestQueue(getContext()).add(mRequest);
                if (isCache()) {
                    Cache.Entry data = getRequestQueue(getContext()).getCache().get(getCacheKey());
                    if (data != null)
                        onResponse(onParseNetworkResponse(new NetworkResponse(200, data.data, data.responseHeaders, true)).result);
                }
            }
        } else {
            Log.e(this.getClass().getSimpleName(), "Error with context = null!");
        }
    }

    public void getCache() {
        mRequest = onMakeRequest();
        Cache.Entry data = getRequestQueue(getContext()).getCache().get(getCacheKey());
        if (data != null)
            onResponse(onParseNetworkResponse(new NetworkResponse(200, data.data, data.responseHeaders, true)).result);
    }

    public String getCacheKey() {
        if (mRequest != null)
            return getRequest().getCacheKey();
        return "";
    }

    public Request getRequest() {
        return mRequest;
    }

    public abstract int getMethod();

    public String getRequestUrl() {
        String url = getBaseUrl();
        RequestParams urlParams = new RequestParams();
        onGetUrlParams(urlParams);
        if (urlParams != null)
            url += urlParams.getParamsString();
        return url;
    }

    public void onGetUrlParams(RequestParams requestParams) {
    }

    public Request onMakeRequest() {
        Request<T> request = new Request<T>(getMethod(), getRequestUrl(), null) {
            @Override
            protected Response<T> parseNetworkResponse(NetworkResponse response) {
                setNetworkResponse(response);
                return onParseNetworkResponse(response);
            }

            public void deliverError(VolleyError error) {
                setVolleyError(error);
                onError(error);
            }

            @Override
            public String getBodyContentType() {
                try {
                    return onGetBodyContentType();
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }

            @Override
            protected void deliverResponse(T response) {
                onResponse(response);
            }

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

            @Override
            public byte[] getBody() {
                return onGetBody();
            }

            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                RequestParams params = new RequestParams();
                onGetParams(params);
                if (params != null) {
                    return params.getParams();
                } else {
                    return super.getParams();
                }
            }
        };
        request.setRetryPolicy(getRetryPolicy());
        request.setShouldCache(isCache());
        request.setTag(getRequestTag());
        return request;
    }

    public byte[] onGetBody() {
        try {
            return onGetBodyRequest().getBytes(PROTOCOL_CHARSET);
        } catch (Exception e) {
            return null;
        }
    }

    public String onGetBodyContentType() {
        return null;
    }

    public void onGetParams(RequestParams params) {
    }

    public void onError(VolleyError error) {
        try {
            if (BuildConfig.DEBUG) {
                if (error != null)
                    error.printStackTrace();
                Log.e("REQUEST_ERROR:>>>", this.getClass().getSimpleName() + " (" + getContext().getClass().getSimpleName() + ") : " + getRequestUrl());
                Log.e("REQUEST_ERROR:>>>", "Cause by : " + new String(error.networkResponse.data));
                Log.e("REQUEST_ERROR:>>>", "Status Code : " + error.networkResponse.statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        Response.ErrorListener mErrorListener = getOnErrorListener();
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

    public String onGetBodyRequest() {
        return null;
    }

    public Map<String, String> onCreateHeader(Map<String, String> header) {
        return header;
    }

    abstract protected Response<T> onParseNetworkResponse(NetworkResponse response);

    public void onResponse(T response) {
        if (getContext() != null) {
            Response.Listener callBack = getOnResponseListener();
            if (callBack != null)
                callBack.onResponse(response);
        }
    }

    public int getTimeOut() {
        return mTimeOut;
    }

    public void setTimeOut(int timeOut) {
        this.mTimeOut = timeOut;
    }

    public Object getRequestTag() {
        return this;
    }

    public RetryPolicy getRetryPolicy() {
        return new DefaultRetryPolicy(getTimeOut(), DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
    }

    public void cancel() {
        if (sRequestQueue != null && getRequestTag() != null) {
            sRequestQueue.cancelAll(getRequestTag());
        }
    }

    public void clearCache() {
        if (sRequestQueue != null) {
            sRequestQueue.getCache().remove(mRequest.getCacheKey());
        }
    }

    public static void cancelAll() {
        if (sRequestQueue != null) {
            sRequestQueue.stop();
        }
    }

    public static void clearAllCache() {
        if (sRequestQueue != null) {
            sRequestQueue.getCache().clear();
        }
    }

    private boolean isCache;

    public boolean isCache() {
        return isCache;
    }

    public void setShouldCache(boolean cache) {
        isCache = cache;
    }

    public abstract String getBaseUrl();

    public class RequestParams {
        private HashMap<String, String> params = new HashMap<>();

        public void put(String key, String value) {
            if (key == null || value == null || key.isEmpty())
                return;
            key = key.trim();
            value = value.trim();
            getParams().put(key, value);
        }

        public void put(String key, int value) {
            put(key, String.valueOf(value));
        }

        public void put(String key, double value) {
            put(key, String.valueOf(value));
        }

        public void put(String key, float value) {
            put(key, String.valueOf(value));
        }

        public void put(String key, boolean value) {
            put(key, String.valueOf(value));
        }

        public HashMap<String, String> getParams() {
            return params;
        }

        public void setParams(HashMap<String, String> params) {
            this.params = params;
        }

        public String getParamsString() {
            String paramsString = "";
            for (String key : params.keySet()) {
                paramsString = paramsString + (paramsString.isEmpty() ? "" : "&") + key + "=" + params.get(key);
            }
            return (paramsString.isEmpty() ? "" : "?") + paramsString;
        }
    }
}
