/*************************************************************************
 *  Kompilacja:  javac Vector.java
 *  Wykonanie:    java Vector
 *
 *  Implementacja wektora liczb rzeczywistych.
 *
 *  Klasa jest zaimplementowana jako niezmienna. Kiedy program kliencki
 *  zainicjuje obiekt Vector, nie może zmienić żadnego pola
 *  (N lub data[i]) ani bezpośrednio, ani pośrednio. Niezmienność to bardzo
 *  pożądana cecha typu danych.
 *
 *  % java Vector
 *     x     = [ 1.0 2.0 3.0 4.0 ]
 *     y     = [ 5.0 2.0 4.0 1.0 ]
 *     z     = [ 6.0 4.0 7.0 5.0 ]
 *   10z     = [ 60.0 40.0 70.0 50.0 ]
 *    |x|    = 5.477225575051661
 *   <x, y>  = 25.0
 * 
 *
 *  Warto zauważyć, że Vector to także nazwa niepowiązanej biblioteki klas Javy.
 *
 *************************************************************************/

public class Vector { 

    private int N;               // Długość wektora
    private double[] data;       // Tablica komponentów wektora


    // Tworzy wektor o długości n
    public Vector(int n) {
        N = n;
        data = new double[N];
    }

    // Tworzy wektor albo na podstawie tablicy, albo na podstawie listy argumentów
    public Vector(double[] d) {
        N = d.length;

        // Kopiowanie zabezpieczające, aby klient nie zmienił kopii tablicy data[]
        data = new double[N];
        for (int i = 0; i < N; i++)
            data[i] = d[i];
    }

    // Tworzenie wektora na podstawie tablicy lub listy argumentów
    // Konstruktor wykorzystuje składnię argumentów Javy, co umożliwia
    // obsługę różnej liczby argumentów, na przykład:
    // Vector x = new Vector(1.0, 2.0, 3.0, 4.0);
    // Vector y = new Vector(5.0, 2.0, 4.0, 1.0);
/*
    public Vector(double... d) {
        N = d.length;

        // Kopiowanie zabezpieczające, tak aby klient nie mógł zmienić kopii tablicy data[]
        data = new double[N];
        for (int i = 0; i < N; i++)
            data[i] = d[i];
    }
*/
    // Zwracanie długości wektora
    public int length() {
        return N;
    }

    // Zwracanie iloczynu skalarnego danego wektora i wektora that
    public double dot(Vector that) {
        if (this.N != that.N) throw new RuntimeException("Niezgodne wymiary");
        double sum = 0.0;
        for (int i = 0; i < N; i++)
            sum = sum + (this.data[i] * that.data[i]);
        return sum;
    }

    // Zwracanie normy euklidesowej dla danego wektora
    public double magnitude() {
        return Math.sqrt(this.dot(this));
    }

    // Zwracanie odległości euklidesowej między danym wektorem a wektorem that
    public double distanceTo(Vector that) {
        if (this.N != that.N) throw new RuntimeException("Niezgodne wymiary");
        return this.minus(that).magnitude();
    }

    // Zwracanie this + that
    public Vector plus(Vector that) {
        if (this.N != that.N) throw new RuntimeException("Niezgodne wymiary");
        Vector c = new Vector(N);
        for (int i = 0; i < N; i++)
            c.data[i] = this.data[i] + that.data[i];
        return c;
    }

    // Zwracanie this - that
    public Vector minus(Vector that) {
        if (this.N != that.N) throw new RuntimeException("Niezgodne wymiary");
        Vector c = new Vector(N);
        for (int i = 0; i < N; i++)
            c.data[i] = this.data[i] - that.data[i];
        return c;
    }

    // Zwracanie określonej współrzędnej
    public double cartesian(int i) {
        return data[i];
    }

    // Tworzenie i zwracanie nowego obiektu, którego wartość to (dany wektor * factor)
    public Vector times(double factor) {
        Vector c = new Vector(N);
        for (int i = 0; i < N; i++)
            c.data[i] = factor * data[i];
        return c;
    }


    // Zwracanie odpowiadającej jednostki wektorowej
    public Vector direction() {
        if (this.magnitude() == 0.0) throw new RuntimeException("Wektor zerowy nie ma kierunku");
        return this.times(1.0 / this.magnitude());
    }


    // Zwracanie łańcucha znaków reprezentującego dany wektor
    public String toString() {
        String s = "";
        for (int i = 0; i < N; i++)
            s = s + data[i] + " ";
        return s;
    }




    // Klient testowy
    public static void main(String[] args) {
        double[] xdata = { 1.0, 2.0, 3.0, 4.0 };
        double[] ydata = { 5.0, 2.0, 4.0, 1.0 };
        Vector x = new Vector(xdata);
        Vector y = new Vector(ydata);

        System.out.println("   x       = " + x);
        System.out.println("   y       = " + y);

        Vector z = x.plus(y);
        System.out.println("   z       = " + z);

        z = z.times(10.0);
        System.out.println(" 10z       = " + z);

        System.out.println("  |x|      = " + x.magnitude());
        System.out.println(" <x, y>    = " + x.dot(y));
        System.out.println("dist(x, y) = " + x.distanceTo(y));
        System.out.println("dir(x)     = " + x.direction());

    }
}
