import _ from 'lodash';
/*
  Order the nodes of a way in reverse order and reverse any direction dependent tags
  other than `oneway`. (We assume that correcting a backwards oneway is the primary
  reason for reversing a way.)

  The following transforms are performed:

    Keys:
          *:right=* ⟺ *:left=*
        *:forward=* ⟺ *:backward=*
       direction=up ⟺ direction=down
         incline=up ⟺ incline=down
            *=right ⟺ *=left

    Relation members:
       role=forward ⟺ role=backward
         role=north ⟺ role=south
          role=east ⟺ role=west

   In addition, numeric-valued `incline` tags are negated.

   The JOSM implementation was used as a guide, but transformations that were of unclear benefit
   or adjusted tags that don't seem to be used in practice were omitted.

   Also, each node on the way is examined for its own tags and the following transformations are performed
   in order to ensure associated nodes (eg a Stop Sign) is also reversed

    Node Keys:
        direction=forward ⟺ direction=backward
        direction=left ⟺ direction=right
        *:forward=* ⟺ *:backward=*
        *:left=* ⟺ *:right=*

   References:
      http://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right
      http://wiki.openstreetmap.org/wiki/Key:direction#Steps
      http://wiki.openstreetmap.org/wiki/Key:incline
      http://wiki.openstreetmap.org/wiki/Route#Members
      http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java
      http://wiki.openstreetmap.org/wiki/Tag:highway%3Dstop
      http://wiki.openstreetmap.org/wiki/Key:traffic_sign#On_a_way_or_area
 */
export function actionReverse(wayId, options) {
    var replacements = [
            [/:right$/, ':left'], [/:left$/, ':right'],
            [/:forward$/, ':backward'], [/:backward$/, ':forward']
        ],
        numeric = /^([+\-]?)(?=[\d.])/,
        roleReversals = {
            forward: 'backward',
            backward: 'forward',
            north: 'south',
            south: 'north',
            east: 'west',
            west: 'east'
        };


    function reverseKey(key) {
        for (var i = 0; i < replacements.length; ++i) {
            var replacement = replacements[i];
            if (replacement[0].test(key)) {
                return key.replace(replacement[0], replacement[1]);
            }
        }
        return key;
    }


    function reverseValue(key, value) {
        if (key === 'incline' && numeric.test(value)) {
            return value.replace(numeric, function(_, sign) { return sign === '-' ? '' : '-'; });
        } else if (key === 'incline' || key === 'direction') {
            return {up: 'down', down: 'up'}[value] || value;
        } else if (options && options.reverseOneway && key === 'oneway') {
            return {yes: '-1', '1': '-1', '-1': 'yes'}[value] || value;
        } else {
            return {left: 'right', right: 'left'}[value] || value;
        }
    }


    function reverseDirectionTags(node) {
        // Update the direction based tags as appropriate then return an updated node
        return node.update({tags: _.transform(node.tags, function(acc, tagValue, tagKey) {
            // See if this is a direction tag and reverse (or use existing value if not recognised)
            if (tagKey === 'direction') {
                acc[tagKey] = {forward: 'backward', backward: 'forward', left: 'right', right: 'left'}[tagValue] || tagValue;
            } else {
                // Use the reverseKey method to cater for situations such as traffic_sign:forward=stop
                // This will pass through other tags unchanged
                acc[reverseKey(tagKey)] = tagValue;
            }
            return acc;
        }, {})});
    }


    function reverseTagsOnNodes(graph, nodeIds) {
        // Reverse the direction of appropriate tags attached to the nodes (#3076)
        return _(nodeIds)
            // Get each node from the graph
            .map(function(nodeId) { return graph.entity(nodeId);})
            // Check tags on the node, if there aren't any, we can skip
            .filter(function(existingNode) { return existingNode.tags !== undefined;})
            // Get a new version of each node with the appropriate tags reversed
            .map(function(existingNode) { return reverseDirectionTags(existingNode);})
            // Chain together consecutive updates to the graph for each updated node and return
            .reduce(function (accGraph, value) { return accGraph.replace(value); }, graph);
    }


    return function(graph) {
        var way = graph.entity(wayId),
            nodes = way.nodes.slice().reverse(),
            tags = {}, key, role;

        for (key in way.tags) {
            tags[reverseKey(key)] = reverseValue(key, way.tags[key]);
        }

        graph.parentRelations(way).forEach(function(relation) {
            relation.members.forEach(function(member, index) {
                if (member.id === way.id && (role = roleReversals[member.role])) {
                    relation = relation.updateMember({role: role}, index);
                    graph = graph.replace(relation);
                }
            });
        });

        // Reverse any associated directions on nodes on the way and then replace
        // the way itself with the reversed node ids and updated way tags
        return reverseTagsOnNodes(graph, nodes).replace(way.update({nodes: nodes, tags: tags}));
    };
}
