using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using OliviaDef;
using SisNetBase;
using System.Diagnostics;
using SisNetBase.Modulos;

namespace OliviaAddIn
{
    /**
     * Clase para realizar las tareas del progreso de la ejecución
     */
    public partial class ProgresoEjec : Form
    {
        //*************************************************************************************
        //Variables     
        /**
         * Socket de conexión al geofoto
         */
        Cigt_str_socket soc = null;
        Thread m_thread;
        int m_out;
        int m_miliseconds;
        int m_tm_progr;
        int modo_fin = 0;
        bool cancela = false;
        bool cancela_fin = false;
        bool conectado = false;
        bool permu = false;
        bool cancela_permu = false;
        int progr = 0;
        string args ="";
        string str_cfg = "";
        string tarea = "";
        public string err_str = "";
        double x, y;

        enum TiposActu
        {
            ActuMal,
            ActuNoActu,
            ActuBien,
            ActuPermu,
            ActuMulti,
            ActuFinOk,
            ActuSect,
            ActuPlan,
            ActuFinNOk,
            ActuN,
        }

        //*************************************************************************************
        //Métodos
        public ProgresoEjec()
        {
            err_str = "";
            try
            {
                InitializeComponent();

                //inicia la barra de proceso
                progressBar_ejec.Step = GeneralDef.ProgrStep;
                progressBar_ejec.Maximum = GeneralDef.ProgrMax;
                //se recoge la distancia que hay entre el borde del botón cancelar y el diálogo, para que cuando se modifique el tamaño de la ventana, el botón se mueva.
                Rectangle r = this.ClientRectangle;
                Rectangle rb = this.button_canc.ClientRectangle;
                r = this.RectangleToScreen(r);
                rb = this.button_canc.RectangleToScreen(rb);
                x = (r.X + r.Width) - (rb.X + rb.Width);
                y = (r.Y + r.Height) - (rb.Y + rb.Height);
            }
            catch (Exception)
            {
                MessageBox.Show("Error al iniciar el progreso de ejecucución.", "Olivia", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
        }

        ~ProgresoEjec()
        {

        }

        /**
         * Lanza el proceso de control de la ventana
         */
        public void start(string cfg)
        {

            soc = new Cigt_str_socket();

            str_cfg = cfg;

            Show();

            //lanza el proceso
            m_out = 0;
            m_miliseconds = 200;
            m_tm_progr = 1000; //tiempo cada el que pide el progreso
            m_thread = new Thread(new ThreadStart(run));
            m_thread.Name = "ProgresoEjec";
            m_thread.Start();
        }

        /**
         * Función principal del bucle
         */
        private void run()
        {
            int nint = 0;
            int nint_max = 10;
            bool sal = false;
            bool first_send_cfg = true;
            bool fin = false;
            int lastprog =0;
            TiposActu pp;

            try
            {
                do
                {
                    Thread.Sleep(m_miliseconds);

                    if (first_send_cfg)//a continuación envía la configuración
                    {
                        Thread.Sleep(500);
                        if (!envia_cfg())
                        {
                            if (nint >= nint_max)
                            {
                                err_str = "Error en la comunicación con Geofoto";
                                actualiza(TiposActu.ActuMal);
                            }
                            else
                                nint++;
                        }
                        else
                            first_send_cfg = false;
                    }

                    if (Interlocked.Equals(m_out, 1)) //mira a ver si ha cancelado el usuario
                    {
                        //se ha cancelado, lo envía al geofoto
                        envia_cancel();
                        if (!cancela_permu)
                            sal = true;
                        else
                        {
                            Interlocked.Exchange(ref m_out, (int)0);
                            cancela_permu = false;
                        }
                    }
                    else if (!first_send_cfg && ((Environment.TickCount - lastprog) >= m_tm_progr) && !fin) //en caso normal, todo va bien, pide el progreso y la tarea
                    {
                        //solo pide la programación cada m_tm_progr milis
                        pp = pide_progr();
                        if (pp > TiposActu.ActuFinOk)
                            fin = true;
                        actualiza(pp);
                        lastprog = Environment.TickCount;
                    }

                } while (!sal);

                //manda cerrar la ventana porque ha terminado
                cierra();
            }
            catch (Exception)
            {
                MessageBox.Show("Error durante el proceso.", "Olivia", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
        }

        /**
         * Termina el proceso porque se ha dado a cancelar
         */ 
        public void cancela_thr()
        {
            Interlocked.Exchange(ref m_out, (int)1);                           
        }

        /*
         * Cierra la ventana desde el thread
         */
        private void cierra()
        {
            MethodInvoker inv = delegate()
            {
                Close();
            };

            if (InvokeRequired)
                BeginInvoke(inv);
        }

        /*
         * Cuando el thread avisa de que ha salido, termina de cerrar
         */
        private void fin_thr()
        {
            if (m_thread == null)
                return;

            m_thread.Join();
            m_thread = null;            
        }

        /**
         * Envía la información de la configuración por socket a geofoto
         */
        public bool envia_cfg()
        {
            string args;

            try
            {
                if (!conectado)
                {
                    if (!soc.conecta(OliviaGlob.ip, OliviaGlob.puerto))
                    {
                        conectado = false;
                        return false;
                    }
                    conectado = true;
                }

                if (str_cfg == null || str_cfg.Length == 0)
                    return false;

                if (!soc.envia(str_cfg))
                {
                    conectado = false;
                    return false;
                }

                args = soc.recibe();
                if ((args.Length == 0) || (args != OliviaDef.GeneralDef.SockConfOk))
                    return false;

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /**
         * Pide por socket la información de sectorización a geofoto
         */
        private bool pide_sect()
        {
            //pide sectorización porque ya ha sido avisado de que ha terminado          
            try
            {
                if (!conectado)
                {
                    if (!soc.conecta(OliviaGlob.ip, OliviaGlob.puerto))
                    {
                        conectado = false;
                        return false;
                    }
                    conectado = true;
                }

                //pide secto
                if (!soc.envia(OliviaDef.GeneralDef.SockSect))
                {
                    conectado = false;
                    return false;
                }

                //recibe sectorización
                args = soc.recibe();
                if (args.Length == 0)
                    return false;

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
        
        /**
         * Pide por socket la información de progreso a geofoto
         * Devuelve 0 si ha ido mal, 1 si ha ido bien, 2 si ha recibido que hay que pedir sectorización
         */
        private TiposActu pide_progr()
        {
            TiposActu tt;
            int i;
            //pide progreso y tarea por la que va            
            try
            {
                if (!conectado)
                {
                    if (!soc.conecta(OliviaGlob.ip, OliviaGlob.puerto))
                    {
                        conectado = false;
                        err_str = "Error en la comunicación con Geofoto";
                        return TiposActu.ActuMal;
                    }
                    conectado = true;
                }

                //pide progreso
                if (!soc.envia(OliviaDef.GeneralDef.SockProgr))
                {
                    conectado = false;
                    err_str = "Error en la comunicación con Geofoto";
                    return TiposActu.ActuMal;
                }

                //recibe progreso
                args = soc.recibe();
                if (args.Length == 0)
                {
                    return TiposActu.ActuNoActu;
                }

                //comprueba progreso
                try
                {
                    progr = Convert.ToInt32(args);
                    if (progr < 0 || progr > OliviaDef.GeneralDef.ProgrMax)
                    {
                        return TiposActu.ActuNoActu;
                    }
                }
                catch
                {
                    return TiposActu.ActuNoActu;
                }

                //pide tarea
                if (!soc.envia(OliviaDef.GeneralDef.SockTarea))
                {
                    err_str = "Error en la comunicación con Geofoto";
                    return TiposActu.ActuMal;
                }

                //recibe tarea
                args = soc.recibe();
                if (args.Length == 0)
                {
                    err_str = "Error recibe tarea mal";
                    return TiposActu.ActuMal;
                }
                if(args=="0") //se ha mezclado el progreso, vuelve a recibir la tarea
                    args = soc.recibe();
                if (args.Length == 0)
                {
                    err_str = "Error recibe tarea mal";
                    return TiposActu.ActuMal;
                }
                
                i = 0;
                tt = TiposActu.ActuBien;
                //comprueba tarea
                if (args.StartsWith(OliviaDef.GeneralDef.SockSectFin))
                {
                    i = OliviaDef.GeneralDef.SockSectFin.Length;
                    tt = TiposActu.ActuSect;
                }
                else if (args.StartsWith(OliviaDef.GeneralDef.SockPlanFin))
                {
                    i = OliviaDef.GeneralDef.SockPlanFin.Length;
                    tt = TiposActu.ActuPlan;
                }
                else if (args.StartsWith(OliviaDef.GeneralDef.SockFinOk))
                {
                    i = OliviaDef.GeneralDef.SockFinOk.Length;
                    tt = TiposActu.ActuFinOk;
                }
                else if (args.StartsWith(OliviaDef.GeneralDef.SockFinNOk))
                {
                    i = OliviaDef.GeneralDef.SockFinNOk.Length;
                    tt = TiposActu.ActuFinNOk;
                }
                else if (args.Contains("permutaciones"))
                {
                    tt = TiposActu.ActuPermu;
                }
                else if (args.Contains("multitask"))
                {
                    tt = TiposActu.ActuMulti;
                }
                modo_fin = (int)tt;

                tarea = args.Substring(i);

                return tt;
            }
            catch (Exception)
            {
                MessageBox.Show("Error al preguntar por el progreso.", "Olivia", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return tt = TiposActu.ActuMal;
            }
        }

        /**
         * Envía a geofoto la orden de cancelar el proceso
         */
        private bool envia_cancel()
        {
            try
            {
                if (!conectado)
                    return false;

                //envía cancel
                if (!soc.envia(OliviaDef.GeneralDef.SockCanc))
                    return false;

                //recibe respuesta
                args = soc.recibe();
                if (args.Length == 0)
                    return false;

                //comprueba que ok
                if (args != OliviaDef.GeneralDef.SockCancOk)
                    return false;

                return true;
            }
            catch (Exception)
            {
                MessageBox.Show("Error al cancelar el progreso.", "Olivia", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
        }

        /**
         * Actualiza la barra de progreso
         * bool fallo_soc : indica si ha habido fallo en el socket
         */
        private void actualiza(TiposActu actu)
        {          

            try
            {
                permu = false;
                //actualiza la barra de tarea y  el texto
                switch (actu)
                {
                    case TiposActu.ActuBien:
                        {
                            pon_progr(progr);
                            pon_texto(tarea);
                            break;
                        }
                    case TiposActu.ActuMal:
                        {
                            if (err_str.Length > 0)
                            {
                                pon_texto(err_str);
                                err_str = "";
                            }
                            else
                                pon_texto(tarea);
                            break;
                        }
                    case TiposActu.ActuFinOk:
                        {
                            pon_progr(100);
                            pon_texto("Finalizado proceso con éxito");
                            break;
                        }
                    case TiposActu.ActuFinNOk:
                        {
                            pon_progr(100);
                            err_str = "Finalizado proceso con fallos\n" + tarea;
                            pon_texto(err_str);
                            break;
                        }
                    case TiposActu.ActuSect:
                        {
                            pon_progr(100);
                            pon_texto("Finalizada sectorización\n" + tarea);
                            break;
                        }
                    case TiposActu.ActuPlan:
                        {
                            pon_progr(100);
                            pon_texto("Finalizada planificación\n" + tarea);
                            break;
                        }
                    case TiposActu.ActuPermu:
                        {
                            permu = true;
                            pon_texto(tarea);
                            pon_estilo(ProgressBarStyle.Marquee);
                            break;
                        }
                    case TiposActu.ActuMulti:
                        {
                            pon_texto(tarea);
                            pon_estilo(ProgressBarStyle.Marquee);
                            break;
                        }
                    default:
                        return;
                }

                if (actu != TiposActu.ActuPermu && actu != TiposActu.ActuMulti)
                    pon_estilo(ProgressBarStyle.Continuous);
                pon_barstate(actu);
            }
            catch (Exception)
            {
                MessageBox.Show("Error al actualizar el progreso.", "Olivia", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /*
         * Para poder actualizar el texto de la ventana de progreso hay que llamar a invoke
         * porque está desde otro thread
         */
        private void pon_texto(String txt)
        {
            MethodInvoker inv = delegate()
            {
                label_progr.Text = txt;
            };

            if (InvokeRequired)
                BeginInvoke(inv);
        }

        /*
         * Para poder actualizar la barra de progreso hay que llamar a invoke
         * porque está desde otro thread
         * Actualiza el tipo de progreso y el texto
         */
        private void pon_estilo(ProgressBarStyle style)
        {
            MethodInvoker inv = delegate()
            {
                progressBar_ejec.Style = style;                
            };

            if (InvokeRequired)
                BeginInvoke(inv);            
        }

        /*
         * Para poder actualizar la barra de progreso hay que llamar a invoke
         * porque está desde otro thread
         * Actualiza el estilo para indicar que ha habido error
         */
        private void pon_barstate(TiposActu actu)
        {
            int col=1;

            MethodInvoker inv = delegate()
            {
                if ((actu == TiposActu.ActuMal) || (actu ==TiposActu.ActuFinNOk))
                    col = 2;
                else
                    col = 1;
                OliviaGlob.ProgressBarSetState(progressBar_ejec, col);

                if (actu > TiposActu.ActuFinOk)
                    button_canc.Text = "Finalizar";
                if ((actu == TiposActu.ActuPermu) && !cancela)
                    button_canc.Text = "Parar permu.";
            };

            if (InvokeRequired)
                BeginInvoke(inv);
        }

        /*
         * Para poder actualizar la barra de progreso hay que llamar a invoke
         * porque está desde otro thread
         * Actualiza el progreso
         */
        private void pon_progr(int pro)
        {
            MethodInvoker inv = delegate()
            {
                progressBar_ejec.Value = pro;                
            };

            if (InvokeRequired)
                BeginInvoke(inv);
        }
        
        /**
         * Itera para comprobar que ha arrancado bien un proceso, o ver si está arrancado
         * int t_max : seg espera
         * bool espera_start : si true, se quiere esperar t_max a que arranque, 
         * si false, se quiere esperar a que se cierre
         * Devuelve el proceso si está, para cancelarlo, y null si no está
         */
        public Process[] is_process(string nombproc, int t_max, bool espera_start)
        {
            bool esta_geo = !espera_start;
            int veces = 0;
            int veces_max;
            int milis_sleep = 50;

            if (t_max <= 0)
                t_max = 1;
            veces_max = t_max * 1000 / milis_sleep;

            Process[] pg = new Process[0];

            //si ya está arrancado y se quiere esperar a que se cierre, esta_geo=true y espera_start=false;
            //por lo que se cumple !espera_start && esta_geo
            //tiene que esperar si esta_geo==true
            //cuando se quiere esperar a que arranque, esta_geo=false y espera_start=true
            //por lo que se cumple (espera_start && !esta_geo)
            //en este caso, tiene que esperar si esta_geo==False
            while (((espera_start && !esta_geo) || (!espera_start && esta_geo)) && (veces < veces_max))
            {
                try
                {
                    pg = Process.GetProcessesByName(nombproc);
                    if (pg.Length == 0)
                        esta_geo = false;
                    else
                        esta_geo = true;
                }
                catch (InvalidOperationException)
                {
                    esta_geo = false;
                }

                if (((espera_start && !esta_geo) || (!espera_start && esta_geo)))
                {
                    veces++;
                    Thread.Sleep(milis_sleep);
                }
            }

            if (esta_geo)
                return pg;
            else
                return null;
        }

        /**
         * Comprueba si geofoto ha salido y le espera durante T seg. Si no, para la tarea
         * return true si ha terminado, false si no
         */
        private bool espera_process(string nombproc)
        {
            bool esta_geo = true;
            Process[] pg;

            //le da 3 seg de margen para que se cierre
            pg = is_process(nombproc, 1, false);
            esta_geo = (pg != null);

            if (esta_geo)
            {
                //mata a la fuerza la tarea
                try
                {
                    for (int i = 0; i < pg.Length; i++)
                    {
                        pg[i].Kill();
                        if (pg[i].WaitForExit(500))
                            esta_geo = false;
                    }
                }
                catch (Exception)
                {
                    esta_geo =  false;
                }
            }
            return !esta_geo;
        }

        //*************************************************************************************
        //Eventos de la ventana
        private void ProgresoEjec_FormClosing(object sender, FormClosingEventArgs e)
        {
            //también hay que cerrar cuando se haya terminado el proceso y llamar a fin_proceso()
            if (cancela_fin)
            {
                //ha terminado el thread, continua cerrando
                fin_thr();
                if (!espera_process(OliviaDef.GeneralDef.NombGeofoto))
                {
                    //no ha podido cerrarlo
                    MessageBox.Show("No se ha podido cerrar GeoFoto", "Olivia", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                espera_process(OliviaDef.GeneralDef.NombOlvTasks);
                e.Cancel = false;
                Hide();  
            }
            else if (cancela)
            {
                //si sí se le ha dado a cancelar
                cancela_thr();       
                e.Cancel = true;
                if (cancela_permu) //si está en modo permu no cancela, fuerza salida
                {
                    button_canc.Text = "Cancelar";//para dejar cancelar después                    
                }
                else
                {
                    button_canc.Enabled = false;
                    cancela_fin = true;
                    err_str = "Se ha cancelado la ejecución";
                }                    
            }
            else//si no se le ha dado a "cancelar" no cierra la ventana, cancela el evento
            {
                e.Cancel = true;
            }
        }
        
        private void button_canc_Click(object sender, EventArgs e)
        {
            cancela = true;
            if (permu)
                cancela_permu=!cancela_permu;
            Close();
        }

        private void ProgresoEjec_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (modo_fin == (int)TiposActu.ActuSect)
                OliviaGlob.fin_proceso(TiposEstado.FinOkSecto);
            else if (modo_fin == (int)TiposActu.ActuPlan)
                OliviaGlob.fin_proceso(TiposEstado.FinOkPlanif);
            else
                OliviaGlob.fin_proceso(TiposEstado.FinNOk);
        }

        private void ProgresoEjec_Resize(object sender, EventArgs e)
        {
            Rectangle r = this.ClientRectangle;
            Rectangle rb = this.button_canc.ClientRectangle;
            r = this.RectangleToScreen(r);
            rb = this.button_canc.RectangleToScreen(rb);
            rb.X = (r.X + r.Width) - (int)x - rb.Width;
            rb.Y = (r.Y + r.Height) - (int)y - rb.Height;
            Point p = new Point();
            p.X = rb.X - r.X;
            p.Y = rb.Y - r.Y;
            
            rb.Location = p;
            button_canc.Location = p;
        }
    }
}
