using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using ArcGIS.Desktop.Catalog;
using ArcGIS.Desktop.Core;
using ArcGIS.Desktop.Framework;
using System.Collections.ObjectModel;
using ArcGIS.Core.Geometry;
using ArcGIS.Core.Data;
using ArcGIS.Desktop.Mapping;
using ArcGIS.Core.Internal.CIM;
using ArcGIS.Desktop.Internal.Layouts.Utilities;

using ArcGIS.Desktop.Core.Geoprocessing;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Editing;
using OliviaAddInPro.Model;
using System.IO;
using System.Diagnostics;
using OliviaAddInPro.View;
using OliviaAddInPro.Model.contract;
using ArcGIS.Core.Data.DDL;
using ArcGIS.Core.CIM;
using System.Threading;
using ArcGIS.Core.Data.Exceptions;
using Microsoft.Win32;
using Microsoft.WindowsAPICodePack.Dialogs;

namespace OliviaAddInPro.Helper
{
    public static class HelperGdb
    {       
        private static string out_str = string.Empty;
        public static string OutStr
        {
            get
            {
                /*string val = "";
                val.CopyFrom(out_str);
                out_str = string.Empty; //lo borra cada vez que se consulta
                return val;*/
                return out_str;
            }
            set { out_str = value; }
        }
        private static string texto_sal = string.Empty;
        public static string TextoSal
        {
            get
            {
                /*string val = "";
                val.CopyFrom(texto_sal);
                texto_sal = string.Empty; //lo borra cada vez que se consulta
                return val; */
                return texto_sal;
            }
            set { texto_sal = value; }
        }
        public static string SHP_EXT = ".shp";
        public static string GDB_EXT = ".gdb";

        [Flags]
        public enum TiposOpenFileDlg
        {
            OpenFtrClassLine = 1,
            OpenFtrClassPoint = 2,
            OpenFtrClassPolygon = 4,
            OpenGdb = 8,
            OpenDataset = 16
        }

        private static void ReiniciaOutStr()
        {
            out_str = string.Empty;
        }

        //Proceso para sacar un diálogo y seleccionar una o varias geometrías de dentro de una fclass
        public static ArcGIS.Core.Geometry.Geometry OpenGeom(TiposOpenFileDlg tipo, out string txt_sal, string initialLoc = "", bool multisel = false)
        {
            //inicialmente 
            ArcGIS.Core.Geometry.Geometry geom = null;
            txt_sal = Resource1.String_selec_capa;

            //abre
            FeatureClass fc = HelperGdb.OpenFtClassDialog(tipo);
            if (fc != null)
            {
                //hace geom
                geom = SelecLeeGeom(fc, out txt_sal, multisel);
                if (geom == null && (HelperGdb.OutStr.Length > 0))
                    HelperGlobal.ponMsg(HelperGdb.OutStr, System.Windows.MessageBoxImage.Error);

                HelperGdb.Free(fc);
            }
            return geom;
        }
        /*
         * Saca ventana para seleccionar el campo del que leer para elegir la geometría
         */
        private static ArcGIS.Core.Geometry.Geometry SelecLeeGeom(FeatureClass fc, out string text_sal, bool multisel)
        {
            ArcGIS.Core.Geometry.Geometry geomsal = null;
            text_sal = "";
            if (fc != null)
            {
                //saca la ventana de selección de campo
                ShowProWndSelectFields selfwnd = new ShowProWndSelectFields(fc, multisel);
                if (selfwnd.SelFieldVals.Count > 0)
                {
                    geomsal = HelperGdb.GetGeomSel(fc, selfwnd.SelField, selfwnd.SelFieldVals).Result;
                    text_sal = HelperGdb.TextoSal;
                }
            }

            return geomsal;
        }

        /**
         * Devuelve el sistema de coordenadas de una capa
         */
        public static ArcGIS.Core.Geometry.SpatialReference GetSpatRef(string ftclassName)
        {
            FeatureClass fc = GetFtClassFromShp(ftclassName).Result;
            if (fc == null)
                return null;
            ArcGIS.Core.Geometry.SpatialReference spatref = null;
            var task = ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
            {
                try
                {
                    spatref = fc.GetDefinition().GetSpatialReference();
                }
                catch
                {
                }
                finally
                {
                    fc.Dispose();
                }
            });
            task.Wait();
            
            return spatref;
        }
        public static ArcGIS.Core.Geometry.SpatialReference GetSpatRefSync(string ftclassName)
        {
            FeatureClass fc = GetFtClassSync(ftclassName);
            if (fc == null)
                return null;
            ArcGIS.Core.Geometry.SpatialReference spatref = null;
            
            try
            {
                spatref = fc.GetDefinition().GetSpatialReference();
            }
            catch
            {
                spatref = null;
            }
            finally
            {
                fc.Dispose();
            }
            return spatref;
        }
        /**
         * Devuelve el sistema de coordenadas de una capa
         */
        public static ArcGIS.Core.Geometry.SpatialReference GetSpatRef(FeatureClass fc)
        {
            if (fc == null)
                return null;
            ArcGIS.Core.Geometry.SpatialReference spatref = null;
            var task = ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
            {
                try
                {
                    spatref = fc.GetDefinition().GetSpatialReference();
                }
                catch
                {
                    spatref = null;
                }
            });
            return spatref;
        }
        public static ArcGIS.Core.Geometry.SpatialReference GetSpatRefSync(FeatureClass fc)
        {
            if (fc == null)
                return null;
            ArcGIS.Core.Geometry.SpatialReference spatref = null;
            
           
            try
            {
                spatref = fc.GetDefinition().GetSpatialReference();
            }
            catch
            {
                spatref = null;
            }
            return spatref;
        }

        /**
         * Devuelve el sistema de coordenadas de un dataset
         */
        public static ArcGIS.Core.Geometry.SpatialReference GetSpatRef(string gdbName, string dataset)
        {
            if (gdbName == null || string.IsNullOrEmpty(dataset))
                return null;
            Geodatabase gdb = null;
            FeatureClassDefinition fcdef = null;
            try
            {
                gdb = GetGdb(gdbName).Result;
                if (gdb == null)
                    return null;
                ArcGIS.Core.Geometry.SpatialReference spatref = null;
                var task = ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
                {
                    try
                    {
                        fcdef = gdb.GetDefinition<FeatureClassDefinition>(dataset);
                        spatref = fcdef.GetSpatialReference();
                    }
                    catch
                    {
                        fcdef = null;
                    }
                });
                task.Wait();
                return spatref;
            }
            catch
            {
                return null;
            }
            finally
            {
                Free(gdb);
                Free(fcdef);
            }
        }
        /**
 * Devuelve el sistema de coordenadas de un dataset
 */
        public static ArcGIS.Core.Geometry.SpatialReference GetSpatRefSync(string gdbName, string dataset)
        {
            if (gdbName == null || string.IsNullOrEmpty(dataset))
                return null;
            Geodatabase gdb = null;
            FeatureClassDefinition fcdef = null;
            try
            {
                gdb = GetGdb(gdbName).Result;
                if (gdb == null)
                    return null;
                ArcGIS.Core.Geometry.SpatialReference spatref = null;
                    try
                    {
                        fcdef = gdb.GetDefinition<FeatureClassDefinition>(dataset);
                        spatref = fcdef.GetSpatialReference();
                    }
                    catch
                    {
                        fcdef = null;
                    }
               
                return spatref;
            }
            catch
            {
                return null;
            }
            finally
            {
                Free(gdb);
                Free(fcdef);
            }
        }
        //Dado el tipo de FtClass y una posición inicial abre un diálogo de búsqueda de ftclass 
        //si se cancela o no es una feature class lo que se ha abierto devuelve null
        //si no, devuelve la featureclass directamente abierta
        public static FeatureClass OpenFtClassDialog(TiposOpenFileDlg tipo, string initialLoc = "")
        {
            FeatureClass fc = null;
            ReiniciaOutStr();
            string path = OpenFileDialog(tipo, initialLoc);
            if (!string.IsNullOrEmpty(path))
            {
                fc = GetFtClass(path);
            }
            return fc;
        }

        //Libera el objeto
        public static void Free(IDisposable obj)
        {
            if (obj != null)
                obj.Dispose();
        }

        //Devuelve el Path del archivo seleccionado o un string vacío si se ha cancelado
        public static string OpenFileDialog(TiposOpenFileDlg tipo, string initialLoc = "", string tit_ = "")
        {
            string titulo;
            ReiniciaOutStr();
            titulo = "Abrir Archivo";
            //Create a browse filter that uses Pro's "esri_browseDialogFilters_geodatabases" filter.
            //The browse filter is used in an OpenItemDialog.
            //fuentes filtros
            //https://github.com/Esri/arcgis-pro-sdk-community-samples/blob/master/Map-Exploration/IdentifyWindow/Daml.cs
            BrowseProjectFilter filtro = new BrowseProjectFilter();
            if ((tipo & TiposOpenFileDlg.OpenFtrClassLine) == TiposOpenFileDlg.OpenFtrClassLine)
            {
                filtro.AddFilter(BrowseProjectFilter.GetFilter("esri_browseDialogFilters_featureClasses_line"));
                titulo = "Abrir Feature Class";
            }
            if ((tipo & TiposOpenFileDlg.OpenFtrClassPoint) == TiposOpenFileDlg.OpenFtrClassPoint)
            {
                filtro.AddFilter(BrowseProjectFilter.GetFilter("esri_browseDialogFilters_featureClasses_point"));
                titulo = "Abrir Feature Class";
            }
            if ((tipo & TiposOpenFileDlg.OpenFtrClassPolygon) == TiposOpenFileDlg.OpenFtrClassPolygon)
            {
                filtro.AddFilter(BrowseProjectFilter.GetFilter("esri_browseDialogFilters_featureClasses_polygon"));
                titulo = "Abrir Feature Class";
            }
            if ((tipo & TiposOpenFileDlg.OpenGdb) == TiposOpenFileDlg.OpenGdb)
            {
                filtro.AddFilter(BrowseProjectFilter.GetFilter("esri_browseDialogFilters_geodatabases"));
                titulo = "Abrir Geodatabase";
            }
            if ((tipo & TiposOpenFileDlg.OpenDataset) == TiposOpenFileDlg.OpenDataset)
            {
                filtro.AddFilter(BrowseProjectFilter.GetFilter("esri_browseDialogFilters_featureDatasets_all"));
                titulo = "Abrir Dataset";
            }
            if (tipo == 0)
            {
                filtro.AddFilter(BrowseProjectFilter.GetFilter(""));
            }
            if (!string.IsNullOrEmpty(tit_))
                titulo = tit_;

            //Display the filter in an Open Item dialog
            OpenItemDialog aNewFilter = new OpenItemDialog
            {
                Title = titulo,
                MultiSelect = false,
                //Set the BrowseFilter property to Pro's Geodatabase filter.
                BrowseFilter = filtro
            };
            if (!string.IsNullOrEmpty(initialLoc))
                aNewFilter.InitialLocation = initialLoc;

            bool? ok = aNewFilter.ShowDialog();
            if ((ok ?? true) && aNewFilter.Items.Count() > 0)
                return aNewFilter.Items.First().Path;
            else
                return string.Empty;
        }

        //Dado un path comprueba que sea de una gdb, termina en .gdb, o si tiene .gdb en medio lo corta ahí
        //y si no tiene devuelve vacío
        public static string GetPathGdb(string path)
        {
            if (path == null)
                return string.Empty;
            string pathGdb = string.Empty;
            int i = 0;
            if (path.Contains(GDB_EXT))
            {
                i = path.IndexOf(GDB_EXT, 0, path.Length);
                pathGdb = path.Substring(0, i + 4);
            }

            return pathGdb;

        }
        //Dado un path aunque sea de una feature class, devuelve el path de la gdb que la contiene, o si
        //es de gdb directamente, y si no tiene .gdb, devuelve null
        public static Task<Geodatabase> GetGdb(string pathGdb)
        {
            Geodatabase fileGeodatabase = null;
            if (string.IsNullOrEmpty(pathGdb))
                return null;
            ReiniciaOutStr();
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<Geodatabase>)(() =>
            {
                string path = GetPathGdb(pathGdb);
                if (!string.IsNullOrEmpty(path))
                {
                    try
                    {
                        fileGeodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(path)));
                    }
                    catch (Exception ex)
                    {
                        HelperGdb.OutStr = "Error al abrir Geodatabase " + path + ": " + ex.Message;
                        return null;
                    }
                }
                return fileGeodatabase;
            }));
            //return fileGeodatabase;
        }

        //Dado un path aunque sea de una feature class, devuelve el path de la gdb que la contiene, o si
        //es de gdb directamente, y si no tiene .gdb, devuelve null
        public static Geodatabase GetGdbSync(string pathGdb)
        {
            Geodatabase fileGeodatabase = null;
            if (string.IsNullOrEmpty(pathGdb))
                return null;
            ReiniciaOutStr();
            
                string path = GetPathGdb(pathGdb);
                if (!string.IsNullOrEmpty(path))
                {
                    try
                    {
                        fileGeodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(path)));
                    }
                    catch (Exception ex)
                    {
                        HelperGdb.OutStr = "Error al abrir Geodatabase " + path + ": " + ex.Message;
                        return null;
                    }
                }
                return fileGeodatabase;
           
        }

        //Dado un path de una feature class devuelve la ftclass abierta directamente,
        //o null si ha habido algún problema o no lo ha encontrado
        public static FeatureClass GetFtClassSync(string pathFtClss)
        {
            FeatureClass ftclss = null;
            FeatureDataset dtset = null;
            if (string.IsNullOrEmpty(pathFtClss))
                return null;
            Geodatabase gdb = GetGdbSync(pathFtClss);
            ReiniciaOutStr();
            if (gdb != null)
            {
                string dtsetName = new DirectoryInfo(System.IO.Path.GetDirectoryName(pathFtClss)).Name;
                try
                {
                    dtset = gdb.OpenDataset<FeatureDataset>(dtsetName);
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al abrir Dataset " + dtsetName + ": " + ex.Message;
                    dtset= null;
                }
                
                string ftclassName = System.IO.Path.GetFileNameWithoutExtension(pathFtClss);
                if (dtset != null)
                {
                    try
                    {
                        ftclss = dtset.OpenDataset<FeatureClass>(ftclassName);                        
                    }
                    catch (Exception ex)
                    {
                        ftclss = null;
                    }
                }   
                else
                {
                    try
                    {
                        ftclss = gdb.OpenDataset<FeatureClass>(ftclassName);
                    }
                    catch (Exception ex)
                    {
                        ftclss = null;
                    }
                }
                if(ftclss==null)
                    HelperGdb.OutStr = "Error al abrir Feature Class " + ftclassName;
            }
            else //mira a ver si es shapefile
            {
                ftclss = GetFtClassFromShpSync(pathFtClss);
            }
            Free(gdb);
            Free(dtset);
            return ftclss;
        }
        //Dado un path de una feature class devuelve la ftclass abierta directamente,
        //o null si ha habido algún problema o no lo ha encontrado
        public static FeatureClass GetFtClass(string pathFtClss)
        {
            FeatureClass ftclss = null;
            FeatureDataset dtset = null;
            if (string.IsNullOrEmpty(pathFtClss))
                return null;
            Geodatabase gdb = GetGdb(pathFtClss).Result;
            ReiniciaOutStr();
            if (gdb != null)
            {
                var task = ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<FeatureClass>)(() =>
                {
                    string dtsetName = new DirectoryInfo(System.IO.Path.GetDirectoryName(pathFtClss)).Name;
                    try
                    {
                        dtset = gdb.OpenDataset<FeatureDataset>(dtsetName);
                    }
                    catch (Exception ex)
                    {
                        HelperGdb.OutStr = "Error al abrir Dataset " + dtsetName + ": " + ex.Message;
                        dtset = null;
                    }

                    string ftclassName = System.IO.Path.GetFileNameWithoutExtension(pathFtClss);
                    if (dtset != null)
                    {
                        try
                        {
                            ftclss = dtset.OpenDataset<FeatureClass>(ftclassName);
                        }
                        catch (Exception ex)
                        {
                            ftclss = null;
                        }
                    }
                    else
                    {
                        try
                        {
                            ftclss = gdb.OpenDataset<FeatureClass>(ftclassName);
                        }
                        catch (Exception ex)
                        {
                            ftclss = null;
                        }
                    }
                    if (ftclss == null)
                        HelperGdb.OutStr = "Error al abrir Feature Class " + ftclassName;
                    return ftclss;
                }));
                task.Wait();
            }
            else //mira a ver si es shapefile
            {
                ftclss = GetFtClassFromShp(pathFtClss).Result;
            }
            Free(gdb);
            Free(dtset);
            return ftclss;
        }
        //Dado el path de una gdb y el nombre de una feature class, devuelve la 
        //feature class abierta, o null si hay algun problema
        public static Task<FeatureClass> GetFtClass(string nameFtclss, Geodatabase gdb)
        {
            FeatureClass ftclss = null;
            if (string.IsNullOrEmpty(nameFtclss))
                return null;
            ReiniciaOutStr();
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<FeatureClass>)(() =>
            {
                try
                {
                    ftclss = gdb.OpenDataset<FeatureClass>(nameFtclss);                    
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al abrir Feature Class " + nameFtclss + ": " + ex.Message;
                    return null;
                }
                return ftclss;
            }));
        }
        //Dado el path de una gdb y el nombre de una feature class, devuelve la 
        //feature class abierta, o null si hay algun problema
        public static FeatureClass GetFtClassSync(string nameFtclss, Geodatabase gdb)
        {
            FeatureClass ftclss = null;
            if (string.IsNullOrEmpty(nameFtclss))
                return null;
            ReiniciaOutStr();
           
                try
                {
                    ftclss = gdb.OpenDataset<FeatureClass>(nameFtclss);
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al abrir Feature Class " + nameFtclss + ": " + ex.Message;
                    return null;
                }
                return ftclss;
        }

        //Abre una feature class cuando es un shapefile
        public static Task<FeatureClass> GetFtClassFromShp(string pathShp)
        {
            FeatureClass ftclss = null;
            ReiniciaOutStr();
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<FeatureClass>)(() =>
            {
                if (!string.IsNullOrEmpty(pathShp) && pathShp.Contains(SHP_EXT))
                {
                    try
                    {
                        string shpname = System.IO.Path.GetFileNameWithoutExtension(pathShp);
                        var shapeFileConnPath = new FileSystemConnectionPath(new Uri(System.IO.Path.GetDirectoryName(pathShp)),
                            FileSystemDatastoreType.Shapefile);
                        var shapefile = new FileSystemDatastore(shapeFileConnPath);
                        ftclss = shapefile.OpenDataset<FeatureClass>(shpname);
                    }
                    catch (Exception ex)
                    {
                        HelperGdb.OutStr = "Error al abrir Shapefile " + pathShp + ": " + ex.Message;
                        return null;
                    }
                }
                return ftclss;
            }));
        }
        //Abre una feature class cuando es un shapefile
        public static FeatureClass GetFtClassFromShpSync(string pathShp)
        {
            FeatureClass ftclss = null;
            ReiniciaOutStr();
            
                if (!string.IsNullOrEmpty(pathShp) && pathShp.Contains(SHP_EXT))
                {
                    try
                    {
                        string shpname = System.IO.Path.GetFileNameWithoutExtension(pathShp);
                        var shapeFileConnPath = new FileSystemConnectionPath(new Uri(System.IO.Path.GetDirectoryName(pathShp)),
                            FileSystemDatastoreType.Shapefile);
                        var shapefile = new FileSystemDatastore(shapeFileConnPath);
                        ftclss = shapefile.OpenDataset<FeatureClass>(shpname);
                    }
                    catch (Exception ex)
                    {
                        HelperGdb.OutStr = "Error al abrir Shapefile " + pathShp + ": " + ex.Message;
                        return null;
                    }
                }
                return ftclss;
        }
        //devuelve el campo dado el nombre
        private static ArcGIS.Core.Data.Field GetFieldByNameSync(FeatureClass ftClss, string fieldName)
        {
            FeatureClassDefinition ftcldef = ftClss.GetDefinition();
            ReiniciaOutStr();
            ArcGIS.Core.Data.Field field = null;
            
                try
                {
                    field = ftcldef.GetFields().First(x => x.Name.Equals(fieldName));
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "No se encuentra el campo " + fieldName + ": " + ex.Message;
                    return null;
                }
                return field;

        }

        //Crea un filtro espacial a partir de una consulta y en todo caso, una geometría
        public static ArcGIS.Core.Data.SpatialQueryFilter CreateFiler(string consulta, ArcGIS.Core.Geometry.Geometry geom, SpatialRelationship rel = SpatialRelationship.Contains)
        {
            ArcGIS.Core.Data.SpatialQueryFilter filt = null;
            if (geom != null)
            {
                SpatialQueryFilter filtSpat = new SpatialQueryFilter
                {
                    WhereClause = consulta,
                    FilterGeometry = geom,
                    SpatialRelationship = rel,
                };
                filt = filtSpat;

            }
            else
            {
                filt = new ArcGIS.Core.Data.SpatialQueryFilter();
                filt.WhereClause = consulta;
            }
            return filt;
        }

        //Devuelve la lista de IDs de la clase que cumplen la consulta
        public static Task<List<long>> GetIds(FeatureClass fc, ArcGIS.Core.Data.QueryFilter filt)
        {
            ReiniciaOutStr();
            List<long> ids = new List<long>();
            Selection sel = null;
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<List<long>>)(() =>
            {
                if (fc == null)
                    return null;
                try
                {
                    if (filt == null)
                        filt = new ArcGIS.Core.Data.QueryFilter();
                    sel = fc.Select(filt, SelectionType.ObjectID, SelectionOption.Normal);
                    long nsel = sel.GetCount();
                    IReadOnlyList<long> ids_ = sel.GetObjectIDs();
                    ids = ids_.ToList();
                    return ids;
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al conseguir IDs " + fc.GetName() + ": " + ex.Message;
                    return ids;
                }
                finally
                {
                    Free(sel);
                }

            }));
        }
        //Devuelve la lista de IDs de la clase que cumplen la consulta
        public static List<long> GetIdsSync(FeatureClass fc, ArcGIS.Core.Data.QueryFilter filt)
        {
            ReiniciaOutStr();
            List<long> ids = new List<long>();
            Selection sel = null;
            
                if (fc == null)
                    return null;
                try
                {
                    if (filt == null)
                        filt = new ArcGIS.Core.Data.QueryFilter();
                    sel = fc.Select(filt, SelectionType.ObjectID, SelectionOption.Normal);
                    var nsel = sel.GetCount();
                    IReadOnlyList<long> ids_ = sel.GetObjectIDs();
                    ids = ids_.ToList();
                    return ids;
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al conseguir IDs " + fc.GetName() + ": " + ex.Message;
                    return ids;
                }
                finally
                {
                    Free(sel);
                }
        }

        //Devuelve una lista con los campos de una feature class
        public static Task<ObservableCollection<string>> GetFields(FeatureClass fc)
        {
            FeatureClassDefinition ftcldef = null;
            IReadOnlyList<ArcGIS.Core.Data.Field> fields = null;
            ReiniciaOutStr();
            ObservableCollection<string> fields_st = new ObservableCollection<string>();
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<ObservableCollection<string>>)(() =>
            {
                if (fc == null)
                    return null;
                try
                {
                    ftcldef = fc.GetDefinition();
                    fields = ftcldef.GetFields();
                    foreach (ArcGIS.Core.Data.Field f in fields)
                    {
                        fields_st.Add(f.Name);
                    }
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al leer los campos " + fc.GetName() + ": " + ex.Message;
                    return fields_st;
                }
                return fields_st;
            }));
        }

        //Devuelve una lista con los campos de una feature class
        public static ObservableCollection<string> GetFieldsSync(FeatureClass fc)
        {
            FeatureClassDefinition ftcldef = null;
            IReadOnlyList<ArcGIS.Core.Data.Field> fields = null;
            ReiniciaOutStr();
            ObservableCollection<string> fields_st = new ObservableCollection<string>();
            
                if (fc == null)
                    return null;
                try
                {
                    ftcldef = fc.GetDefinition();
                    fields = ftcldef.GetFields();
                    foreach (ArcGIS.Core.Data.Field f in fields)
                    {
                        fields_st.Add(f.Name);
                    }
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al leer los campos " + fc.GetName() + ": " + ex.Message;
                    return fields_st;
                }
                return fields_st;
        }

        //Devuelve una lista con los campos de una feature class
        public static IReadOnlyList<ArcGIS.Core.Data.Field> GetFieldsSyncEx(FeatureClass fc)
        {
            FeatureClassDefinition ftcldef = null;
            IReadOnlyList<ArcGIS.Core.Data.Field> fields = null;
            ReiniciaOutStr();

            if (fc == null)
                return null;
            try
            {
                ftcldef = fc.GetDefinition();
                fields = ftcldef.GetFields();                
            }
            catch (Exception ex)
            {
                HelperGdb.OutStr = "Error al leer los campos " + fc.GetName() + ": " + ex.Message;
                return fields;
            }
            return fields;
        }

        /*
         * Comprueba que en la capa dada exista un campo con nombre 'field'
         */
        public static bool CheckField(string pathCapa, string field)
        {
            FeatureClass fc = GetFtClass(pathCapa);
            if (fc == null)
            {
                HelperGdb.OutStr = "No se puede abrir la capa";
                return false;
            }

            ObservableCollection<string> fields = GetFields(fc).Result;
            if (!fields.Contains(field))
            {
                HelperGdb.OutStr = "No se encuentra el campo " + field;
                Free(fc);
                return false;
            }
            Free(fc);
            return true;
        }
        /*
 * Comprueba que en la capa dada exista un campo con nombre 'field'
 */
        public static bool CheckFieldSync(string pathCapa, string field)
        {
            FeatureClass fc = GetFtClassSync(pathCapa);
            if (fc == null)
            {
                HelperGdb.OutStr = "No se puede abrir la capa";
                return false;
            }

            ObservableCollection<string> fields = GetFieldsSync(fc);
            bool encuentra = true;
            if (!fields.Contains(field))
            {
                HelperGdb.OutStr = "No se encuentra el campo " + field;
                encuentra= false;
            }
            Free(fc);
            return encuentra;
        }
        /*
         * Lee la capa que se ha seleccionzdo de recogida y se comprueba que contiene los campos necesarios
         * Devuelve 0 si va todo bien, -1 si da error, y num>0 con los campos que no encuentra
         */
        public static int CheckFileds(string pathCapa, string[] camps)
        {
            int i;
            FeatureClass fc = null;
            try
            {
                fc = GetFtClass(pathCapa);
                if (fc == null)
                {
                    OutStr = "No se puede abrir la capa " + pathCapa;
                    return -1;
                }

                ObservableCollection<string> fields = GetFields(fc).Result;
                OutStr = "No se encuentran el/los campo/s: ";
                int mal = 0;
                for (i = 0; i < camps.Length; i++)
                {
                    //if (!fields.Any(f=>f.ToUpper().Trim().Contains(camps[i].Substring (0,Math.Min(camps[i].Length, ComunDef.MaxCaracteresBBDD)).ToUpper().Trim())))
                    if (!fields.Any(f=>f.ToUpper().Trim().Equals(camps[i].ToUpper().Trim())))
                    {
                        OutStr = OutStr + camps[i] + " ";
                        mal++;
                    }
                }
                if (mal == 0)
                    OutStr = "";
                return mal;
            }
            catch
            {
                return -1;
            }
            finally
            {
                Free(fc);
            }
        }
        /*
         * Lee la capa que se ha seleccionzdo de recogida y se comprueba que contiene los campos necesarios
         * Devuelve 0 si va todo bien, -1 si da error, y num>0 con los campos que no encuentra
         */
        public static int CheckFiledsSync(string pathCapa, string[] camps)
        {
            int i;
            FeatureClass fc = null;
            try
            {
                fc = GetFtClassSync(pathCapa);
                if (fc == null)
                {
                    OutStr = "No se puede abrir la capa " + pathCapa;
                    return -1;
                }

                ObservableCollection<string> fields = GetFieldsSync(fc);
                OutStr = "No se encuentran el/los campo/s: ";
                int mal = 0;
                for (i = 0; i < camps.Length; i++)
                {
                    //if (!fields.Any(f => f.ToUpper().Trim().Contains(camps[i].Substring(0, Math.Min(camps[i].Length, ComunDef.MaxCaracteresBBDD)).ToUpper().Trim())))

                    if (!fields.Any(f => f.ToUpper().Trim().Equals(camps[i].ToUpper().Trim())))
                    {
                        OutStr = OutStr + camps[i] + " ";
                        mal++;
                    }
                }
                if (mal == 0)
                    OutStr = "";
                return mal;
            }
            catch
            {
                return -1;
            }
            finally
            {
                Free(fc);
            }
        }

        //Devuelve comilla simple si el campo es de texto, o nada si es númerico
        //var whereClause = $"{SelectedField} = {Quote(f)}{FieldValue}{Quote(f)}";
        public static string Quote(ArcGIS.Core.Data.Field f)
        {
            return f.FieldType == FieldType.String ? "'" : "";
        }

        //Dado un nombre de campo, devuelve los valores que encuentra para ese nombre de campo
        public static Task<ObservableCollection<string>> GetFieldVals(string capa, string fieldName, bool uniquevals)
        {
            FeatureClass fc = GetFtClass(capa);
            if (fc == null)
                return null;

            return GetFieldVals(fc, fieldName, uniquevals);
        }
        //Dado un nombre de campo, devuelve los valores que encuentra para ese nombre de campo
        public static ObservableCollection<string> GetFieldValsSync(string capa, string fieldName, bool uniquevals)
        {
            FeatureClass fc = GetFtClassSync(capa);
            if (fc == null)
                return null;

            return GetFieldValsSync(fc, fieldName, uniquevals);
        }
        //Dado un nombre de campo, devuelve los valores que encuentra para ese nombre de campo
        public static Task<ObservableCollection<string>> GetFieldVals(FeatureClass fc, string fieldName, bool uniquevals)
        {
            ObservableCollection<string> attribs_st = new ObservableCollection<string>();
            ReiniciaOutStr();
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<ObservableCollection<string>>)(() =>
            {
                try
                {
                    using (FeatureClassDefinition ftcldef = fc.GetDefinition())
                    {
                        ArcGIS.Core.Data.Field field = ftcldef.GetFields().First(x => x.Name.Equals(fieldName));
                        using (RowCursor rowCursor = fc.Search())
                        {
                            string str;
                            while (rowCursor.MoveNext())
                            {
                                using (Row row = rowCursor.Current)
                                {
                                    str = Convert.ToString(row[fieldName]);
                                    if (uniquevals)
                                    {
                                        if (attribs_st.Contains(str))
                                            continue;
                                    }
                                    attribs_st.Add(str);
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al leer los campos " + fc.GetName() + ": " + ex.Message;
                    return attribs_st;

                }
                return attribs_st;
            }));
        }
        //Dado un nombre de campo, devuelve los valores que encuentra para ese nombre de campo
        public static ObservableCollection<string> GetFieldValsSync(FeatureClass fc, string fieldName, bool uniquevals)
        {
            ObservableCollection<string> attribs_st = new ObservableCollection<string>();
            ReiniciaOutStr();
            
                try
                {
                    using (FeatureClassDefinition ftcldef = fc.GetDefinition())
                    {
                        ArcGIS.Core.Data.Field field = ftcldef.GetFields().First(x => x.Name.Equals(fieldName));
                        using (RowCursor rowCursor = fc.Search())
                        {
                            string str;
                            while (rowCursor.MoveNext())
                            {
                                using (Row row = rowCursor.Current)
                                {
                                    str = Convert.ToString(row[fieldName]);
                                    if (uniquevals)
                                    {
                                        if (attribs_st.Contains(str))
                                            continue;
                                    }
                                    attribs_st.Add(str);
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al leer los campos " + fc.GetName() + ": " + ex.Message;
                    return attribs_st;

                }
                return attribs_st;
        }
        /**
         * Devuelve una geometría que es la suma de la inicial y la que se añade Add
         */
        public static ArcGIS.Core.Geometry.Geometry IntersectGeom(ArcGIS.Core.Geometry.Geometry geomIni, ArcGIS.Core.Geometry.Geometry geomInters,
             GeometryDimensionType resultDimension = GeometryDimensionType.EsriGeometryUnknownDimension)//esriGeometryNoDimension)
        {
            if (geomIni == null)
                return geomInters;
            if (geomInters == null)
                return geomIni;
            ArcGIS.Core.Geometry.Geometry geomSal = null;
            try
            {
                if (resultDimension == GeometryDimensionType.EsriGeometryUnknownDimension)//GeometryDimension.esriGeometryNoDimension)
                    geomSal = GeometryEngine.Instance.Intersection(geomIni, geomInters);
                else
                    geomSal = GeometryEngine.Instance.Intersection(geomIni, geomInters, resultDimension);

                return geomSal;
            }
            catch
            {
                return null;
            }
        }

        /**
         * Devuelve una geometría que es la suma de la inicial y la que se añade Add
         */
        public static ArcGIS.Core.Geometry.Geometry UneGeom(ArcGIS.Core.Geometry.Geometry geomIni, ArcGIS.Core.Geometry.Geometry geomUne)
        {
            if (geomIni == null)
                return geomUne;
            if (geomUne == null)
                return geomIni;
            ArcGIS.Core.Geometry.Geometry geomSal = null;
            try
            {
                geomSal = GeometryEngine.Instance.Union(geomIni, geomUne);

                return geomSal;
            }
            catch
            {
                return null;
            }
        }

        /**
         * Devuelve una geometría a la que dada una geometría inicial se le quita Excl
         */
        public static ArcGIS.Core.Geometry.Geometry QuitaGeom(ArcGIS.Core.Geometry.Geometry geomIni, ArcGIS.Core.Geometry.Geometry geomQuita)
        {
            if (geomIni == null)
                return geomQuita;
            if (geomQuita == null)
                return geomIni;
            ArcGIS.Core.Geometry.Geometry geomSal = null;
            try
            {
                geomSal = GeometryEngine.Instance.Difference(geomIni, geomQuita);
                return geomSal;
            }
            catch
            {
                return null;
            }
        }

        /**
         * Reproyecta la geometría dada a la spatial reference, si son diferentes
         */
        public static ArcGIS.Core.Geometry.Geometry ReproyectaGeom(ArcGIS.Core.Geometry.Geometry geom, ArcGIS.Core.Geometry.SpatialReference spatref)
        {
            ArcGIS.Core.Geometry.Geometry geomSal = null;
            try
            {
                if (geom == null) 
                    return null;
                if (geom.SpatialReference == null)
                    return geom;
                if (geom.SpatialReference.Equals(spatref))
                    return geom;
                geomSal = GeometryEngine.Instance.Project(geom, spatref);
                return geomSal;
            }
            catch
            {
                return null;
            }
        }

        /**
         * Forma la envolvente convexa, el mínimo polígono,
         * que contiene los ámbitos para exportar, a partir de ahí la red navegable ampliando a un buffer
         */
        public static Task<ArcGIS.Core.Geometry.Geometry> GetGeomConvexHull(FeatureClass fclss, ArcGIS.Core.Data.QueryFilter filter)
        {
            ArcGIS.Core.Geometry.Geometry geomIni = null;

            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<ArcGIS.Core.Geometry.Geometry>)(() =>
            {
                geomIni = GetGeomUnique(fclss, filter);
                geomIni = GeometryEngine.Instance.ConvexHull(geomIni);
                return geomIni;
            }));
        }
        /**
         * Forma la envolvente convexa, el mínimo polígono,
         * que contiene los ámbitos para exportar, a partir de ahí la red navegable ampliando a un buffer
         */
        public static ArcGIS.Core.Geometry.Geometry GetGeomConvexHullSync(FeatureClass fclss, ArcGIS.Core.Data.QueryFilter filter)
        {
            ArcGIS.Core.Geometry.Geometry geomIni = null;
            geomIni = GetGeomUnique(fclss, filter);
            geomIni = GeometryEngine.Instance.ConvexHull(geomIni);
            return geomIni;
        }
        /*
        * A partir de una capa recorre todos los elementos que cumplen el filtro y los une en una única geometría
        */
        public static ArcGIS.Core.Geometry.Geometry GetGeomUnique(FeatureClass fclss, ArcGIS.Core.Data.QueryFilter filtro)
        {
            ArcGIS.Core.Geometry.Geometry geomsal = null;
            ReiniciaOutStr();

            try
            {
                var geom = new List<ArcGIS.Core.Geometry.Geometry>();
                using (RowCursor rowCursor = fclss.Search(filtro))
                {

                    while (rowCursor.MoveNext())
                    {
                        using (Row row = rowCursor.Current)
                        {
                            if (row is Feature ft)
                                geom.Add(ft.GetShape());
                        }
                    }
                    geomsal = GeometryEngine.Instance.Union(geom);
                }
            }
            catch (Exception ex)
            {
                OutStr = "Error al leer generar geometría única " + OutStr + ex.Message;
                return geomsal;
            }

            return geomsal;
        }
        /**
        * Dado un campo de una feature class y el valor que se quiere para el campo
        * devuelve las geometrías que cumplen dicho criterio
        */
        public static Task<ArcGIS.Core.Geometry.Geometry> GetGeomSel(FeatureClass fclss, string fieldName, ObservableCollection<string> selFieldVals)
        {
            ArcGIS.Core.Geometry.Geometry geomsal = null;
            ArcGIS.Core.Geometry.Geometry geomAux = null;
            string where = "";
            ArcGIS.Core.Data.Field f;
            ArcGIS.Core.Data.QueryFilter filtro;
            bool ok = true;
            ReiniciaOutStr();
            string txtsal = string.Empty;
            ReiniciaOutStr();
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<ArcGIS.Core.Geometry.Geometry>)(() =>
            {
                try
                {
                    using (FeatureClassDefinition ftcldef = fclss.GetDefinition())
                    {
                        f = ftcldef.GetFields().First(x => x.Name.Equals(fieldName));
                        //se embucla para unir las geoms
                        for (int i = 0; i < selFieldVals.Count && ok; i++)
                        {
                            where = $"{fieldName} = {Quote(f)}{selFieldVals[i]}{Quote(f)}";
                            filtro = new ArcGIS.Core.Data.QueryFilter { WhereClause = where };
                            geomAux = GetGeomUnique(fclss, filtro);
                            if (geomAux == null)
                            {
                                ok = false;
                                continue;
                            }
                            geomsal = UneGeom(geomsal, geomAux);
                            txtsal = txtsal + HelperGlobal.RevisaText(selFieldVals[i]);
                        }
                        TextoSal = txtsal;
                    }
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al generar geometría en " + fclss.GetName() + " Con filtro " + where + ": " + ex.Message;
                    return geomsal;

                }
                if (!ok)
                    HelperGdb.OutStr = "Error al generar geometría en " + fclss.GetName() + " Con filtro " + where + HelperGdb.OutStr;
                return geomsal;
            }));
        }
        /**
         * Devuelve el número de entidades de una FeatureClass que cumplen la consulta, o todos si la consulta es empty
         */
        public static long GetNumElems(string pathGdb, string ftclssName, string consulta = "")
        {
            Geodatabase gdb = GetGdb(pathGdb).Result;
            FeatureClass fc = null;
            long n = -1;
            if (gdb != null)
            {
                fc = GetFtClass(ftclssName, gdb).Result;
                if (fc != null)
                    n = GetNumElems(fc, consulta).Result;
            }
            Free(fc);
            Free(gdb);
            return n;

        }
        /**
         * Devuelve los valores únicos de un campo dado
         */
        public static ObservableCollection<string> GetUniqueVals(string capa, string campo)
        {
            ObservableCollection<string> uniqueVals = null;
            FeatureClass ftclass = GetFtClass(capa);
            if (ftclass == null)
                return null;

            using (FeatureClassDefinition featureClassDefinition = ftclass.GetDefinition())
            {
                // Get fields
                ArcGIS.Core.Data.Field fld = featureClassDefinition.GetFields().First(x => x.Name.Equals(campo));

                // Create StatisticsDescriptions
                StatisticsDescription uniqStatDesc = new StatisticsDescription(fld, new List<ArcGIS.Core.Data.StatisticsFunction>()
                { ArcGIS.Core.Data.StatisticsFunction.Count });

                // Create TableStatisticsDescription
                TableStatisticsDescription tableStatisticsDescription = new TableStatisticsDescription(new List<StatisticsDescription>() { uniqStatDesc });
                tableStatisticsDescription.GroupBy = new List<ArcGIS.Core.Data.Field>() { fld };
                tableStatisticsDescription.OrderBy = new List<SortDescription>() { new SortDescription(fld) };

                // Calculate Statistics
                IReadOnlyList<TableStatisticsResult> statisticsResults = ftclass.CalculateStatistics(tableStatisticsDescription);
                TableStatisticsResult statRes = statisticsResults.ElementAt(0); //solo hay 1 grupo

                // Code to process results goes here...
                uniqueVals = new ObservableCollection<string>();
                for (int i = 0; i < statRes.StatisticsResults.Count; i++)
                {
                    uniqueVals.Add(statRes.StatisticsResults.ElementAt(i).ToString());
                }
            }
            Free(ftclass);
            return uniqueVals;
        }
        /**
            * Devuelve el número de entidades de una FeatureClass que cumplen la consulta, o todos si la consulta es empty
            */
        public static Task<long> GetNumElems(FeatureClass fc, string consulta = "")
        {
            long n = -1;
            ReiniciaOutStr();
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<long>)(() =>
            {
                try
                {
                    if (fc != null)
                    {
                        if (string.IsNullOrEmpty(consulta))
                            n = fc.GetCount();
                        else
                        {
                            //realiza consulta
                            n = 0;
                            //fc.GetDefinition().GetFields().First().Name kfadpskfpasp
                            using (Selection sel = fc.Select(new ArcGIS.Core.Data.QueryFilter { WhereClause = consulta }, SelectionType.ObjectID, SelectionOption.Normal))
                                n = sel.GetCount();
                        }
                    }
                    return n;
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al contar filas en " + fc.GetName() + ": " + ex.Message;
                    return n;

                }
            }));
        }
        /**
            * Devuelve el número de entidades de una FeatureClass que cumplen la consulta, o todos si la consulta es empty
            */
        public static long GetNumElemsSync(FeatureClass fc, string consulta = "")
        {
            long n = -1;
            ReiniciaOutStr();
             try
                {
                    if (fc != null)
                    {
                        if (string.IsNullOrEmpty(consulta))
                            n = fc.GetCount();
                        else
                        {
                            //realiza consulta
                            n = 0;
                            using (Selection sel = fc.Select(new ArcGIS.Core.Data.QueryFilter { WhereClause = consulta }, SelectionType.ObjectID, SelectionOption.Normal))
                                n = sel.GetCount();
                        }
                    }
                    return n;
                }
                catch (Exception ex)
                {
                    HelperGdb.OutStr = "Error al contar filas en " + fc.GetName() + ": " + ex.Message;
                    return n;

                }
        }

        /**
            * Devuelve el número de entidades de una FeatureClass que cumplen la consulta, o todos si la consulta es empty
            */
        public static long GetNumElems(string pathFtClss, string consulta = "")
        {
            FeatureClass fc = GetFtClass(pathFtClss);
            var n = GetNumElems(fc, consulta).Result;
            Free(fc);
            return n;
        }
        public static long GetNumElemsSync(string pathFtClss, string consulta = "")
        {
            FeatureClass fc = GetFtClassSync(pathFtClss);
            var n = GetNumElemsSync(fc, consulta);
            Free(fc);
            return n;
        }
        static public bool RenameSHP(string dir, string path, string nameNew)
        {
            var extensions = new[] { "cpg", "dbf", "prj", "sbn", "sbx", "shp", "shp.xml", "shx" };
            string old_ext = System.IO.Path.GetExtension(path);

            string name = System.IO.Path.GetFileName(path);
            if (!string.IsNullOrEmpty(old_ext))
                name.Replace(old_ext, "");
            string _ex = System.IO.Path.GetExtension(nameNew);
            string nn = nameNew;
            if (!string.IsNullOrEmpty(_ex))
                nn = nn.Replace(_ex, "");
            var res = true;
            foreach (var ex in extensions)
            {
                try
                {
                    var po = string.Format("{0}{1}.{2}", dir, name, ex);
                    var pn = string.Format("{0}{1}.{2}", dir, nn, ex);

                    if (!File.Exists(po))
                        res = false;//no existe
                    if (File.Exists(pn))
                        File.Delete(pn);
                    File.Move(po, pn);
                }
                catch
                {
                    res = false;
                }
            }

            return res;
        }

        public static bool ExportShp2(string pathLayerIn, SpatialQueryFilter filter, string nameShp, string outpath, IprocessManager cps, ArcGIS.Core.Geometry.SpatialReference sr = null, int incmax = 100)
        {
            if (!System.IO.Directory.Exists(outpath))
                System.IO.Directory.CreateDirectory(outpath);
            FeatureClass fc = null;
            bool debug = false;
            /////////////////////////////////////////////////////////
            //Prepara una where_clause con las entidades que cumplen el filtro
            //Abre la featureclass
            try
            {
                if (debug)
                    HelperGlobal.ponMsg("pathLayerIn " + pathLayerIn);
                fc = GetFtClassSync(pathLayerIn);
                if (fc == null)
                {
                    OutStr = "Error al abrir Feature Class en exportación";
                    return false;
                }
                List<long> ids = new List<long>();

                if (debug)
                    HelperGlobal.ponMsg("fc " + fc.GetName() + " tipo " + fc.GetType().Name);
                /////////////////////////////////////////////////////////
                //Añade a la lista los ids que cumplen el filtro espacial
                try
                {
                  ids = fc.Select(filter, SelectionType.ObjectID, SelectionOption.Normal).GetObjectIDs().ToList();
                }
                catch(Exception ex)
                {                 
                    OutStr = "Error al seleccionar IDs. "+ex.Message;
                    ids = new List<long>();
                }

                /*using (RowCursor cursor = fc.Search(filter))
                {
                    while (cursor.MoveNext() && !cps.Getcancelled())
                    {
                        using (Feature feature = (Feature)cursor.Current)
                        {
                            ids.Add(feature.GetObjectID());
                        }
                    }
                }*/
                if (ids.Count <= 0)
                {
                    OutStr = "Error al aplicar filtro espacial en exportación: "+ OutStr;
                    return false;
                }
                /////////////////////////////////////////////////////////
                if (cps.Getcancelled())
                {
                    OutStr = Resource1.String_cancel_progreso;
                    return false;
                }
                //convierte la lista de ids a string para la clause de la forma
                //(id1, id2, id3..)            
                string whereClause = GetWhereClauseFromIds(ids,fc.GetDefinition().GetObjectIDField());

                ///////////////////////////////////////////////////////////
                ///Exporta                
                string[] args = { pathLayerIn, outpath, nameShp, whereClause };
                // execute the tool
                if (sr == null)
                {
                    sr = fc.GetDefinition().GetSpatialReference();
                }
                var environments = Geoprocessing.MakeEnvironmentArray(outputCoordinateSystem: sr);
                //SpatialReferenceBuilder.CreateSpatialReference(3857)
                double valini = cps.GetProgress();
                var gpres = Geoprocessing.ExecuteToolAsync("FeatureClassToFeatureClass_conversion", args,
                               environments, null,

                               (event_name, o) =>  // implement delegate and handle events
                               {
                                   switch (event_name)
                                   {
                                       case "OnValidate": // stop execute if any warnings
                                                          //if ((o as IGPMessage[]).Any(it => it.Type == GPMessageType.Warning))
                                                          //_cts.Cancel();
                                           break;

                                       case "OnProgressMessage":
                                           {
                                               string msg = string.Format("{0}: {1}", new object[] { event_name, (string)o });
                                               Debug.WriteLine(msg); ;
                                           }
                                           break;

                                       case "OnProgressPos":
                                           {
                                               string msg2 = string.Format("{0}: {1} %", new object[] { event_name, (int)o });
                                               Debug.WriteLine(msg2);
                                               var av = (int)o;
                                               if (av > 0)
                                               {
                                                   cps.SetProgress(valini + (Math.Min((double)av / 100.0, 100.0) * Math.Min(incmax, 100 - valini)));
                                               }
                                               // if ((int)o < 0)
                                               //System.Windows.MessageBox.Show(msg2);
                                               //_cts.Cancel();
                                               break;
                                           }
                                   }
                               });
                while (!gpres.IsCompleted && !gpres.IsCanceled && !gpres.IsFaulted)
                    Thread.Sleep(10);
                IGPResult gpResult = gpres.Result;
                if (gpResult.IsCanceled)
                {
                    cps.Setcancelled();
                    return false;
                }
                if (gpResult.IsFailed)
                {
                    string msg;
                    if (gpResult.ErrorMessages != null)
                        msg = gpResult.ErrorMessages.First().Text;
                    else
                        msg = "Errores en la exportación";
                    OutStr = "Error: " + msg;
                    return false;
                }

                gpres.Dispose();
                return true;
            }
            catch (Exception ex)
            {
                OutStr = "Error: " + ex.Message;
                return false;
            }
            finally
            {
                Free(fc);
            }

        }

        public static bool ExportShp(string pathLayerIn, SpatialQueryFilter filter, string nameShp, string outpath, CancelableProgressorSource cps, out string msgOut, ProgressorSource progrDialog = null)
        {
            msgOut = "";
            if (!System.IO.Directory.Exists(outpath))
                System.IO.Directory.CreateDirectory(outpath);


            //fc.
            /*
            using (RowCursor rowCursor = fc.Search(filter))
            {

                while (rowCursor.MoveNext())
                {
                    using (Row row = rowCursor.Current)
                    {
                        if (row is Feature ft)
                        {
                            var sh = ft.GetShape();
                            sh = ft.GetShape();
                            sh.
                        }
                    }
                }
            }*/


            //Selection selFeatures = featureClass.Select(filter, SelectionType.ObjectID, SelectionOption.Normal); 
            // make a value array of strings to be passed to ExecuteToolAsync
            /*
             System.Threading.CancellationTokenSource _cts1 = new System.Threading.CancellationTokenSource();
             cps.Status = "Guardando geometria a shp";

            string[] args1 = { pathLayerIn, outpath };

            IGPResult gpResult1 = Geoprocessing.ExecuteToolAsync("FeatureClassToFeatureClass", args1,
                            null, _cts1.Token,

                            (event_name, o) =>  // implement delegate and handle events
                            {
                                switch (event_name)
                                {
                                    case "OnValidate": 
                                        break;

                                    case "OnProgressMessage":
                                        {
                                            string msg = string.Format("{0}: {1}", new object[] { event_name, (string)o });
                                            //progrDialog.Message = (string)o;
                                            Debug.WriteLine(msg);

                                        }
                                        break;

                                    case "OnProgressPos":
                                        {
                                            string msg2 = string.Format("{0}: {1} %", new object[] { event_name, (int)o });
                                            Debug.WriteLine(msg2);

                                            break;
                                        }
                                }
                            }).Result;


             
            
           */
            cps.Status = "Guardando geometria a shp";

            string[] args = { pathLayerIn, outpath };
            // execute the tool
            IGPResult gpResult = Geoprocessing.ExecuteToolAsync("FeatureClassToShapefile_conversion", args,
                           null, cps.CancellationTokenSource.Token,

                           (event_name, o) =>  // implement delegate and handle events
                           {
                               switch (event_name)
                               {
                                   case "OnValidate": // stop execute if any warnings
                                                      //if ((o as IGPMessage[]).Any(it => it.Type == GPMessageType.Warning))
                                                      //_cts.Cancel();
                                       break;

                                   case "OnProgressMessage":
                                       {
                                           string msg = string.Format("{0}: {1}", new object[] { event_name, (string)o });
                                           //progrDialog.Message = (string)o;
                                           Debug.WriteLine(msg);

                                           ;
                                           //System.Windows.MessageBox.Show(msg);
                                           //_cts.Cancel();
                                       }
                                       break;

                                   case "OnProgressPos":
                                       {
                                           string msg2 = string.Format("{0}: {1} %", new object[] { event_name, (int)o });
                                           Debug.WriteLine(msg2);
                                           var av = (int)0;
                                           if (av > 0)
                                           {
                                               cps.Value = (uint)(80 * av * 0.2);
                                           }
                                           // if ((int)o < 0)
                                           //System.Windows.MessageBox.Show(msg2);
                                           //_cts.Cancel();
                                           break;
                                       }
                               }
                           }).Result;
            cps.Status = "Finalizando exportacion";
            //renombrado de ficheros:
            if (!RenameSHP(outpath, pathLayerIn, nameShp))
            {
                msgOut = "Error al exportar a shp.";
                return false;

            }

            /*

            //filtra entidades---
            var pn = string.Format("{0}{1}.{2}", pathLayerIn, nameShp);
            FeatureClass fc = HelperGdb.GetFtClass(pn);
            var f = new ArcGIS.Core.Data.QueryFilter();
            //f.
            fc.DeleteRows*/
            return true;
        }

        /**
         * Comprueba si un punto está contenido en un polígono
         */
        public static Respuesta<bool> IsPtoInGeom(Coordinate2D pto, ArcGIS.Core.Geometry.Geometry geom)
        {
            Respuesta<bool> resp = new Respuesta<bool>();
            try
            {
                //ArcGIS.Core.Geometry.Geometry geom_convexhull = GeometryEngine.Instance.ConvexHull(geom);
                //ArcGIS.Core.Geometry.Polygon poli = (ArcGIS.Core.Geometry.Polygon)geom_convexhull;
                ArcGIS.Core.Geometry.Polygon poli = (ArcGIS.Core.Geometry.Polygon)geom;
                bool contains = GeometryEngine.Instance.Contains(poli, pto.ToMapPoint());
                resp.Value = contains;
                return resp;
            }
            catch
            {
                resp.Value = false;
                resp.Error.Add("Error al comprobar si punto está contenido en polígono");
                return resp;
            }
        }
        /**
         * Amplía la geometría para añadirele buffer
         */
        public static Respuesta<ArcGIS.Core.Geometry.Geometry> BufferGeom(ArcGIS.Core.Geometry.Geometry geom, double buffer)
        {
            Respuesta<ArcGIS.Core.Geometry.Geometry> resp = new Respuesta<ArcGIS.Core.Geometry.Geometry>();
            ArcGIS.Core.Geometry.Geometry geomBuff = null;
            try
            {
                geomBuff = GeometryEngine.Instance.Buffer(geom, buffer);
            }
            catch
            {

            }
            resp.Value = geomBuff;
            return resp;
        }

        /**Amplía la geometría para que incluya el punto
         */
        public static Respuesta<ArcGIS.Core.Geometry.Geometry> AddPtoInGeom(Coordinate2D pto, ArcGIS.Core.Geometry.Geometry geom, int buff=100)
        {
            Respuesta<ArcGIS.Core.Geometry.Geometry> resp = new Respuesta<ArcGIS.Core.Geometry.Geometry>();
            double buffer = 0;//m de distancia desde la instalación
            bool repite = true;
            ArcGIS.Core.Geometry.Geometry geom_pto, geom_sal;
            try
            {
                /*do
                {
                    buffer += buff;
                    geom_pto = GeometryEngine.Instance.Buffer(pto.ToMapPoint(), buffer);
                    if (geom_pto == null || geom_pto.IsEmpty)
                    {
                        resp.Value = null;
                        resp.Error.Add("Error al bufferear punto para incluirlo en polígono");
                        return resp;
                    }
                    //hace la convex hull, por si no fueran conexas las zonas
                    repite = GeometryEngine.Instance.Disjoint(geom_pto, geom);
                    /*if (geom_aux == null || geom_aux.IsEmpty)
                    {
                        ArcGIS.Core.Geometry.Envelope env1 = geom_sal.Extent;
                        ArcGIS.Core.Geometry.Envelope env2 = geom_pto.Extent;
                        ArcGIS.Core.Geometry.Envelope env3 = env1.Union(env2);
                        geom_sal = (ArcGIS.Core.Geometry.Geometry) env3;
                        //geom_sal = GeometryEngine.Instance.Envelope(geom_sal);
                        if (geom_sal == null || geom_sal.IsEmpty)
                        {
                            resp.Value = null;
                            resp.Error.Add("Error al hacer envolvente en unir punto al polígono");
                            return resp;
                        }
                    } 
                }
                while (repite);*/
                buffer = buff;
                geom_pto = GeometryEngine.Instance.Buffer(pto.ToMapPoint(), buffer);
                if (geom_pto == null || geom_pto.IsEmpty)
                {
                    resp.Value = null;
                    resp.Error.Add("Error al bufferear punto para incluirlo en polígono");
                    return resp;
                }
                geom_sal = GeometryEngine.Instance.Union(geom_pto, geom);
                geom_sal = GeometryEngine.Instance.ConvexHull(geom_sal);
                if (geom_sal == null || geom_sal.IsEmpty)
                {
                    resp.Value = null;
                    resp.Error.Add("Error al unir punto a polígono");
                    return resp;
                }
                resp.Value = geom_sal;
                return resp;
            }
            catch
            {
                resp.Value = null;
                resp.Error.Add("Error al incluir punto en polígono");
                return resp;
            }
        }
        /*
         * Dada una lista de ids de elemento, crea una consulta en la que se llama a todos esos elementos
         * campo IN (id1, id2, id3..)
         */
        public static string GetWhereClauseFromIds(List<long> ids, string ObjectId = "OBJECTID")
        {
            if (ids.Count <= 0)
                return string.Empty;

            //string ids_str = "("+;
            return ObjectId+" IN (" + String.Join(",", ids) + ")";
            /*
            bool first = true;
            foreach (long i in ids)
            {
                if (first)
                {
                    ids_str += i.ToString();
                    first = false;
                }
                else
                    ids_str += "," + i.ToString();
            }
            //el último
            ids_str += ")";*/
            //string whereClause = ObjectId + " IN " + ids_str; //Cambiar id a otro sitio
            //return whereClause;
            /*string consulta, orstr;

            consulta = string.Empty;            
            orstr = string.Empty;
            var n = ids.Count;

            for (int i = 0; i < n; i++)
            {
                consulta = consulta + orstr + "(" + RecogidaDef.campos.cons_id + "=" + ids.ElementAt(i) + ")";
                if (String.IsNullOrEmpty(orstr))
                    orstr = " OR ";
            }
            if (consulta == "()")
                consulta = string.Empty;

            return consulta;*/
        }

        /**
         * Elimina las filas indicadas del shp
         **/
        public static bool RemoveRowsFromShp(FeatureClass fc, List<long> quita)
        {
            //Crea la consulta
            var consulta = GetWhereClauseFromIds(quita, fc.GetDefinition().GetObjectIDField());

            if (String.IsNullOrEmpty(consulta))
                return false;

            string message = String.Empty;
            bool deletionResult = false;

            var task = ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
            {

                EditOperation editOperation = new EditOperation();
                editOperation.Callback(context =>
                {
                    ArcGIS.Core.Data.QueryFilter openCutFilter = new ArcGIS.Core.Data.QueryFilter { WhereClause = consulta };

                    using (RowCursor rowCursor = fc.Search(openCutFilter, false))
                    {
                        while (rowCursor.MoveNext())
                        {
                            using (Row row = rowCursor.Current)
                            {
                                // In order to update the Map and/or the attribute table. Has to be called before the delete.
                                context.Invalidate(row);

                                row.Delete();
                            }
                        }
                    }
                }, fc);

                try
                {
                    deletionResult = editOperation.Execute();
                    if (!deletionResult)
                    {
                        message = editOperation.ErrorMessage;
                    }
                }
                catch (GeodatabaseException exObj)
                {
                    message = exObj.Message;
                }

            });
            task.Wait();

            if (!string.IsNullOrEmpty(message))
            {
                OutStr = message;
                return false;
            }
            else
                return true;
            /*FeatureClass fc = HelperGdb.GetFtClassFromShp(shp_path).Result;
            if (fc == null)
                return false;

            //Crea la consulta
            var consulta = GetWhereClauseFromIds(quita);
            if (String.IsNullOrEmpty(consulta))
                return false;

           string message = String.Empty;
            bool deletionResult = false;

            ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() => {
                string shpname = System.IO.Path.GetFileNameWithoutExtension(shp_path);
                var shapeFileConnPath = new FileSystemConnectionPath(new Uri(System.IO.Path.GetDirectoryName(shp_path)), 
                    FileSystemDatastoreType.Shapefile);
                var shapefile = new FileSystemDatastore(shapeFileConnPath);
                using (Table table = shapefile.OpenDataset<Table>(shp_path))
                {
                    EditOperation editOperation = new EditOperation();
                    editOperation.Callback(context =>
                    {
                        ArcGIS.Core.Data.QueryFilter openCutFilter = new ArcGIS.Core.Data.QueryFilter { WhereClause = consulta };

                        using (RowCursor rowCursor = table.Search(openCutFilter, false))
                        {
                            while (rowCursor.MoveNext())
                            {
                                using (Row row = rowCursor.Current)
                                {
                                    // In order to update the Map and/or the attribute table. Has to be called before the delete.
                                    context.Invalidate(row);

                                    row.Delete();
                                }
                            }
                        }
                    }, table);

                    try
                    {
                        deletionResult = editOperation.Execute();
                        if (!deletionResult)
                        {
                            message = editOperation.ErrorMessage;
                        }
                    }
                    catch (GeodatabaseException exObj)
                    {
                        message = exObj.Message;
                    }
                }
            });

            if (!string.IsNullOrEmpty(message))
            {
                OutStr = message;
                return false;
            }
            else
                return true;
                */
        }

        /**
         * Recorre los ámbitos lineales del shp viendo qué longitud tienen dentro de la geometría, y si
         * es menos de un porcentaje, lo quitan del shp
         **/
        public static bool RemoveRowsGeom(string shp_path, ArcGIS.Core.Geometry.Geometry geom_zon, double porc)
        {
            FeatureClass fc = HelperGdb.GetFtClassFromShp(shp_path).Result;
            if (fc == null)
                return false;
            ArcGIS.Core.Geometry.Geometry geom = null;
            ArcGIS.Core.Geometry.Polyline line = null;
            Feature f = null;
            double longi_zon = 0, longi_tot = 0;
            List<long> quita = new List<long>();

            int j = 0;
            List<long> ids;
            try
            {
                //Obtiene los IDs
                ids = GetIds(fc, null).Result;
                //Recorre las features de la capa   
                RowCursor cursor = fc.Search();
                while (cursor.MoveNext())
                {
                    f = (Feature)cursor.Current;
                    geom = f.GetShape();
                    line = (ArcGIS.Core.Geometry.Polyline)geom;
                    longi_tot = line.Length;
                    geom = IntersectGeom(geom_zon, line, GeometryDimensionType.EsriGeometry1Dimension);//GeometryDimension.esriGeometry1Dimension);
                    line = (ArcGIS.Core.Geometry.Polyline)geom;
                    longi_zon = line.Length;//se consigue la longitud de ámbito (linea) que interseca con el nivel)
                    if ((longi_zon / longi_tot) < porc)
                    {
                        //quita esa línea
                        quita.Add(ids[j]);
                    }
                    j++;
                    Free(f);
                }
                Free(f);
                Free(cursor);

                //comprueba que no se haya quedado sin ámbitos
                if (quita.Count >= fc.GetCount())
                {
                    OutStr = "No quedan ámbitos que cumplan la geometría seleccionada.";
                    return false;
                }
                //Quita los ámbitos del shp
                if (quita.Count > 0)
                {
                    //borra las líneas que se han indicado
                    if (!RemoveRowsFromShp(fc, quita))
                        return false;
                }
                Free(fc);
                return true;

            }
            catch (Exception)
            {
                return false;
            }
            finally
            {
                HelperGdb.Free(fc);
                HelperGdb.Free(f);
            }

        }

        /**
         * Dado un nombre de capa mira si está abierta y la cierra
         */
        public static bool CloseLayer(string nombCapa)
        {
            try
            {
                var lyr = MapView.Active.Map.FindLayers(nombCapa).FirstOrDefault() as FeatureLayer;
                if (lyr!= null)
                      MapView.Active.Map.RemoveLayer(lyr);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /**
         * Cierra todas las capas abiertas
         */
        public static bool CloseAllLayers()
        {
            try
            {
                ReadOnlyObservableCollection<Layer> lyrs= MapView.Active.Map.Layers;                
                while(lyrs.Any())
                {
                    var fl = lyrs.FirstOrDefault();
                    if (fl != null)
                        MapView.Active.Map.RemoveLayer(fl);
                }
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /**
         * Saca diálogo para guardar un archivo 
         */
        public static string SaveFileDlg(string title_, string initloc_, string ext_, string filt_, BrowseProjectFilter brwsFilt = null)
        {
            //Display the filter in an Open Item dialog
            SaveItemDialog dlg = new SaveItemDialog
            {
                Title = title_,
                OverwritePrompt = true,
            };
            if (!string.IsNullOrEmpty(initloc_))
                dlg.InitialLocation = initloc_;
            else
                dlg.AlwaysUseInitialLocation = false;
            if (!string.IsNullOrEmpty(filt_))
                dlg.Filter = filt_;
            if (!string.IsNullOrEmpty(ext_))
                dlg.DefaultExt = ext_;
            if (brwsFilt != null)
            {
                brwsFilt.BrowsingFilesMode = true;
                dlg.BrowseFilter = brwsFilt;
            }
            try
            {
                bool? ok = dlg.ShowDialog();
                if ((ok ?? true) && dlg.FilePath.Length > 0)
                    return dlg.FilePath;
                else
                    return string.Empty;
            }
            catch(Exception ex)
            {
                return string.Empty;
            }
        }

        /**
         * Saca diálogo para elegir directorio
         */
        public static string FolderBrowseDlg(string title_, string initloc_)
        {
            var openFileDialog = new CommonOpenFileDialog();
            //{ IsFolderPicker = true };
            if(!String.IsNullOrEmpty(initloc_))
                openFileDialog.InitialDirectory = initloc_;
            openFileDialog.IsFolderPicker = true;
            //openFileDialog.InitialDirectory = initloc_;
            openFileDialog.Title = title_;
            //openFileDialog.Filter = "Folders";
            //if (openFileDialog.ShowDialog() == true)
            if (openFileDialog.ShowDialog() == CommonFileDialogResult.Ok)
                return openFileDialog.FileName;
            return null;
        }

        /**
         * Comprueva si existe un Dataset, con ruta completa
         */
        public static Task<bool> CheckDataset(string datasetName)
        {            
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<bool>)(() =>
            {
                if (string.IsNullOrEmpty(datasetName))
                    return false;
                try
                {
                    Geodatabase gdb = GetGdbSync(datasetName);
                    if (gdb == null)
                        return false;
                    var datname = new System.IO.DirectoryInfo(datasetName).Name;
                    FeatureDataset ftdst = gdb.OpenDataset<FeatureDataset>(datname);
                    ftdst.Dispose();
                    return true;
                }
                catch
                {
                    return false;
                }
            }));
        }

        /**
         * Comprueva si una GDB contiene un Dataset
         */
        public static Task<bool> CheckDataset(Geodatabase gdb, string datasetName)
        {

            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<bool>)(() =>
            {
                try
                {
                    FeatureDataset ftdst = gdb.OpenDataset<FeatureDataset>(datasetName);
                    ftdst.Dispose();
                    return true;
                }
                catch
                {
                    return false;
                }
            }));
        }

        /**
         * Comprueba si el dataset dado tiene la referencia espacial dada
         * Devuelve 0 si es la misma, 2 si no es la misma, 1 si ha dado error
         */
         public static Task<int> CheckSpatRefDataset(Geodatabase gdb, string datasetName, ArcGIS.Core.Geometry.SpatialReference spatref)
        {
            return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<int>)(() =>
            {
                try
                {
                    FeatureDatasetDefinition featureDatasetDefinition = gdb.GetDefinition<FeatureDatasetDefinition>(datasetName);
                    ArcGIS.Core.Geometry.SpatialReference entr = featureDatasetDefinition.GetSpatialReference();
                    if (entr.Wkid==spatref.Wkid)
                        return 0;
                    return 2;
                }
                catch (Exception ex)
                {
                    return 1;
                }
            }));
        }

        /**
         * Crea un FeatureDataset con el nombre dado y la spatialrefernece dada en la gdb dada
         * Devuelve 0 si no hay que crear nada, existe y coincide ref espac
         * 2 si existe el dataset pero con otra referencia espacial, así que se crea otro
         * 1 si da error
         * 4 si no existe así que se crea
         */
        public static Respuesta<int> CreateDataset(string gdbPath, string datasetName, ArcGIS.Core.Geometry.SpatialReference spatref, out string datasetNameOut)
        {
            Respuesta<int> res = new Respuesta<int> { Value = 1 };
            datasetNameOut = string.Empty;
            Geodatabase gdb = null;
            FeatureDatasetDefinition featureDatasetDefinition = null;
            try
            {
                gdb = GetGdb(gdbPath).Result;
                if (gdb == null)
                {
                    res.Value = 1;
                    res.Error.Add("Error al abrir la gdb " + gdbPath);
                    return res;
                }
                //comprueba si extiste ya el dataset
                bool repite = false;
                bool crea = false;
                int idat = 1;
                int r = 2;
                bool refspatdif = false;
                while(r==2)
                {                
                    var task1 = CheckDataset(gdb, datasetName);
                    task1.Wait();
                    if (task1.Result)
                    {
                        //comprueba si tiene la misma referencia espacial       
                        r = CheckSpatRefDataset(gdb, datasetName, spatref).Result;
                        if (r == 0)
                        {
                            res.Value = 0; //no hay nada que crear, existe y coincide la spatial ref                        
                            //return res;
                        }
                        else if (r == 2)
                        {
                            //existe ese nombre, pero con otra ref espacial
                            //crea un nuevo dataset y avisa                        
                            datasetName = string.Format("{0}_{1}",datasetName,idat);
                            refspatdif = true;
                            idat++;
                        }
                        else//r==1
                        {
                            //ha dado error al comprobar
                            res.Value = 1;
                            res.Error.Add("Errores al crear el Dataset " + datasetName);
                            //return res;
                        }
                    }
                    else
                        r = 3; //no existe, lo crea con ese nombre
                };
                datasetNameOut = datasetName;
                if (r!=3)
                    return res;
                //no existe, lo crea
                var task = ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<Respuesta<bool>>)(() =>
                {
                    Respuesta<bool> res2 = new Respuesta<bool> { Value = false };
                    SchemaBuilder schemaBuilder = null;
                    try
                    {
                        schemaBuilder = new SchemaBuilder(gdb);
                        // Create a FeatureDataset
                        FeatureDatasetDescription featureDatasetDescription = new FeatureDatasetDescription(datasetName, spatref);
                        schemaBuilder.Create(featureDatasetDescription);
                        // Build status
                        res2.Value = schemaBuilder.Build();
                        if (!res2.Value && schemaBuilder.ErrorMessages.Count > 0)
                            res2.Error.Add(schemaBuilder.ErrorMessages.FirstOrDefault());
                        return res2;
                    }
                    catch
                    {
                        if (schemaBuilder != null && schemaBuilder.ErrorMessages.Count > 0)
                            res2.Error.Add(schemaBuilder.ErrorMessages.FirstOrDefault());
                        else
                            res2.Error.Add("Error 1 al crear Dataset.");
                        return res2;
                    }
                }));
                task.Wait();
                if (task.Result.Value)//ha ido bien
                {
                    //avisa
                    if (refspatdif)
                        res.Value = 2;
                    else
                        res.Value = 4;
                    //actualiza la gdb
                    Refresh(gdbPath);
                }
                else// Build errors
                {
                    res.Value = 1;
                    res.Error.Add(task.Result.Error.FirstOrDefault());
                }
                return res;
            }
            catch (Exception ex)
            {
                res.Value = 1;
                res.Error.Add("Errores al crear el Dataset " + datasetName + " " + ex.Message);
                return res;
            }
            finally
            {
                Free(featureDatasetDefinition);
                Free(gdb);

            }
        }

        /**
         * Importa el shp en la gdb y el dataset, como una featureclass
         */
        public static Respuesta<bool> ImportShp(string nom_shp, string Gdb_dataset, string namefc)
        {
            var res = new Respuesta<bool> { Value = false };
            string[] args = { nom_shp, Gdb_dataset, namefc };
            // execute the tool           

            try
            {
                ArcGIS.Core.Geometry.SpatialReference spatref = GetSpatRef(nom_shp);
                var environments = Geoprocessing.MakeEnvironmentArray(outputCoordinateSystem: spatref);
                //CancelableProgressorSource cps = new CancelableProgressorSource();
                var _cts = new System.Threading.CancellationTokenSource();
                var completa = false;
                var gpres = Geoprocessing.ExecuteToolAsync("FeatureClassToFeatureClass_conversion", args, environments, _cts.Token, 
                               (event_name, o) =>  // implement delegate and handle events
                               {
                                   switch (event_name)
                                   {
                                       case "OnValidate": // stop execute if any warnings
                                                          //if ((o as IGPMessage[]).Any(it => it.Type == GPMessageType.Warning))
                                                          //_cts.Cancel();
                                           break;

                                       case "OnProgressMessage":
                                           {
                                               string msg = string.Format("{0}: {1}", new object[] { event_name, (string)o });
                                               Debug.WriteLine(msg); ;
                                           }
                                           break;

                                       case "OnProgressPos":
                                           {
                                               string msg2 = string.Format("{0}: {1} %", new object[] { event_name, (int)o });
                                               Debug.WriteLine(msg2);
                                               var av = (int)o;
                                               if ((int)o < 0)
                                               {
                                                   //completa = true;
                                                   //System.Windows.MessageBox.Show(msg2);
                                                  // _cts.Cancel();
                                                   
                                               }
                                               break;
                                           }
                                   }
                               });

                while (!gpres.IsCompleted && !gpres.IsCanceled && !gpres.IsFaulted)
                    Thread.Sleep(10);
                
                if (gpres.IsCanceled)
                {
                    return res;
                }
                if (gpres.IsFaulted)
                {
                    var gpResult = gpres.Result;
                    string msg;
                    if (gpResult.ErrorMessages != null)
                        msg = gpResult.ErrorMessages.First().Text;
                    else
                        msg = "Errores en la importación";
                    res.Error.Add("Error: " + msg);
                    return res;
                }
                /*
                _cts.Cancel();
                for (int ii = 0; ii < 1000; ii++)
                {
                    if (gpres.IsCompleted && gpres.IsCanceled && gpres.IsFaulted)
                        break;
                    Thread.Sleep(10);
                }
                try
                {
                    if (!gpres.IsCompleted && !gpres.IsCanceled && !gpres.IsFaulted)
                        gpres.Dispose();
                }
                catch (Exception e)
                {
                    string msg = "";
                    msg = e.Message;
                }*/
                res.Value = true;
                gpres.Dispose();
                //actualiza la gdb
                Refresh(System.IO.Path.GetDirectoryName(Gdb_dataset));
                return res;
            }
            catch (Exception ex)
            {
                res.Error.Add("Error: " + ex.Message);
                return res;
            }
        }

        /**
         * Borrar una feature class de un dataset
         * Devuelve -1 si da error al abrir la gdb, -2 si da error al abrir el ftclass (igual porque no existe)
         * y 0 si da error el proceso de borrar, y 1 si va todo bien
         */
        public static Respuesta<int> DeleteFeatureClassSync(string gdbPathDataset, string featureClassName)
        {
            var res = new Respuesta<int> { Value = 0 };

            Geodatabase gdb = GetGdbSync(gdbPathDataset);
            if (gdb == null)
            {
                res.Value = -1;
                return res;
            }

            FeatureClass ftclss = GetFtClassSync(gdbPathDataset + " \\"+ featureClassName);
            if (ftclss == null)
            {
                res.Value = -2;
                return res;
            }
            
            Respuesta<bool> resp = new Respuesta<bool> { Value = false };
            SchemaBuilder schemaBuilder = null;
            try
            {
                // Create a FeatureClassDescription object
                FeatureClassDescription featureClassDescription = new FeatureClassDescription(ftclss.GetDefinition());
                // Create a SchemaBuilder object
                schemaBuilder = new SchemaBuilder(gdb);
                // Add the deletion fo the feature class to our list of DDL tasks
                schemaBuilder.Delete(featureClassDescription);
                // Execute the DDL
                resp.Value = schemaBuilder.Build();
                if (!resp.Value && schemaBuilder.ErrorMessages.Count > 0)
                    resp.Error.Add(schemaBuilder.ErrorMessages.FirstOrDefault());                
            }
            catch
            {
                resp.Error.Add(schemaBuilder.ErrorMessages.FirstOrDefault());                
            }
            
            if (resp.Value)
                res.Value = 1;
            else
            {
                res.Error.Add(resp.Error.FirstOrDefault());
            }
            Free(gdb);
            Free(ftclss);
            return res;
        }

        /**
         * Borrar una feature class de un dataset
         */
        public static Respuesta<bool> DeleteFeatureClass(string gdbPathDataset, string featureClassName)
        {
            var res = new Respuesta<bool> { Value = false };
            
            Geodatabase gdb = GetGdb(gdbPathDataset).Result;
            if (gdb == null)
                return res;

            FeatureClass ftclss = GetFtClass(gdbPathDataset + " \\" + featureClassName);
            if (ftclss == null)
                return res;

            var task = ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run((Func<Respuesta<bool>>)(() =>
            {
                Respuesta<bool> resp = new Respuesta<bool> { Value = false };
                SchemaBuilder schemaBuilder = null;
                try
                {
                    // Create a FeatureClassDescription object
                    FeatureClassDescription featureClassDescription = new FeatureClassDescription(ftclss.GetDefinition());
                    // Create a SchemaBuilder object
                    schemaBuilder = new SchemaBuilder(gdb);
                    // Add the deletion fo the feature class to our list of DDL tasks
                    schemaBuilder.Delete(featureClassDescription);
                    // Execute the DDL
                    resp.Value = schemaBuilder.Build();
                    if (!resp.Value && schemaBuilder.ErrorMessages.Count > 0)
                        resp.Error.Add(schemaBuilder.ErrorMessages.FirstOrDefault());
                    return resp;
                }
                catch
                {
                    resp.Error.Add(schemaBuilder.ErrorMessages.FirstOrDefault());
                    return resp;
                }
            }));
            if (task.Result.Value)
                res.Value = true;
            else
            {
                res.Error.Add(task.Result.Error.FirstOrDefault());
            }
            Free(gdb);
            Free(ftclss);
            return res;
        }

        /**
         * Refresca la gdb después de añadir dataset y featureclass
         */
        public static void Refresh(string gdbPath)
        {
            Item gdb = null;
            try
            {
                gdb = ItemFactory.Instance.Create(gdbPath);
                if (gdb == null)
                    return;
                var contentItem = gdb;
                //Check if the MCT is required for Refresh()
                if (contentItem.IsMainThreadRequired)
                {
                    //QueuedTask.Run must be used if item.IsMainThreadRequired
                    //returns true
                    QueuedTask.Run(() => contentItem.Refresh());
                }
                else
                {
                    //if item.IsMainThreadRequired returns false, any
                    //thread can be used to invoke Refresh(), though
                    //BackgroundTask is preferred.
                    contentItem.Refresh();

                    //Or, via BackgroundTask
                    ArcGIS.Core.Threading.Tasks.BackgroundTask.Run(() =>
                        contentItem.Refresh(), ArcGIS.Core.Threading.Tasks.BackgroundProgressor.None);
                }
            }
            catch
            {

            }
        }

        /**
         * Abre en el mapa la capa seleccionada, con los parámetros seleccionados
         */
        public static Respuesta<bool> OpenLayerUniqueValue(string ftclasspath, string campValUniq, bool visible = true)
        {
            var res = new Respuesta<bool> { Value = false };
            var mapView = MapView.Active;
            int indexNumber = mapView.Map.Layers.Count;
            string ftclassname = System.IO.Path.GetFileNameWithoutExtension(ftclasspath);
            if (mapView != null)//Check if there is a map
            {
                try
                {
                    System.Uri ftclass_uri = new System.Uri(ftclasspath); //creating uri for the ftclass
                    //mira el tipo de geometría
                    GeometryType tipo = GetGeomType(ftclasspath);
                    if (tipo == GeometryType.Unknown)
                    {
                        res.Error.Add("Tipo de geometría desconocido en la capa a importar");
                        return res;
                    }
                    var task = QueuedTask.Run(() =>
                      {
                          Layer layer = null;
                          FeatureLayer selectedLayer = null;
                          try
                          {
                              layer = mapView.Map.FindLayers(ftclassname).FirstOrDefault();
                          }
                          catch
                          {
                              layer = null;
                          }
                          if (layer == null)
                          {
                              layer = LayerFactory.Instance.CreateLayer(ftclass_uri, mapView.Map, indexNumber, ftclassname);                              
                              //selectedLayer = mapView.GetSelectedLayers()[0] as FeatureLayer;
                          }
                          selectedLayer = layer as FeatureLayer;
                          //Do something with selected layer
                          SetSimpleRendererPoint(selectedLayer, campValUniq, tipo);
                          selectedLayer.SetVisibility(visible);
                      });
                    task.Wait();
                }
                catch
                {
                    res.Error.Add("Errores en creación de renderer para valor único");
                    return res;
                }
            }
            res.Value = true;
            return res;
        }
        /*
         * Crea el renderer para la capa, dado un campo en el que fijarse y una simbología
         */ 
        internal static void SetSimpleRendererPoint(FeatureLayer featureLayer, string field, GeometryType tipo)
        {
            if (featureLayer == null)
                return;
            //QueuedTask.Run(() =>
            //{
                try
                {
                List<String> fields = new List<string>();
                fields.Add(field);
                    UniqueValueRendererDefinition uniqueValueRendererDef;
                    CIMSymbolReference symbolTemplate=new CIMSymbolReference();
                    if (tipo == GeometryType.Point)//puntual
                    {
                        CIMPointSymbol pointSym = SymbolFactory.Instance.ConstructPointSymbol(
                                  ColorFactory.Instance.GreenRGB, 10.0, SimpleMarkerStyle.Circle);  //constructing a point symbol as a template symbol
                        symbolTemplate = pointSym.MakeSymbolReference();
                    }
                    else if(tipo== GeometryType.Polyline)//lineal
                    {
                        CIMLineSymbol lineSym = SymbolFactory.Instance.ConstructLineSymbol(ColorFactory.Instance.GreenRGB, 3);
                        symbolTemplate = lineSym.MakeSymbolReference();
                    }
                    //constructing renderer definition for unique value renderer
                    uniqueValueRendererDef = new UniqueValueRendererDefinition(fields, symbolTemplate);

                    //creating a unique value renderer                    
                    CIMUniqueValueRenderer uniqueValueRenderer = (CIMUniqueValueRenderer)featureLayer.CreateRenderer(uniqueValueRendererDef);

                    //setting the renderer to the feature layer
                    featureLayer.SetRenderer(uniqueValueRenderer);

                }
                catch (Exception ex)
                {

                }
            //});
        }
        public static GeometryType GetGeomType(string ftclassName)
        {
            FeatureClass ftcl = GetFtClass(ftclassName);
            GeometryType tipo = GeometryType.Unknown;
            if (ftcl == null)
                return tipo ;
            var task = QueuedTask.Run(() =>
            {
                tipo = ftcl.GetDefinition().GetShapeType();
            });
            task.Wait();
            Free(ftcl);
            return tipo;
        }

        /*
         * Renombra un campo de la feature class dada
         */ 
        public static bool RenameFieldSync(string fcname, string oldFieldName, string newFieldName, string newFieldAlias)
        {
            bool res = false;
            string[] args = { fcname, oldFieldName, newFieldName,newFieldAlias };
            // execute the tool   
            try
            {
                var gpres = Geoprocessing.ExecuteToolAsync("management.AlterField", args);
                while (!gpres.IsCompleted && !gpres.IsCanceled && !gpres.IsFaulted)
                    Thread.Sleep(10);
                if (!gpres.IsCompletedSuccessfully)
                {
                    var gpResult = gpres.Result;
                    string msg;
                    if (gpResult.ErrorMessages != null)
                        msg = gpResult.ErrorMessages.First().Text;
                    else
                        msg = "Errores al renombrar campo "+newFieldName;                    
                }
                else
                    res= true; //ha ido bien
                gpres.Dispose();
                return res;
            }
            catch
            {
            }

            return res;
        }
        public struct FieldToAdd
        {
            public string Name;
            public string Tipo; //LONG, TEXT, ...
            public string Alias;
            public int Length;

            public FieldToAdd(string nomb, string tip, string al, int len)
            {
                Name = nomb; //obligatorio
                Tipo = tip; //obligatorio
                Alias = al;
                Length = len;
            }
        }
        /*
         * Renombra un campo de la feature class dada
         */
        public static bool AddFieldsSync(string fcname, FieldToAdd[] fields)
        {
            bool res = false;            

            // set up the arguments
            string fields_str = "";
            int camps = 0;
            for(int i=0;i<fields.Length;i++)
            {
                if (CheckFieldSync(fcname, fields[i].Name))
                    continue;
                camps++;

                fields_str += fields[i].Name + " " + fields[i].Tipo + " ";
                if (!string.IsNullOrEmpty(fields[i].Alias))
                    fields_str += fields[i].Alias;
                else
                    fields_str += "#";

                fields_str += " ";
                if (fields[i].Length != 0)
                    fields_str += fields[i].Length;
                else
                    fields_str += "#";
                fields_str += " #"; //def value siempre #

                if (i < fields.Length - 1)
                    fields_str += ";";
            }
            if (camps == 0)
                return true;//no tiene que añadir ningún campo porque los tiene

            //var f1 = "field1 long field1 # #;field2 Text # 255 myDefaultValue"; //los # son opciones sin rellenar el orden es: Nombre Tipo Alias Longitud Valor Defecto

            var args = Geoprocessing.MakeValueArray(fcname, fields_str);

            // run the tool
            try
            {
                var gpres = Geoprocessing.ExecuteToolAsync("management.AddFields", args);
                while (!gpres.IsCompleted && !gpres.IsCanceled && !gpres.IsFaulted)
                    Thread.Sleep(10);
                if (gpres.IsFaulted)
                {
                    var gpResult = gpres.Result;
                    string msg;
                    if (gpResult.ErrorMessages != null)
                        msg = gpResult.ErrorMessages.First().Text;
                    else
                        msg = "Errores al añadir campos";
                }
                else
                    res = true; //ha ido bien
                gpres.Dispose();
                return res;
            }
            catch
            {
            }

            return res;
        }
        /*
         *  Añadir columna calculada
         */
        public static bool CalculateFieldsSync(string fcname, string field, string sqlquery)
        {
            bool res = false;

            var args = Geoprocessing.MakeValueArray(fcname, field, sqlquery, "ARCADE");

            // run the tool
            try
            {
                var gpres = Geoprocessing.ExecuteToolAsync("management.CalculateField", args);

                while (!gpres.IsCompleted && !gpres.IsCanceled && !gpres.IsFaulted)
                    Thread.Sleep(10);
                if (gpres.IsFaulted)
                {
                    var gpResult = gpres.Result;
                    string msg;
                    if (gpResult.ErrorMessages != null)
                        msg = gpResult.ErrorMessages.First().Text;
                    else
                        msg = "Errores al calcular campo "+ field;
                }
                else
                {
                    res = true; //ha ido bien
                }
                gpres.Dispose();
                return res;
            }
            catch
            {
            }

            return res;
        }
    }
}
