package fi.nls.paikkatietoikkuna.coordtransform;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import fi.nls.oskari.annotation.OskariActionRoute;
import fi.nls.oskari.control.ActionException;
import fi.nls.oskari.control.ActionHandler;
import fi.nls.oskari.control.ActionParameters;
import fi.nls.oskari.control.ActionParamsException;
import fi.nls.oskari.util.IOHelper;
import fi.nls.oskari.util.PropertyUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import javax.servlet.http.HttpServletResponse;

/**
 * Handles CoordinateTransformation action_route requests
 */
@OskariActionRoute("CoordinateTransformation")
public class CoordinateTransformationActionHandler extends ActionHandler {

    private static final String PROP_END_POINT = "coordtransform.endpoint";

    protected static final String PARAM_SOURCE_CRS = "sourceCrs";
    protected static final String PARAM_SOURCE_H_CRS = "sourceHeightCrs";
    protected static final String PARAM_TARGET_CRS = "targetCrs";
    protected static final String PARAM_TARGET_H_CRS = "targetHeightCrs";

    private JsonFactory jf;
    private String endPoint;

    public CoordinateTransformationActionHandler() {
        this(null);
    }

    protected CoordinateTransformationActionHandler(String endPoint) {
        this.jf = new JsonFactory();
        this.endPoint = endPoint;
    }

    @Override
    public void init() {
        if (endPoint != null) {
            endPoint = PropertyUtil.getNecessary(PROP_END_POINT);
        }
    }

    @Override
    public void handleAction(ActionParameters params) throws ActionException {
        String sourceCrs = params.getRequiredParam(PARAM_SOURCE_CRS);
        String targetCrs = params.getRequiredParam(PARAM_TARGET_CRS);
        String sourceHeightCrs = params.getHttpParam(PARAM_SOURCE_H_CRS);
        String targetHeightCrs = params.getHttpParam(PARAM_TARGET_H_CRS);

        if (sourceHeightCrs != null && !sourceHeightCrs.isEmpty()) {
            sourceCrs = sourceCrs + ',' + sourceHeightCrs;
        } else {
            sourceCrs = getCrs(sourceCrs);
        }

        if (targetHeightCrs != null && !targetHeightCrs.isEmpty()) {
            targetCrs = targetCrs + ',' + targetHeightCrs;
        } else {
            targetCrs = getCrs(targetCrs);
        }

        int dimension = sourceCrs.indexOf(',') > 0 ? 3 : 2;

        double[] coords;
        try (InputStream in = params.getRequest().getInputStream()) {
            coords = parseInputCoordinates(in, dimension);
        } catch (IOException e) {
            throw new ActionException("Failed to parse input JSON!");
        }

        String query = CoordTransService.createQuery(
                sourceCrs, targetCrs, dimension, coords);

        HttpURLConnection conn;
        try {
            conn = IOHelper.getConnection(endPoint + query);
        } catch (IOException e) {
            throw new ActionException("Failed to connect to CoordTrans service");
        }

        byte[] serviceResponseBytes;
        try {
            serviceResponseBytes = IOHelper.readBytes(conn);
        } catch (IOException e) {
            throw new ActionException("Failed to read response from CoordTrans service");
        }

        try {
            // Reuse the double array
            CoordTransService.parseResponse(serviceResponseBytes, coords, dimension);
        } catch (IllegalArgumentException e) {
            throw new ActionException(e.getMessage());
        }

        HttpServletResponse response = params.getResponse();
        response.setContentType(IOHelper.CONTENT_TYPE_JSON);
        try (OutputStream out = response.getOutputStream()) {
            writeJsonResponse(out, dimension, coords);
        } catch (IOException e) {
            throw new ActionException("Failed to write JSON to client");
        }
    }

    private String getCrs(String crs) {
        // Handle compound coordinate systems
        switch (crs) {
        case "EPSG:3901":
            return "EPSG:2393,EPSG:5717";
        case "EPSG:3902":
            return "EPSG:3067,EPSG:5717";
        case "EPSG:3903":
            return "EPSG:3067,EPSG:3900";
        default:
            return crs;
        }
    }

    protected double[] parseInputCoordinates(InputStream in, final int dimension)
            throws IOException, ActionParamsException {
        try (JsonParser parser = jf.createParser(in)) {
            if (parser.nextToken() != JsonToken.START_ARRAY) {
                throw new ActionParamsException(
                        "Expected an array of arrays of " + dimension + " doubles");
            }

            double[] coordinates = new double[8 * dimension];
            int i = 0;
            JsonToken token;

            while (true) {
                token = parser.nextToken();
                if (token == JsonToken.END_ARRAY) {
                    break;
                }
                if (token != JsonToken.START_ARRAY) {
                    throw new ActionParamsException(
                            "Expected an array of arrays of " + dimension + " doubles");
                }

                token = parser.nextToken();
                if (token != JsonToken.VALUE_NUMBER_FLOAT
                        && token != JsonToken.VALUE_NUMBER_INT) {
                    throw new ActionParamsException(
                            "Expected an array of arrays of " + dimension + " doubles");
                }
                if (i == coordinates.length) {
                    coordinates = grow(coordinates);
                }
                coordinates[i++] = parser.getDoubleValue();

                token = parser.nextToken();
                if (token != JsonToken.VALUE_NUMBER_FLOAT
                        && token != JsonToken.VALUE_NUMBER_INT) {
                    throw new ActionParamsException(
                            "Expected an array of arrays of " + dimension + " doubles");
                }
                coordinates[i++] = parser.getDoubleValue();

                // The if here should be pretty cheap since dimension is final
                // meaning it's not worth creating separate functions for 2d and 3D
                if (dimension == 3) {
                    token = parser.nextToken();
                    if (token != JsonToken.VALUE_NUMBER_FLOAT
                            && token != JsonToken.VALUE_NUMBER_INT) {
                        throw new ActionParamsException(
                                "Expected an array of arrays of " + dimension + " doubles");
                    }
                    coordinates[i++] = parser.getDoubleValue();
                }

                if (parser.nextToken() != JsonToken.END_ARRAY) {
                    throw new ActionParamsException(
                            "Expected an array of arrays of " + dimension + " doubles");
                }
            }

            return Arrays.copyOf(coordinates, i);
        }
    }

    private double[] grow(double[] a) {
        double[] b = new double[a.length * 2];
        System.arraycopy(a, 0, b, 0, a.length);
        return b;
    }

    protected void writeJsonResponse(OutputStream out, final int dimension, double[] coords)
            throws ActionException {
        try (JsonGenerator json = jf.createGenerator(out)) {
            json.writeStartObject();
            json.writeNumberField("dimension", dimension);
            json.writeStartArray();
            for (int i = 0; i < coords.length;) {
                json.writeStartArray();
                json.writeNumber(coords[i++]);
                json.writeNumber(coords[i++]);
                if (dimension == 3) {
                    json.writeNumber(coords[i++]);
                }
                json.writeEndArray();
            }
            json.writeEndArray();
            json.writeEndObject();
        } catch (IOException e) {
            throw new ActionException("Failed to write JSON");
        }
    }

}

@@ -2250,69 +2250,8 @@
    %7D
- else %7B%0A            sourceCrs = getCrs(sourceCrs);%0A        %7D%0A
 %0A   
@@ -2388,68 +2388,8 @@
    %7D
- else %7B%0A            targetCrs = getCrs(targetCrs);%0A        %7D
 %0A%0A  
@@ -3928,379 +3928,8 @@
  %7D%0A%0A
-    private String getCrs(String crs) %7B%0A        // Handle compound coordinate systems%0A        switch (crs) %7B%0A        case %22EPSG:3901%22:%0A            return %22EPSG:2393,EPSG:5717%22;%0A        case %22EPSG:3902%22:%0A            return %22EPSG:3067,EPSG:5717%22;%0A        case %22EPSG:3903%22:%0A            return %22EPSG:3067,EPSG:3900%22;%0A        default:%0A            return crs;%0A        %7D%0A    %7D%0A%0A
     
@@ -5878,17 +5878,17 @@
 ns for 2
-d
+D
  and 3D%0A

Remove special support for compound systems, trust frontend to handle them