// 
// 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.
// 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using ateneo.sockets;
using System.IO;
using ateneo.utiles;
using operador.utiles;

namespace ateneo
{
    public partial class Cargador : Form
    {
        private const int MENSAJE_ENVIAR_ARCHIVO = 883;
        private const int MENSAJE_LISTO_PARA_RECIBIR_ARCHIVO = 884;
        private const int MENSAJE_COMENZAR_ENVIO_ARCHIVO = 885;
        private const int MENSAJE_PEDIR_LISTA_ARCHIVOS = 889;
        private const int COD_ENVIO_VERSION = 880;
        private const int COD_ENVIO_ARCHIVO = 882;
        private const int COD_ENVIO_LISTA = 883;
        private const int COD_ERROR_TRANSFERENCIA = 889;

        string nombreModulo;
        string nombreZip;
        string nombreExe;
        string nombreTemp = "temporal.tmp";

        MessageSocket sock;
        delegate void MensajeDelServidor(int codigo, String msj);
        MensajeDelServidor mensajeDelegate;
        delegate void BuferDelServidor(byte[] bufer);
        BuferDelServidor buferDelegate;
        string param;
        ConfiguracionCargador conf;
        long versionEnviada;
        DateTime fechaVersionEnviada;
        long epoch;

        bool recibiendoZIP = false;
        string ficheroFTP = null;
        ReceptorFichero receptor;
        bool esperandoParaSalir = false;

        public Cargador(ConfiguracionCargador conf, string nombreModulo) {
            this.conf = conf;
            this.nombreModulo = nombreModulo;
            nombreZip = nombreModulo + ".zip";
            if (nombreModulo == "minigestor") {
                nombreExe = "minigestor.dll";
            } else {
                nombreExe = nombreModulo + ".exe";
            }

            InitializeComponent();
            DateTime dtEpoch = new DateTime(1970, 1, 1, 0, 0, 0);
            epoch = dtEpoch.ToFileTimeUtc() / 10000;
        }

        public DialogResult lanzar(string param) {
            this.param = param;
            Size = new Size(380, 300);
            StartPosition = FormStartPosition.CenterScreen;
            return ShowDialog();
        }

        void mm(String str) {
            memo.AppendText(str + "\r\n");
        }

        void conectar() {
            sock = new MessageSocket();
            mensajeDelegate = new MensajeDelServidor(mensajeDelServidor);
            buferDelegate = new BuferDelServidor(buferRecibido);
            sock.errorSocket += new ErrorSocketEventHandler(sock_errorSocket);
            sock.mensajeRecibido += new MensajeRecibidoEventHandler(sock_mensajeRecibido);
            sock.datosRecibidos += new DatosRecibidosEventHandler(sock_datosRecibidos);
            if (conf.usandoUDP) {
                SocketUDP udp = new SocketUDP(conf.nombreServidor, conf.ipPort);
                string ip = udp.buscarServidorEnLaRed(2000);
                if (ip != "") {
                    sock.host = ip;
                    sock.puerto = conf.ipPort;
                }
                if (ip == "" || !sock.open()) {
                    mm("No se puede localizar el servidor " + conf.nombreServidor);
                    mm("Se cancela la ejecución");
                    mm("Puede cerrar esta ventana");
                    return;
                }
                udp.close();
            } else {
                sock.host = conf.ipServidor;
                sock.puerto = conf.ipPort;
                if (!sock.open()) {
                    if (!conectarSecundario()) {
                        mm("El servidor ATENEO en " + conf.ipServidor + " no contesta");
                        mm("Se cancela la ejecución");
                        mm("Puede cerrar esta ventana");
                    }
                    return;
                }
            }
            mm("Conectado al servidor " + conf.ipServidor);
            enviar(MENSAJE_PEDIR_LISTA_ARCHIVOS, nombreModulo);
        }

        void sock_datosRecibidos(object sender, DatosRecibidosArgs e) {
            this.Invoke(buferDelegate, new Object[] { e.bufer });
        }

        void buferRecibido(byte[] bufer) {
            try {
                if (!receptor.masBytes(bufer)) {
                    sock.modoBin = false;
                    mm("Recibido " + ficheroFTP);
                    mm("Bytes: " + receptor.getEscritos());
                    if (receptor.isCrcOK()) {
                        File.SetLastWriteTime(receptor.fichero, fechaVersionEnviada);
                        mm("CRC OK");
                        if (!recibiendoZIP) {
                            lanzaCliente(true);
                        } else {
                            zipRecibido(true);
                        }
                    } else {
                        mm("CRC  MAL !!");
                        mm("CRC Esperado: " + receptor.getCrcEsperado() + "  CRC Recibido: " + receptor.getCrc() + "\n");
                        if (!recibiendoZIP) {
                            mm("IMPOSIBLE CONTINUAR");
                            mm("Cierre esta ventana");
                            volcarLogConsola();
                            esperandoParaSalir = true;
                            File.Delete(ficheroFTP);
                        }
                    }
                }
            } catch {
                MessageBox.Show("Error en la grabacion de los bytes recibidos");
                receptor.Dispose();
                sock.modoBin = false;
            }
        }

        private void zipRecibido(bool procesar) {
            if(procesar) procesarZip();
            recibiendoZIP = false;
            ficheroFTP = nombreExe;
            enviar(MENSAJE_ENVIAR_ARCHIVO, ficheroFTP);
        }

        void procesarZip() {
            try {
                SieteZipWraper zip = new SieteZipWraper(nombreZip);
                if (!zip.disponible) return;
                ManifiestoZip manifiesto = zip.extraerManifiesto();
                mm("Extrayendo contenido de " + nombreZip);
                mm("    en " +  (manifiesto.raizZip == ""? Environment.CurrentDirectory : manifiesto.raizZip));
                zip.procesar(manifiesto.raizZip);
                if (manifiesto.autoRun != "") {
                    ejecutarAutoRun(manifiesto.autoRun);
                }
            } catch (Exception exc) {
                MessageBox.Show(exc.Message);
            }
        }

        private void ejecutarAutoRun(string autorun) {
            try {
                mm("Ejecuto " + autorun);
                string[] pars = autorun.Split(new char[] { ' ' }, 2);
                if (pars.Length == 2) {
                    ProgramaExterno.ejecuta(pars[0], pars[1]);
                } else {
                    ProgramaExterno.ejecuta(autorun, "");
                }                
            } catch {}
        }

        void sock_errorSocket(object sender, MensajeRecibidoArgs e) {
            //bool aqui = true;
        }

        void sock_mensajeRecibido(object sender, MensajeRecibidoArgs e) {
            this.Invoke(mensajeDelegate, new Object[] { e.codigo, e.mensaje });
        }

        void mensajeDelServidor(int codigo, string mensaje) {
            switch (codigo) {
                case COD_ENVIO_VERSION:
                    if (!recibiendoZIP && mensaje == "0") {
                        DialogResult = DialogResult.Cancel;
                        Close(); 
                        break;
                    }
                    ficheroFTP = recibiendoZIP ? nombreZip : nombreExe;
                    if (!esMismaVersion(mensaje, ficheroFTP)) {
                        enviar(MENSAJE_LISTO_PARA_RECIBIR_ARCHIVO, ficheroFTP);
                    } else {
                        if (recibiendoZIP) {
                            zipRecibido(false);
                        } else {
                            lanzaCliente(false);
                        }
                    }
                    break;
                case COD_ENVIO_ARCHIVO:
                    preparaRecepcionFichero(mensaje, false);
                    enviar(MENSAJE_COMENZAR_ENVIO_ARCHIVO, ficheroFTP);
                    break;
                case COD_ENVIO_LISTA:
                    if (mensaje != "") {    // hay un zip, lo pedimos   
                        pedirZip(mensaje);
                    } else {
                        zipRecibido(false);     // no procesar si ya tenemos el zip
                    }
                    break;
                case COD_ERROR_TRANSFERENCIA:
                    mm("Transferencia abortada por el servidor");
                    mm("");
                    mm("* Pulse una tecla para continuar con la ejecución");
                    mm("* de la versión actualmente instalada");
                    esperandoParaSalir = true;
                    break;
            }
        }

        private void pedirZip(string msj) {
            string[] listaZip = msj.ToLower().Split('|');
            foreach (string nzip in listaZip) {
                if (nzip.Equals(nombreZip, StringComparison.CurrentCultureIgnoreCase)) {
                    recibiendoZIP = true;
                    enviar(MENSAJE_ENVIAR_ARCHIVO, nombreZip);
                }
            }
        }

        private void preparaRecepcionFichero(string mensaje, bool p) {
            try {
                string[] partes = mensaje.Split('|');
                receptor = new ReceptorFichero(partes[0], partes[1]);
                mm("Preparo recepción de " + ficheroFTP);
                if (!recibiendoZIP) receptor.abrir(nombreTemp);
                else receptor.abrir(ficheroFTP);                
                sock.modoBin = true;
            } catch (Exception exc) {
                MessageBox.Show(exc.Message);
            }
        }

        private bool esMismaVersion(string sVersionEnviada, string archivo) {
            long versionActual = 0;
            if (!long.TryParse(sVersionEnviada, out versionEnviada)) {
                versionEnviada = 0;
            }
            fechaVersionEnviada = JavaTimeToDateTime(versionEnviada);
            try {
                versionActual = DateTimeToJavaTime(File.GetLastWriteTime(archivo));
            } catch {
                versionActual = 1L;
            }
            return versionEnviada == versionActual;
        }

        long DateTimeToJavaTime(DateTime dt) {
            long l1 = dt.ToFileTimeUtc() / 10000;
            return l1 - epoch;
        }

        DateTime JavaTimeToDateTime(long jt) {
            DateTime rd = DateTime.FromFileTime((jt + epoch) * 10000);
            return rd;
        }

        void enviar(int codigo, string param) {
            sock.enviar(param, codigo);
        }

        bool conectarSecundario() {
            sock = new MessageSocket();
            sock.host = conf.ipServidorSec;
            sock.puerto = conf.ipPortSec;
            sock.modoBin = true;
            if (!sock.open()) {
                return false;
            }
            mm("Conectado al servidor " + conf.ipServidorSec);
            sock.close();
            lanzaCliente(false);
            return true;
        }

        void lanzaCliente(bool externo) {
            mm("Lanzando aplicación");
            volcarLogConsola();
            System.Threading.Thread.Sleep(1000);
            sock.close();
            if (externo) {
                try {
                    StreamWriter salida = new StreamWriter("ejecutor.vbs", false, Encoding.Default);
                    salida.WriteLine("WScript.sleep 1500");
                    salida.WriteLine("Set fso = CreateObject(\"Scripting.FileSystemObject\")");
                    salida.WriteLine("if fso.FileExists(\"temporal.tmp\") then");
                    salida.WriteLine("    if fso.FileExists(\"" + nombreExe + "\") then");
                    salida.WriteLine("        fso.DeleteFile \"" + nombreExe + "\""); //, \"anterior.jar\"");
                    salida.WriteLine("    end if");
                    salida.WriteLine("    fso.MoveFile \"temporal.tmp\", \"" + nombreExe + "\"");
                    salida.WriteLine("end if");
                    if (nombreExe.EndsWith(".exe")) {
                        salida.WriteLine("set shell = wscript.CreateObject(\"wscript.Shell\")"); 
                        salida.WriteLine("shell.Exec \"" + nombreExe + " ejecutarSinDescargar actualizarMiniGestor " + param + "\"");
                    }
                    salida.WriteLine("fso.DeleteFile \"ejecutor.vbs\"");
                    salida.Close();
                    ProgramaExterno.ejecucionSilenciosa("CSCRIPT.EXE", "//nologo ejecutor.vbs");
                    DialogResult = DialogResult.OK;
                    Close();
                } catch (Exception exc) {
                    MessageBox.Show(exc.Message);
                }
            } else {
                DialogResult = DialogResult.Cancel;
                Close();
            }
        }

        public static void reiniciaCliente(int estado, string nombreCliente, string param) {
            string argEstado = " /conservarEstado=" + estado + " ";
            try {
                StreamWriter salida = new StreamWriter("Reiniciar.vbs", false, Encoding.Default);
                salida.WriteLine("WScript.sleep 3000");
                salida.WriteLine("Set fso = CreateObject(\"Scripting.FileSystemObject\")");
                salida.WriteLine("set shell = wscript.CreateObject(\"wscript.Shell\")");
                salida.WriteLine("shell.Exec \"" + nombreCliente + argEstado + param + "\"");
                salida.WriteLine("fso.DeleteFile \"Reiniciar.vbs\"");
                salida.Close();
                ProgramaExterno.ejecucionSilenciosa("CSCRIPT.EXE", "//nologo reiniciar.vbs");                   
            } catch (Exception exc) {
                MessageBox.Show(exc.Message);
            }
        }

        void volcarLogConsola() {
            using(StreamWriter log = new StreamWriter("logConsola.txt", false, Encoding.Default)) {
                log.Write(memo.Text);
            }
        }

        private void Cargador_Shown(object sender, EventArgs e) {
            conectar();
        }

        private void memo_KeyPress(object sender, KeyPressEventArgs e) {
            if (esperandoParaSalir) {
                DialogResult = DialogResult.Cancel;
                Close();
            }
        }

    }


    public class ConfiguracionCargador
    {
        public string ipServidor;
        public string ipServidorSec;
        public string nombreServidor = "";
        public bool usandoUDP = false;
        public int ipPort = 5000;
        public int ipPortSec;

        public ConfiguracionCargador() {
        }
    }

    class ReceptorFichero : IDisposable
    {
        private long numBytes;
        private long crc;
        private AdlerChecksum adler32;
        private FileStream fp;
        private long escritos;
        public string fichero;

        public ReceptorFichero(string numBytes, string crcEsperado) {
            this.numBytes = long.Parse(numBytes);
            this.crc = long.Parse(crcEsperado);
        }

        public bool abrir(string fichero) {
            this.fichero = fichero;
            adler32 = new AdlerChecksum();
            try {
                fp = new FileStream(fichero, FileMode.Create, FileAccess.Write);
                escritos = 0;
            } catch {                
                return false;
            }
            return true;
        }

        public bool masBytes(byte[] bufer) {
            fp.Write(bufer, 0, bufer.Length);
            escritos += bufer.Length;
            adler32.update(bufer);
            if (escritos >= numBytes) {
                fp.Close();
                return false;
            }
            return true;
        }

        public long getEscritos() {
            return escritos;
        }

        public long getCrc() {
            return (long)adler32.ChecksumValue;
        }

        public long getCrcEsperado() {
            return crc;
        }

        public bool isCrcOK() {
            return getCrc() == crc;
        }

        public void Dispose() {
            fp.Close();
        }

    }
}