import _ from 'lodash';
import { geoAngle } from '../geo/index';
import { osmWay } from './way';


export function osmTurn(turn) {
    if (!(this instanceof osmTurn))
        return new osmTurn(turn);
    _.extend(this, turn);
}


export function osmIntersection(graph, vertexId) {
    var vertex = graph.entity(vertexId),
        parentWays = graph.parentWays(vertex),
        coincident = [],
        highways = {};

    function addHighway(way, adjacentNodeId) {
        if (highways[adjacentNodeId]) {
            coincident.push(adjacentNodeId);
        } else {
            highways[adjacentNodeId] = way;
        }
    }

    // Pre-split ways that would need to be split in
    // order to add a restriction. The real split will
    // happen when the restriction is added.
    parentWays.forEach(function(way) {
        if (!way.tags.highway || way.isArea() || way.isDegenerate())
            return;

        var isFirst = (vertexId === way.first()),
            isLast = (vertexId === way.last()),
            isAffix = (isFirst || isLast),
            isClosingNode = (isFirst && isLast);

        if (isAffix && !isClosingNode) {
            var index = (isFirst ? 1 : way.nodes.length - 2);
            addHighway(way, way.nodes[index]);

        } else {
            var splitIndex, wayA, wayB, indexA, indexB;
            if (isClosingNode) {
                splitIndex = Math.ceil(way.nodes.length / 2);  // split at midpoint
                wayA = osmWay({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex)});
                wayB = osmWay({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)});
                indexA = 1;
                indexB = way.nodes.length - 2;
            } else {
                splitIndex = _.indexOf(way.nodes, vertex.id, 1);  // split at vertexid
                wayA = osmWay({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex + 1)});
                wayB = osmWay({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)});
                indexA = splitIndex - 1;
                indexB = splitIndex + 1;
            }
            graph = graph.replace(wayA).replace(wayB);
            addHighway(wayA, way.nodes[indexA]);
            addHighway(wayB, way.nodes[indexB]);
        }
    });

    // remove any ways from this intersection that are coincident
    // (i.e. any adjacent node used by more than one intersecting way)
    coincident.forEach(function (n) {
        delete highways[n];
    });


    var intersection = {
        highways: highways,
        ways: _.values(highways),
        graph: graph
    };


    intersection.adjacentNodeId = function(fromWayId) {
        return _.find(_.keys(highways), function(k) {
            return highways[k].id === fromWayId;
        });
    };


    intersection.turns = function(fromNodeId) {
        var start = highways[fromNodeId];
        if (!start)
            return [];

        if (start.first() === vertex.id && start.tags.oneway === 'yes')
            return [];
        if (start.last() === vertex.id && start.tags.oneway === '-1')
            return [];

        function withRestriction(turn) {
            graph.parentRelations(graph.entity(turn.from.way)).forEach(function(relation) {
                if (relation.tags.type !== 'restriction')
                    return;

                var f = relation.memberByRole('from'),
                    t = relation.memberByRole('to'),
                    v = relation.memberByRole('via');

                if (f && f.id === turn.from.way &&
                    v && v.id === turn.via.node &&
                    t && t.id === turn.to.way) {
                    turn.restriction = relation.id;
                } else if (/^only_/.test(relation.tags.restriction) &&
                    f && f.id === turn.from.way &&
                    v && v.id === turn.via.node &&
                    t && t.id !== turn.to.way) {
                    turn.restriction = relation.id;
                    turn.indirect_restriction = true;
                }
            });

            return osmTurn(turn);
        }


        var from = {
                node: fromNodeId,
                way: start.id.split(/-(a|b)/)[0]
            },
            via = { node: vertex.id },
            turns = [];

        _.each(highways, function(end, adjacentNodeId) {
            if (end === start)
                return;

            // backward
            if (end.first() !== vertex.id && end.tags.oneway !== 'yes') {
                turns.push(withRestriction({
                    from: from,
                    via: via,
                    to: {
                        node: adjacentNodeId,
                        way: end.id.split(/-(a|b)/)[0]
                    }
                }));
            }

            // forward
            if (end.last() !== vertex.id && end.tags.oneway !== '-1') {
                turns.push(withRestriction({
                    from: from,
                    via: via,
                    to: {
                        node: adjacentNodeId,
                        way: end.id.split(/-(a|b)/)[0]
                    }
                }));
            }

        });

        // U-turn
        if (start.tags.oneway !== 'yes' && start.tags.oneway !== '-1') {
            turns.push(withRestriction({
                from: from,
                via: via,
                to: from,
                u: true
            }));
        }

        return turns;
    };

    return intersection;
}


export function osmInferRestriction(graph, from, via, to, projection) {
    var fromWay = graph.entity(from.way),
        fromNode = graph.entity(from.node),
        toWay = graph.entity(to.way),
        toNode = graph.entity(to.node),
        viaNode = graph.entity(via.node),
        fromOneWay = (fromWay.tags.oneway === 'yes' && fromWay.last() === via.node) ||
            (fromWay.tags.oneway === '-1' && fromWay.first() === via.node),
        toOneWay = (toWay.tags.oneway === 'yes' && toWay.first() === via.node) ||
            (toWay.tags.oneway === '-1' && toWay.last() === via.node),
        angle = geoAngle(viaNode, fromNode, projection) -
                geoAngle(viaNode, toNode, projection);

    angle = angle * 180 / Math.PI;

    while (angle < 0)
        angle += 360;

    if (fromNode === toNode)
        return 'no_u_turn';
    if ((angle < 23 || angle > 336) && fromOneWay && toOneWay)
        return 'no_u_turn';
    if (angle < 158)
        return 'no_right_turn';
    if (angle > 202)
        return 'no_left_turn';

    return 'no_straight_on';
}
