package com.vmichalak.sonoscontroller;

import com.vmichalak.sonoscontroller.exception.SonosControllerException;
import com.vmichalak.sonoscontroller.exception.UPnPSonosControllerException;
import okhttp3.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

class CommandBuilder {
    private final static int    SOAP_PORT                    = 1400;
    private final static String TRANSPORT_ENDPOINT           = "/MediaRenderer/AVTransport/Control";
    private final static String TRANSPORT_SERVICE            = "urn:schemas-upnp-org:service:AVTransport:1";
    private final static String RENDERING_ENDPOINT           = "/MediaRenderer/RenderingControl/Control";
    private final static String RENDERING_SERVICE            = "urn:schemas-upnp-org:service:RenderingControl:1";
    private final static String DEVICE_ENDPOINT              = "/DeviceProperties/Control";
    private final static String DEVICE_SERVICE               = "urn:schemas-upnp-org:service:DeviceProperties:1";
    private final static String CONTENT_DIRECTORY_ENDPOINT   = "/MediaServer/ContentDirectory/Control";
    private final static String CONTENT_DIRECTORY_SERVICE    = "urn:schemas-upnp-org:service:ContentDirectory:1";
    private final static String ZONE_GROUP_TOPOLOGY_ENDPOINT = "/ZoneGroupTopology/Control";
    private final static String ZONE_GROUP_TOPOLOGY_SERVICE  = "urn:upnp-org:serviceId:ZoneGroupTopology";

    private final static HashMap<Integer, String> ERROR_DESCRIPTION_MAP = new HashMap<Integer, String>();

    static {
        ERROR_DESCRIPTION_MAP.put(400, "Bad Request");
        ERROR_DESCRIPTION_MAP.put(401, "Invalid Action");
        ERROR_DESCRIPTION_MAP.put(402, "Invalid Args");
        ERROR_DESCRIPTION_MAP.put(404, "Invalid Var");
        ERROR_DESCRIPTION_MAP.put(412, "Precondition Failed");
        ERROR_DESCRIPTION_MAP.put(501, "Action Failed");
        ERROR_DESCRIPTION_MAP.put(600, "Argument Value Invalid");
        ERROR_DESCRIPTION_MAP.put(601, "Argument Value Out of Range");
        ERROR_DESCRIPTION_MAP.put(602, "Option Action Not Implemented");
        ERROR_DESCRIPTION_MAP.put(603, "Out Of Memory");
        ERROR_DESCRIPTION_MAP.put(604, "Human Intervention Required");
        ERROR_DESCRIPTION_MAP.put(605, "String Argument Too Long");
        ERROR_DESCRIPTION_MAP.put(606, "Action Not Authorized");
        ERROR_DESCRIPTION_MAP.put(607, "Signature Failure");
        ERROR_DESCRIPTION_MAP.put(608, "Signature Missing");
        ERROR_DESCRIPTION_MAP.put(609, "Not Encrypted");
        ERROR_DESCRIPTION_MAP.put(610, "Invalid Sequence");
        ERROR_DESCRIPTION_MAP.put(611, "Invalid Control Url");
        ERROR_DESCRIPTION_MAP.put(612, "No Such Session");
        ERROR_DESCRIPTION_MAP.put(714, "Illegal MIME-Type");
    }

    private static OkHttpClient httpClient;

    private final String endpoint;
    private final String service;
    private final String action;
    private final HashMap<String, String> bodyEntries = new HashMap<String, String>();

    public CommandBuilder(String endpoint, String service, String action) {
        this.endpoint = endpoint;
        this.service = service;
        this.action = action;
    }

    public static CommandBuilder transport(String action) {
        return new CommandBuilder(TRANSPORT_ENDPOINT, TRANSPORT_SERVICE, action);
    }

    public static CommandBuilder rendering(String action) {
        return new CommandBuilder(RENDERING_ENDPOINT, RENDERING_SERVICE, action);
    }

    public static CommandBuilder device(String action) {
        return new CommandBuilder(DEVICE_ENDPOINT, DEVICE_SERVICE, action);
    }

    public static CommandBuilder contentDirectory(String action) {
        return new CommandBuilder(CONTENT_DIRECTORY_ENDPOINT, CONTENT_DIRECTORY_SERVICE, action);
    }

    public static CommandBuilder zoneGroupTopology(String action) {
        return new CommandBuilder(ZONE_GROUP_TOPOLOGY_ENDPOINT, ZONE_GROUP_TOPOLOGY_SERVICE, action);
    }

    public static String downloadSpeakerInfo(String ip) throws IOException, SonosControllerException {
        String uri = "http://" + ip + ":" + SOAP_PORT + "/status/zp";
        Request request = new Request.Builder().url(uri).get().build();
        String response = getHttpClient().newCall(request).execute().body().string();
        handleError(ip, response);
        return response;
    }

    public CommandBuilder put(String key, String value) {
        this.bodyEntries.put(key, value);
        return this;
    }

    public String executeOn(String ip) throws IOException, SonosControllerException {
        String uri = "http://" + ip + ":" + SOAP_PORT + this.endpoint;
        String content = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\""
                + " s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body>"
                + "<u:" + this.action + " xmlns:u=\"" + this.service + "\">"
                + this.getBody()
                + "</u:" + this.action + ">"
                + "</s:Body></s:Envelope>";
        RequestBody body = RequestBody.create(MediaType.parse("application/text"), content.getBytes("UTF-8"));
        Request request = new Request.Builder().url(uri).addHeader("Content-Type", "text/xml")
                .addHeader("SOAPACTION", this.service + "#" + this.action).post(body).build();
        String response = getHttpClient().newCall(request).execute().body().string();
        handleError(ip, response);
        return response;
    }

    protected static void handleError(String ip, String response) throws SonosControllerException {
        if(!response.contains("errorCode")) { return; }
        int errorCode = Integer.parseInt(ParserHelper.findOne("<errorCode>([0-9]*)</errorCode>", response));
        String desc = ERROR_DESCRIPTION_MAP.get(errorCode);
        throw new UPnPSonosControllerException(
                "UPnP Error " + errorCode +" (" + desc + ") received from " + ip,
                errorCode, desc, response);
    }

    protected String getBody() {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry entry : bodyEntries.entrySet()) {
            sb.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">");
        }
        return sb.toString();
    }

    private static OkHttpClient getHttpClient() {
        if(httpClient == null) { httpClient = new OkHttpClient(); }
        return httpClient;
    }

    /*private static HttpClient getHttpClient() {
        if(httpClient == null) { httpClient = HttpClientBuilder.create().build(); }
        return httpClient;
    }*/

    private String escapeSpecialCharacters(String value) {
        if (value == null) {
            return null;
        }

        StringBuilder sb = new StringBuilder(value.length());
        for(int i = 0; i < value.length(); i++) {
            char c = value.charAt(i);
            switch(c) {
                case '&':
                    sb.append("&amp;");
                    break;
                case '<':
                    sb.append("&lt;");
                    break;
                case '>':
                    sb.append("&gt;");
                    break;
                case '"':
                    sb.append("&quot;");
                    break;
                case '\'':
                    sb.append("&apos;");
                    break;
                default:
                    sb.append(c);
                    break;
            }
        }
        return sb.toString();
    }


}

@@ -2718,24 +2718,205 @@
  Session%22);%0A
+        ERROR_DESCRIPTION_MAP.put(701, %22Invalid transition%22);%0A        ERROR_DESCRIPTION_MAP.put(702, %22No content%22);%0A        ERROR_DESCRIPTION_MAP.put(712, %22Unsupported Play Mode%22);%0A
         ERRO

Add some UPnP errors