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

class Slave {
  private static boolean addSlave=false; 
  
  public static void main(String argv[]) {
    
    if (argv.length<4) { System.out.println("\njava Slave query_port master_addr master_port master_port_to_query [carico] [ag]\n");
                         System.exit(1);
    	                 }
    
    	                 
    	                 
    // check parametro di test carico e di slave aggiunto (se master gia' in esecuzione).
    if (argv.length!=4)
     try { if (argv.length==5) 
                 if (argv[4].equals("ag"))
                       Slave.addSlave=true;
                  else ConstantsS.slaveLoad=(new Integer(argv[4])).intValue();
            else if (argv.length==6 && argv[5].equals("ag"))
                       { Slave.addSlave=true;
                         ConstantsS.slaveLoad=(new Integer(argv[4])).intValue();
                       }
                  else { System.out.println("\njava Slave query_port master_addr master_port master_port_to_query [carico] [ag]\n");
                         System.exit(1);
    	                 }
     } catch(NumberFormatException ex) {
        System.out.println("errore nei parametri: "+ex.toString());
        System.exit(1);
      }
    System.out.println("queryport: " + (new Integer(argv[0])).intValue());
    System.out.println("master addr: " + argv[1]);
    System.out.println("masterport: " + (new Integer(argv[2])).intValue());
    System.out.println("masterportQuery: " + (new Integer(argv[3])).intValue());
    System.out.println("carico: "+ConstantsS.slaveLoad);
    System.out.println("addSlave: "+Slave.addSlave);
    // inizializzazione comando di avvio del nuovo master
    String fsep=System.getProperty("file.separator");
    ConstantsS.masterPath=new String(".."+fsep+"Master"+fsep);
    System.out.println("masterPath: "+ConstantsS.masterPath);
    if ((System.getProperty("os.name")).indexOf("Windows")!=-1)
          // sistema windows
    	    ConstantsS.startMasterCmd=new String("command.com /c start_master_win.bat "+ConstantsS.masterPath+" "+argv[2]);
     else	// si suppone che sia un sistema unix con tcsh
          ConstantsS.startMasterCmd=new String("tcsh -c \"start_master_unx "+ConstantsS.masterPath+" "+argv[2]+"\"");
    System.out.println("startMasterCmd: "+ConstantsS.startMasterCmd);
    // determina l'ip locale dello slave
    try { ConstantsS.slaveIP=(InetAddress.getLocalHost()).getHostAddress();
    
    } catch(UnknownHostException ex) {
        System.out.println("Slave: problemi nel determinare l'ip locale: "+ex.toString());
        System.exit(2);
      }
    Slave ist=new Slave((new Integer(argv[0])).intValue(),argv[1],(new Integer(argv[2])).intValue(), (new Integer(argv[3])).intValue());
  }	
  
  Slave(int p,String ip,int mp, int queryport) {
    super();
    ConstantsS.queryPort=p;
    ConstantsS.portaSlave=p;
    ConstantsS.contactMasterUDP=p+10;
    ConstantsS.contactMasterVivi=p+1000;
    ConstantsS.masterIP=ip;
    ConstantsS.masterPort=mp;
    ConstantsS.masterPortQuery=queryport;
    ConstantsS.sv=new SlaveVivi();
    sendPortToMaster();
    
    
    
    
    
    ChkMaster cm=new ChkMaster();
    UpdateQueryMaster uq=new UpdateQueryMaster();
    MasterReelection mr= new MasterReelection();
    RegisterNewSlave rt=new RegisterNewSlave();
    cm.start();
    uq.start();
    mr.start();
    rt.start();
    ConstantsS.ackArrivato=new Vector();
    UpdateListaMaster ulm = new UpdateListaMaster(ConstantsS.queryPort+5);
    ulm.start();
    SlaveAcceptMaster sam = new SlaveAcceptMaster(ConstantsS.queryPort);
    sam.start();
    System.out.println("Slave "+ConstantsS.slaveIP+" inizializzato (fine costruttore).");
  }

  private void buildSlaveList(String lst) {
    
    if (lst.equals("")) return;
    StringReader sr;
    BufferedReader br;
    boolean dead=false;
    br=new BufferedReader(new StringReader(lst));
    try { String l,p;
          int pos;
          while ((l=br.readLine())!=null)
           { // controlla l'inizio della lista degli slaves morti
             if (l.equals("***")) { dead=true;
                                    continue;
                                  }  
             pos=l.indexOf(':');
             p=l.substring(pos+1,l.length());
             if (!dead)
                   { ConstantsS.sv.putSlave(l.substring(0,pos),(new Integer(p)).intValue());
                     System.out.println("Slave.buildSlaveList: aggiunto slave (vivo) "+l);
                   }
              else { ConstantsS.sv.putDeadSlave(l.substring(0,pos),(new Integer(p)).intValue());
                     System.out.println("Slave.buildSlaveList: aggiunto slave (morto) "+l);
                   }
           }
          br.close(); 	
    	
    } catch(IOException ex) {
    	  System.out.println("Slave.buildSlaveList: "+ex.toString());
    	}	
  }

  // trasmette a tutti i destinatari in dest il msg pac, tranne che
  // a se stesso.
  private void transmit(KeySlave[] dest,DatagramPacket pac,DatagramSocket soc) {
    
    byte[] b="".getBytes();
    DatagramPacket ack=new DatagramPacket(b,b.length);
    for (int i=0;i<dest.length;i++)
     try { // test per non inviare il msg a se stesso
           if (dest[i].ipSlave.equals(ConstantsS.slaveIP) && dest[i].portaSlave==ConstantsS.queryPort)
            continue;
           pac.setAddress(InetAddress.getByName(dest[i].ipSlave));
           pac.setPort(dest[i].portaSlave+4);
           soc.send(pac);
           System.out.println("Slave.transmit: inviato msg di registrazione a "+dest[i].ipSlave+":"+dest[i].portaSlave);
           // aspetta un ack per continuare
           soc.receive(ack);
           
     } catch(IOException ex) {
        System.out.println("Slave.transmit: "+ex.toString());
       }
  }

  // invia la porta queryPort al master
  private void sendPortToMaster() {
    
    try { System.out.println("Invio porta al Master ...");
          DatagramSocket s=new DatagramSocket(ConstantsS.queryPort);
          byte[] outb;
          int destPort;
          if (!Slave.addSlave)
                { // l'intero sistema si sta avviando
                  outb=(String.valueOf(ConstantsS.queryPort)).getBytes();
                  destPort=ConstantsS.masterPort;
                }
           else { // il master e' gia' avviato (aggiunta slave al sys)
                  outb=(new String("aggiungi slave")).getBytes();
                  // la destinazione e' lo thread 'sono vivo'
                  destPort=ConstantsS.masterPort+3;
                }  
          DatagramPacket outpac=new DatagramPacket(outb,outb.length,InetAddress.getByName(ConstantsS.masterIP),destPort);
          s.send(outpac);
          // aspetta l'ack con la lista di tutti gli slaves vivi (e
          // quelli morti in caso di addSlave==true)
          byte[] inb=new byte[1024];
          DatagramPacket inpac=new DatagramPacket(inb,inb.length);
          s.receive(inpac);
          buildSlaveList((new String(inpac.getData())).trim());
          // se lo slave si sta registrando al sistema, avendo ricevuto
          // la lista di tutti gli slaves, manda a tutti i 'fratelli'
          // un msg "aggiungi slave" per informarli della sua presenza
          if (Slave.addSlave)
           transmit(ConstantsS.sv.getSlave(),outpac,s);
          s.close();
          System.out.println("fatto");
           
    } catch(SocketException ex) {
    	  System.out.println("Slave.sendPortToMaster: "+ex.toString());
    	  System.exit(3);
    	}
    	catch(SecurityException ex) {
    	  System.out.println("Slave.sendPortToMaster: "+ex.toString());
    	  System.exit(4);
    	}
      catch(IOException ex) {
    	  System.out.println("Slave.sendPortToMaster: "+ex.toString());
    	  System.exit(5);
    	}
  }	// fine sendPortToMaster
}	// fine Slave