using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.GeoDatabaseUI;
using ESRI.ArcGIS.Carto;
using System.Windows.Forms;
using ESRI.ArcGIS.esriSystem;
using System.IO;
using ESRI.ArcGIS.DataSourcesGDB;
using ESRI.ArcGIS.DataSourcesFile;

namespace OliviaAddIn
{
    /**
     *	@file DatosGDBReco.cs
     *	Clase con funciones para lectura de GDB relacionado con la recogida de residuos.
     *	Contiene definiciones y funciones necesarias para exportar los datos de GDB a SHP, y viceversa
     *	concretados con las opciones de configuración de la recogida de residuos.
     */
    /**
     *	Clase con funciones para lectura de GDB relacionado con la recogida de residuos.
     *	Contiene definiciones y funciones necesarias para exportar los datos de GDB a SHP, y viceversa
     *	concretados con las opciones de configuración de la recogida de residuos.
     */
    public class DatosGDBReco : DatosGDB
    {        
        //*************************************************************************************
        //Variables
        /**
         * Otro tipo de fracción de recogida de basuras
         */
        public string fra_otra = "";
        /**
         * otro tipo de carga en la recogida de basuras
         */
        public string carg_otra = "";

        //variables de la GDB de contenedores
        public IWorkspaceFactory wsf_gdb_rec;
        public IWorkspace gdb_ws_rec;
        public IDataset gdb_data_rec;
        public IName name_gdb_rec;
        public IWorkspaceName name_gdb_ws_rec;
        public IFeatureWorkspace feat_ws_rec;
        //variables de la capa de turnos
        public IWorkspaceFactory wsf_turn;
        public IWorkspace ws_turn;
        public IDataset data_turn;
        public IName name_turn;
        public IWorkspaceName name_ws_turn;
        public IFeatureWorkspace feat_ws_turn;
        //*************************************************************************************
        //Constructor
        public DatosGDBReco()
        {
        }
        //*************************************************************************************
        //Métodos        
        /*
         * Lee la capa que se ha seleccionzdo de recogida de residuos y se comprueba que los campos que se han editado corresponden con la capa (es decir, se puede leer la capa con los campos configurados)
         */
        public bool lee_capa_recogida(Recogida reco)
        {
            int ind_campo;
            string[] camps;
            int i;
            int NCAMPS = 6;

            ITable tabla_recog = FunGDB.dame_tabla_clase(path_class);

            //se comprueba que existen todos los campos que se han configurado en la capa seleccionada para la recogida
            camps = new string[NCAMPS];
            camps[0]=RecogidaDef.campos_def.cons_id;
            camps[1] = RecogidaDef.campos_def.cons_fracc;
            camps[2] = RecogidaDef.campos_def.cons_nomrec;
            camps[3] = RecogidaDef.campos_def.cons_lateral;
            camps[4] = RecogidaDef.campos_def.cons_uds;
            camps[5] = RecogidaDef.campos_def.cons_kgrec;
            for (i = 0; i < NCAMPS; i++)
            {
                if ((i == (NCAMPS-1)) && (reco.rec_kgrec_cont != 0))
                    continue;
                ind_campo = tabla_recog.FindField(camps[i]);
                if (ind_campo == -1)
                {
                    reco.err_st = "No se encuentra el campo "+ camps[i];
                    break;
                }
            }
            if (i < NCAMPS)
                return false;
            return true;
        }

        /*
         * A partir de los datos leidos de la capa de recogida se rellena el desplegable de fracción a partir de los datos existentes en la capa.
         */
        public void lee_frac_gdb(Recogida reco)
        {
            string[] valores;
            int err=0;
            int i, j, k;
            bool encontrado;

            valores = FunGDB.dame_valunic_consulta(path_class, RecogidaDef.campos_def.cons_fracc, "", out err);
            
            //se redimensionan los arrays de nuevo por si se ha pinchado varias veces en el botón de leer tabla
            System.Array.Resize(ref RecogidaDef.tipos_fracc_str, (int)RecogidaDef.TiposFracción.N - 1);
            System.Array.Resize(ref reco.tipos_fracc_bool, (int)RecogidaDef.TiposFracción.N - 1);

            for (i = 0; i < valores.Length; i++)
            {
                //para que ponga '-' si es un campo vacío
                if (valores[i] == "")
                    valores[i] = "-";

                encontrado = false;
                for (j = 0; j < (int)RecogidaDef.TiposFracción.N - 1; j++)
                {
                    if (RecogidaDef.tipos_fracc_str[j] == valores[i])
                    {
                        reco.tipos_fracc_bool[j] = true;
                        encontrado = true;
                        break;
                    }
                }
                if (encontrado == false)
                {
                    System.Array.Resize(ref RecogidaDef.tipos_fracc_str, RecogidaDef.tipos_fracc_str.Length + 1);
                    System.Array.Resize(ref reco.tipos_fracc_bool, reco.tipos_fracc_bool.Length + 1);
                    RecogidaDef.tipos_fracc_str[RecogidaDef.tipos_fracc_str.Length - 1] = valores[i];
                    reco.tipos_fracc_bool[reco.tipos_fracc_bool.Length - 1] = true;
                }
            }

        }

        /*
        * A partir de los datos leidos de la capa de receogida se rellena el desplegable del tipo de carga a partir de los datos existentes en la capa.
        */
        public bool lee_tipocarga_gdb(Recogida reco)
        {
            string[] valores;
            int err = 0;
            int i, j, k;
            bool encontrado;

            valores = FunGDB.dame_valunic_consulta(path_class, RecogidaDef.campos_def.cons_nomrec, "", out err);

            //se redimensionan los arrays de nuevo por si se ha pinchado varias veces en el botón de leer tabla
            System.Array.Resize(ref RecogidaDef.tipos_carg_str, (int)RecogidaDef.TiposCarga.N - 1);
            System.Array.Resize(ref reco.tipos_carg_bool, (int)RecogidaDef.TiposCarga.N - 1);
            
            for (i = 0; i < valores.Length; i++)
            {
                //para que ponga '-' si es un campo vacío
                if (valores[i] == "")
                    valores[i] = "-";

                encontrado = false;
                for (j = 0; j < (int)RecogidaDef.TiposCarga.N - 1; j++)
                {
                    if (RecogidaDef.tipos_carg_str[j] == valores[i])
                    {
                        reco.tipos_carg_bool[j] = true;
                        encontrado = true;
                        break;
                    }
                }
                if (encontrado == false)
                {
                    System.Array.Resize(ref RecogidaDef.tipos_carg_str, RecogidaDef.tipos_carg_str.Length + 1);
                    System.Array.Resize(ref reco.tipos_carg_bool, reco.tipos_carg_bool.Length + 1);
                    //reco.tipos_carg_bool[i] = false;
                    RecogidaDef.tipos_carg_str[RecogidaDef.tipos_carg_str.Length - 1] = valores[i];
                    reco.tipos_carg_bool[reco.tipos_carg_bool.Length - 1] = true;
                }
            }

            return true;
        }        

        /*
         * Crea la consulta que hay que realizar a la capa de recogida de residuos para la exportación de los datos de interés.
         * Devuelve también la abreviatura que se le añadirá a la capa exportada (se identifica la fracción y el tipo de carga del vehículo)
         */
        public string dame_consulta(Recogida rec, out string cap_abrev)
        {
            string consulta, orstr, aux;

            consulta = null;
            cap_abrev = "";
            aux = "";
            orstr = null;

            if (rec.tipo_frac != -1)
            {
                consulta = consulta + orstr + "(" + RecogidaDef.filtro_str[rec.tipo_frac] + ")";
                cap_abrev = rec.tipo_frac.ToString("00");                    
                cap_abrev = "F" + cap_abrev + "_";
                if (consulta == "()")
                    consulta = "";
            }
            if (orstr == null)
                orstr = " AND ";
            if (rec.tipo_carg != -1)
            {
                consulta = consulta + orstr + "(" + RecogidaDef.filtro_str[rec.tipos_fracc_bool.Length + rec.tipo_carg] + ")";
                aux = rec.tipo_carg.ToString("00");
                cap_abrev = cap_abrev + "C" + aux;
                if (consulta == "()")
                    consulta = "";
            }
            if (rec.lateralidad > 0)
            {
                consulta = consulta + orstr + "(" + RecogidaDef.campos_def.cons_lateral + " = '" + RecogidaDef.tipos_lateralidad[rec.lateralidad] +  "'" + ")";
                if (consulta == "()")
                    consulta = "";
            }
            return consulta;
        }

        /*
         * Dado un nombre de featureclass, una consulta y una geometría, interseca entre un poligono dado (geom) y una capa lineal (fc1)
         */
        public IQueryFilter dame_filt_inter(string ftclass, string consulta, IGeometry geom)
        {
            IQueryFilter fil = null;
            IFeatureClass fc = FunGDB.abre_ftclass(ftclass);
            if (fc == null)
                return null;

            fil = base.dame_filt_inter(fc, consulta, geom);
            FunGDB.libera(fc);

            return fil;
        }

        /**
        * Realiza las intersecciones espaciales de los ámbitos
        */
        public IQueryFilter crea_filtro(Recogida rec, string consulta, IGeometry geom1)
        {
            IQueryFilter filtro;

            //crea el filtro espacial y el añadido de consulta de atributos si fuera necesario
            filtro = dame_filt_inter(path_class, consulta, geom1);
            if (filtro == null)
            {
                err_st = "Error al intersecar el ámbito de limpieza con la zona de estudio";
                return null;
            }
            
            //FALTA REDEFINIR - elena jun 18
            //Cuando se trate del vaciado de papeleras habrá que verificar si está marcado el checkbox que indica si sólo van a recogerse los que superen el umbral de carga.
            //De modo que la secotrizacion y la planificacion se realizará sólo sobre los contenedores que superan un porcetaje de carga en la basura.
            //Se conocen esos contenedores por el nombre de un campo que indicará si SI(hay que ir a recogerlos) o NO (no tienen suficiente carga y se pueden recoger en otro momento)
            if ((rec.carga_max > 0) && (rec.id == null) && (rec.id_sens == null))
            {
                if (filtro.WhereClause == "")
                    filtro.WhereClause = carga_conten + " >= " + rec.carga_max;
                else
                    filtro.WhereClause = "(" + filtro.WhereClause + ") AND (" + carga_conten + " >= " + rec.carga_max + ")";
            }
            return filtro;
        }

        /**
        * Descodifica el nombre del sahpefile de entrada identificando el tipo de fracción y la carga para la recogida de residuos
        */
        public void decode_gdb(string shapefile, out string fraccion, out string carga)
        {
            int aux, auxl, carg, indice, fracc;
            string auxili, fra, auxi;

            indice = shapefile.IndexOf("_");
            indice = indice + 2;//para saltarse la F que va antes del identificador del tipo de fracción
            auxili = shapefile.Substring(indice, 2);
            fracc = Convert.ToInt32(auxili);
            fraccion = RecogidaDef.tipos_fracc_str[fracc];
            indice = shapefile.IndexOf("_", indice);
            indice = indice + 2;//para saltarse la C que va antes del identificador de los tipos de carga de los vehículos
            aux = shapefile.IndexOf("_", indice);

            auxi = "";

            fra = shapefile.Substring(indice, 2);
            carg = Convert.ToInt32(fra);

            carga = RecogidaDef.tipos_fracc_str[fracc] + "_" + RecogidaDef.tipos_carg_str[carg];

            //quita los espacios
            fraccion = fraccion.Replace(" ", "_");
            carga = carga.Replace(" ", "_");
            ////////////////////////

            auxi = shapefile;
            for (int i = 0; i < 2; i++)
            {
                indice = auxi.LastIndexOf("_");
                auxi = auxi.Substring(0, indice);
            }
            auxl = indice - aux;
            if (auxl <= 0)
                return;
            auxili = shapefile.Substring(aux, auxl);
            carga = carga + auxili;

        }

        /**
        * Exporta a shapefile las red navegable y los ámbitos de trabajo seleccionados. 
         * El archivo de salida será el nombre del tratamiento escogido con el timestampo de la fecha hora
        */
        public bool exporta(Recogida rec, bool comp_planif)
        {
            int cont = 0;
            string fecha_hora = "", ambitos = "";
            string consulta = "";
            IQueryFilter filtro = null;
            IGeometry geom_zontur = null;
            IGeometry geom_ambits = null;
            IDatasetName datasetn = null;

            if (rec == null)
            {
                err_st = "No se han recibido correctamente los datos para la exportación";
                return false;
            }

            /////////////////////////////////////////////
            //elabora la consulta a realizar con los parámertos de fracción y tipo de recogida
            consulta = dame_consulta(rec, out ambitos);
            if (consulta == null)
            {
                err_st = "Error al configurar consulta";
                return false;
            }
            
            /////////////////////////////////////////////
            //si va a planificar antes comprueba que estén las columnas de sector y secuencia
            if (comp_planif)
            {
                if (!comprueba_planif(path_class, null))
                {
                    err_st = "No existen en la tabla " + path_class + " columna de SECTOR y/o SECUENCIA, necesarias para planificar";
                    return false;
                }

            }

            cont = FunGDB.cuenta_filas_ftclass(path_class, consulta);
            if (cont <= 0)
            {
                err_st = "No existen elementos geométricos que cumplan las condiciones introducidas para la exportación";
                return false;
            }

            /////////////////////////////////////////////
            //consigue el polígono común a las dos geometrías: zonas y turnos
            //si no hay zonas ni turnos, es null
            geom_zontur = FunGDB.une_geoms(rec.geom_zonas, rec.geom_turnos);
            //además, le quita las restricciones
            if (geom_zontur != null && rec.geom_rest_acces != null)
            {
                geom_zontur = FunGDB.diferencia_geoms(geom_zontur, rec.geom_rest_acces);
            }

            if (geom_zontur != null && geom_zontur.IsEmpty)
            {
                err_st = "Error, la geometría resultado de intersecar zonas, turnos y restricciones de circulación está vacía";
                return false;
            }

            /////////////////////////////////////////////
            //crea el filtro en base a las consultas configuradas
            filtro = crea_filtro(rec, consulta, geom_zontur);
            if (filtro == null)
            {
                err_st = "Error al crear el filtro para la exportación";
                return false;
            }

            /////////////////////////////////////////////
            //se consigue el tiempo en este instante para añadirlo a los nombres de los archivos de salida (shapefiles)
            fecha_hora = DateTime.Now.ToString("yyyyMMdd_Hmmss");

            //exporta los ámbitos
            if (!exporta_data(rec, filtro, ambitos, fecha_hora, out datasetn))
            {
                err_st = "Error al exportar data. " + err_st;
                return false;
            }

            OliviaGlob.Paths.PathData = OliviaGlob.Paths.DirData + datasetn.Name;
            
            //quita los contenedores que caen en la zona de restricciones, si es que no había zonas y no se ha quitado ya
            if (geom_zontur == null && rec.geom_rest_acces != null)
            {
                if (!quita_amb_restric(OliviaGlob.Paths.PathData, rec.geom_rest_acces, false))
                {
                    err_st = "Error al quitar los ámbitos en zona de restricción";
                    return false;
                }
            }

            //comprueba que sigue habiendo ámbitos exportados
            cont = FunGDB.cuenta_filas_ftclass(OliviaGlob.Paths.PathData, "");
            if (cont <= 0)
            {
                err_st = "No existen ámbitos que cumplan las condiciones geométricas introducidas";
                return false;
            } 

            if (rec.id != null)
            {
                int err = 0;
                FunGDB.add_campo(OliviaGlob.Paths.PathData, RecogidaDef.campos.cons_kgrec, esriFieldType.esriFieldTypeDouble, out err);
                if (!pon_datos_csv(rec, OliviaGlob.Paths.PathData))
                {
                    err_st = "Error al consultar el CSV con los datos de los contenedores.";
                    return false;
                }
            }

            //ahora si está en modo planificación exporta la sectorización
            if (comp_planif && (path_secto != null))
            {
                if (!actualiza_secto(OliviaGlob.Paths.PathData, path_secto))
                {
                    err_st = "Error al exportar campos de SECTOR y/o SECUENCIA de la capa " + path_secto + " " + err_st;
                    return false;
                }
            }

            ///////////////////////////////////////////////////////////////////////////            
            //obtiene el polígono que engloba los ámbitos
            geom_ambits = FunGDB.dame_geom_envelope(OliviaGlob.Paths.PathData);
            if (geom_ambits == null)
            {
                err_st = "Error al obtener polígono de datos";
                return false;
            }

            ///////////////////////////////////////////////////////////////////////////            
            //amplia el poligono para englobar las instalaciones
            if (rec.coords_instala[0] != 0 && rec.coords_instala[1] != 0)
            {
                //hay instalación
                if (!FunGDB.is_pto_in_geom(rec.coords_instala[0], rec.coords_instala[1], geom_ambits))
                {
                    geom_ambits = FunGDB.amplia_geom_convexhull(geom_ambits, rec.coords_instala[0], rec.coords_instala[1]);
                    if (geom_ambits == null)
                    {
                        err_st = "Error al obtener envolvente convexa con instalación";
                        return false;
                    }
                }

                //comprueba, si hay restricciones de circulación, que la instalación no está en ellas
                if (rec.geom_rest_acces != null)
                {                    
                    if (FunGDB.is_pto_in_geom(rec.coords_instala[0], rec.coords_instala[1], rec.geom_rest_acces))
                    {
                        err_st = "Error, la instalación sal/lleg está en la zona restringida a la circulación";
                        return false;
                    }
                }
            }
            if (rec.coords_descarg[0] != 0 && rec.coords_descarg[1] != 0)
            {
                //hay instalación de descarga
                if (!FunGDB.is_pto_in_geom(rec.coords_descarg[0], rec.coords_descarg[1], geom_ambits))
                {
                    geom_ambits = FunGDB.amplia_geom_convexhull(geom_ambits, rec.coords_descarg[0], rec.coords_descarg[1]);
                    if (geom_ambits == null)
                    {
                        err_st = "Error al obtener envolvente convexa con instalación";
                        return false;
                    }
                }

                //comprueba, si hay restricciones de circulación, que la instalación no está en ellas
                if (rec.geom_rest_acces != null)
                {
                    if (FunGDB.is_pto_in_geom(rec.coords_descarg[0], rec.coords_descarg[1], rec.geom_rest_acces))
                    {
                        err_st = "Error, la planta de descarga está en la zona restringida a la circulación";
                        return false;
                    }
                }
            }

            ///////////////////////////////////////////////////////////////////////////     
            //exporta la red navegable
            if (!exporta_nw(geom_ambits, rec.geom_rest_acces, true, fecha_hora, out datasetn))
            {
                return false;
            }

            //guarda los nombres del shape
            OliviaGlob.Paths.PathNW = OliviaGlob.Paths.DirData + datasetn.Name;            

            return true;
        }

        /**
         * Devuelve el string a concatenar en el nombre del path dependiendo de los polígonos seleccionados (zonas, turnos... etc)
         */
        public string dame_str_poligs(Recogida rec)
        {
            string str = "";

            if (rec.text_turnos != "" && rec.geom_turnos != null)
                str += "_T" + rec.text_turnos;
            if (rec.text_zon != "" && rec.geom_zonas != null)
                str += "_Z" + rec.text_zon;
            if (rec.text_restr != "" && rec.geom_rest_acces != null)
                str += "_R" + rec.text_restr;

            return str;
        }

        /**
         * Exporta los ámbitos de la gdb a un shape en datasetname
         */
        public bool exporta_data(Recogida rec, IQueryFilter filtro, string ambitos, string fecha_hora, out IDatasetName shp_data_name)
        {
            string name = "", err_str = "";
            IDatasetName shp_dsn = null;

            shp_data_name = null;

            //Pone nombre al shape en función de los ámbitos, el tratamiento, y los polígonos + timestamp
            name = name_export_amb + ambitos + dame_str_poligs(rec) + "_" + fecha_hora + ext_shp;


            inicia_coords(path_class, null);

            if (!FunGDB.exporta(path_class, null, OliviaGlob.Paths.DirData, name, filtro, out shp_dsn, out err_str))
            {
                err_st = err_str;
                return false;
            }
            
            shp_data_name = (IDatasetName)shp_dsn;
            return true;
        }     
        
        /*
         * REVISAR
         * Modifica el shapefile exportado de modo que elimina aquellos elementos (contenedores) que no contengan un porcentaje de cargaigual o superior al indicado en la ventana de recogida (rec.carga_max).
         */
        public bool pon_datos_csv(Recogida rec, string capa)
        {
            int col_kg = -1, col_capa;
            object obj, capacidad;
            IFeature feat = null;
            IFeatureClass fc = null;
            IFeatureCursor cursor = null;
            IQueryFilter filtro = new QueryFilterClass();
            ITable tabla = null;
            IRow fila = null;

            fc = FunGDB.abre_ftclass(capa);
            if (fc == null)
                return false;
            tabla = FunGDB.dame_tabla_clase(capa);
            if (tabla == null)
                return false;
            col_kg = tabla.FindField(RecogidaDef.campos.cons_kgrec);
            col_capa = tabla.FindField(RecogidaDef.campos.cons_capac);
            for (int i = 0; i < rec.id.Count; i++)
			{
			    filtro.WhereClause = RecogidaDef.campos.cons_id + "=" + rec.id[i];
                cursor = fc.Search(filtro, false);
                feat = cursor.NextFeature();
                if (feat == null)
                {
                    //libera
                    FunGDB.libera(fila);
                    FunGDB.libera(cursor);
                    FunGDB.libera(feat);
                    break;
                }
                fila = tabla.GetRow(feat.OID);
                //si el procentaje de carga indicado es superior al indicado por el archivo CSV se omite ese elemento a exportar, por lo que se elimina de la lista
                if (rec.carga_max > rec.cargas[i])
                {
                    fila.Delete();
                    //libera
                    FunGDB.libera(fila);
                    FunGDB.libera(cursor);
                    FunGDB.libera(feat);
                    continue;
                }
                capacidad = fila.get_Value(col_capa);
                if (rec.dens_cont >= 0)
                    fila.set_Value(col_kg, rec.cargas[i] * rec.dens_cont * (int)capacidad / 1000);
                else
                    fila.set_Value(col_kg, rec.cargas[i] * RecogidaDef.dens_frac_cont[rec.tipo_frac] * (int)capacidad / 1000);
                fila.Store();
                //libera
                FunGDB.libera(fila);
                FunGDB.libera(cursor);
                FunGDB.libera(feat);
			}
            cursor = fc.Search(null, false);
            feat = cursor.NextFeature();
            while (feat != null)
            {
                fila = tabla.GetRow(feat.OID);
                obj = fila.get_Value(col_kg);
                capacidad = fila.get_Value(col_capa);
                if ((Double)obj == 0.0)
                {
                    fila.set_Value(col_kg, rec.dens_cont * (int)capacidad/1000);
                    fila.Store();
                    //libera
                    FunGDB.libera(fila);
                    FunGDB.libera(feat);
                }
                //libera
                FunGDB.libera(fila);
                FunGDB.libera(feat);
                
                feat = cursor.NextFeature();
            }
            //libera
            FunGDB.libera(fila);
            FunGDB.libera(cursor);
            FunGDB.libera(feat);
            FunGDB.libera(filtro);
            FunGDB.libera(tabla);
            FunGDB.libera(fc);
            return true;
        }
        
    }
}
