/*--------------------------------------------------------------
    SIMULACIÓN GALÁCTICA: programa que simula la evolución
    de una galaxia desde unas condiciones iniciales aleatorias
    haciendo uso del algoritmo de velocidades de Verlet.
    La galaxia está formada por muchos sistemas solares
    y por un agujero negro supermasivo central. 
    El objetivo será ver como giran alrededor de dicho agujero
    negro, alcanzando un estado estacionario.
----------------------------------------------------------------*/
 
#include <iostream>
#include <random>
#include <cmath>
#include <fstream>
 
#define numsist 10 //Número de sistemas solares
#define G 6.351695379e-10 //Constante de grav en kly^3/(M_sol*My^2)
//Donde G=(6.67e-11 m^3/(kgs^2))*(1.989e30 kg/M_sol)*
//*(365.25*24*60*60e6 s/My)^2/(c*365.25*24*60*60e3 m/kly)^3
#define cte_vel 148.6271359 // 1 kly/My= 148627.1359 m/s = 148.6271359 km/s
                            //porque prefiero escribir en mi fichero en km/s
 
 
/*CLASES*/
 
 
//-----------------------------
 
 
//CLASE de los sistemas solares
class cSistema
{
private:
    //Características de los sistemas (comienza por s de Sistema Solar)
    double sPosicionX, sVelocidadX, sPosicionY;
    double  sVelocidadY, sMasa, sExcentricidad;
public:
    cSistema(void); //Constructor
    bool enAgujero(void); //Método para comprobar si ha sido absorbido
    //Métodos para la muestra de los datos de posiciones
    double getPosX(void){return sPosicionX;}; double getPosY(void){return sPosicionY;};
    //Métodos para la muestra de los datos de velocidades
    double getVelX(void){return sVelocidadX;}; double getVelY(void){return sVelocidadY;};
    //Método para la muestra de la masa
    double masa(){return sMasa;};
    //Métodos para establecer las posiciones y las velocidades
    void setPosX(double px){sPosicionX=px;}; void setPosY(double py){sPosicionY=py;};
    void setVelX(double vx){sVelocidadX=vx;}; void setVelY(double vy){sVelocidadY=vy;};
    ~cSistema(void){}; //Destructor
};
 
 
//Función global que me genera un número aleatorio con una semilla diferente
//cada vez que el programa se inicia
std::mt19937& prng() {
    // seed only one PRNG per thread that needs is:
    static thread_local std::mt19937 gen(std::random_device{}());
    return gen;
}
 
 
//MÉTODOS DE cSistema
 
//Constructor de los sistemas solares
cSistema::cSistema(void)
{
    //La galaxia mide unos 200 kly, distibuyo los
    //sistemas en un circulo de radio 100 kly aleatoriamente
    double a=100.0; double pi=3.14159265;
 
    //Distribución de radios desde a/8 hasta a
    std::uniform_real_distribution<double> distribucion(a/8,a);
    double r = distribucion(prng());
    //Distribución de ángulos de 0 a 2pi (circulo completo)
    std::uniform_real_distribution<double> distribucion_phi(0.0,2.0*pi);
    double phi=distribucion_phi(prng());
 
 
    /*Inicialización de parámetros de los sistemas*/
    //La masa de cada sistema solar en masas solares
    sMasa=1.0014;
    //Posiciones aleatorias entre -100 y 100 kly
    sPosicionX= r*cos(phi);
    sPosicionY= r*sin(phi);
    //Velocidades aleatorias entre 0 y cte_vel=0.667 kly/My (=200km/s)
    double rotacion=-1; // 1: clockwise   -1: counter-clockwise 
    double v=sqrt(G*4.154e6/r); //Le doy justo la velocidad orbital, para que trace orbitas círculares
    sVelocidadX=rotacion*v*sin(phi);
    sVelocidadY=-rotacion*v*cos(phi);
 
    //Control posiciones y velocidades por consola
    //std::cout << "Posicion ("<<sPosicionX<<", "<<sPosicionY<<"), velocidad ("<<sVelocidadX<<", "
    //<<sVelocidadY<<")"<<std::endl;
    //La excentricidad se obtiene a partir de las velocidades
    //ver "Vector de excentricidad"
    }
 
//Método para saber si el sistema solar ha sido
//absorbido por el agujero negro (SIN USO, aún)
bool cSistema::enAgujero(void)
{
    if (sPosicionX<2e-5 && sPosicionY<2e-5) //R_aguj=17R_sol=2e-5 klyr
        return true; //Devuelve verdad si está dentro
    else return false; //Falso si no
}
 
 
 
//-----------------------------
 
 
//CLASE del agujero negro
class cAgujero
{
private:
    //Características del agujero negro (comienza por a de Agujero Negro)
    double aPosicionX, aVelocidadX, aPosicionY;
    double  aVelocidadY, aMasa, aExcentricidad;
public:
    cAgujero(void); //Constructor
    //Métodos para la muestra de los datos de posiciones (BH=blackhole)
    double getPosXBH(void){return aPosicionX;}; double getPosYBH(void){return aPosicionY;};
    //Métodos para la muestra de los datos de velocidades (BH=blackhole)
    double getVelXBH(void){return aVelocidadX;}; double getVelYBH(void){return aVelocidadY;};
    //Método para la muestra de la masa (BH=blackhole)
    double masaBH(){return aMasa;};
    //Métodos para establecer las posiciones, las velocidades y la masa
    void setPosXBH(double pxBH){aPosicionX=pxBH;}; void setPosYBH(double pyBH){aPosicionY=pyBH;};
    void setVelXBH(double vxBH){aVelocidadX=vxBH;}; void setVelYBH(double vyBH){aVelocidadY=vyBH;};
    void setMasaBH(double mBH){aMasa=mBH;};
    ~cAgujero(void){}; //Destructor
};
 
//MÉTODOS DE cAgujero
 
//Constructor del agujero negro
cAgujero::cAgujero(void)
{
    /*Valores iniciales para el agujero negro*/
    aMasa=4.154e6; //La masa del agujero negro en masas solares
    aPosicionX=0; aPosicionY=0; //Lo coloco en el centro
    aVelocidadX=0; aVelocidadY=0; //Y sin velocidad
}
 
 
//-----------------------------
 
 
 
//CLASE de la galaxia
class cGalaxia {
private:
    cSistema Solar[numsist];
    cAgujero Negro;
 
public :
   cGalaxia(){};
   ~cGalaxia(){}; //Constructor y destructor
   //Método del algoritmo de verlet
   void verlet(cSistema (&Solar)[numsist], cAgujero Negro,
   double (&ax)[numsist+1], double (&ay)[numsist+1], double h);
   //Método que inicializa las aceleraciones (verlet, pero solo un uso)
   void AceleracionesIniciales(cSistema (&Solar)[numsist], cAgujero Negro,
   double (&ax)[numsist+1], double (&ay)[numsist+1]);
   //HACER FUNCION choques(); //Método de los choques inelésticos
};
 
//Método de las aceleraciones iniciales
//ACELERACION AGUJERO NEGRO ("elemento 1000")
void cGalaxia::AceleracionesIniciales(cSistema (&Solar)[numsist], cAgujero Negro,
double (&ax)[numsist+1], double (&ay)[numsist+1])
 {
    int i, j;
    double xaux,yaux,denom;
    double xauxBH, yauxBH, denomBH;
    //Aceleración de cada sistema
    ax[numsist]=0; ay[numsist]=0; //Inicialización para el agujero negro
    for(i=0;i<numsist;i++) //Evalúo cada sistema
    {
        ax[i]=0;ay[i]=0; //Inicialización aceleraciones 
        for(j=0;j<numsist;j++) //Evaluo la interacción con cada otro sistema
        if (j!=i && (abs(Solar[i].getPosX()-Solar[j].getPosX())<5) 
        && (abs(Solar[i].getPosY()-Solar[j].getPosY())<5)) //Cuadrado de 5x5 para evitar
                                                           //calculos inútiles, solo influencia cercana
        {
            //Variables auxiliares
            xaux=Solar[i].getPosX()-Solar[j].getPosX();
            yaux=Solar[i].getPosY()-Solar[j].getPosY();
            denom=pow(((xaux*xaux)+(yaux*yaux)),1.5);
 
            //Aceleraciones
            ax[i]-=G*Solar[j].masa()*xaux/denom;
            ay[i]-=G*Solar[j].masa()*yaux/denom;
        }
        //Añado la contribución del agujero negro a cada sistema i
        xaux=Solar[i].getPosX()-Negro.getPosXBH();
        yaux=Solar[i].getPosY()-Negro.getPosYBH();
        denom=pow(((xaux*xaux)+(yaux*yaux)),1.5);
        ax[i]-=G*Negro.masaBH()*xaux/denom;
        ay[i]-=G*Negro.masaBH()*yaux/denom;
 
        //control aceleraciones por consola
        //std::cout<<"Aceleracion x"<<i<<": "<<ax[i]<<std::endl;
        //std::cout<<"Aceleracion y"<<i<<": "<<ay[i]<<std::endl;
 
        //Calculo la aceleración del agujero negro
        xauxBH=Negro.getPosXBH()-Solar[i].getPosX();
        yauxBH=Negro.getPosYBH()-Solar[i].getPosY();
        denomBH=pow(((xauxBH*xauxBH)+(yauxBH*yauxBH)),1.5);
        ax[numsist]-=G*Solar[i].masa()*xauxBH/denomBH;
        ay[numsist]-=G*Solar[i].masa()*yauxBH/denomBH;
    }
    //control aceleraciones por consola
    //std::cout<<"Aceleracion xBH: "<<ax[numsist]<<std::endl;
    //std::cout<<"Aceleracion yBH: "<<ay[numsist]<<std::endl;
    return; 
 }
 
 
//Método de aplicación del algoritmo de verlet a la galaxia completa
void cGalaxia::verlet(cSistema (&Solar)[numsist], cAgujero Negro,
 double (&ax)[numsist+1], double (&ay)[numsist+1], double h)
{
    //Declaración de variables
    double wx[numsist+1],wy[numsist+1],xaux,yaux,denom, xauxBH, yauxBH, denomBH;
    int i,j;
 
    //Posiciones, y velocidades auxiliares
    for(i=0; i<numsist; i++)
    {
        //Cambio de r(t) a r(t+h)
        Solar[i].setPosX(Solar[i].getPosX()+(h*Solar[i].getVelX())+((h*h/2.)*ax[i]));
        Solar[i].setPosY(Solar[i].getPosY()+(h*Solar[i].getVelY())+((h*h/2.)*ay[i]));
        //Velocidades auxiliares
        wx[i]=Solar[i].getVelX()+((h/2.)*ax[i]);
        wy[i]=Solar[i].getVelY()+((h/2.)*ay[i]);
    }
    //Para el agujero negro
    Negro.setPosXBH(Negro.getPosXBH()+(h*Negro.getVelXBH())+((h*h/2.)*ax[numsist]));
    Negro.setPosYBH(Negro.getPosYBH()+(h*Negro.getVelYBH())+((h*h/2.)*ay[numsist]));
    wx[numsist]=Negro.getVelXBH()+((h/2.)*ax[numsist]);
    wy[numsist]=Negro.getVelYBH()+((h/2.)*ay[numsist]);
 
 
    //Aceleraciones
    for(i=0; i<numsist;i++) //Evalúo cada sistema
    {
        ax[i]=0;ay[i]=0; //Inicialización aceleraciones
        for(j=0;j<numsist;j++) //Evalúo la interacción con los demás sistemas
        if (j!=i && (abs(Solar[i].getPosX()-Solar[j].getPosX())<5) 
        && (abs(Solar[i].getPosY()-Solar[j].getPosY())<5)) //Cuadrado de 5x5 para no hacer 
                                                           //calculos inútiles, solo influencia cercana
        {
            //Variables auxiliares
            xaux=Solar[i].getPosX()-Solar[j].getPosX();
            yaux=Solar[i].getPosY()-Solar[j].getPosY();
            denom=pow(((xaux*xaux)+(yaux*yaux)),1.5);
 
            //Aceleraciones
            ax[i]-=G*Solar[j].masa()*xaux/denom;
            ay[i]-=G*Solar[j].masa()*yaux/denom;
        }   
        //Añado la contribución del agujero negro a cada sistema i
        xaux=Solar[i].getPosX()-Negro.getPosXBH();
        yaux=Solar[i].getPosY()-Negro.getPosYBH();
        denom=pow(((xaux*xaux)+(yaux*yaux)),1.5);
        ax[i]-=G*Negro.masaBH()*xaux/denom;
        ay[i]-=G*Negro.masaBH()*yaux/denom;
        //Calculo la aceleración del agujero negro
        xauxBH=Negro.getPosXBH()-Solar[i].getPosX();
        yauxBH=Negro.getPosYBH()-Solar[i].getPosY();
        denomBH=pow(((xauxBH*xauxBH)+(yauxBH*yauxBH)),1.5);
        ax[numsist]-=G*Solar[i].masa()*xauxBH/denomBH;
        ay[numsist]-=G*Solar[i].masa()*yauxBH/denomBH;
    }
 
    //Velocidades
    for(i=0;i<numsist;i++)
    {
        Solar[i].setVelX(wx[i]+((h/2.)*ax[i]));
        Solar[i].setVelY(wy[i]+((h/2.)*ay[i]));
    }
    //Para el BH
    Negro.setVelXBH(wx[numsist]+((h/2.)*ax[numsist]));
    Negro.setVelYBH(wy[numsist]+((h/2.)*ay[numsist]));
 
    return;
}
 
 
//=====================================================
 
 
 
 
 
//=====================================================
 
 
 
 
 
/*FUNCIÓN PRINCIPAL*/
 
int main (void)
{
    //DECLARACIÓN DE VARIABLES PRINCIPALES
    cSistema sistemas[numsist]; //Todos los sistemas solares
    cAgujero agujero;
    cGalaxia galaxia;
    int i, j, iter, escribo;
    double aceleracionx[numsist+1], aceleraciony[numsist+1];
    double t_fin,t_aux, h;
    std::ofstream pos; //Llamo a mi fichero de posiciones "pos"
    std::ofstream vel; //Llamo a mi fichero de velocidades "vel"
 
    //Puesta a punto de los ficheros
    pos.open("posiciones.txt"); //Lo abro en un fichero llamado posiciones.txt
    if (!pos.is_open()) std::cout<<"Error al abrir el fichero posiciones.txt";
    vel.open("velocidades.txt"); //Lo abro en un fichero llamado velocidades.txt
    if (!vel.is_open()) std::cout<<"Error al abrir el fichero velocidades.txt";
    pos << "#PosicionX   PosicionY"<<std::endl; //Almohadilla porque es comentario en gnuplot
    vel << "#Radio    Velocidad"<<std::endl; //Almohadilla porque es comentario en gnuplot
 
    //Lectura de datos temporales por pantalla
    /*std::cout<<"Introduzca el paso infinitesimal temporal: ";
    std::cin>>h;std::cout<<std::endl;
    std::cout<<"Introduzca el tiempo de evolucion galactica: ";
    std::cin>>t_fin;std::cout<<std::endl;
    std::cout<<"Escribir datos en ficheros cada cuantas iteraciones: ";
    std::cin>>escribo;std::cout<<std::endl;*/
 
    //Por si me cansa la escritura en la consola:
    //Año galactico ~250My
    h=0.005;t_fin=2e4;
    escribo=(int) t_fin/(h*1e3);
 
    //Implementación del algoritmo a la función principal, INICIALIZACIÓN
    galaxia.AceleracionesIniciales(sistemas,agujero,aceleracionx,aceleraciony);
 
    //Para simplificar las cosas, uso numeros enteros
    //Porque me es útil para saber el porcentaje de ejecución
    //y tambien el numero de escrituras en archivo (va con division entera)
    int hmod=lround(h/h);
    int t_auxmod=lround(t_aux/h); 
    int t_finmod=lround(t_fin/h);
    int orden=floor(log10(t_fin));
 
    for(t_auxmod=hmod; t_auxmod<=t_finmod+hmod; t_auxmod=t_auxmod+hmod) //de h a t_fin con pasos de h
    {
        galaxia.verlet(sistemas,agujero,aceleracionx,aceleracionx,h); //ALGORITMO
 
        //ESCRITURA
        //El numero de iteraciones del algoritmo será:
        iter=t_auxmod/hmod;
        //Escribimos cada determinado número de iteraciones
        if((iter%escribo)==0){
        //Escritura en fichero pos y vel del agujero negro
        pos << agujero.getPosXBH() << "\t" << agujero.getPosYBH() << std::endl;
            vel << sqrt(agujero.getPosXBH()*agujero.getPosXBH()+agujero.getPosYBH()*agujero.getPosYBH())
        << "\t" <<sqrt(agujero.getVelXBH()*agujero.getVelXBH()+agujero.getVelYBH()*agujero.getVelYBH())*cte_vel<<std::endl;
        //Escritura de los sistemas solares
        for(i=0;i<numsist;i++){
        pos<<sistemas[i].getPosX() <<"\t"<< sistemas[i].getPosY() << std::endl;  
            vel<<sqrt(sistemas[i].getPosX()*sistemas[i].getPosX()+sistemas[i].getPosY()*sistemas[i].getPosY())
        <<"\t"<<sqrt(sistemas[i].getVelX()*sistemas[i].getVelX()+sistemas[i].getVelY()*sistemas[i].getVelY())*cte_vel<<std::endl;
        }//endfor
        pos<<std::endl<<std::endl;vel<<std::endl<<std::endl;//Dos espacios en blanco separan cada iteración en ambos ficheros
        }//endif
 
 
        //PORCENTAJE DE EJECUCIÓN
        int pt=10; //Paso porcentual que se muestra en pantalla
        //Exponentes
        int grande=1+orden;
        int exp1=2+grande; int exp2=1+grande; int exp3=grande;
        //Porcentaje de ejecución, cada 10%, fallos por ser numeros inexactos y redondeo de int
        unsigned long int porcentaje=100*t_aux/t_fin;
        if(porcentaje%pt==0){ //Detección paso a paso no muy precisa
        porcentaje = pow(pt,exp1)*t_auxmod/t_finmod; //Para ser más preciso en mis cálculos (division de enteros redondea)
        if(porcentaje%(int)pow(pt,exp2)==0) std::cout << "Ejecutado al "<<porcentaje/pow(pt,exp3)<<"%"<<std::endl;
        //^Detección precisa
        }//endif
    }
 
    //Cierro los ficheros
    pos.close();
    vel.close();
 
    return 0;
}