using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Windows.Forms;
using GMLC01HerramientasCad.Logica;

namespace GMLC01HerramientasCad.Logica
{
    /// <summary>
    /// Clase para transferir al módulo CAD la información necesaria del fichero XSD
    /// </summary>
    public class XSDInfo
    {
        private XmlDocument xmlDoc;
        private XmlNamespaceManager xmlNSM;

        private void CargarVersion()
        {
            XmlNode version = xmlDoc.SelectSingleNode("/xs:schema/xs:element[@name='metadata']/xs:annotation[@id='conf']/xs:appinfo/version", xmlNSM);
        
            if (version == null)
                throw new ExcepcionGMLC(Properties.Resources.NoSeEncontroLaDefinicionDeVersionEnElFicheroXSD);

            this.Version = version.InnerText;
        }

           

        private void CargarClave()
        {
            XmlNode clave = xmlDoc.SelectSingleNode("/xs:schema/xs:element[@name='metadata']/xs:annotation[@id='conf']/xs:appinfo/clave", xmlNSM);

            if (clave == null)
                throw new ExcepcionGMLC(Properties.Resources.NoSeEncontroLaDefinicionDeClaveEnElFicheroXSD);

            this.Clave = clave.InnerText;
        }

        private void CargarIdiomas()
        {
            XPathNavigator nav = xmlDoc.CreateNavigator();
            XPathNodeIterator iterator = nav.Select("/xs:schema/xs:annotation[@id='idiomas']/xs:appinfo/idioma", xmlNSM);

            if (iterator == null || iterator.Count == 0)
                throw new ExcepcionGMLC(Properties.Resources.NoSeEncontroLaDefinicionDeIdiomasEnElFicheroXSD);

            this.Idiomas = new ListaIdiomas();
            while (iterator.MoveNext())
                this.Idiomas.Add(new Idioma(iterator.Current.GetAttribute("codigo",""),
                                            iterator.Current.GetAttribute("nombre","")));

            XmlNode idiomaDefault = xmlDoc.SelectSingleNode("/xs:schema/xs:annotation[@id='idiomas']/xs:appinfo/idioma[@default='true']", xmlNSM);

            if (idiomaDefault == null)
                ModComun.Idioma = this.Idiomas[0].Codigo;
            else
                ModComun.Idioma = idiomaDefault.Attributes["codigo", ""].Value;
        }

        private void CargarMetadatos()
        {
            Metadata = new Entidad("Metadatos", new Dictionary<string, string>(), new Dictionary<string, string>(),
                                        TiposGeometria.Punto, null, "", null,
                                        new ListaAtributos());

            XPathNodeIterator iterator = xmlDoc.CreateNavigator().Select("/xs:schema/xs:element[@name='metadata']/xs:complexType/xs:sequence/xs:element", xmlNSM);

            if (iterator == null || iterator.Count == 0)
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.NoExisteLaDefinicionDeLaEntidad, "Metadatos"));

            while (iterator.MoveNext())
            {
                XPathNavigator xpAtr = iterator.Current;

                if (xpAtr.GetAttribute("type", "").StartsWith("gml:", StringComparison.Ordinal))
                    continue;

                Atributo unAtributo = new Atributo(xpAtr.GetAttribute("name", ""), Metadata, new Dictionary<string, string>(),
                                        new Dictionary<string, string>(), ObtenerTipoEdicion(xpAtr.GetAttribute("type", "")), null);

                foreach (Idioma idioma in this.Idiomas)
                {
                    unAtributo.Nombres.Add(idioma.Codigo,
                                xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/name/" + idioma.Codigo, xmlNSM).Value);
                    unAtributo.Descripciones.Add(idioma.Codigo,
                                xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/descripcion/" + idioma.Codigo, xmlNSM).Value);
                }

                String nomDominio = xpAtr.GetAttribute("type", "");
                // VAlores nulos
                String nillable = xpAtr.GetAttribute("nillable", "");
                if (nillable != null && (String.Compare(nillable, "true", StringComparison.CurrentCulture) == 0))
                {
                    unAtributo.Nillable = true;
                }
                // Unidades
                if (xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/uom", xmlNSM) != null)
                {
                    String uom = xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/uom", xmlNSM).Value;
                    if (uom != null && uom.Length > 0)
                    {
                        unAtributo.Unidades = uom;
                    }
                }
                if (nomDominio.StartsWith("app:", StringComparison.Ordinal))
                    nomDominio = nomDominio.Replace("app:", "");
                else
                    nomDominio = null;

                unAtributo.NombreDominio = nomDominio;

                try
                {
                    Metadata.Atributos.Add(unAtributo);
                }
                catch (System.ArgumentException)
                {
                    //MessageBox.Show("El atributo <" + unAtributo.Entidad.Codigo + "." + unAtributo.Codigo + "> repetido.", "GMLC", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, 0);
                }
            }

        }     

        private void CargarDominio(XPathNavigator nav)
        {
            XPathNodeIterator iterator = nav.Select("xs:restriction/xs:enumeration", xmlNSM);

            if (iterator == null || iterator.Count == 0)
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.ElDominioEnElFicheroXSDNoTieneValores, nav.GetAttribute("name", "")));

            Dominio unDominio = new Dominio(nav.GetAttribute("name", ""));

            ValorDominio unValorDominio = new ValorDominio ("", new Dictionary<string,string>(), new Dictionary<string,string>());
            foreach (Idioma idioma in this.Idiomas)
            {
                unValorDominio.Textos.Add (idioma.Codigo, "");
                unValorDominio.Descripciones.Add(idioma.Codigo, "");
            }
            unDominio.Valores.Add(unValorDominio);

            while (iterator.MoveNext())
            {
                XPathNavigator currVal = iterator.Current;
                unValorDominio = new ValorDominio(currVal.GetAttribute("value", ""), new Dictionary<string, string>(), new Dictionary<string, string>());

                foreach (Idioma idioma in this.Idiomas)
                {
                    unValorDominio.Textos.Add(idioma.Codigo,
                                currVal.SelectSingleNode("xs:annotation/xs:appinfo/name/" + idioma.Codigo, xmlNSM).Value);
                    unValorDominio.Descripciones.Add(idioma.Codigo,
                                currVal.SelectSingleNode("xs:annotation/xs:appinfo/descripcion/" + idioma.Codigo, xmlNSM).Value);
                }

                unDominio.Valores.Add(unValorDominio);
            }
            this.Dominios.Add(unDominio);
        }

        private void CargarDominios()
        {
            XPathNavigator nav = xmlDoc.CreateNavigator();
            XPathNodeIterator iterator = nav.Select("/xs:schema/xs:simpleType", xmlNSM);

            if (iterator == null || iterator.Count == 0)
                return;

            this.Dominios = new ListaDominios();

            while (iterator.MoveNext())
                CargarDominio(iterator.Current);
        }

        private TiposGeometria ObtenerTipoGeometria(XPathNavigator xpathEntidad)
        {
            XPathNavigator atrGeom = xpathEntidad.SelectSingleNode("xs:complexContent/xs:extension/xs:sequence/xs:element[@name='the_geom']", xmlNSM);

            if (atrGeom == null)
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.LaEntidadNoTieneAtributoDeGeometria, xpathEntidad.GetAttribute("name", "")));

            String tipo = atrGeom.GetAttribute("type", "");

            if (atrGeom == null)
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.LaEntidadNoTieneTipoDeGeometria, xpathEntidad.GetAttribute("name", "")));

            if (tipo == "gml:PointPropertyType")
                return TiposGeometria.Punto;
            else if (tipo == "gml:LineStringPropertyType")
                return TiposGeometria.Linea;
            else if (tipo == "gml:PolygonPropertyType")
                return TiposGeometria.Area;
            else
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.LaEntidadTieneUnTipoDeGeometriaInvalido, xpathEntidad.GetAttribute("name", "")));
        }

        private Collection<SubTipo> ObtenerSubtipos(XPathNavigator entDef, String campoSubtipo, Entidad entidad)
        {
            String nomDominio = entDef.SelectSingleNode("xs:complexContent/xs:extension/xs:sequence/xs:element[@name='" + campoSubtipo + "']", xmlNSM).GetAttribute("type", "");
        
            if (nomDominio == null)
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.NoSePuedenDeterminarLosValoresDeLosSubtiposDeLaEntidad, entDef.GetAttribute("name", "")));

            nomDominio = nomDominio.Replace("app:", "");
            
            if (!this.Dominios.Contains(nomDominio))
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.NoSeEncuentraElDominioDeLaEntidad, nomDominio, entDef.GetAttribute("name", "")));

            Collection<SubTipo> subtipos = new Collection<SubTipo>();

            foreach (ValorDominio valor in this.Dominios[nomDominio].Valores)
                if (!String.IsNullOrEmpty(valor.Codigo))
                    subtipos.Add(new SubTipo(valor.Codigo, valor.Textos, valor.Descripciones, entidad));
                else
                    continue;

            return subtipos;
        }

        private static TiposEdicion ObtenerTipoEdicion(String tipo)
        {
            if (tipo.StartsWith("app:", StringComparison.Ordinal))
                return TiposEdicion.Combo;
            else if (tipo == "xs:string")
                return TiposEdicion.Texto;
            else if (tipo == "xs:double" || tipo == "gml:MeasureType")
                return TiposEdicion.Doble;
            else if (tipo == "xs:integer")
                return TiposEdicion.Entero;
            else
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.TipoNoSoportado, tipo));
        }

        private void CargaAtributos (XPathNavigator entDef, Entidad entidad)
        {
            XPathNodeIterator iterator = entDef.Select("xs:complexContent/xs:extension/xs:sequence/xs:element", xmlNSM);

            if (iterator == null)
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.NoSeEncontraronAtributosParaLaEntidad, entidad.Nombre));

            while (iterator.MoveNext())
            {
                XPathNavigator xpAtr = iterator.Current;

                if (xpAtr.GetAttribute("type", "").StartsWith("gml:", StringComparison.Ordinal) && !xpAtr.GetAttribute("type", "").Equals("gml:MeasureType", StringComparison.OrdinalIgnoreCase))
                    continue;

                Atributo unAtributo = new Atributo(xpAtr.GetAttribute("name", ""), entidad, new Dictionary<string, string>(),
                                        new Dictionary<string, string>(), ObtenerTipoEdicion(xpAtr.GetAttribute("type", "")), null);

                foreach (Idioma idioma in this.Idiomas)
                {
                    unAtributo.Nombres.Add(idioma.Codigo,
                                xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/name/" + idioma.Codigo, xmlNSM).Value);
                    unAtributo.Descripciones.Add(idioma.Codigo,
                                xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/descripcion/" + idioma.Codigo, xmlNSM).Value);
                }

                String nomDominio = xpAtr.GetAttribute("type", "");
                // VAlores nulos
                String nillable = xpAtr.GetAttribute("nillable", "");
                if (nillable != null && (String.Compare(nillable, "true", StringComparison.CurrentCulture) == 0)) {
                    unAtributo.Nillable = true;
                }
                // Unidades
                if(xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/uom", xmlNSM)!=null){
                    String uom = xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/uom", xmlNSM).Value;
                    if (uom != null && uom.Length > 0) {
                        unAtributo.Unidades = uom;
                    }
                }
                // Action
                XPathNavigator xpn = xpAtr.SelectSingleNode("xs:annotation/xs:appinfo/action", xmlNSM);
                if (xpn != null)
                {
                    String actiom = xpn.Value;
                    if (!string.IsNullOrEmpty(actiom))
                    {
                        unAtributo.Action = actiom;
                    }
                }
                if (nomDominio.StartsWith("app:", StringComparison.Ordinal))
                    nomDominio = nomDominio.Replace("app:", "");
                else
                    nomDominio = null;

                unAtributo.NombreDominio = nomDominio;

                try
                {
                    entidad.Atributos.Add(unAtributo);
                }
                catch (System.ArgumentException)
                {
                    //MessageBox.Show("El atributo <" + unAtributo.Entidad.Codigo + "." + unAtributo.Codigo + "> repetido.", "GMLC", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, 0);
                }
            }
        }

        private void CargarEntidad(XPathNavigator nav, Categoria categoria)
        {
            XPathNavigator entDef = nav.SelectSingleNode("/xs:schema/xs:complexType[@name='" + nav.Name + "_Type']", xmlNSM);
            
            if (entDef == null)
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.NoExisteLaDefinicionDeLaEntidad, nav.Name));

            String campoSubtipo = nav.GetAttribute("subtipo", "");

            Entidad unaEntidad = new Entidad(nav.Name, new Dictionary<string, string>(), new Dictionary<string, string>(),
                                    ObtenerTipoGeometria(entDef), categoria, campoSubtipo, null,
                                    new ListaAtributos());

            foreach (Idioma idioma in this.Idiomas)
            {
                unaEntidad.Nombres.Add(idioma.Codigo,
                            entDef.SelectSingleNode("xs:annotation/xs:appinfo/name/" + idioma.Codigo, xmlNSM).Value);
                unaEntidad.Descripciones.Add(idioma.Codigo,
                            entDef.SelectSingleNode("xs:annotation/xs:appinfo/descripcion/" + idioma.Codigo, xmlNSM).Value);
            }

            if (!String.IsNullOrEmpty(campoSubtipo))
                unaEntidad.SubTipos = ObtenerSubtipos(entDef, campoSubtipo, unaEntidad);

            CargaAtributos(entDef, unaEntidad);

            this.Entidades.Add(unaEntidad);
            categoria.Entidades.Add(unaEntidad);
        }

        private void CargarCategoria(XPathNavigator nav)
        {
            Categoria unaCategoria = new Categoria (nav.Name,
                            new Dictionary<string,string>(),new Dictionary<string,string>(),new Collection<Entidad>());

            foreach (Idioma idioma in this.Idiomas)
            {
                unaCategoria.Nombres.Add(idioma.Codigo,
                            nav.SelectSingleNode("name/" + idioma.Codigo, xmlNSM).Value);
                unaCategoria.Descripciones.Add(idioma.Codigo,
                            nav.SelectSingleNode("descripcion/" + idioma.Codigo, xmlNSM).Value);
            }

            XPathNodeIterator iterator = nav.Select("clases/*", xmlNSM);

            if (iterator == null || iterator.Count == 0)
                throw new ExcepcionGMLC(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.NoHayDefinidasEntidadesParaLaCategoria, nav.Name));

            while (iterator.MoveNext())
                CargarEntidad(iterator.Current, unaCategoria);

            this.Categorias.Add(unaCategoria);
        }

        private void CargarCategorias()
        {
            XPathNodeIterator iterator = xmlDoc.CreateNavigator().Select("/xs:schema/xs:annotation[@id='grupos']/xs:appinfo/*", xmlNSM);

            if (iterator == null || iterator.Count == 0)
                throw new ExcepcionGMLC(Properties.Resources.NoHayDefinidasCategoriasEnElFicheroXSD);

            this.Categorias = new ListaCategorias();
            this.Entidades = new ListaEntidades();

            while (iterator.MoveNext())
                CargarCategoria(iterator.Current);
        }

        private void CargaXSD(String xsdFile)
        {
            xmlDoc = new XmlDocument();
            XmlTextReader reader = new XmlTextReader(xsdFile);
            xmlNSM = new XmlNamespaceManager(reader.NameTable);
            xmlNSM.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema");
            xmlDoc.Load(xsdFile);

            CargarVersion();
            CargarClave();
            CargarIdiomas();
            CargarMetadatos();
            CargarDominios();
            CargarCategorias();
        }

        /// <summary>
        /// Constructor de la clase
        /// </summary>
        /// <param name="xsdFile">Fichero XSD a usar</param>
        /// <param name="versionCAD">Versión proveniente del fichero CAD a contrastar contra la versión del XSD</param>
        public XSDInfo(String xsdFile, String versionCAD)
        {
            try
            {
                CargaXSD(xsdFile);

                if (versionCAD != null && versionCAD != this.Version)
                    switch (MessageBox.Show(System.String.Format(CultureInfo.CurrentCulture, Properties.Resources.LaVersionDelFicheroCADDifiere, versionCAD, this.Version), "GMLC", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1, 0))
                    {
                        case DialogResult.Yes:
                            break;
                        case DialogResult.No:
                            throw new ExcepcionGMLC(Properties.Resources.VersionesDiferentes);
                    }
            }
            catch (ExcepcionGMLC)
            {
                throw;
            }
            catch (Exception e)
            {
                Log.RegisterError(e);
                throw new ExcepcionGMLC(e.Message, e);
            }
        }

        /// <summary>
        /// Versión del fichero XSD
        /// </summary>
        public string Version { get; internal set; }

        /// <summary>
        /// Atributo clave en el modelo
        /// </summary>
        public string Clave { get; internal set; }

        /// <summary>
        /// Lista de categorías presentes en el fichero XSD
        /// </summary>
        public Collection<Categoria> Categorias { get; internal set; }

        /// <summary>
        /// Lista de entidades presentes en el fichero XSD
        /// </summary>
        public ListaEntidades Entidades { get; internal set; }

        /// <summary>
        /// Lista de idiomas usados en el XSD
        /// </summary>
        public Collection<Idioma> Idiomas { get; internal set; }

        /// <summary>
        /// Lista de Dominios presentes en el XSD
        /// </summary>
        public ListaDominios Dominios { get; internal set; }

        public Entidad Metadata { get; internal set; }


    }
}
