package de.lcs.wep.rest.request;

import de.lcs.wep.rest.AClient;
import de.lcs.wep.rest.Constants;
import de.lcs.wep.rest.request.post.IPostRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Sönke Greve on 20.10.2014.
 */
public class Request {

    public static final Logger logger = LogManager.getLogger(Request.class);

    private final String URL_PREFIX = "http://";
    private AClient client;
    private Constants.METHOD method;
    private Map<String, String> params;
    private Constants.REQUEST_TYPE type;

    /**
     * Creates a new REST Request specific to the WeP <i>(Wireless ePaper)</i> environment.
     *
     * @param client the client handling the request
     * @param type   the type of request <i>(GET or POST)</i>
     * @param method the method to be requested
     */
    public Request(AClient client, Constants.REQUEST_TYPE type, Constants.METHOD method) {
        this.client = client;
        this.type = type;
        this.method = method;
        this.params = new HashMap<String, String>();
    }

    public void addUrlParam(String name, String value) {
        params.put(name, value);
    }

    public String send() {
        HttpURLConnection con = null;
        String response = null;
        try {
            // build url and open connection
            logger.trace("creating connection");
            con = createConnection();
            logger.trace("connection created");
            // build and apply the authentication if necessary
            createAuthentication(con);
            // define the request method
            logger.trace("setting request method to " + type);
            con.setRequestMethod(type.toString());

            // define and post the input
            if (type.equals(Constants.REQUEST_TYPE.POST)) {
                logger.trace("writing post content");
                writeContent(con);
                logger.trace("post content written");
            }
            // handles return codes indicating http error
            handleHttpError(con);
            // retrieve the response string
            response = readResponse(con);

            logger.debug("Response is: " + response);
        } catch (Exception e) {
            logger.error(e);
        } finally {
            forceDisconnect(con);
        }
        return response;
    }

    private HttpURLConnection createConnection() throws IOException {
        String urlString = buildUrlString();
        URL url = new URL(urlString);
        logger.trace("Opening connection");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setDoOutput(true);
        return con;
    }

    private void forceDisconnect(HttpURLConnection con) {
    /* make sure request disconnects */
        if (con != null) {
            try {
                con.disconnect();
            } catch (Exception e) {
                logger.error(e);
            }
        }
    }

    private void handleHttpError(HttpURLConnection con) throws IOException {
        if (con.getResponseCode() != 200) {
            throw new RuntimeException("HTTP Request Failed with Error code : "
                    + con.getResponseCode());
        }
    }

    private void createAuthentication(HttpURLConnection con) {
        if (client.isAuthenticationRequired()) {
            logger.trace("creating authentication");
            String authProp = Constants.REQUEST_PROPERTY.AUTHORIZATION.toString();
            String authVal = client.buildBasicAuthString();
            con.setRequestProperty(authProp, authVal);
            logger.trace("authentication created");
        }else
        {
            logger.trace("no authentication required");
        }
    }

    private String readResponse(HttpURLConnection con) throws IOException {
        BufferedReader responseBuffer = new BufferedReader(new InputStreamReader(
                (con.getInputStream())));
        String output;
        StringBuilder sb = new StringBuilder();
        while ((output = responseBuffer.readLine()) != null) {
            sb.append(output);
        }
        String response = sb.toString();
        return response;
    }

    private void writeContent(HttpURLConnection con) throws IOException {

        if (!(this instanceof IPostRequest)) {
            throw new IllegalArgumentException("The request claims to contain HTTP POST content without implementing IPostRequest");
        }

        IPostRequest postRequest = (IPostRequest) this;

        // define the content type
        String prop = Constants.REQUEST_PROPERTY.CONTENT_TYPE.toString();
        String val = postRequest.getContentType();
        con.setRequestProperty(prop, val);

        String input = postRequest.getContent();

        logger.debug("Sending content: " + input);
        OutputStream outputStream = con.getOutputStream();
        outputStream.write(input.getBytes());
        outputStream.flush();
    }

    private String buildUrlString() {
        StringBuilder sb = new StringBuilder();
        sb.append(URL_PREFIX).append(client.getHost()).append(":").append(client.getPort());
        sb.append(method);

        boolean isFirstParam = true;
        if (params.size() > 0) {

            // add each parameter
            for (Map.Entry<String, String> param : params.entrySet()) {
                if (isFirstParam) {
                    sb.append("?");
                    isFirstParam = false;
                } else {
                    sb.append("&");
                }
                sb.append(param.getKey()).append("=").append(param.getValue());
            }
        }

        String result = sb.toString();
        try {
            result = java.net.URLDecoder.decode(result, "UTF-8");
            logger.debug("URL = " +  result);
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage());
        }

        return result;
    }
}
