package math.matrix;

import math.shapes.Line;
import math.utils.ArrayUtil;
import math.utils.MathUtil;

import java.io.*;
import java.util.*;

public class MatrixUtil{
	private MatrixUtil(){}

	/**
	 * Wczytuje macierz z pliku
	 * @param path - œcie¿ka do pliku z macierz¹
	 * @return zwraca wczytan¹ macierz 
	 */
	public static double[][] read(String path) {
		double[][] tabl = null;
		ArrayList<String> al = new ArrayList<>();
		BufferedReader in = null;
		StringTokenizer st = null;
		int len = -1;
		int len1 = -1;
		try{
			in = new BufferedReader(new FileReader(new File(path)));
			try{
				String s = null;
				while((s = in.readLine()) != null){
					al.add(s);
				}
			} finally{
				in.close();
			}
		} catch (IOException ioe){
			throw new RuntimeException(ioe);
		}
		st = new StringTokenizer(al.get(0), " ;");
		len = st.countTokens();
		len1 = al.size();
		tabl = new double[len1][len];
		for(int i = 0; i < len1; i++){
			st = new StringTokenizer(al.get(i), " ;");
			int j = 0;
			while(st.hasMoreTokens()){
				tabl[i][j] = Double.valueOf(st.nextToken());
				j++;
			}
		}
		return tabl;
	}

	/**
	 * Zamienia macierz na tablice
	 * @param tabl - macierz do przekszta³cenia
	 * @return - zwraca tablicê
	 */
	public static double[] twoToOne(double[][] tabl) {
		int row = tabl.length;
		int col = tabl[0].length;
		int len = row * col;
		double[] temp = new double[len];
		int k = 0;
		for (double[] doubles : tabl) {
			for (int j = 0; j < col; j++) {
				temp[k] = doubles[j];
				k++;
			}
		}
		return temp;
	}

	/**
	 * Drukuje wskazan¹ macierz na konsoli
	 * @param plist - macierz od wydrukowania
	 */
	public static void print(double[][] plist) {
		for (double[] temp : plist) {
			for (double v : temp) {
				System.out.print(v + " ");
			}
			System.out.print("\n");
		}
	}

	/**
	 * Zapisuje macierz do DataOutput np. do RandomAccessFile
	 * @param array - macierz do zapisania
	 * @param output - wyjœcie, w którym umieszczona zostanie macierz
	 */
	public static void write(double[][] array, DataOutput output) {
		int rows = array.length;
		int cols = array[0].length;
		try{
			output.writeInt(rows);
			output.writeInt(cols);
			for (double[] doubles : array) {
				for (int j = 0; j < cols; j++) {
					output.writeDouble(doubles[j]);
				}
			}
		} catch (IOException e){
			e.printStackTrace();
		}
	}

	/**
	 * Odczytuje macierz ze ¿ród³a danych naprzyk³ad RandomAccessFile
	 * @param input - ¿ród³o danych
	 * @return zwraca macierz
	 */
	public static double[][] read(DataInput input) {
		int rows = 0;
		int cols;
		double[][] temp = null;
		try{
			rows = input.readInt();
			cols = input.readInt();
			temp = new double[rows][cols];
			for(int i = 0; i < rows; i++){
				for(int j = 0; j < cols; j++){
					temp[i][j] = input.readDouble();
				}
			}
		} catch (IOException e){
			e.printStackTrace();
		}
		return temp;
	}

	/**
	 * Zapisuje macierz jako obiekt
	 * @param array - macierz do zapisania
	 * @param output - wyjœcie
	 */
	public static void write(double[][] array, ObjectOutput output) {
		try{
			output.writeObject(array);
		} catch (IOException e){
			e.printStackTrace();
		}
	}

	/**
	 * Odczytuje obiekt macierzy
	 * @param input - ¿ród³o
	 * @return zwraca macierz
	 */
	public static double[][] read(ObjectInput input) {
		double[][] tabl = null;
		try{
			try{
				tabl = (double[][])input.readObject();
			} catch (ClassNotFoundException e){
				e.printStackTrace();
			}
		} catch (IOException e){
			e.printStackTrace();
		}
		return tabl;
	}

	public static Matrix deleteRow(Matrix matrix, int row) {
		int r = row - 1;
		int cc = matrix.getColCount();
		int c = cc - 1;
		double[] ar = ArrayUtil.twoToOne(matrix.getArray());
		double[] ar1 = ArrayUtil.shortenArray(ar, r * cc, r * cc + c);
		return new Matrix(ar1, cc);
	}

	public static Matrix deleteCol(Matrix matrix, int col) {
		double[][] array = ArrayUtil.clone(matrix.getArray());
		for(int i = 0; i < array.length; i++){
			array[i] = ArrayUtil.shortenArray(array[i], col - 1);
		}
		return new Matrix(array);
	}

	//usuwa podany rz¹d i kolumne 
	public static Matrix submatrix(Matrix matrix, int row, int col) {
		Matrix mat1 = deleteRow(matrix, row);
		return deleteCol(mat1, col);
	}

	public static double minor(Matrix matrix, int row, int col) {
		return det(submatrix(matrix, row, col));
	}

	public static double det(Matrix matrix) {
		double det = 0.0;
		switch(matrix.getRowCount()){
			case 1:
				if(matrix.isSquareMatrix()){
					det = matrix.getArray()[0][0];
				}
				break;
			case 2:
				if(matrix.isSquareMatrix()){
					det = matrix.getArray()[0][0] * matrix.getArray()[1][1]
							- matrix.getArray()[0][1] * matrix.getArray()[1][0];
				}
				break;
			case 3:
				if(matrix.isSquareMatrix()){
					double a = matrix.getArray()[0][0];
					double b = matrix.getArray()[0][1];
					double e = matrix.getArray()[0][2];
					double c = matrix.getArray()[1][0];
					double d = matrix.getArray()[1][1];
					double f = matrix.getArray()[1][2];
					double g = matrix.getArray()[2][0];
					double h = matrix.getArray()[2][1];
					double i = matrix.getArray()[2][2];
					det = ((a * d * i + b * f * g + e * c * h) - (e * d * g + a
							* f * h + b * c * i));
				}
				break;
			default :
				break;
		}
		return det;
	}

	public static double algComplement(Matrix matrix, int row, int col) {
		return minor(matrix, row, col) * Math.pow(-1, row + col);
	}

	public static Matrix transpose(Matrix matrix) {
		int r = matrix.getRowCount();
		int c = matrix.getColCount();
		Matrix temp = new Matrix(r, c);
		double[][] temp1 = new double[r][c];
		for(int i = 0; i < r; i++){
			for(int j = 0; j < c; j++){
				temp1[j][i] = matrix.getArray()[i][j];
			}
		}
		temp.setArray(temp1);
		return temp;
	}

	public static Matrix reverse(Matrix matrix) {
		int r = matrix.getRowCount();
		int c = matrix.getColCount();
		Matrix matrixac = new Matrix(r, c);
		for(int i = 0; i < r; i++){
			for(int j = 0; j < c; j++){
				double ac = algComplement(matrix, i + 1, j + 1);
				try{
					matrixac.setCell(i, j, ac);
				} catch (MatrixException e){
					e.printStackTrace();
				}
			}
		}
		Matrix matrix1 = MatrixUtil.transpose(matrixac);
		double det = matrix.det();
		return matrix1.mult2(1.0 / det);
	}

	public static Matrix setToTranslate(Matrix matrix, double dx, double dy) {
		try{
			matrix.setCell(0, 2, dx);
			matrix.setCell(1, 2, dy);
		} catch (MatrixException e){
			e.printStackTrace();
		}
		return matrix;
	}

	public static Matrix setToScale(Matrix matrix, double sx, double sy) {
		try{
			matrix.setCell(0, 0, sx);
			matrix.setCell(1, 1, sy);
		} catch (MatrixException e){
			e.printStackTrace();
		}
		return matrix;
	}

	public static Matrix setToRotation(Matrix matrix, double angleDeg) {
		double angleRad = MathUtil.degToRad(angleDeg);
		double t1 = Math.cos(angleRad);
		double t2 = Math.sin(angleRad);
		try{
			matrix.setCell(0, 0, t1);
			matrix.setCell(0, 1, t2);
			matrix.setCell(1, 0, -t2);
			matrix.setCell(1, 1, t1);
		} catch (MatrixException e){
			e.printStackTrace();
		}
		return matrix;
	}

	public Matrix setToReflection(Matrix matrix, Line line) {
		double slope = line.slope();
		double angle = Math.atan(slope);
		double dangle = 2 * angle;
		double cosa = Math.cos(dangle);
		double sina = Math.sin(dangle);
		try{
			matrix.setCell(0, 0, cosa);
			matrix.setCell(0, 1, sina);
			matrix.setCell(1, 0, sina);
			matrix.setCell(1, 1, -cosa);
		} catch (MatrixException e){
			e.printStackTrace();
		}
		return matrix;
	}

	public static Matrix setToShear(Matrix matrix, double shx, double shy) {
		try{
			matrix.setCell(0, 1, shx);
			matrix.setCell(1, 0, shy);
		} catch (MatrixException e){
			e.printStackTrace();
		}
		return matrix;
	}
}
