package com.matthiasgraf.shoppingapp.tasks;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;

import com.matthiasgraf.shoppingapp.R;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;


/**
 * This class should handle all connection to the webAPI
 * @author Matthias Graf <matthias.graf.for.president@gmail.com>
 */
public class ApiConnector {

    private String mSite;
    private Map<String, String> mQueryParameter;
    private JSONObject mJsonPayload;
    private JSONObject mJsonAnswerObject;
    private JSONArray mJsonAnswerArray;
    private SharedPreferences mPreferences;
    private Context mContext;
    private String mMethod;

    private static String setUpUrl(String base, String site,
                                 Map<String, String> queryParameter){
        String url = base;
        if(url.charAt(url.length()-1) != '/')
            url += '/';
        url += site;
        if(queryParameter != null && !queryParameter.isEmpty()){
            url += '?';
            boolean first = true;
            for(String key : queryParameter.keySet()) {
                if (!first)
                    url += '&';
                else
                    first = false;
                url += key + "=" + queryParameter.get(key);
            }
        }
        return url;
    }

    private static HttpURLConnection setUpConnection(String urlStr, String requestMethod,
                                 String username, String password) throws IOException {
        //Set up the URL
        URL url = new URL(urlStr);

        //Create connection
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod(requestMethod);
        String basicAuth = username + ":" + password;
        basicAuth = "Basic " + new String(Base64.encode(basicAuth.getBytes(), Base64.NO_WRAP));
        urlConnection.setRequestProperty("Authorization", basicAuth);

        return urlConnection;

    }

    private static void writeToConnection(HttpURLConnection urlConnection,
                                 String payload) throws IOException {
        //Write into the connection
        urlConnection.setDoOutput(true);
        OutputStream outputStream = urlConnection.getOutputStream();
        OutputStreamWriter outputWriter = new OutputStreamWriter(outputStream);
        outputWriter.write(payload);
        outputWriter.close();
    }

    private static String readFromConnection(HttpURLConnection urlConnection)
                                 throws IOException {
        BufferedReader reader = null;
        try {
            //Read from the connection
            InputStream inputStream = urlConnection.getInputStream();
            if (inputStream == null) {
                return null;
            }
            reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder b = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                b.append(line);
            }
            return b.toString();
        } finally {
            if(reader != null)
                reader.close();
        }
    }

    private void checkForError(HttpURLConnection urlConnection) throws IOException, ApiException {
        int response = urlConnection.getResponseCode();
        Log.w(ApiConnector.class.getName(), "The response code was " + response);
        if(response < 200)
            throw new ApiException(mContext.getString(R.string.illegal_response_code) + response + "("+ urlConnection.getURL().toString() + ")");
        if(response < 300)
            return;
        if(response < 400)
            throw new ApiException(mContext.getString(R.string.illegal_response_code) + response + "("+ urlConnection.getURL().toString() + ")");
        if(response == 400)
            throw new ApiException(mContext.getString(R.string.illegal_response_code) + response + "("+ urlConnection.getURL().toString() + ")");
        if(response == 401)
            throw new ApiException(mContext.getString(R.string.not_authorized));
        if(response == 403)
            throw new ApiException(mContext.getString(R.string.forbidden));
        if(response == 404)
            throw new ApiException(mContext.getString(R.string.not_found) + "("+ urlConnection.getURL().toString() + ")");
        if(response > 404)
            throw new ApiException(mContext.getString(R.string.illegal_response_code) + response + "("+ urlConnection.getURL().toString() + ")");
    }

    /**
     * Set the JSON Payload of the request
     * @param mJsonPayload The payload
     */
    public void setJsonPayload(JSONObject mJsonPayload) {
        this.mJsonPayload = mJsonPayload;
    }

    /**
     * Set the query parameter as a map key=>value
     * @param mQueryParameter The query parameters
     */
    public void setQueryParameter(Map<String, String> mQueryParameter) {
        this.mQueryParameter = mQueryParameter;
    }

    /**
     * Set the site address
     * @param mSite The site address
     */
    public void setSite(String mSite) {
        this.mSite = mSite;
    }

    /**
     * Set the request method (GET, POST etc.)
     * @param mMethod The request method
     */
    public void setMethod(String mMethod){this.mMethod = mMethod;}

    /**
     * Get the JSON answer. This has to be called after perform()
     * @return The JSON answer from the server
     */
    public JSONObject getJsonAnswerObject() {
        return mJsonAnswerObject;
    }

    /**
     * Get the JSON answer. This has to be called after perform()
     * @return The JSON answer from the server
     */
    public JSONArray getJsonAnswerArray() {
        return mJsonAnswerArray;
    }

    /**
     * Perform the actual query. The success of this query can be checked with getSuccess().
     * (Potential) errors can be found with getErrorMessage(), the output with getJsonAnswer()
     */
    public void perform() throws ApiException {
        String defaultUrl = mContext.getString(R.string.default_server);
        String url = mPreferences.getString("url",defaultUrl);
        String username = mPreferences.getString("username", null);
        String password = mPreferences.getString("password", null);
        if(username == null || password == null)
            throw new ApiException("Username or password not given");

        HttpURLConnection urlConnection = null;
        try {
            urlConnection = setUpConnection(setUpUrl(url, mSite, mQueryParameter), mMethod, username, password);
            Log.d(ApiConnector.class.getName(), urlConnection.getURL().toString());
            if (mJsonPayload != null) {
                writeToConnection(urlConnection, mJsonPayload.toString());
            }
            urlConnection.connect();
            checkForError(urlConnection);
            String answer = readFromConnection(urlConnection);
            Log.d(ApiConnector.class.getName(), answer);
            try {
                mJsonAnswerObject = new JSONObject(answer);
            } catch (JSONException e) {
                try {
                    mJsonAnswerArray = new JSONArray(answer);
                } catch (JSONException e2) {
                    throw e;
                }

            }
        } catch (ApiException e){
            mJsonAnswerArray = null;
            mJsonAnswerObject = null;
            throw e;
        } catch (IOException|JSONException e) {
            mJsonAnswerArray = null;
            mJsonAnswerObject = null;
            throw new ApiException("Error while request " + e.getMessage(), e);
        } finally{
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
    }

    public ApiConnector(Context c){
        mContext = c;
        mPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
    }
}
