// License: GPL. For details, see LICENSE file.
package buildings_tools;

import static buildings_tools.BuildingsToolsPlugin.latlon2eastNorth;

import java.util.TreeSet;

import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.tools.Pair;

public class AngleSnap {
    private static final double PI_2 = Math.PI / 2;
    final TreeSet<Double> snapSet = new TreeSet<>();

    public final void clear() {
        snapSet.clear();
    }

    public final void addSnap(double snap) {
        snapSet.add(snap % PI_2);
    }

    public final Double addSnap(Node[] nodes) {
        if (nodes.length == 2) {
            EastNorth p1, p2;
            p1 = latlon2eastNorth(nodes[0].getCoor());
            p2 = latlon2eastNorth(nodes[1].getCoor());
            double heading = p1.heading(p2);
            addSnap(heading);
            addSnap(heading + Math.PI / 4);
            return heading;
        } else {
            return null;
        }
    }

    public final void addSnap(Way way) {
        for (Pair<Node, Node> pair : way.getNodePairs(false)) {
            EastNorth a, b;
            a = latlon2eastNorth(pair.a.getCoor());
            b = latlon2eastNorth(pair.b.getCoor());
            double heading = a.heading(b);
            addSnap(heading);
        }
    }

    public final Double getAngle() {
        if (snapSet.isEmpty()) {
            return null;
        }
        double first = snapSet.first();
        double last = snapSet.last();
        if (first < Math.PI / 4 && last > Math.PI / 4) {
            last -= PI_2;
        }
        if (Math.abs(first - last) < 0.001) {
            double center = (first + last) / 2;
            if (center < 0)
                center += PI_2;
            return center;
        } else {
            return null;
        }
    }

    public final double snapAngle(double angle) {
        if (snapSet.isEmpty()) {
            return angle;
        }
        int quadrant = (int) Math.floor(angle / PI_2);
        double ang = angle % PI_2;
        Double prev = snapSet.floor(ang);
        if (prev == null)
            prev = snapSet.last() - PI_2;
        Double next = snapSet.ceiling(ang);
        if (next == null)
            next = snapSet.first() + PI_2;

        if (Math.abs(ang - next) > Math.abs(ang - prev)) {
            if (Math.abs(ang - prev) > Math.PI / 8) {
                return angle;
            } else {
                double ret = prev + PI_2 * quadrant;
                if (ret < 0)
                    ret += 2 * Math.PI;
                return ret;
            }
        } else {
            if (Math.abs(ang - next) > Math.PI / 8) {
                return angle;
            } else {
                double ret = next + PI_2 * quadrant;
                if (ret > 2 * Math.PI)
                    ret -= 2 * Math.PI;
                return ret;
            }
        }
    }
}
