/* 
* Copyright 2016 Agencia Estatal de Administración Tibutaria
*
* Licencia con arreglo a la EUPL, Versión 1.1 o –en cuanto
* sean aprobadas por la Comisión Europea– versiones
* posteriores de la EUPL (la «Licencia»);
* Solo podrá usarse esta obra si se respeta la Licencia.
* Puede obtenerse una copia de la Licencia en:
*  
* https://joinup.ec.europa.eu/software/page/eupl
*  
* Salvo cuando lo exija la legislación aplicable o se acuerde
* por escrito, el programa distribuido con arreglo a la
* Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES
* DE NINGÚN TIPO, ni expresasni implícitas.
*
* Véase la Licencia en el idioma concreto que rige los permisos y 
* limitaciones que establece la Licencia.
*/ 

package servidor.utiles;

import java.io.File;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import javax.swing.JOptionPane;
import servidor.*;
import servidor.comunicaciones.*;

public class ProgramadorTareas {

   protected ArrayList<Tarea> listaTareas;
   ExecutorService ejecutor;
   protected ArrayList<Tarea> listaTareasEnEjecucion;
   boolean ejecutando = false;;

   public ProgramadorTareas() {
      listaTareas = new ArrayList<>();
      listaTareasEnEjecucion = new ArrayList<>();
      ejecutor = Executors.newSingleThreadExecutor();
   }

   public void add(Tarea t) {
      while(ejecutando) {
         Global.sleep(1000);
      }
      listaTareas.add(t);
      Collections.sort(listaTareas);
   }

   public boolean hayTareasEnEjecucion() {
      for(Tarea t: listaTareasEnEjecucion) {
         if(!t.future.isDone()) {
            return true;
         }
      }
      return false;
   }

   public void mostrarTareasEnEjecucion() {
      for(Tarea t: listaTareasEnEjecucion) {
         Date hora = t.horaInicio;
         if(!t.future.isDone()) {
            String txt = String.format("    %1$-15s(%2$s)", t.nombre, (hora.equals(Fecha.DateNulo)? "pendiente" : "en ejecuc."));
            System.out.println(txt);
         }
      }
   }

   public Tarea IsTareaEnEjecucion(String nombre) {
      for(Tarea t: listaTareasEnEjecucion) {
         if(t.nombre.equals(nombre) && !t.future.isDone()) {
            return t;
         }
      }
      return null;
   }

   public void ejecutarTareasPendientes() {
      ejecutando = true;
      try {
         Date Ahora = new Date();
         if (listaTareas.size() > 0) {
            ListIterator<Tarea> iter = listaTareas.listIterator();
            Tarea reg = null;
            while (iter.hasNext()) {
               reg = iter.next();
               if (reg.hora.before(Ahora)) {
                  iter.remove();
                  if (reg.ejecucionEnParalelo) { // introducirla en el planificador
                     Future future = ejecutor.submit(reg);
                     reg.future = future;
                     listaTareasEnEjecucion.add(reg);
                  } else {    // ejecucion inmediata
                     reg.run();
                  }
               }
            }
         }
      } catch(Exception exc) {}
      ejecutando = false;
   }

   public void ejecutarTareasPendientesObligatorias() {
      ejecutando = true;
      if (listaTareas.size() > 0) {
         ListIterator<Tarea> iter = listaTareas.listIterator();
         while (iter.hasNext()) {
            Tarea reg = iter.next();
            if (reg.obligatorio) {
               iter.remove();
               java.awt.Toolkit.getDefaultToolkit().beep();
               int respuesta = JOptionPane.showConfirmDialog(null, "¿ Ejecutar esta tarea pendiente ?\n" + reg.codigo + " " + reg.parametros, "TAREAS PROGRAMADAS", JOptionPane.YES_NO_OPTION);
               if (respuesta == JOptionPane.YES_OPTION) {
                  if(reg.ejecucionEnParalelo) {
                     Future future = ejecutor.submit(reg);
                     reg.future = future;
                     listaTareasEnEjecucion.add(reg);
                  } else {
                     reg.run();
                  }
               }
            }
         }
      }
      ejecutando = false;
   }

   public void crearTareaScriptUsuario(Date hora, String path, String[] params) {
      add(new TareaScriptUsuario(path, params, hora));
   }

   public void crearTareaCierre() {
      // calcular un intervalo de tiempo distinto para cada servidor...
      long semilla = 0;
      try {
         semilla = Long.parseLong(Configuracion.getConfiguracion().codigoCentro);
      } catch (Exception exc) {
         // semilla tomará el valor por defecto...
      }
      semilla += System.currentTimeMillis();
      Random random = new Random(semilla);
      int minutos = 1 + random.nextInt(5);
      int segundos = random.nextInt(60);
      String strHora = "24:" + minutos + ":" + segundos;
      add(new TareaCierreDia(Fecha.convertirHora(strHora)));
   }

   public void crearTareaCierreYa() {  // para pruebas
      GregorianCalendar gc = new GregorianCalendar();
      gc.add(Calendar.SECOND, 30);
      add(new TareaCierreDia(gc.getTime()));
   }

   public void crearTareaTurno(Date hora) {
      add(new TareaTurno(hora));
   }

   public void crearTareaBackup(String strHora) {
      Tarea t = IsTareaEnEjecucion("BackupBD");
      if(t != null) {
         System.err.println("Hay una tarea de BACKUP en curso. NO se iniciará de nuevo");
         System.err.println("Compruebe la configuracion del servidor");
         return;
      }
      add(new TareaBackup(Fecha.convertirHora(strHora)));
   }

   public void crearTareaGenerarCitas(Date hora) {    
      hora.setTime(hora.getTime() + 500);
      add(new TareaGenerarCitas(hora));
   }
   
   public void crearTareaLiberarMemoria(Date hora) {
      hora.setTime(hora.getTime() + 500);
      add(new TareaLiberarMemoria(hora));
   }

   public void crearTareaReleer(Date hora) {
      hora.setTime(hora.getTime() + 1000);
      add(new TareaReleer(hora));
   }

   public void crearTareaTest() {
      Tarea t = IsTareaEnEjecucion("prueba");
      if(t != null) {
         System.out.println("Tarea Test ejecutandose " + t.transcurrido().toString());
         t.matarTarea();
      }
      add(new TareaTest(new Date(), "prueba"));

   }

   public List<String> listarTareasPendientes() {
      ArrayList<String> lista = new ArrayList<>();
      int n = listaTareas.size();
      for (int i = 0; i < n; i++) {
         Tarea r = listaTareas.get(i);
         String resu = r.codigo + " - " + r.hora + " - Params: " + r.parametros;
         lista.add(resu);
      }
      return lista;
   }

   protected class Tarea implements Comparable<Tarea>, Runnable {

      public String nombre = "TareaProgramada";
      public boolean ejecucionEnParalelo = false;
      public String codigo;
      public String parametros;
      public java.util.Date hora;
      public java.util.Date horaInicio = new java.util.Date(0);
      public boolean obligatorio;
      protected boolean emitirMensajeAlFinal = true;
      protected Future future = null;

      public Tarea(String cod, String params, Date hora, boolean obli) {
         codigo = cod;
         parametros = params;
         this.hora = hora;
         obligatorio = obli;
      }

//      public Tarea(String cod, String[] params, Date fec, boolean obli) {
//      }
      @Override
      public int compareTo(Tarea t) {
         return hora.compareTo(t.hora);
      }

      @Override
      public void run() {
         horaInicio = new java.util.Date();
         if (procesoTarea() && emitirMensajeAlFinal) {
            Global.mensajeDep("Ejecutado " + codigo + " " + parametros);
         }
      }

      public boolean procesoTarea() {
         return false;
      }

      public void matarTarea() {
         if(future != null) future.cancel(true);
      }

      public TimeSpan transcurrido() {
         return new TimeSpan(horaInicio, new Date());
      }
   }

  

  public class TareaScriptUsuario extends Tarea {

      public TareaScriptUsuario(String path, String[] params, Date fec) {
          super(path, "", fec, false);
          String parametro = "";
          if(params != null) {
              for(int i=0;i<params.length;i++) {
                if(params[i].equalsIgnoreCase("serverSql")) {
                    params[i] = servidor.datos.BaseDatos.bd().nombreServidorSqlParaScript();
                }
                if(params[i].equalsIgnoreCase("passwordSql")) {
                    params[i] = Global.passwordServidorSql;
                }
                parametro += params[i] + " ";
              }
          }
          else {
           parametro += servidor.datos.BaseDatos.bd().nombreServidorSqlParaScript() + " "+Global.passwordServidorSql;
          }

          parametros = parametro;
      }

      @Override
      public boolean procesoTarea() {
         try {
            File f = new File(codigo);
            if (!f.exists()) {
               return false;
            }
            if (codigo.endsWith(".vbs") || codigo.endsWith(".wsf")) {
               codigo = "cscript \"" + codigo + "\"";
            }
            Process proceso = Runtime.getRuntime().exec(codigo + " " + parametros);
            int resultado = proceso.waitFor();
            System.out.println(f.getName() + " devolvió: " + resultado);
            return true;
         } catch (InterruptedException interruptedExc) {
            System.err.println("(procesoTarea) " + interruptedExc.getMessage());
            return false;
         } catch (Exception exc) {
            exc.printStackTrace();
            return false;
         }
      }
   }

   public class TareaTurno extends Tarea {

      public TareaTurno(Date hora) {
         super("Cambio de turno", "", hora, false);
         nombre = "CambioTurno";
      }

      @Override
      public boolean procesoTarea() {
         servidor.comunicaciones.GestorColas.getGestor().cambiodeTurnoProgramado();
         return true;
      }
   }

   public class TareaGenerarCitas extends Tarea {

      public TareaGenerarCitas(Date hora) {
         super("Generacion local de citas", "", hora, false);
         nombre = "GenCitas";
      }

      @Override
      public boolean procesoTarea() {
         servidor.datos.BaseDatos.bd().generarCitas();
         return true;
      }
   }
     public class TareaLiberarMemoria extends Tarea {

      public TareaLiberarMemoria(Date hora) {
         super("Liberar memoria", "", hora, false);
         nombre = "LibMemoria";
      }

      @Override
      public boolean procesoTarea() {
         servidor.datos.BaseDatos.bd().ajustarMemoria();
         return true;
      }
   }

   public class TareaCierreDia extends Tarea {

      public TareaCierreDia(Date hora) {
         super("Cierre de dia", "", hora, false);
         nombre = "TareaCierreDia"; // para evitar que se espere en hayTareasEnEjecucion
      }

      @Override
      public boolean procesoTarea() {
         servidor.utiles.Global.main.cierreYApertura();
         return true;
      }
   }

   public class TareaBackup extends Tarea {

      public Process procesoBackup = null;
      public TareaBackup(Date hora) {
         super("Backup de BDatos", "", hora, false);
         nombre = "BackupBD";
         ejecucionEnParalelo = true;
      }

      @Override
      public boolean procesoTarea() {
         AtomicReference<Process> ref  =new AtomicReference<>();
         boolean ret = servidor.datos.BaseDatos.bd().backup(ref);
         procesoBackup = ref.get();
         return ret;
      }

      @Override
      public void matarTarea() {
         if(procesoBackup != null) procesoBackup.destroy();
         super.matarTarea();
      }
   }

   public class TareaReleer extends Tarea {

      public TareaReleer(Date hora) {
         super("Recarga de tablas", "", hora, false);
         nombre = "Releer";
      }

      @Override
      public boolean procesoTarea() {
         GestorColas.getGestor().autoMensaje(MensajeInterno.MSG_REFRESCA_TABLAS);
         return true;
      }
   }

   public class TareaTest extends Tarea {

      public TareaTest(Date hora, String nombre) {
         super("Test ProgramadorTareas", "", hora, false);
         ejecucionEnParalelo = true;
         this.nombre = nombre;
      }

      @Override
      public boolean procesoTarea() {
         System.out.println(nombre + "-> " + new java.util.Date().toString());
         Global.sleep(1000 * 300);
         return true;
      }

      @Override
      public void matarTarea() {
         System.out.println("Se interrumpió " + nombre);
         super.matarTarea();
      }

   }
}
