package projet_freyja.math;

import java.io.Serializable;
import projet_freyja.nombre.*;

public class Polynome implements Serializable{
    //Attributs
    protected String libelle;
    protected double[] coefficients;
    protected int degre;
    protected EspaceVectoriel dep;
    protected EspaceVectoriel arr;
    protected Polynome pDerive;
    protected Polynome pPrimitive;
    
    //Méthodes
    //****Constructeurs****
    
    public Polynome(){
        this.libelle = "";
        this.coefficients[0] = 0;
        this.degre = 0;
        this.dep = new EspaceVectoriel();
        this.arr = new EspaceVectoriel();
    }
    
    public Polynome(String l, double[] coeff, int d, EspaceVectoriel depart, EspaceVectoriel arrive){
        this.libelle = l;
        //System.arraycopy(coeff, 0, coefficients, 0, coeff.length);
        coefficients = coeff; 
        this.degre = d;
        this.dep = depart;
        this.arr = arrive;
    }
    
    public Polynome(Polynome p){
        this.libelle = p.getLibelle();
        this.coefficients = p.getCoefficients();
        this.degre = p.getDegre();
        this.dep = p.getDep();
        this.arr = p.getArr();
    }
    
    //****Setter&Getter****
    
    public String getLibelle() {
        return libelle;
    }

    public double[] getCoefficients() {
        return coefficients;
    }

    public int getDegre() {
        return degre;
    }

    public void setLibelle(String libelle) {
        this.libelle = libelle;
    }

    public void setCoefficients(double[] coefficients) {
        this.coefficients = coefficients;
    }

    public void setDegre(int degre) {
        this.degre = degre;
    }
    
    public EspaceVectoriel getDep(){
        return dep;
    }
    
    public void setDep(EspaceVectoriel e){
        this.dep = e;
    }
    
    public EspaceVectoriel getArr(){
        return arr;
    }
    
    public void setArr(EspaceVectoriel e){
        this.arr = e;
    }
    
    public Polynome getPDerive(){
        return this.pDerive;
    }
    
    public Polynome getPPrimitive(){
        return this.pPrimitive;
    }
    
    //****toString****
    
    @Override
    public String toString() {
        String p = "";
        p += "Libellé : "+getLibelle()+"\n";
        p += "Degre : "+getDegre()+"\n";

        p += "***Espace de départ***\n";
        p += "Libellé : "+getDep().getLibelle()+"\n";
        p += "Dimension : "+getDep().getDimension()+"\n";
        //p +="Corps : "+p.getDep().getCorps()+"\n");
        p += "  ***Base associée***\n";
        p += "      Libellé : "+getDep().getBase().getLibelle()+"\n";
        p += "      Dimension : "+getDep().getBase().getDimension()+"\n";
        p += "      Dimension des vecteurs : "+getDep().getBase().getDimensionVecteur()+"\n";
        p += "      Famille : {";
        for(int i = 0; i < getDep().getBase().getElements().length; i++){
            p +="(";
            for(int j = 0; j < getDep().getBase().getValeur(i).getValeurs().length; j++){
                if(j != getDep().getBase().getDimensionVecteur()-1){
                    p += getDep().getBase().getValeur(i).getElement(j)+", ";
                }
                else{
                    p += (getDep().getBase().getValeur(i).getElement(j));
                }

            }
            if(i != getDep().getBase().getDimension()-1){
                p +=");";
            }
            else{
                p +=")";
            }

        }
        p += "}\n\n";

        p += "***Espace d'arrivé***\n";
        p += "Libellé : "+getArr().getLibelle()+"\n";
        p += "Dimension : "+getArr().getDimension()+"\n";
        //p +="Corps : "+p.getArr().getCorps()+"\n");
        p += "  ***Base associée***\n";
        p += "      Libellé : "+getArr().getBase().getLibelle()+"\n";
        p += "      Dimension : "+getArr().getBase().getDimension()+"\n";
        p += "      Dimension des vecteurs : "+getArr().getBase().getDimensionVecteur()+"\n";
        p += "      Famille : {";
        for(int i = 0; i < getArr().getBase().getElements().length; i++){
            p += "(";
            for(int j = 0; j < getArr().getBase().getValeur(i).getValeurs().length; j++){
                if(j != getArr().getBase().getDimensionVecteur()-1){
                    p += getArr().getBase().getValeur(i).getElement(j)+", ";
                }
                else{
                    p += (getArr().getBase().getValeur(i).getElement(j));
                }

            }
            if(i != getArr().getBase().getDimension()-1){
                p += ");";
            }
            else{
                p += ")";
            }

        }
        p += "}\n\n";
        p +="Définition : "+coeffToString()+"\n";
        if(getPDerive() != null){
            p +="Dérivé : "+getPDerive().coeffToString()+"\n";
        }
        if(getPPrimitive() != null){
            p +="Primitive : "+getPPrimitive().coeffToString()+"*x\n";
        }
        return p;
    }
    
    public String coeffToString(){
        double[] coeff = this.getCoefficients();
        int deg = this.getDegre();
        String res = "";
        for(int i = 0; i < coeff.length; i++){
            if(i == 0){
                res = coeff[i]+"*x^"+deg;
                deg--;
            }
            else if(i == coeff.length-1){
                if(coeff[i] >= 0){
                    res += "+"+Double.toString(coeff[i]);
                }
                else{
                    res += Double.toString(coeff[i]);
                }
            }
            else{
                if(coeff[i] >= 0){
                    res += "+"+coeff[i]+"*x^"+deg;
                    deg--;
                }
                else{
                    res += coeff[i]+"*x^"+deg;
                    deg--;
                }
            }
        }
        return res;
    }
    
    //****Autres méthodes****
    
    public Nombre[] calculerRacines(){
        Nombre[] racines = null;
        if(degre == 1) { //Méthode du discriminant
            racines = resolutionDegreUn();
        } else if(degre == 2) {
            racines = resolutionDegreDeux();
        } else if(degre == 3) {
            racines = resolutionDegreTrois();
        } else if(degre == 4) {
            racines = resolutionDegreQuatre();
        }
        return racines;
    }
    
    public Nombre[] resolutionDegreUn() {
        Nombre[] racines = null;
        racines[0] = new Nombre(-this.coefficients[1]/this.coefficients[0],0);
        return racines;
    }
    
    public Nombre[] resolutionDegreDeux() {
        Nombre[] racines = null;
        double delta = Math.pow(this.coefficients[1],2) - 4 * this.coefficients[0] * this.coefficients[2];
        if(delta > 0) {   
            racines = new Nombre[2];
            racines[0] = new Nombre((-this.coefficients[1] - Math.sqrt(delta))/(2*this.coefficients[0]), 0);
            racines[1] = new Nombre((-this.coefficients[1] + Math.sqrt(delta))/(2*this.coefficients[0]), 0);
        } else if(delta < 0) {
            racines = new Nombre[2];
            racines[0] = new Nombre(-this.coefficients[1]/(2*this.coefficients[0]),Math.sqrt(-delta)/2*this.coefficients[0]);
            racines[1] = new Nombre(-this.coefficients[1]/(2*this.coefficients[0]),-Math.sqrt(-delta)/2*this.coefficients[0]);
        } else {
            racines = new Nombre[1];
            racines[0] = new Nombre(-this.coefficients[1]/(2*this.coefficients[0]), 0);
        }
        return racines;
    }
    
    public Nombre[] resolutionDegreTrois() {
        Nombre b = new Nombre(this.coefficients[1]/this.coefficients[0], 0);
        Nombre c = new Nombre(this.coefficients[2]/this.coefficients[0], 0);
        Nombre d = new Nombre(this.coefficients[3]/this.coefficients[0], 0);
        Nombre p = c.soustraction(b.multiplication(b.division(new Nombre(3.0,0.0))));
        Nombre q = ((d.soustraction(b.multiplication(c.division(new Nombre(3.0,0.0))))).soustraction(b.multiplication(b.multiplication(b.division(new Nombre(27.0,0.0)))))).additioner(b.multiplication(b.multiplication(b.division(new Nombre(9.0,0.0)))));
        Nombre B = new Nombre(q);
        Nombre C = (new Nombre(-1.0,0.0)).multiplication(p.multiplication(p.multiplication(p.division(new Nombre(27.0,0.0)))));
        Nombre D = (B.multiplication(B)).soustraction(C.multiplication(new Nombre(4.0,0.0)));
        Nombre[] R = D.racine(2);
        Nombre U = (R[0].additioner(B.multiplication(new Nombre(-1.0,0.0)))).division(new Nombre(2.0,0.0));
        Nombre[] roots = U.racine(3);
        Nombre[] sol1 = new Nombre[roots.length];
        for(int i = 0; i < roots.length; i++) {
            sol1[i] = roots[i].soustraction(p.division(roots[i].multiplication(new Nombre(3.0,0.0))));
        }
        Nombre[] racines = new Nombre[sol1.length];
        for(int i = 0; i < sol1.length; i++) {
            racines[i] = sol1[i].soustraction(b.division(new Nombre(3.0,0.0)));
        }
        return racines;
    } 
    
    public Nombre[] resolutionDegreQuatre() {
        double a = this.coefficients[0];
        double b = this.coefficients[1];
        double c = this.coefficients[2];
        double d = this.coefficients[3];
        double e = this.coefficients[4];
        
        double z = b/(2.0*a);
        double aa =  c / a - 3.0*(z*z)/2.0;
        double bb = d/a+Math.pow(z,3.0)-c*z/a;
        double cc = e/a-3*Math.pow(z,4.0)/16.0+c*Math.pow(z,2.0)/(4.0*a)-d*z/(2.0*a);
        double d2 = -2.0*Math.pow(aa,3.0)/27.0-Math.pow(bb,2.0)+8.0*aa*cc/3.0;
        double c2 = -(Math.pow(aa,2.0)+12.0*cc)/3.0;
        double delta = Math.pow(c2/3.0, 3.0)+Math.pow(d2/2.0,2.0);
        
        double u;
        if(delta > 0) {
            double w = Math.cbrt(-d2/2.0+Math.sqrt(delta));
            u = w-(c2/3.0)/w;
        } else if(delta == 0) {
            u = 3.0*d2/c2;
        } else {
            u = 2*Math.sqrt(-c2/3.0)*Math.cos(Math.acos(-d2/2.0/Math.pow((-c2/3.0),(3.0/2.0)))/3.0);
        }
        double t = aa/3.0 + u;       
        double r = Math.sqrt(t-aa);
        double s = Math.sqrt(Math.pow(t/2.0,2.0)-cc);
        double decal = -b/(4.0*a);
        
        double delta2 = r*r-2.0*t-4.0*s;
        double partie1 = -r/2.0;
        if(bb > 0) {
            partie1 = -partie1;
        }
        double partie2 = Math.sqrt(Math.abs(delta2)) / 2.0;
        double[] sol1;
        if(delta2 >= 0) {
            sol1 = new double[2];
            sol1[0] = partie1+partie2+decal;
            sol1[1] = partie1-partie2+decal;
        } else {
            sol1 = null;
        }
        
        double delta3 = r*r-2.0*t+4.0*s;
        partie1 = r/2.0;
        if(bb>0) {
            partie1 = -partie1;
        }
        partie2 = Math.sqrt(Math.abs(delta3))/2.0;
        double[] sol2;
        if(delta3 >= 0) {
            sol2 = new double[2];
            sol2[0] = partie1+partie2+decal;
            sol2[1] = partie1-partie2+decal;
        } else {
            sol2 = null;
        }
        
        int nbrRacine = 0;
        if(sol1 != null) {
            nbrRacine += 2;
        }
        if(sol2 != null) {
            nbrRacine += 2;
        }
        Nombre[] racines = new Nombre[nbrRacine];
        if(sol1 != null) {
            racines[0] = new Nombre(sol1[0],0);
            racines[1] = new Nombre(sol1[1],0);
        }
        if(sol2 != null && sol1 == null) {
            racines[0] = new Nombre(sol2[0],0);
            racines[1] = new Nombre(sol2[1],0);
        } else if(sol2 != null && sol1 != null) {
            racines[2] = new Nombre(sol2[0],0);
            racines[3] = new Nombre(sol2[1],0);
        }
        return racines;
    }
    
    public void getDerive(){
        double[] coeffDerives = new double[degre];
        for(int i = 0; i < degre; i++){
            coeffDerives[i] = coefficients[i]*(degre-i);
        }
        Polynome p = new Polynome(this.getLibelle()+"Derivé", coeffDerives, degre-1, this.getDep(), this.getArr());
        this.pDerive = p;
    }
    
    public void getPrimitive(){
        double[] coeffPrimitive = new double[degre+1];
        for(int i = 0; i < degre+1; i++){
            coeffPrimitive[i] = coefficients[i]/((degre-i)+1);
        }
        Polynome p = new Polynome(this.getLibelle()+"Primitive", coeffPrimitive, degre+1, this.getDep(), this.getArr());
        this.pPrimitive = p;
    }
    
    public Vecteur calculerImage(Vecteur v){
        Vecteur[] retenue = new Vecteur[degre+1];
        String[] valRes = new String[v.getValeurs().length];
        for(int i = 0; i < valRes.length; i++){
            valRes[i] = "";
        }
        Vecteur res = new Vecteur("res", valRes);
        int deg = this.degre;
        for(int i = 0; i < degre+1; i++){
            retenue[i] = new Vecteur(v);   
            retenue[i] = retenue[i].puissance(deg);
            retenue[i] = retenue[i].multiplierScalaire(Double.toString(coefficients[i]));
            deg--;
        }
        for(int i = 0; i < retenue.length; i++){
            res.additionner(retenue[i]);
        }
        return res;
    }
}
