package com.socrata.datasync.publishers;

import com.socrata.datasync.SocrataConnectionInfo;
import com.socrata.datasync.config.userpreferences.UserPreferences;
import com.socrata.datasync.imports2.*;
import com.socrata.datasync.job.GISJob;
import com.socrata.datasync.job.JobStatus;
import com.socrata.datasync.HttpUtility;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.util.EntityUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class GISPublisher {

    private static final Logger logging = Logger.getLogger(GISJob.class.getName());
    private static String ticket = "";

    public static JobStatus replaceGeo(File file,
                                       SocrataConnectionInfo connectionInfo,
                                       String datasetID,
                                       Map<String, String> layerMap,
                                       UserPreferences userPrefs) {
        try {
            URI scan_url = makeUri(connectionInfo.getUrl(), "scan", "");
            Blueprint blueprint = postRawFile(scan_url, file, userPrefs);
            return replaceGeoFile(blueprint, file, userPrefs, connectionInfo, datasetID, layerMap);
        } catch (IOException e) {
            String message = e.getMessage();
            JobStatus status = JobStatus.PUBLISH_ERROR;
            status.setMessage(message);
            return status;
        }
    }

    private static JobStatus replaceGeoFile(Blueprint blueprint,
                                            File file,
                                            UserPreferences userPrefs,
                                            SocrataConnectionInfo connectionInfo,
                                            String datasetID,
                                            Map<String, String> layerMap) {
        try {
            if (blueprint.getError() != null) {
                String message = blueprint.getError().getMessage();
                logging.log(Level.INFO, message);
                JobStatus status = JobStatus.PUBLISH_ERROR;
                status.setMessage(message);
                return status;
            }

            ObjectMapper mapper = new ObjectMapper();

            applyLayerMapToBlueprintSummary(blueprint.getSummary(), layerMap);
            String blueprintSummary = mapper.writeValueAsString(blueprint.getSummary());

            String query = "&fileId=" +  URLEncoder.encode(blueprint.getFileId(),"UTF-8");
            query = query + "&name=" + URLEncoder.encode(file.getName(),"UTF-8");
            query = query + "&blueprint=" + URLEncoder.encode(blueprintSummary,"UTF-8");
            query = query + "&viewUid=" + URLEncoder.encode(datasetID,"UTF-8");

            URI uri = makeUri(connectionInfo.getUrl(),"replace",query);
            logging.log(Level.INFO, uri.toString());
            return postReplaceGeoFile(uri, connectionInfo, userPrefs);
        } catch (IOException e) {
            String message = e.getMessage();
            JobStatus status = JobStatus.PUBLISH_ERROR;
            status.setMessage(message);
            return status;
        }
    }

    private static void applyLayerMapToBlueprintSummary(Summary summary, Map<String, String> layerMap) {
        for (Layer layer : summary.getLayers()) {
            if (layerMap.containsKey(layer.getName())) {
                layer.setReplacingUid(layerMap.get(layer.getName()));
            }
        }
    }

    private static JobStatus postReplaceGeoFile(URI uri,
                                                SocrataConnectionInfo connectionInfo,
                                                UserPreferences userPrefs) {
        HttpUtility httpUtility = new HttpUtility(userPrefs, true, 3, 2);

        try {
            HttpEntity empty = MultipartEntityBuilder.create().build();
            HttpResponse response = httpUtility.post(uri,empty);


            HttpEntity resEntity = response.getEntity();
            String result = EntityUtils.toString(resEntity);
            JSONParser parser = new JSONParser();
            JSONObject resJson = (JSONObject) parser.parse(result);
            logging.log(Level.FINE, result);

            boolean error = (boolean) resJson.get("error");

            if (error) {
                JobStatus s = JobStatus.PUBLISH_ERROR;
                String error_message = (String) resJson.get("message");
                s.setMessage(error_message);
                return s;
            }

            ticket = resJson.get("ticket").toString();

            try {
                logging.log(Level.INFO, "Polling for Status...");
                return pollForStatus(ticket, userPrefs, connectionInfo, false);
            } catch (InterruptedException e) {
                // This should be very rare, but we should throw if it happens.
                throw new RuntimeException(e);
            }
        } catch (IOException | ParseException e) {
            String message = e.getMessage();
            JobStatus status = JobStatus.PUBLISH_ERROR;
            status.setMessage(message);
            return status;
        }
    }

    private static JobStatus pollForStatus(String ticket, UserPreferences userPrefs, SocrataConnectionInfo connectionInfo, boolean complete) throws InterruptedException {
        if (!complete) {
            URI status_url = makeUri(connectionInfo.getUrl(), "status", ticket);
            String[] status = getStatus(status_url, userPrefs);
            logging.log(Level.FINE,status[1]);
            Thread.sleep(1000);

            if (status[0].equals("Complete")) {
                return JobStatus.SUCCESS;
            }

            if (status[0].equals("Error")) {
                JobStatus s = JobStatus.PUBLISH_ERROR;
                s.setMessage(status[1]);
                return s;
            }

            pollForStatus(ticket, userPrefs, connectionInfo, false);
        }

        return JobStatus.SUCCESS;
    }

    private static String[] getStatus(URI uri,
                                      UserPreferences userPrefs) {
        HttpUtility httpUtility = new HttpUtility(userPrefs, true, 3, 2);
        String[] status = new String[2];

        try {
            HttpResponse response = httpUtility.get(uri,"application/json");
            HttpEntity resEntity = response.getEntity();
            String result = EntityUtils.toString(resEntity);
            JSONParser parser = new JSONParser();
            JSONObject resJson = (JSONObject) parser.parse(result);
            try {
                boolean error = (boolean) resJson.get("error");
                JSONObject details = (JSONObject) resJson.get("details");
                if (error){
                    status[0] = "Error";
                    status[1] = resJson.get("message").toString() + " with code: " + resJson.get("code").toString();
                    return status;
                } else {
                    try {
                        status[0] = "Progress";
                        status[1] = details.get("status").toString() + ": " + details.get("progress").toString() + " features completed";
                    } catch (NullPointerException e) {
                        status[0] = "Progress";
                        status[1] = details.get("stage").toString();

                        return status;
                    }
                }
            } catch (NullPointerException e) {
                // For once the ticket is complete
                status[0] = "Complete";
                status[1] = "Complete";

                return status;
            }

            return status;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ParseException e) {
            status[0] = "Complete";
            status[1] = "Complete";

            return status;
        }
    }

    private static Blueprint postRawFile(URI uri,
                                         File file,
                                         UserPreferences userPrefs) throws IOException {
        HttpUtility httpUtility = new HttpUtility(userPrefs, true, 3, 2);

        logging.log(Level.INFO, "Posting file...");
        HttpEntity httpEntity = MultipartEntityBuilder.create()
            .addBinaryBody(file.getName(), file, ContentType.APPLICATION_OCTET_STREAM,file.getName())
            .build();

        HttpResponse response = httpUtility.post(uri, httpEntity);
        HttpEntity resEntity = response.getEntity();
        String result = EntityUtils.toString(resEntity);
        logging.log(Level.FINE,result);

        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(result, Blueprint.class);
    }

    private static URI makeUri(String domain, String method, String query) {
        switch(method) {
            case "scan":
                return URI.create(domain + "/api/imports2?method=scanShape");
            case "replace":
                return URI.create(domain + "/api/imports2?method=replaceShapefile" + query);
            case "status":
                return URI.create(domain + "/api/imports2?ticket=" + query);
            default:
                return URI.create("");
        }
    }
}

@@ -3254,20 +3254,20 @@
 g(Level.
+F
 IN
-FO
+E
 , uri.to

Downgrade extraneous logging