import java.awt.Graphics;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import Punto;

public class SistGravit
{
  private Punto[] ptos = null;
  private Malla malla;
  private int fil = -1;
  private int col = -1;

  private double h,h2,h3,h6;

  private double g;      // Cte de gravitacion
  private double gamma;  // Factor de rozamiento

  private boolean autoZoom = false;
  private boolean verPuntos = true;
  private boolean estela = false;

  private int ptoSel = -1;
  private int ptoOsc = -1;

  public SistGravit( )
  {
     malla = new Malla(Malla.CUADRADA);
     h = 0.05;
     h2 = h/2.0;
     h3 = h/3.0;
     h6 = h/6.0;
  }

  public void setDimensiones( int tipo, int fil, int col )
  {
     this.fil = fil;
     this.col = col;
     malla.setTipo(tipo);

     // Inicializacion de las posiciones
     ajusteInicial();

     // Creacion del vector de puntos
     ptos = new Punto[ malla.numPuntos(fil,col) ];
     for( int i = 0; i < ptos.length; i++ ) {
       ptos[i] = new Punto( malla.getPosPunto(i,fil,col) );
     }
     // Establecer el rango de la pantalla
     ajusteAutomatico();
  }

  public void reiniciar( )
  {
     for( int i = 0; i < ptos.length; i++ ) ptos[i].reiniciar();
  }

  public void ajusteInicial( )
  {
     Punto.minX = +1e100;
     Punto.maxX = -1e100;
     Punto.minY = +1e100;
     Punto.maxY = -1e100;
  }

  public void ajusteAutomatico( )
  {
     double lx = Punto.maxX - Punto.minX;
     double ly = Punto.maxY - Punto.minY;
     double mx = (lx < 1e-6) ? 0.5 : 0.1*lx;
     double my = (ly < 1e-6) ? 0.5 : 0.1*ly; 
     Punto.panX0 = Punto.minX - mx;
     Punto.panY0 = Punto.minY - my;
     Punto.paniLx = 1.0/(lx+2*mx);
     Punto.paniLy = 1.0/(ly+2*my);
  }

  public void setIncTpo( double dt )
  {
     h = dt;
     h2 = h/2.0;
     h3 = h/3.0;
     h6 = h/6.0;
  }

  public void setCoefSis( double k )
  {
     g = k;
  }

  public void setAmortig( double gamma )
  {
     this.gamma = gamma;
  }

  public void setPropsDibujo( boolean autoZoom, boolean verPuntos,
                              boolean estela )
  {
     this.autoZoom = autoZoom;
     this.verPuntos = verPuntos;
     this.estela = estela;
  }

  public void calculaFuerzas( )
  {
     for( int i = 0; i < ptos.length-1; i++ ) {
       for( int j = i+1; j < ptos.length; j++ ) {
         double ix = ptos[i].qc[0] - ptos[j].qc[0];
         double iy = ptos[i].qc[1] - ptos[j].qc[1];
         double d = Math.pow(ix*ix + iy*iy,-1.5);
         double fg  = g*ptos[i].masa*ptos[j].masa*d;
         double fx = fg*ix;
         double fy = fg*iy;
         ptos[i].fx -= fx; ptos[i].fy -= fy;
         ptos[j].fx += fx; ptos[j].fy += fy;
       }
     }
  }

  public synchronized void preparaIntegracion( )
  {
     if( ptos == null ) return;
     for(int i = 0; i < ptos.length; i++) ptos[i].rungeKuttaPreparacion();
  }

  public synchronized void integrar( )
  {
     if( ptos == null ) return;
     int i;
     // Metodo Runge-Kutta
     for(i = 0; i < ptos.length; i++) ptos[i].rungeKuttaInic();
     calculaFuerzas();
     for(i = 0; i < ptos.length; i++) ptos[i].rungeKuttaIter(h6,h2);
     calculaFuerzas();
     for(i = 0; i < ptos.length; i++) ptos[i].rungeKuttaIter(h3,h2);
     calculaFuerzas();
     for(i = 0; i < ptos.length; i++) ptos[i].rungeKuttaIter(h3,h);
     calculaFuerzas();
     for(i = 0; i < ptos.length; i++) ptos[i].rungeKuttaFinal(h6);
  }

  public void dibujar( Graphics g, int lx, int ly )
  {
     if( ptos == null ) return;
     if( autoZoom ) ajusteAutomatico();
     if( !estela ) {
       g.setColor(Color.white);
       g.fillRect(0,0,lx,ly);
     }
     for( int i = 0; i < ptos.length; i++ ) ptos[i].calculaPosicion(lx,ly);
     if( verPuntos )
       for( int i = 0; i < ptos.length; i++ ) ptos[i].dibujar(g,i == ptoSel);
  }

  public void dibujarVelocidad( Graphics g, int lx, int ly )
  {
     if( ptos == null ) return;
     g.setColor(Color.white);
     g.fillRect(0,0,lx,ly);
     for( int i = 0; i < ptos.length; i++ ) ptos[i].calculaPosicion(lx,ly);
     for( int i = 0; i < ptos.length; i++ ) ptos[i].calculaPosVel(lx,ly);
     for( int i = 0; i < ptos.length; i++ ) ptos[i].dibujar(g,i == ptoSel);
     for( int i = 0; i < ptos.length; i++ ) ptos[i].dibujarVeloc(g);
  }

  public void dibujarMasa( Graphics g, int lx, int ly )
  {
     if( ptos == null ) return;
     g.setColor(Color.white);
     g.fillRect(0,0,lx,ly);
     for( int i = 0; i < ptos.length; i++ ) ptos[i].calculaPosicion(lx,ly);
     for( int i = 0; i < ptos.length; i++ ) ptos[i].dibujar(g,i == ptoSel);
     for( int i = 0; i < ptos.length; i++ ) {
        double r = Math.log(ptos[i].masa)/Math.log(10.0);
        if(r<0) r = 1.0/(1.0-r); else r = r+1.0;
        int l = (int) Math.round(r*lx/6.0);
        g.setColor( ((i==ptoSel) ? Color.magenta : Color.lightGray) );
        g.drawOval(ptos[i].panX-l,ptos[i].panY-l,2*l,2*l);
     }
  }

  public void azarPosiciones( )
  {
     // Inicializacion de las posiciones
     ajusteInicial();
     for( int i = 0; i < ptos.length; i++ ) ptos[i].azarPosicion();
     ajusteAutomatico();
  }

  public void azarVelocidades( )
  {
     for( int i = 0; i < ptos.length; i++ ) ptos[i].azarVelocidad();
     double pxt = 0.0;
     double pyt = 0.0;
     double mt = 0.0;
     for( int i = 0; i < ptos.length; i++ ) {
       pxt += ptos[i].q[2]*ptos[i].masa;
       pyt += ptos[i].q[3]*ptos[i].masa;
       mt += ptos[i].masa;
     }
     double vcmx = pxt/mt;
     double vcmy = pyt/mt;
     for( int i = 0; i < ptos.length; i++ ) {
       ptos[i].q[2] -= vcmx;
       ptos[i].q[3] -= vcmy;
     }
  }

  public void azarMasas( )
  {
     for( int i = 0; i < ptos.length; i++ ) ptos[i].azarMasa();
  }

  public boolean selecPunto( Point p )
  {
     int dmin = Math.abs(ptos[0].panX-p.x) + Math.abs(ptos[0].panY-p.y);
     ptoSel = 0;

     for( int i = 1; i < ptos.length; i++ ) {
       int d = Math.abs(ptos[i].panX-p.x) + Math.abs(ptos[i].panY-p.y);
       if( d < dmin ) { dmin = d; ptoSel = i; }
     }
     if( dmin < 10 ) {
       return(true);
     } else {
       ptoSel = -1;
       return(false);
     }
  }

  public boolean selecPtoVel( Point p )
  {
     int dmin = Math.abs(ptos[0].panVx-p.x) + Math.abs(ptos[0].panVy-p.y);
     ptoSel = 0;

     for( int i = 1; i < ptos.length; i++ ) {
       int d = Math.abs(ptos[i].panVx-p.x) + Math.abs(ptos[i].panVy-p.y);
       if( d < dmin ) { dmin = d; ptoSel = i; }
     }
     if( dmin < 10 ) {
       return(true);
     } else {
       ptoSel = -1;
       return(false);
     }
  }

  public void muevePunto( Point p, int lx, int ly )
  {
     if( ptoSel == -1 ) return;
     ptos[ptoSel].moverPos(p,lx,ly);
  }

  public void muevePtoVel( Point p, int lx, int ly )
  {
     if( ptoSel == -1 ) return;
     ptos[ptoSel].moverVel(p,lx,ly);
  }

  public boolean deselecPunto( )
  {
     if( ptoSel == -1 ) return(false);
     ptoSel = -1;
     return(true);
  }

  public String infoVel( )
  {
     if( ptoSel == -1 ) return("");
     double vx = ptos[ptoSel].q[2];
     double vy = ptos[ptoSel].q[3];
     double r = Math.sqrt(vx*vx+vy*vy);
     double a = 180.0*Math.atan2(-vy,vx)/Math.PI;
     if(a < 0) a += 360.0;
     a = Math.round(1000.0*a)/1000.0;
     return(" ["+((float) vx)+", "+((float) vy)+"] r = "+
            ((float) r)+", ang = "+a+"\u00BA");
  }

  public void setMasaPunto( double masa )
  {
     if(ptoSel == -1) return;
     ptos[ptoSel].setMasa(masa);
  }

  public double getMasaPunto( )
  {
     if(ptoSel == -1) return(0.0);
     return(ptos[ptoSel].masa);
  }

  public boolean setTipoPunto( Point p, int tipo )
  {
     if( selecPunto(p) ) {
        ptos[ptoSel].setTipo(tipo);
        if( ptoOsc == ptoSel ) ptoOsc = -1;
        ptoSel = -1;
        return(true);
     } else {
        return(false);
     }
  }

  public void setTipoTodos( int tipo )
  {
     for( int i = 0; i < ptos.length; i++ ) ptos[i].setTipo(tipo);
     ptoOsc = -1;
  }

  public boolean setPuntoOscilante( Point p, double a, double w,
                                    boolean ejex, boolean ejey, boolean imp )
  {
     if( selecPunto(p) ) {
        ptos[ptoSel].setTipoOscilante(a,w,ejex,ejey,imp);
        ptoOsc = ptoSel;
        ptoSel = -1;
        return(true);
     } else {
        return(false);
     }
  }

  public void setAmplPtoOsc( double valor )
  {
     if( ptoOsc == -1 ) return;
     ptos[ptoOsc].setAmplOsc(valor);
  }

  public void setFrecPtoOsc( double valor )
  {
     if( ptoOsc == -1 ) return;
     ptos[ptoOsc].setFrecOsc(valor);
  }

  public void ampliar( Point pto, int lx, int ly )
  {
     double x = Punto.panX0 + pto.x/(lx*Punto.paniLx);
     double y = Punto.panY0 + pto.y/(ly*Punto.paniLy);
     Punto.paniLx *= 1.5;
     Punto.paniLy *= 1.5;
     Punto.panX0 = x - 0.5/Punto.paniLx;
     Punto.panY0 = y - 0.5/Punto.paniLy;
  }

  public void reducir( Point pto, int lx, int ly )
  {
     double x = Punto.panX0 + pto.x/(lx*Punto.paniLx);
     double y = Punto.panY0 + pto.y/(ly*Punto.paniLy);
     Punto.paniLx /= 1.5;
     Punto.paniLy /= 1.5;
     Punto.panX0 = x - 0.5/Punto.paniLx;
     Punto.panY0 = y - 0.5/Punto.paniLy;
  }
}

