package math.line;

import math.shapes.Line;
import math.utils.Tuple2d;
import math.utils.Tuple3d;

import java.awt.*;

public class LineUtil {
    private LineUtil() {
    }

    /**
     * Oblicza współczynnik nachylenia linii przechodzącej przez dwa dane
     * punkty
     *
     * @param point1 Point2D.Double - pierwszy punkt
     * @param point2 Point2D.Double - drugi punkt
     * @return double - zwraca współczynnik nachylenia linii
     */
    public static double slope(Tuple2d point1, Tuple2d point2)
            throws ArithmeticException {
        double xx2 = point1.getX() - point2.getX();
        double yy2 = point1.getY() - point2.getY();
        if (xx2 != 0.0) {
            return yy2 / xx2;
        } else {
            throw new ArithmeticException("Próba dzielenia przez 0");
        }
    }

    /**
     * Zwraca współczynniki równania linii w postaci Vectora3d
     *
     * @param line Line - badana linia
     * @return Vector3D - zawierający współczynniki równania liniowego
     * Vector3d.x = a, Vector3d.y = b, Vector3d.z = c
     * otrzymujemy równanie linii aX + bY + c = 0;
     */
    public static Tuple3d findEquation(Line line) {
        if (line.getStart().getX() - line.getEnd().getX() == 0) {
            return new Tuple3d(0, 1, -line.getStart().getY());
        } else {
            double m = line.slope();
            double a = -m;
            double b = 1;
            double c = m * line.getStart().getX() - line.getStart().getY();
            return (new Tuple3d(a, b, c));
        }
    }

    /**
     * Sprawdza czy dwie wskazane linie przecinają się
     *
     * @param line1 Line - pierwsza linia
     * @param line2 Line - druga linia
     * @return boolean - zwraca <code>true</code> jeśli linie przecinają się albo
     * <code>false</code> jeśli nie
     */
    public static boolean areIntersect(Line line1, Line line2) {
        double m1 = line1.slope();
        double m2 = line2.slope();
        return (m1 != m2);
    }

    /**
     * Oblicza i zwraca punkt przecięcia dwóch linii podanych jako obiekty typu
     * Line
     *
     * @param line1 Line - linia 1
     * @param line2 Line - linia 2
     * @return Point2D.Double - zwraca punkt przecięcia wskazanych
     * linii lub <code>null</code> jeśli punkt przecięcia nie istnieje
     */
    public static Tuple2d crossPoint(Line line1, Line line2) {
        double xx = 0;
        double yy = 0;
        if (areIntersect(line1, line2)) {
            Tuple3d v1 = findEquation(line1);
            Tuple3d v2 = findEquation(line2);
            xx = (v2.getY() * v1.getZ() - v1.getY() * v2.getZ())
                    / (v2.getX() * v1.getY() - v1.getX() * v2.getY());
            yy = (-v2.getZ() - v2.getX() * xx) / v2.getY();
        } else {
            return null;
        }
        return (new Tuple2d(xx, yy));
    }

    /**
     * Tworzy obiekt typu Line z równania linii,
     * jeśli równanie linii wyrażone jest w postaci aX + bY + c;
     *
     * @param a double - wspólczynnik a równania linii;
     * @param b double - współczynnik b równania linii
     * @param c double - współczynnik c rówaia linii
     * @return Line - obiekt linii wyrażonej równaniem o podanych
     * wspólczynnikach
     */
    public static Line findLine(double a, double b, double c) {
        double x1 = 0;
        double x2 = 3;
        if (b != 0) {
            double y1 = -c / b - a / b * x1;
            double y2 = -c / b - a / b * x2;
            return new Line(x1, y1, x2, y2);
        } else {
            throw new IllegalArgumentException("b nie może być równe 0");
        }
    }

    /**
     * Oblicza i zwraca punkt przecięcia się dwóch linii podanych
     * w postaci Vectora. Jeżeli równanie linii jest wyrażone jako
     * aX + bY + c = 0, to parametrem równania będzie new Vector(a,b,c)
     *
     * @param v1 Vector3d - wektor 1
     * @param v2 Vector3d - wektor 2
     * @return Point2D.Double - zwraca punkt przecięcia, jeśli istnieje
     * lub <code>null</code> jeśli nie istnieje
     */
    public static Tuple2d crossPoint(Tuple3d v1, Tuple3d v2) {
        double xx = 0;
        double yy = 0;
        Line line1 = findLine(v1.getX(), v1.getY(), v1.getZ());
        Line line2 = findLine(v2.getX(), v2.getY(), v2.getZ());
        if (areIntersect(line1, line2)) {
            xx = (v2.getY() * v1.getZ() - v1.getY() * v2.getZ())
                    / (v2.getX() * v1.getY() - v1.getX() * v2.getY());
            yy = (-v2.getZ() - v2.getX() * xx) / v2.getY();
        } else {
            return null;
        }
        return (new Tuple2d(xx, yy));
    }

    /**
     * Oblicza wskazaną liczbę punktów na tej linii
     *
     * @param liczbaPunktow int - liczba obliczanych punktów
     * @return Point2D[] - tablica punktów leżących na tej linii
     */
    public static Tuple2d[] points(Line line, int liczbaPunktow) {
        Tuple2d[] punkty = new Tuple2d[liczbaPunktow + 2];
        punkty[0] = line.getStart();
        double odcinek = line.getLength() / ((liczbaPunktow + 1));
        //	System.out.println(line.getLength());
        for (int i = 0; i < liczbaPunktow; i++) {
            double r = (i + 1) * odcinek;
            double x = r * line.getCosinus() + line.getStart().getX();
            double y = r * line.getSinus() + line.getStart().getY();
            punkty[i + 1] = new Tuple2d(x, y);
        }
        punkty[liczbaPunktow + 1] = line.getEnd();
        return punkty;
    }

    /**
     * Oblicza współrzędne x wskazanej liczby punktów na tej linii
     *
     * @param liczbaPunktow int - liczba obliczanych punktów
     * @return double[] - tablica współrzędnych x punktów leżących na tej linii
     */
    public static double[] pointsX(Line line, int liczbaPunktow) {
        double[] punkty = new double[liczbaPunktow + 2];
        punkty[0] = line.getStart().getX();
        double odcinek = line.getLength() / ((liczbaPunktow + 1));
        for (int i = 0; i < liczbaPunktow; i++) {
            double r = (i + 1) * odcinek;
            punkty[i + 1] = r * line.getCosinus() + line.getStart().getX();
        }
        punkty[liczbaPunktow + 1] = line.getEnd().getX();
        return punkty;
    }

    /**
     * Oblicza współrzędne y wskazanej liczby punktów na tej linii
     *
     * @param liczbaPunktow int - liczba obliczanych punktów
     * @return double[] - tablica współrzędnych y punktów leżących na tej linii
     */
    public static double[] pointsY(Line line, int liczbaPunktow) {
        double[] punkty = new double[liczbaPunktow + 2];
        punkty[0] = line.getStart().getY();
        double odcinek = line.getLength() / ((liczbaPunktow + 1));
        for (int i = 0; i < liczbaPunktow; i++) {
            double r = (i + 1) * odcinek;
            punkty[i + 1] = r * line.getSinus() + line.getStart().getY();
        }
        punkty[liczbaPunktow + 1] = line.getEnd().getY();
        return punkty;
    }

    public static void drawLine(float x1, float y1, float x2, float y2,
                                Graphics2D g2, Color color) {
        float x0 = 800f;
        float y0 = 800f;
        Line line1 = new Line(x0 / 2f + x1 * 30f, (y0 - 30f) / 2f - y1 * 30f,
                x0 / 2f + x2 * 30f, (y0 - 30f) / 2f - y2 * 30f);
        Line line2 = LineUtil.prolongate(line1, 10);
        g2.setColor(color);
        g2.draw(line2);
        g2.setColor(Color.BLACK);
    }

    public static void drawLine(Line line, Graphics2D g2, Color color,
                                double percent) {
        float x0 = 800f;
        float y0 = 800f;
        float x1 = (float) line.getStart().getX();
        float y1 = (float) line.getStart().getY();
        float x2 = (float) line.getEnd().getX();
        float y2 = (float) line.getEnd().getY();
        Line line1 = new Line(x0 / 2f + x1 * 30f, (y0 - 30f) / 2f - y1 * 30f,
                x0 / 2f + x2 * 30f, (y0 - 30f) / 2f - y2 * 30f);
        Line line2 = LineUtil.prolongate(line1, percent);
        g2.setColor(color);
        g2.draw(line2);
        g2.setColor(Color.BLACK);
    }

    public static Line prolongate(Line line, double percent) {
        double prol = line.getLength() * percent / 100.0;
        double endx = line.getEnd().getX() + prol * line.getCosinus();
        double endy = line.getEnd().getY() + prol * line.getSinus();
        double startx = line.getStart().getX() - prol * line.getCosinus();
        double starty = line.getStart().getY() - prol * line.getSinus();
        return new Line(startx, starty, endx, endy);
    }

    public static double distance(Line line1, Line line2) {
        Tuple3d v1 = findEquation(line1);
        Tuple3d v2 = findEquation(line2);
        return Math.abs(v2.getZ() - v1.getZ()
                / Math.sqrt(v1.getX() * v1.getX() + v1.getY() * v1.getY()));
    }

    public static double distance(Line line, Tuple2d point) {
        Tuple3d v = findEquation(line);
        return (v.getX() * point.getX() + v.getY() * point.getY() + v
                .getZ())
                / (Math.sqrt(v.getX() * v.getX() + v.getY() * v.getY()));
    }

    public static boolean areNormal(Line line1, Line line2) {
        double m1 = line1.slope();
        double m2 = line2.slope();
        return m1 * m2 == -1;
    }

    public static double angleBetween(Line line1, Line line2) {
        double m1 = line1.slope();
        double m2 = line2.slope();
        double angle = (m2 - m1) / (1 + m1 * m2);
        return Math.atan(angle);
    }

    //Linia prostopadla do podanej prostej i przechodzaca
    //przez podany punkt
    public static Line lineThroughPoint(Line line, Tuple2d point) {
        Tuple3d v3f = findEquation(line);
        return findLine(v3f.getY(), -v3f.getX(),
                v3f.getX() * point.getY() - v3f.getY() * point.getX());
    }

    public static Line lineParallelThroughPoint(Line line, Tuple2d point) {
        Tuple3d v3f = findEquation(line);
        System.out.println(v3f);
        return findLine(v3f.getX(), v3f.getY(), v3f.getX() * point.getX()
                + v3f.getY() * point.getY());
    }

    public static double findb(Line line) {
        double m = line.slope();
        double C = m * line.getStart().getX() - line.getStart().getY();
        return -C;
    }

}
