// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui;

import static org.openstreetmap.josm.tools.I18n.tr;
import static org.openstreetmap.josm.tools.I18n.trc;
import static org.openstreetmap.josm.tools.I18n.trn;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.coor.CoordinateFormat;
import org.openstreetmap.josm.data.osm.Changeset;
import org.openstreetmap.josm.data.osm.NameFormatter;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmUtils;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.osm.history.HistoryNameFormatter;
import org.openstreetmap.josm.data.osm.history.HistoryNode;
import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
import org.openstreetmap.josm.data.osm.history.HistoryRelation;
import org.openstreetmap.josm.data.osm.history.HistoryWay;

/**
 * This is the default implementation of a {@see NameFormatter} for names of {@see OsmPrimitive}s.
 *
 */
public class DefaultNameFormatter implements NameFormatter, HistoryNameFormatter {

    static private DefaultNameFormatter instance;

    /**
     * Replies the unique instance of this formatter
     *
     * @return the unique instance of this formatter
     */
    static public DefaultNameFormatter getInstance() {
        if (instance == null) {
            instance = new DefaultNameFormatter();
        }
        return instance;
    }

    /** the default list of tags which are used as naming tags in relations */
    static public final String[] DEFAULT_NAMING_TAGS_FOR_RELATIONS = {"name", "ref", "restriction", "landuse", "natural",
    "public_transport", ":LocationCode", "note"};

    /** the current list of tags used as naming tags in relations */
    static private List<String> namingTagsForRelations =  null;

    /**
     * Replies the list of naming tags used in relations. The list is given (in this order) by:
     * <ul>
     *   <li>by the tag names in the preference <tt>relation.nameOrder</tt></li>
     *   <li>by the default tags in {@see #DEFAULT_NAMING_TAGS_FOR_RELATIONS}
     * </ul>
     *
     * @return the list of naming tags used in relations
     */
    static public List<String> getNamingtagsForRelations() {
        if (namingTagsForRelations == null) {
            namingTagsForRelations = new ArrayList<String>(
                    Main.pref.getCollection("relation.nameOrder", Arrays.asList(DEFAULT_NAMING_TAGS_FOR_RELATIONS))
            );
        }
        return namingTagsForRelations;
    }

    /**
     * Decorates the name of primitive with its id, if the preference
     * <tt>osm-primitives.showid</tt> is set. Shows unique id if osm-primitives.showid.new-primitives is set
     *
     * @param name  the name without the id
     * @param primitive the primitive
     * @return the decorated name
     */
    protected String decorateNameWithId(String name, OsmPrimitive primitive) {
        if (Main.pref.getBoolean("osm-primitives.showid"))
            if (Main.pref.getBoolean("osm-primitives.showid.new-primitives"))
                return name + tr(" [id: {0}]", primitive.getUniqueId());
            else
                return name + tr(" [id: {0}]", primitive.getId());
        else
            return name;
    }

    /**
     * Formats a name for a node
     *
     * @param node the node
     * @return the name
     */
    public String format(Node node) {
        String name = "";
        if (node.isIncomplete()) {
            name = tr("incomplete");
        } else {
            if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
                name = node.getLocalName();
            } else {
                name = node.getName();
            }
            if (name == null) {
                name = node.isNew() ? tr("node") : ""+ node.getId();
            }
            name += " (" + node.getCoor().latToString(CoordinateFormat.getDefaultFormat()) + ", " + node.getCoor().lonToString(CoordinateFormat.getDefaultFormat()) + ")";
        }
        name = decorateNameWithId(name, node);
        return name;
    }

    /**
     * Formats a name for a way
     *
     * @param way the way
     * @return the name
     */
    public String format(Way way) {
        String name = "";
        if (way.isIncomplete()) {
            name = tr("incomplete");
        } else {
            if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
                name = way.getLocalName();
            } else {
                name = way.getName();
            }
            if (name == null) {
                name = way.get("ref");
            }
            if (name == null) {
                name =
                    (way.get("highway") != null) ? tr("highway") :
                        (way.get("railway") != null) ? tr("railway") :
                            (way.get("waterway") != null) ? tr("waterway") :
                                (way.get("landuse") != null) ? tr("landuse") : "";
            }

            int nodesNo = way.getNodesCount();
            if (nodesNo > 1 && way.isClosed()) {
                nodesNo--;
            }
            if(name.length() == 0 )
                name = String.valueOf(way.getId());
            /* note: length == 0 should no longer happen, but leave the bracket code
               nevertheless, who knows what future brings */
            String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
            name += (name.length() > 0) ? " ("+nodes+")" : nodes;
        }
        name = decorateNameWithId(name, way);
        return name;
    }

    /**
     * Formats a name for a relation
     *
     * @param relation the relation
     * @return the name
     */
    public String format(Relation relation) {
        String name;
        if (relation.isIncomplete()) {
            name = tr("incomplete");
        } else {
            name = trc("Relation type", relation.get("type"));
            if (name == null) {
                name = (relation.get("public_transport") != null) ? tr("public transport") : "";
            }
            if (name == null) {
                String building  = relation.get("building");
                if(OsmUtils.isTrue(building))
                    name = tr("building");
                else if(building != null)
                    name = tr(building); // translate tag!
            }
            if (name == null) {
                name = tr("relation");
            }
            String admin_level = relation.get("admin_level");
            if (admin_level != null) {
                name += "["+admin_level+"]";
            }

            name += " (";
            String nameTag = null;
            for (String n : getNamingtagsForRelations()) {
                if (n.equals("name")) {
                    if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
                        nameTag = relation.getLocalName();
                    } else {
                        nameTag = relation.getName();
                    }
                } else if (n.equals(":LocationCode")) {
                    for (String m : relation.keySet()) {
                        if (m.endsWith(n)) {
                            nameTag = relation.get(m);
                            break;
                        }
                    }
                } else {
                    String str = relation.get(n);
                    if(str != null)
                        nameTag = tr(str);
                }
                if (nameTag != null) {
                    break;
                }
            }
            if (nameTag == null) {
                name += Long.toString(relation.getId()) + ", ";
            } else {
                name += "\"" + nameTag + "\", ";
            }

            int mbno = relation.getMembersCount();
            name += trn("{0} member", "{0} members", mbno, mbno);

            boolean incomplete = false;
            for (RelationMember m : relation.getMembers()) {
                if (m.getMember().isIncomplete()) {
                    incomplete = true;
                    break;
                }
            }
            if (incomplete) {
                name += ", "+tr("incomplete");
            }

            name += ")";
        }
        name = decorateNameWithId(name, relation);
        return name;
    }

    /**
     * Formats a name for a changeset
     *
     * @param changeset the changeset
     * @return the name
     */
    public String format(Changeset changeset) {
        return tr("Changeset {0}",changeset.getId());
    }

    /**
     * Builds a default tooltip text for the primitive <code>primitive</code>.
     *
     * @param primitive the primitmive
     * @return the tooltip text
     */
    public String buildDefaultToolTip(OsmPrimitive primitive) {
        StringBuilder sb = new StringBuilder();
        sb.append("<html>");
        sb.append("<strong>id</strong>=")
        .append(primitive.getId())
        .append("<br>");
        ArrayList<String> keyList = new ArrayList<String>(primitive.keySet());
        Collections.sort(keyList);
        for (int i = 0; i < keyList.size(); i++) {
            if (i > 0) {
                sb.append("<br>");
            }
            String key = keyList.get(i);
            sb.append("<strong>")
            .append(key)
            .append("</strong>")
            .append("=");
            String value = primitive.get(key);
            while(value.length() != 0) {
                sb.append(value.substring(0,Math.min(50, value.length())));
                if (value.length() > 50) {
                    sb.append("<br>");
                    value = value.substring(50);
                } else {
                    value = "";
                }
            }
        }
        sb.append("</html>");
        return sb.toString();
    }

    /**
     * Decorates the name of primitive with its id, if the preference
     * <tt>osm-primitives.showid</tt> is set.
     *
     * The id is append to the {@see StringBuilder} passed in in <code>name</code>.
     *
     * @param name  the name without the id
     * @param primitive the primitive
     */
    protected void decorateNameWithId(StringBuilder name, HistoryOsmPrimitive primitive) {
        if (Main.pref.getBoolean("osm-primitives.showid")) {
            name.append(tr(" [id: {0}]", primitive.getId()));
        }
    }

    /**
     * Formats a name for a history node
     *
     * @param node the node
     * @return the name
     */
    public String format(HistoryNode node) {
        StringBuilder sb = new StringBuilder();
        String name;
        if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
            name = node.getLocalName();
        } else {
            name = node.getName();
        }
        if (name == null) {
            sb.append(node.getId());
        } else {
            sb.append(name);
        }
        sb.append(" (")
        .append(node.getCoords().latToString(CoordinateFormat.getDefaultFormat()))
        .append(", ")
        .append(node.getCoords().lonToString(CoordinateFormat.getDefaultFormat()))
        .append(")");
        decorateNameWithId(sb, node);
        return sb.toString();
    }

    /**
     * Formats a name for a way
     *
     * @param way the way
     * @return the name
     */
    public String format(HistoryWay way) {
        StringBuilder sb = new StringBuilder();
        String name;
        if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
            name = way.getLocalName();
        } else {
            name = way.getName();
        }
        if (name != null) {
            sb.append(name);
        }
        if (sb.length() == 0 && way.get("ref") != null) {
            sb.append(way.get("ref"));
        }
        if (sb.length() == 0) {
            sb.append(
                    (way.get("highway") != null) ? tr("highway") :
                        (way.get("railway") != null) ? tr("railway") :
                            (way.get("waterway") != null) ? tr("waterway") :
                                (way.get("landuse") != null) ? tr("landuse") : ""
            );
        }

        int nodesNo = way.isClosed() ? way.getNumNodes() -1 : way.getNumNodes();
        String nodes = trn("{0} node", "{0} nodes", nodesNo, nodesNo);
        if(sb.length() == 0 )
            sb.append(way.getId());
        /* note: length == 0 should no longer happen, but leave the bracket code
           nevertheless, who knows what future brings */
        sb.append((sb.length() > 0) ? " ("+nodes+")" : nodes);
        decorateNameWithId(sb, way);
        return sb.toString();
    }

    /**
     * Formats a name for a {@see HistoryRelation})
     *
     * @param relation the relation
     * @return the name
     */
    public String format(HistoryRelation relation) {
        StringBuilder sb = new StringBuilder();
        if (relation.get("type") != null) {
            sb.append(relation.get("type"));
        } else {
            sb.append(tr("relation"));
        }
        sb.append(" (");
        String nameTag = null;
        Set<String> namingTags = new HashSet<String>(getNamingtagsForRelations());
        for (String n : relation.getTags().keySet()) {
            // #3328: "note " and " note" are name tags too
            if (namingTags.contains(n.trim())) {
                if (Main.pref.getBoolean("osm-primitives.localize-name", true)) {
                    nameTag = relation.getLocalName();
                } else {
                    nameTag = relation.getName();
                }
                if (nameTag == null) {
                    nameTag = relation.get(n);
                }
            }
            if (nameTag != null) {
                break;
            }
        }
        if (nameTag == null) {
            sb.append(Long.toString(relation.getId())).append(", ");
        } else {
            sb.append("\"").append(nameTag).append("\", ");
        }

        int mbno = relation.getNumMembers();
        sb.append(trn("{0} member", "{0} members", mbno, mbno)).append(")");

        decorateNameWithId(sb, relation);
        return sb.toString();
    }

    /**
     * Builds a default tooltip text for an HistoryOsmPrimitive <code>primitive</code>.
     *
     * @param primitive the primitmive
     * @return the tooltip text
     */
    public String buildDefaultToolTip(HistoryOsmPrimitive primitive) {
        StringBuilder sb = new StringBuilder();
        sb.append("<html>");
        sb.append("<strong>id</strong>=")
        .append(primitive.getId())
        .append("<br>");
        ArrayList<String> keyList = new ArrayList<String>(primitive.getTags().keySet());
        Collections.sort(keyList);
        for (int i = 0; i < keyList.size(); i++) {
            if (i > 0) {
                sb.append("<br>");
            }
            String key = keyList.get(i);
            sb.append("<strong>")
            .append(key)
            .append("</strong>")
            .append("=");
            String value = primitive.get(key);
            while(value.length() != 0) {
                sb.append(value.substring(0,Math.min(50, value.length())));
                if (value.length() > 50) {
                    sb.append("<br>");
                    value = value.substring(50);
                } else {
                    value = "";
                }
            }
        }
        sb.append("</html>");
        return sb.toString();
    }
}
