package cgeo.geocaching.geopoint;


import cgeo.geocaching.utils.MatcherWrapper;

import org.apache.commons.lang3.StringUtils;

import java.util.regex.Pattern;

/**
 * Parse coordinates.
 */
class GeopointParser {

    private static class ResultWrapper {
        final double result;
        final int matcherPos;
        final int matcherLength;

        public ResultWrapper(final double result, int matcherPos, int stringLength) {
            this.result = result;
            this.matcherPos = matcherPos;
            this.matcherLength = stringLength;
        }
    }

    //                                                            ( 1  )    ( 2  )         ( 3  )       ( 4  )       (        5        )
    private static final Pattern PATTERN_LAT = Pattern.compile("\\b([NS]|)\\s*(\\d+)°?(?:\\s*(\\d+)(?:[.,](\\d+)|'?\\s*(\\d+(?:[.,]\\d+)?)(?:''|\")?)?)?", Pattern.CASE_INSENSITIVE);
    private static final Pattern PATTERN_LON = Pattern.compile("\\b([WE]|)\\s*(\\d+)°?(?:\\s*(\\d+)(?:[.,](\\d+)|'?\\s*(\\d+(?:[.,]\\d+)?)(?:''|\")?)?)?", Pattern.CASE_INSENSITIVE);

    private static final Pattern PATTERN_BAD_BLANK = Pattern.compile("(\\d)[,.] (\\d{2,})");

    enum LatLon {
        LAT,
        LON
    }

    /**
     * Parses a pair of coordinates (latitude and longitude) out of a String.
     * Accepts following formats and combinations of it:
     * X DD
     * X DD°
     * X DD° MM
     * X DD° MM.MMM
     * X DD° MM SS
     *
     * as well as:
     * DD.DDDDDDD
     *
     * Both . and , are accepted, also variable count of spaces (also 0)
     *
     * @param text
     *            the string to parse
     * @return an Geopoint with parsed latitude and longitude
     * @throws Geopoint.ParseException
     *             if lat or lon could not be parsed
     */
    public static Geopoint parse(final String text) {
        final ResultWrapper latitudeWrapper = parseHelper(text, LatLon.LAT);
        final double lat = latitudeWrapper.result;
        // cut away the latitude part when parsing the longitude
        final ResultWrapper longitudeWrapper = parseHelper(text.substring(latitudeWrapper.matcherPos + latitudeWrapper.matcherLength), LatLon.LON);

        if (longitudeWrapper.matcherPos - (latitudeWrapper.matcherPos + latitudeWrapper.matcherLength) >= 10) {
            throw new Geopoint.ParseException("Distance between latitude and longitude text is to large.", LatLon.LON);
        }

        final double lon = longitudeWrapper.result;
        return new Geopoint(lat, lon);
    }

    /**
     * Parses a pair of coordinates (latitude and longitude) out of a String.
     * Accepts following formats and combinations of it:
     * X DD
     * X DD°
     * X DD° MM
     * X DD° MM.MMM
     * X DD° MM SS
     *
     * as well as:
     * DD.DDDDDDD
     *
     * Both . and , are accepted, also variable count of spaces (also 0)
     *
     * @param latitude
     *            the latitude string to parse
     * @param longitude
     *            the longitude string to parse
     * @return an Geopoint with parsed latitude and longitude
     * @throws Geopoint.ParseException
     *             if lat or lon could not be parsed
     */
    public static Geopoint parse(final String latitude, final String longitude) {
        final double lat = parseLatitude(latitude);
        final double lon = parseLongitude(longitude);

        return new Geopoint(lat, lon);
    }

    /*
     * (non JavaDoc)
     * Helper for coordinates-parsing.
     */
    private static ResultWrapper parseHelper(final String text, final LatLon latlon) {

        MatcherWrapper matcher = new MatcherWrapper(PATTERN_BAD_BLANK, text);
        String replaceSpaceAfterComma = matcher.replaceAll("$1.$2");

        final Pattern pattern = LatLon.LAT == latlon ? PATTERN_LAT : PATTERN_LON;
        matcher = new MatcherWrapper(pattern, replaceSpaceAfterComma);

        if (matcher.find()) {
            final double sign = matcher.group(1).equalsIgnoreCase("S") || matcher.group(1).equalsIgnoreCase("W") ? -1.0 : 1.0;
            final double degree = Integer.valueOf(matcher.group(2)).doubleValue();

            double minutes = 0.0;
            double seconds = 0.0;

            if (null != matcher.group(3)) {
                minutes = Integer.valueOf(matcher.group(3)).doubleValue();

                if (null != matcher.group(4)) {
                    seconds = Double.parseDouble("0." + matcher.group(4)) * 60.0;
                } else if (null != matcher.group(5)) {
                    seconds = Double.parseDouble(matcher.group(5).replace(",", "."));
                }
            }

            return new ResultWrapper(sign * (degree + minutes / 60.0 + seconds / 3600.0), matcher.start(), matcher.group().length());

        }

        // Nothing found with "N 52...", try to match string as decimal degree
        try {
            final String[] items = StringUtils.split(text.trim());
            if (items.length > 0) {
                final int index = (latlon == LatLon.LON ? items.length - 1 : 0);
                final int pos = (latlon == LatLon.LON ? text.lastIndexOf(items[index]) : text.indexOf(items[index]));
                return new ResultWrapper(Double.parseDouble(items[index]), pos, items[index].length());
            }
        } catch (NumberFormatException e) {
            // The right exception will be raised below.
        }

        throw new Geopoint.ParseException("Could not parse coordinates as " + latlon + ": \"" + text + "\"", latlon);
    }

    /**
     * Parses latitude out of a given string.
     *
     * @see #parse(String)
     * @param text
     *            the string to be parsed
     * @return the latitude as decimal degree
     * @throws Geopoint.ParseException
     *             if latitude could not be parsed
     */
    public static double parseLatitude(final String text) {
        return parseHelper(text, LatLon.LAT).result;
    }

    /**
     * Parses longitude out of a given string.
     *
     * @see #parse(String)
     * @param text
     *            the string to be parsed
     * @return the longitude as decimal degree
     * @throws Geopoint.ParseException
     *             if longitude could not be parsed
     */
    public static double parseLongitude(final String text) {
        return parseHelper(text, LatLon.LON).result;
    }
}

@@ -3878,24 +3878,238 @@
 terComma);%0A%0A
+        try %7B%0A            return new ResultWrapper(Double.valueOf(replaceSpaceAfterComma), 0, text.length());%0A        %7D catch (NumberFormatException e1) %7B%0A            // fall through to advanced parsing%0A        %7D%0A%0A
         if (
@@ -5050,25 +5050,55 @@
 cimal degree
+ parts (i.e. multiple doubles)
 %0A
-
         try 
@@ -5198,16 +5198,37 @@
 ngth %3E 0
+ && items.length %3C= 2
 ) %7B%0A    
@@ -5304,16 +5304,70 @@
 1 : 0);%0A
+                final String textPart = items%5Bindex%5D;%0A
         
@@ -5431,28 +5431,24 @@
 IndexOf(
-i
 te
-ms%5Bindex%5D
+xtPart
 ) : text
@@ -5456,28 +5456,24 @@
 indexOf(
-i
 te
-ms%5Bindex%5D
+xtPart
 ));%0A    
@@ -5532,28 +5532,24 @@
 ble(
-i
 te
-ms%5Bindex%5D
+xtPart
 ), pos, 
 item
@@ -5544,28 +5544,24 @@
 ), pos, 
-i
 te
-ms%5Bindex%5D
+xtPart
 .length(

fix failing test