package AlgLin;

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

public class Matrice {
	/** Définir ici les attributs de la classe **/
	protected double coefficient[][];
	
	// Déclaration de la constante epsilon numérique
    public static final double EPSILON = 1.0E-06;
    
	/** Définir ici les constructeur de la classe **/
	Matrice (int nbligne, int nbcolonne){
		this.coefficient = new double[nbligne][nbcolonne];
	}
	
	Matrice(double[][] tableau){
		coefficient = tableau;
	}
	
	Matrice(String fichier){
		try {
			Scanner sc = new Scanner(new File(fichier));
			int ligne = sc.nextInt();
			int colonne = sc.nextInt();
			this.coefficient = new double[ligne][colonne];
			for(int i=0; i<ligne;i++)
				for(int j=0; j< colonne; j++)
					this.coefficient[i][j]=sc.nextDouble();
			sc.close();	
			
		}
		catch(FileNotFoundException e) {
			System.out.println("Fichier absent");
		}
	}
	
	
	
	public void recopie(Matrice arecopier){
		int ligne, colonne;
		ligne = arecopier.nbLigne(); colonne = arecopier.nbColonne();
		this.coefficient = new double[ligne][colonne];
		for(int i=0; i<ligne; i++)
			for (int j=0;j<colonne;j++)
				this.coefficient[i][j]= arecopier.coefficient[i][j];	
	}
	
	public int nbLigne(){
		return this.coefficient.length;
	}
	
	public int nbColonne(){
		return this.coefficient[0].length;
	}
	
	public double getCoef(int ligne, int colonne){
		return this.coefficient[ligne][colonne];
	}
	
	public void remplacecoef(int ligne, int colonne, double value){
		this.coefficient[ligne][colonne]=value;
	}
	
	public String toString(){
		int ligne = this.nbLigne();
		int colonne = this.nbColonne();
		String matr = "";
		for(int i = 0; i<ligne;i++){
			for(int j =0; j< colonne;j++){
				if(j == 0)
				{
					matr += this.getCoef(i, j);
				}
				else{
					matr += " " + this.getCoef(i, j);
				}
			}
			matr += "\n";
		}
		return matr;
	}
	
	public Matrice produit(double scalaire){
		int ligne = this.nbLigne();
		int colonne = this.nbColonne();
		for(int i=0; i<ligne;i++)
			for(int j=0; j< colonne; j++)
				this.coefficient[i][j]*=scalaire;
		return this;
	}
	
	static Matrice addition(Matrice a, Matrice b){
		int ligne = a.nbLigne();
		int colonne = a.nbColonne();
		Matrice mat = new Matrice(ligne, colonne);
		for(int i=0; i<ligne;i++)
			for(int j=0; j< colonne; j++)
				mat.coefficient[i][j]=a.coefficient[i][j] + b.coefficient[i][j];
		return mat;
	}
	
	static Matrice verif_addition(Matrice a, Matrice b) throws Exception{
		if((a.nbLigne() == b.nbLigne()) && (a.nbColonne() == b.nbColonne()))
		{
			int ligne = a.nbLigne();
			int colonne = a.nbColonne();
			Matrice mat = new Matrice(ligne, colonne);
			for(int i=0; i<ligne;i++)
				for(int j=0; j< colonne; j++)
					mat.coefficient[i][j]=a.coefficient[i][j] + b.coefficient[i][j];
			return mat;
		}
		else {
			throw new Exception("Les deux matrices n'ont pas les mêmes dimensions !!!"); 
		}
	}
	
	static Matrice produit(Matrice a, Matrice b){
		int ligne, colonne;
		ligne = a.nbLigne();
		colonne = b.nbColonne();		
		Matrice mat = new Matrice(ligne, colonne);
		for(int i=0; i<ligne;i++)
			for(int j=0; j< colonne; j++)
			{
				mat.coefficient[i][j]=0;
				for(int k=0; k <a.nbColonne();k++)
					mat.coefficient[i][j] += a.coefficient[i][k] * b.coefficient[k][j];
			}
		return mat;					
	}
	
	static Matrice verif_produit(Matrice a, Matrice b) throws Exception{
		int ligne = 0;
		int colonne = 0;
		if(a.nbColonne()==b.nbLigne())
		{
			ligne = a.nbLigne();
			colonne = b.nbColonne();
		}
		else{
			throw new Exception("Dimensions des matrices à multiplier incorrectes");
		}
		
		Matrice mat = new Matrice(ligne, colonne);
		for(int i=0; i<ligne;i++)
			for(int j=0; j< colonne; j++)
			{
				mat.coefficient[i][j]=0;
				for(int k=0; k <a.nbColonne();k++)
					mat.coefficient[i][j] += a.coefficient[i][k] * b.coefficient[k][j];
			}
		return mat;					
	}
	
	
    public Matrice inverse() throws IllegalOperationException, IrregularSysLinException {
    	
        if (this.nbLigne() != this.nbColonne()) {
            throw new IllegalOperationException();
        } // nous indique si la matrice est carré d ordre n ou non

        int taille = this.nbLigne();
        Matrice copieMatrice = new Matrice(taille, taille);
        copieMatrice.recopie(this);
        
        //On crée un systeme linéaire pour l'inversion en utilisant Helder 
        Helder solveurSysteme = new Helder(copieMatrice, new Vecteur(taille));
        solveurSysteme.factorLDR();

        // la matrice qui va stocker notre inverse
        Matrice matriceInverse = new Matrice(taille, taille);

        // Parcours les colonnes de la matrice inverse
        for (int colonne = 0; colonne < taille; colonne++) {
            // Crée un vecteur unitaire dans la direction de la colonne courante
            Vecteur vecteurUnitaire = new Vecteur(taille);
            for (int ligne = 0; ligne < taille; ligne++) {
                if (ligne == colonne) {
                    vecteurUnitaire.remplacerCoefficient(ligne, 1);
                } else {
                    vecteurUnitaire.remplacerCoefficient(ligne, 0);
                }
            }

            // Utilise le vecteur unitaire comme second membre dans le système linéaire
            solveurSysteme.setSecondMembre(vecteurUnitaire);
            
            // Résoud le système linéaire pour obtenir la colonne de la matrice inverse
            Vecteur resultatSysteme = solveurSysteme.resolutionPartielle();
            
            // Ajoute la colonne à la matrice inverse
            for (int ligne = 0; ligne < taille; ligne++) {
                matriceInverse.remplacecoef(ligne, colonne, resultatSysteme.getCoef(ligne));
            }
        }
        return matriceInverse;
    }
    
    // calcul de la norme matricielle associée à la norme vectorielle de l'espace L1
    // on prend le max de la somme des valeurs abs de chaque colonne
    public double norme_1() {
        double normeL1 = 0.0;
        int dimension = this.nbLigne();
        // Parcourt les lignes de la matrice
        for (int i = 0; i < dimension; i++) {
            double sommeL1 = 0.0;
            // Pour chaque ligne, somme les valeurs absolues des coefficients
            for (int j = 0; j < dimension; j++) {
                sommeL1 += Math.abs(this.getCoef(i, j));
            }
            // Garde le maximum des sommes
            normeL1 = Math.max(normeL1, sommeL1);
        }
        return normeL1;
    }
    
    // calcul du conditionnement de la matrice en utilisant la norme 1
    public double cond_1() throws IllegalOperationException, IrregularSysLinException {
        // Calcul de la matrice inverse
        Matrice matriceInverse = this.inverse();
        // Retourne le produit de la norme 1 de la matrice inverse avec la norme 1 de la matrice d'origine
        return matriceInverse.norme_1() * this.norme_1();
    }
    
 // calcul de la norme matricielle associée à la norme vectorielle de l'espace Linfini
 // on prend le max de la somme des valeurs abs de chaque ligne
    public double norme_inf() {
        double normeLinfini = 0.0;
        int dimension = this.nbLigne();
        // Parcourt les colonnes de la matrice
        for (int i = 0; i < dimension; i++) {
            double sommeLinfini = 0.0;
            // Pour chaque colonne, somme les valeurs absolues des coefficients
            for (int j = 0; j < dimension; j++) {
                sommeLinfini += Math.abs(this.getCoef(j, i));
            }
            // Garde le maximum des sommes
            normeLinfini = Math.max(normeLinfini, sommeLinfini);
        }
        return normeLinfini;
    }
    
    // calcule du conditionnement de la matrice en utilisant la norme infinie
    public double cond_inf() throws IllegalOperationException, IrregularSysLinException {
        // Calcul de la matrice inverse
        Matrice matriceInverse = this.inverse();
        // Retourne le produit de la norme infinie de la matrice inverse avec la norme infinie de la matrice d'origine
        return matriceInverse.norme_inf() * this.norme_inf();
    }
	
 // Crée une matrice identité de taille n
    public static Matrice matriceIdentite(int n) {
        Matrice matriceIdentite = new Matrice(n, n);
        // Parcourt les lignes et colonnes de la matrice identité
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                // Si l'indice de ligne est égal à l'indice de colonne, le coefficient est 1, sinon 0
                if (j == i) {
                    matriceIdentite.coefficient[i][j] = 1;
                } else {
                    matriceIdentite.coefficient[i][j] = 0;
                }
            }
        }
        return matriceIdentite;
    }
    
    //Ajout de la methode transpose
    public Matrice transpose() {
        int ligne = this.nbLigne();
        int colonne = this.nbColonne();
        Matrice matriceTransposee = new Matrice(colonne, ligne);
        for (int i = 0; i < ligne; i++) {
            for (int j = 0; j < colonne; j++) {
                matriceTransposee.remplacecoef(j, i, this.getCoef(i, j));
            }
        }
        return matriceTransposee;
    }
  
	
	public static void main(String[] args) throws Exception {
		double mat[][]= {{2,1},{0,1}};
		Matrice a = new Matrice(mat);
		System.out.println("construction d'une matrice par affectation d'un tableau :\n"+a);
		Matrice b = new Matrice("matriceTestHelder.txt");
		System.out.println("Construction d'une matrice par lecture d'un fichier :\n"+b);
		Matrice c = new Matrice(2,2);
		c.recopie(b);
		System.out.println("Recopie de la matrice b :\n"+c);
		System.out.println("Nombre de lignes et colonnes de la matrice c : "+c.nbLigne()+
				", "+c.nbColonne());
		System.out.println("Coefficient (2,2) de la matrice b : "+b.getCoef(1, 1));
		System.out.println("Nouvelle valeur de ce coefficient : 8");
		b.remplacecoef(1, 1, 8);
		System.out.println("Vérification de la modification du coefficient");
		System.out.println("Coefficient (2,2) de la matrice b : "+b.getCoef(1, 1));
		System.out.println("Addition de 2 matrices : affichage des 2 matrices "+
				"puis de leur addition");
		System.out.println("matrice 1 :\n"+a+"matrice 2 :\n"+b+"somme :\n"+
				Matrice.addition(a,b));
		System.out.println("Produit de 2 matrices : affichage des 2 matrices "+
				"puis de leur produit");
		System.out.println("matrice 1 :\n"+a+"matrice 2 :\n"+b+"produit :\n"+
				produit(a,b));
		
		
		//inversion de matrice
		System.out.println("test TP3\n------------");
		
	    Matrice origine = new Matrice("matriceTestHelder.txt");
	    System.out.println("matrice a inverser\n" + origine);
	    Matrice inverse = origine.inverse();
	    System.out.println("cette matrice une fois inverser\n"+inverse);

	    
	    //Verification 
	    System.out.println("Produit de la matrice d origine avec son inverse");
	    Matrice produit = produit(inverse,origine);
	    System.out.println(produit);
	    
	    
	    //Comparaison de ce produit avec la matrice identite
	    Matrice identite = Matrice.matriceIdentite(origine.nbLigne());
	    identite = identite.produit(-1);
	    Matrice difference = Matrice.addition(produit,identite);
	    System.out.println("Calcule de la norme de la difference de la matrice produit avec la matrice identite\n" + difference.norme_1());	
	   

	    //calcul de conditionnement avec norme L1 et norme inf
	    System.out.println("Conditionnement avec la norme l1 : " +origine.cond_1());
	    System.out.println("Conditionnemnent avec la norme inf : " +origine.cond_inf());

	}
}
