package cgeo.geocaching;

import cgeo.geocaching.enumerations.WaypointType;
import cgeo.geocaching.geopoint.Geopoint;

import org.apache.commons.lang3.StringUtils;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Waypoint implements IWaypoint {

    public static final String PREFIX_OWN = "OWN";
    private static final int ORDER_UNDEFINED = -2;
    private int id = -1;
    private String geocode = "geocode";
    private WaypointType waypointType = WaypointType.WAYPOINT;
    private String prefix = "";
    private String lookup = "";
    private String name = "";
    private String latlon = "";
    private Geopoint coords = null;
    private String note = "";
    private int cachedOrder = ORDER_UNDEFINED;
    private boolean own = false;
    private boolean visited = false;

    /**
     * require name and type for every waypoint
     *
     * @param name
     * @param type
     */
    public Waypoint(final String name, final WaypointType type, final boolean own) {
        this.name = name;
        this.waypointType = type;
        this.own = own;
    }

    /**
     * copy constructor
     *
     * @param other
     */
    public Waypoint(final Waypoint other) {
        merge(other);
        this.waypointType = other.waypointType;
        id = -1;
    }

    public void merge(final Waypoint old) {
        if (StringUtils.isBlank(prefix)) {
            setPrefix(old.prefix);
        }
        if (StringUtils.isBlank(lookup)) {
            lookup = old.lookup;
        }
        if (StringUtils.isBlank(name)) {
            setName(old.name);
        }
        if (StringUtils.isBlank(latlon) || latlon.startsWith("?")) { // there are waypoints containing "???"
            latlon = old.latlon;
        }
        if (coords == null) {
            coords = old.coords;
        }
        if (StringUtils.isBlank(note)) {
            note = old.note;
        }
        if (note != null && old.note != null) {
            if (old.note.length() > note.length()) {
                note = old.note;
            }
        }
        if (id < 0) {
            id = old.id;
        }
        visited = old.visited;
    }

    public static void mergeWayPoints(final List<Waypoint> newPoints, final List<Waypoint> oldPoints, final boolean forceMerge) {
        // Build a map of new waypoints for faster subsequent lookups
        final Map<String, Waypoint> newPrefixes = new HashMap<String, Waypoint>(newPoints.size());
        for (final Waypoint waypoint : newPoints) {
            newPrefixes.put(waypoint.getPrefix(), waypoint);
        }

        // Copy user modified details of the old waypoints over the new ones
        for (final Waypoint oldWaypoint : oldPoints) {
            final String prefix = oldWaypoint.getPrefix();
            if (newPrefixes.containsKey(prefix)) {
                newPrefixes.get(prefix).merge(oldWaypoint);
            } else if (oldWaypoint.isUserDefined() || forceMerge) {
                newPoints.add(oldWaypoint);
            }
        }
    }

    public boolean isUserDefined() {
        return own || WaypointType.OWN == waypointType;
    }

    public void setUserDefined() {
        own = true;
        setPrefix(PREFIX_OWN);
    }

    private int computeOrder() {
        switch (waypointType) {
            case PARKING:
                return -1;
            case TRAILHEAD:
                return 1;
            case STAGE: // puzzles and stages with same value
                return 2;
            case PUZZLE:
                return 2;
            case FINAL:
                return 3;
            case OWN:
                return 4;
            default:
                return 0;
        }
    }

    private int order() {
        if (cachedOrder == ORDER_UNDEFINED) {
            cachedOrder = computeOrder();
        }
        return cachedOrder;
    }

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
        cachedOrder = ORDER_UNDEFINED;
    }

    public String getUrl() {
        return "http://www.geocaching.com//seek/cache_details.aspx?wp=" + geocode;
    }

    @Override
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String getGeocode() {
        return geocode;
    }

    public void setGeocode(String geocode) {
        this.geocode = StringUtils.upperCase(geocode);
    }

    @Override
    public WaypointType getWaypointType() {
        return waypointType;
    }

    public String getLookup() {
        return lookup;
    }

    public void setLookup(String lookup) {
        this.lookup = lookup;
    }

    @Override
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLatlon() {
        return latlon;
    }

    public void setLatlon(String latlon) {
        this.latlon = latlon;
    }

    @Override
    public Geopoint getCoords() {
        return coords;
    }

    public void setCoords(Geopoint coords) {
        this.coords = coords;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    @Override
    public String toString() {
        return name + " " + waypointType.getL10n();
    }

    /**
     * Checks whether a given waypoint is a final and has coordinates
     *
     * @return True - waypoint is final and has coordinates, False - otherwise
     */
    public boolean isFinalWithCoords() {
        return WaypointType.FINAL == waypointType && null != coords;
    }

    @Override
    public String getCoordType() {
        return "waypoint";
    }

    public void setVisited(boolean visited) {
        this.visited = visited;
    }

    public boolean isVisited() {
        return visited;
    }

    public int getStaticMapsHashcode() {
        long hash = 0;
        if (coords != null) {
            hash = coords.hashCode();
        }
        hash ^= waypointType.markerId;
        return (int) hash;
    }

    /**
     * Sort waypoints by their probable order (e.g. parking first, final last).
     */
    public static final Comparator<? super Waypoint> WAYPOINT_COMPARATOR = new Comparator<Waypoint>() {

        @Override
        public int compare(Waypoint left, Waypoint right) {
            return left.order() - right.order();
        }
    };
}

@@ -156,16 +156,59 @@
 ngUtils;
+%0Aimport org.eclipse.jdt.annotation.NonNull;
 %0A%0Aimport
@@ -3021,32 +3021,185 @@
 %7C forceMerge) %7B%0A
+                // personal note waypoints should always be taken from the new list only%0A                if (!isPersonalNoteWaypoint(oldWaypoint)) %7B%0A    
                 
@@ -3230,32 +3230,36 @@
 t);%0A            
+    
 %7D%0A        %7D%0A    
@@ -3244,33 +3244,276 @@
       %7D%0A        
+ 
-%7D
+   %7D%0A        %7D%0A    %7D%0A%0A    private static boolean isPersonalNoteWaypoint(final @NonNull Waypoint waypoint) %7B%0A        return StringUtils.startsWith(waypoint.getName(), CgeoApplication.getInstance().getString(R.string.cache_personal_note) + %22 %22);
 %0A    %7D%0A%0A    publ

fix #3421: WP from personal note doubled