/**
 *
 * <p>Titulo: Servidor de respuestas a preguntas</p>
 * <p>Descripcion: Esta clase gestiona el servidor.
 * </p>
 * @author Diego Gonzalez Barrientos
 * @version 1.0
 */
 //Paquete en el que se encapsula todo el codigo del servidor
package servidorpreguntas;
//Paquetes requeridos para el programa
import javax.bluetooth.*;
import javax.microedition.io.*;
import java.io.*;
import java.util.Vector;
import java.awt.Color;
import java.awt.Paint;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import org.jfree.ui.TextAnchor;

/**Clase que implementa el servicio de preguntas, recepciona respuestas y acepta conexiones de clientes
*/
   public class SPP_Server
   {
/** Metodo constructor de la clase Servidor
*@param pregunta Pregunta que se planteara para ser contestada
*@param numero_alternativas numero de alternativas para la pregunta
*@param alternativa_correcta alternativa correcta dentro las especificadas
*@param numero_clientes Numero de clientes que se espera respondan la pregunta 
*/   
       public SPP_Server(String pregunta,int numero_alternativas,int alternativa_correcta,int numero_clientes,String[] contenidoAlternativas)
      {
          this.pregunta = pregunta;
			 this.numero_alternativas = numero_alternativas;
			 this.alternativa_correcta = alternativa_correcta;
			 this.numero_clientes = numero_clientes;
			 this.arregloAlternativas = contenidoAlternativas;
      }
 /**Metodo principal de la clase, basicamente provee el servicio de administrador de respuestas a la pregunta
 */  
       public void run_server()
      {
         try
         {
           //Referencia al dispositivo local
            device = LocalDevice.getLocalDevice(); 
				//Configurando la visibilidad del dispositivo
            device.setDiscoverable(DiscoveryAgent.GIAC); 
				//Creando Hebra que aceptara conexiones de clientes
            new ThreadedWaiting (device,uuid,"Servicio de Preguntas",this).start();
				//Iniciando Hebras creadas
				for (int i = 0; i < HebraEsperando.size(); i++)
					((ThreadedWaiting) HebraEsperando.elementAt(i)).start();            
         } 
             catch ( BluetoothStateException e )
            {
               e.printStackTrace();
            }
      
      }
		/**Funcion que permite imprimir informacion de debugeo
		*@param s mensaje que se imprimira en pantalla
		*/
       public void log( String s )
      {
         SPP_Main.log( s );
      }
		/**Funcion que permite finalizar las conexiones establecidas
		*/
		public void doneTrue() {
		   for (int i = 0; i < HebraEsperando.size(); i++){
					((ThreadedWaiting) HebraEsperando.elementAt(i)).doneTrue();
					((ThreadedWaiting) HebraEsperando.elementAt(i)).serverClose();
			}
      }
/**Referencia al dispositivo donde se ejecuta el programa
*/      
      public LocalDevice device;
		/**Referencia a Agente que busca dispositivos y servicios
		*/
      public DiscoveryAgent agent;
  /** Pregunta que se realiza
  */
  public static String pregunta;
  /**Numero de alternativas para la pregunta
  */
  public static int numero_alternativas;
  /**Alternativa de la pregunta que es la correcta
  */
  public static int alternativa_correcta;
  /**Numero de clientes que se espera respondan la pregunta
  */
  public static int numero_clientes;		
  /**Contenido de alternativa
  */
  public static String[] arregloAlternativas;
  /**Arreglo que contiene la letra para las alternativas
  */
  public static String[] alt_arreglo = new String[]{"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"};
   
/**UUID que especifica el servicio provisto
*/
      public final static UUID uuid = new UUID("102030405060708090A0B0C0D0E0F012", false);
/**Vector que mantiene la lista de las Hebras en espera de conexiones
*/
	   public Vector HebraEsperando = new Vector();      
/**Referencia al manejador del servidor
*/
      public StreamConnectionNotifier server;

   }
/**Clase que maneja las conexiones de los servicios creados
*/
    class ThreadedWaiting extends Thread
   {
/**Constructor.
*@param device Referencia al dispositivo
*@param uuid Identificador del servicio
*@param appName Nombre del servicio
*@param spp_servidor Instancia del servidor
*/	
		 public ThreadedWaiting(LocalDevice device, UUID uuid, String appName, SPP_Server spp_servidor) {
		     this.device = device;
			  this.uuid = uuid;
			  this.appName = appName;
			  this.spp_servidor = spp_servidor;
			  contador = new int[SPP_Server.numero_alternativas];
			  for (int i = 0; i < SPP_Server.numero_alternativas; i++)
			   contador[i] = 0;
			  counter=0;
		 }
/**Metodo que gestiona el cierre del servidor
*/		 
		 public void serverClose(){
		 	try{
			   server.close();
			} catch(IOException es) {}
		 
		 }
		 /**Metodo que incrementa contador de respuestas contestadas, y de alternativa contestada
		 *@param posicion alternativa contestada por cliente
		 */
		 synchronized public void increaseCounter(int posicion){
		     contador[posicion]++;
			  counter++;
		 }
		 /** Metodo que permite cerrar conexiones establecidas para el servicio
		 */
		 public void doneTrue(){
		   done = true;
		 	for (int i = 0; i < HebraEnviando.size(); i++) {
				((ThreadedSending) HebraEnviando.elementAt(i)).doneTrue();
			}
			
		 }
		 /** Metodo principal de la clase que inicia el servicio especificado y espera por conexiones de clientes
		 */
       public void run()
       { 
		      StreamConnection c = null;
         try
         {
			   //Url que especifica el servicio
            String url = "btspp://localhost:" + uuid.toString() +";name="+ appName;
            log("url del servidor: " + url );         
            server =  (StreamConnectionNotifier)Connector.open( url );
            ServiceRecord rec = device.getRecord( server );
            DataElement titulo = new DataElement(DataElement.STRING,(String)appName);
            rec.setAttributeValue( 0x0008, new DataElement( DataElement.U_INT_1, 0xFF ) );
            rec.setAttributeValue(0x4321,titulo);
				DataElement base = new DataElement(DataElement.DATSEQ);
				DataElement de = new DataElement(DataElement.STRING, appName);
				base.addElement(de);
            rec.setAttributeValue(0x4444, base);
			   log("Nombre de servicio: " + appName);
 //           Util.printServiceRecord( rec );
            rec.setDeviceServiceClasses( SERVICE_TELEPHONY  );
            } catch (Exception e){
               e.printStackTrace();
               log(e.getClass().getName()+" "+e.getMessage());
          }
   		 log("Ingresando a aceptar conexiones de clientes ...");
          while((!done)&&((counter)<(SPP_Server.numero_clientes - 1))) {
            try {
               c = server.acceptAndOpen();
				if((!done)&&(counter<SPP_Server.numero_clientes)) {ThreadedSending t = new ThreadedSending(c, server, this );
				HebraEnviando.addElement(t);
				t.start();}
            }catch(Exception e) {
                  e.printStackTrace();
                  SPP_Main.alert(e, SPP_Main.instance.spp_screen );
               }
         }
		  while(counter<SPP_Server.numero_clientes){}
		  final BarChart bar = new BarChart(SPP_Server.pregunta,contador,SPP_Server.alternativa_correcta,SPP_Server.numero_alternativas);
        bar.pack();
        RefineryUtilities.centerFrameOnScreen(bar);
        bar.setVisible(true);  
      }
   /**
   * Funcion que muestra informacion de debugeo
   * @param s Mensaje que se imprimira
   */
      public void log( String s )
      {
         SPP_Main.log( s );
      }
      /** Referencia de la conexion establecida
		*/   
      StreamConnection c = null;
		/**Objeto que acepta conexiones
		*/
      StreamConnectionNotifier server = null;
		/**Referencia del Dispositivo local
		*/
		LocalDevice device = null;
		/**Objeto que referencia identificador de servicio;
		*/
		UUID uuid = null;
		/**Nombre del servicio
		*/
		String appName = null;
		/**Identificador del tipo de servicio
		*/
		private final static int SERVICE_TELEPHONY = 0x400000;
		/**Referencia al servidor
		*/
		SPP_Server spp_servidor;
		/**Maneja conexiones al servicio establecidos
		*/
		Vector HebraEnviando = new Vector();
		/**Permite controlar el funcionamiento del servicio
		*/
      boolean done = false;
		/**Cuenta por alternativas la cantidad de veces que ha sido contestada
		*/
	   public int contador[];
		/**Contador de la cantidad de veces que se ha contestado una pregunta
		*/
		public int counter;
   }
	/**Clase que gestiona conexion con cliente que ha seleccionado pregunta
	*/
class ThreadedSending extends Thread
   {
	/**Constructor de la hebra
	*@param c Referencia a Conexion establecida con cliente
	*@param serv referencia a servidor que espera conexiones
	*@param tw referencia a hebra que maneja conexiones con clientes para el servicio
	*/
       public ThreadedSending( StreamConnection c, StreamConnectionNotifier serv, ThreadedWaiting tw) {
       
         this.c = c;     
         server = serv;
			this.tw = tw;
       }
		 /**Metodo que permite cerrar conexion con cliente 
		 */
		 public void doneTrue(){
		    done = true;
		 }
		 /**Metodo principal de la clase que provee servicio de informar sobre pregunta y recepcionar respuesta
		 */
		 public void run(){
            try {
               log("Servicio local esperando por la conexion del cliente...");
               Vector alternativas = new Vector();
					for (int i = 0; i < SPP_Server.numero_alternativas; i++)
						alternativas.addElement(SPP_Server.alt_arreglo[i]);
               //Obteniendo referencia al dispositivo remoto
               RemoteDevice rdev = RemoteDevice.getRemoteDevice( c );
            
              // Obteniendo flujo de entrada con cliente
               DataInputStream in = c.openDataInputStream();
              // Obteniendo flujo de salida con cliente					
               DataOutputStream out = c.openDataOutputStream();
					//Enviandole pregunta al cliente
					out.writeUTF(SPP_Server.pregunta);
					//Enviandole numero de alternativas
               out.writeInt(SPP_Server.numero_alternativas);
					//Enviandole alternativas 
     				for ( int i = 0; i < alternativas.size(); i++){
					    String alt = (String)alternativas.elementAt(i);
						 out.writeUTF(alt);
					}
					if (SPP_Server.arregloAlternativas == null) out.writeInt(1);
					else {
					      out.writeInt(0);
							for ( int i = 0; i < SPP_Server.arregloAlternativas.length; i++) 
							   out.writeUTF(SPP_Server.arregloAlternativas[i]);
							}
					          
					while (!done) {
               // read in a string from the string
					   
                  int i = in.readInt();            
						String s = in.readUTF();
                  log("La alternativa contestada fue: " + s + " ,posicion: " + i);
						 tw.increaseCounter(i);
						 done = true;
               }
					//Cerrando flujo de entrada
              in.close();
				  //Cerrando flujo de salida
               out.close();
            // cerrando conexion
               c.close();
            } 
                catch (Exception e)
               {
                  e.printStackTrace();
                  SPP_Main.alert(e, SPP_Main.instance.spp_screen );
               }

		 
		 }
   /**
   * Funcion que permite imprimir informacion de debugeo
   * @param s String
   */

       public void log( String s )
      {
         SPP_Main.log( s );
      }
/**Referencia a Hebra que gestiona conexiones de clientes al servicio
*/
	      public ThreadedWaiting tw = null;
/**Estado de la conexion. false implica que no ha terminado
*/
			boolean done = false;		 
/**Administrador de conexiones
*/			
         StreamConnectionNotifier server = null;
/**Referencia a conexion establecida
*/
		   StreamConnection c = null;
	}	 