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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.openstreetmap.josm.data.coor.ILatLon;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.plugins.elevation.ElevationHelper;

public class EleVertex {
    private static final int NPOINTS = 3;
    private static final double MIN_DIST = 90;

    private double avrgEle = Double.NaN;
    private double area = Double.NaN;
    private final EleCoordinate[] points = new EleCoordinate[NPOINTS];

    public EleVertex(EleCoordinate p1, EleCoordinate p2, EleCoordinate p3) {
        points[0] = p1;
        points[1] = p2;
        points[2] = p3;

        // compute elevation
        double z = 0D;
        boolean eleValid = true;
        for (EleCoordinate point : points) {
            if (ElevationHelper.isValidElevation(p1.getEle())) {
                z += point.getEle();
            } else {
                eleValid = false;
                break;
            }
        }

        if (eleValid) {
            avrgEle = z / NPOINTS;
        } else {
            avrgEle = ElevationHelper.NO_ELEVATION;
        }

        // compute the (approx.!) area of the vertex using heron's formula
        double a = p1.greatCircleDistance((ILatLon) p2);
        double b = p2.greatCircleDistance((ILatLon) p3);
        double c = p1.greatCircleDistance((ILatLon) p3);

        double s = (a + b + c) / 2D;
        double sq = s * (s - a) * (s - b) * (s - c);
        area = Math.sqrt(sq);
    }

    public List<EleVertex> divide() {
        TriangleEdge[] edges = new TriangleEdge[NPOINTS];

        int k = 0;
        for (int i = 0; i < points.length; i++) {
            ILatLon c1 = points[i];

            for (int j = i + 1; j < points.length; j++) {
                ILatLon c2 = points[j];
                edges[k++] = new TriangleEdge(i, j, c1.greatCircleDistance(c2));
            }
        }

        // sort by distance
        Arrays.sort(edges);
        // pick the longest edge
        TriangleEdge longest = edges[0];

        EleCoordinate pI = points[longest.getI()];
        EleCoordinate pJ = points[longest.getJ()];
        EleCoordinate pK = points[longest.getK()];
        EleCoordinate newP = getMid(pI, pJ);

        List<EleVertex> res = new ArrayList<>();
        res.add(new EleVertex(pI, pK, newP));
        res.add(new EleVertex(pJ, pK, newP));

        return res;
    }

    /**
     * Checks if vertex requires further processing or is finished. Currently this
     * method returns <code>true</code>, if the average deviation is &lt; 5m
     *
     * @return true, if is finished
     */
    public boolean isFinished() {
        /*double z = 0D;
        double avrgEle = getEle();

        for (EleCoordinate point : points) {
            z += (avrgEle - point.getEle()) * (avrgEle - point.getEle());
        }*/

        // TODO: Check for proper limit
        return /*z < 75 || */getArea() < (30 * 30); // = 3 * 25
    }

    /**
     * Gets the approximate area of this vertex in square meters.
     *
     * @return the area
     */
    public double getArea() {
        return area;
    }

    /**
     * Gets the (linear interpolated) mid point of c1 and c2.
     *
     * @param c1 the first coordinate
     * @param c2 the second coordinate
     * @return the mid point
     */
    public EleCoordinate getMid(EleCoordinate c1, EleCoordinate c2) {
        double x = (c1.getX() + c2.getX()) / 2.0;
        double y = (c1.getY() + c2.getY()) / 2.0;

        double z = (c1.getEle() + c2.getEle()) / 2.0;
        if (c1.greatCircleDistance((ILatLon) c2) > MIN_DIST) {
            double hgtZ = ElevationHelper.getSrtmElevation(new LatLon(y, x));

            if (ElevationHelper.isValidElevation(hgtZ)) {
                z = hgtZ;
            }
        }

        return new EleCoordinate(y, x, z);
    }

    /**
     * Gets the coordinate for the given index.
     *
     * @param index the index between 0 and NPOINTS:
     * @return the elevation coordinate instance
     * @throws IllegalArgumentException if index is invalid
     */
    public EleCoordinate get(int index) {
        if (index < 0 || index >= NPOINTS) throw new IllegalArgumentException("Invalid index: " + index);

        return points[index];
    }

    /**
     * Gets the average elevation of this vertex.
     *
     * @return the ele
     */
    public double getEle() {
        return avrgEle;
    }

    @Override
    public String toString() {
        return "EleVertex [avrgEle=" + avrgEle + ", area=" + area + ", points="
                + Arrays.toString(points) + ']';
    }

    static class TriangleEdge implements Comparable<TriangleEdge> {
        private final int i;
        private final int j;
        private final double dist;

        TriangleEdge(int i, int j, double dist) {
            super();
            this.i = i;
            this.j = j;
            this.dist = dist;
        }

        public int getI() {
            return i;
        }

        public int getJ() {
            return j;
        }

        public int getK() {
            if (i == 0) {
                return j == 1 ? 2 : 1;
            } else if (i == 1) {
                return j == 0 ? 2 : 0;
            } else {
                return j == 0 ? 1 : 0;
            }
        }

        public double getDist() {
            return dist;
        }

        @Override
        public int compareTo(TriangleEdge o) {
            return (int) (o.getDist() - dist);
        }

        @Override
        public String toString() {
            return "TriangleEdge [i=" + i + ", j=" + j + ", dist=" + dist + "]";
        }
    }
}
