using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Runtime.Serialization;

using GMLC01HerramientasCad.Logica;

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
//using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;

namespace GMLC01HerramientasCad.Acad
{
    class GML : IGML
    {

        #region "Exportado de entidades"
        public Collection<Elemento> ObtenerElementos()
        {
            Collection<Elemento> lista = null;

            Database db = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Database;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                //seleccionar todas las entidades del dibujo (pertenecientes al modelo).
                //ObjectId[] oIds = SeleccionObjetosAutoCAD.GetEntitiesWithXData("*");
                ObjectId[] oIdsH = SeleccionObjetosAutoCAD.GetHatchesWithXData(); 
                ObjectId[] oIdsL = SeleccionObjetosAutoCAD.GetLinesWithXData(); 
                ObjectId[] oIdsP = SeleccionObjetosAutoCAD.GetPointsWithXData();

                int numHs = 0;
                int numLs = 0;
                int numPs = 0;

                if (oIdsH != null) numHs += oIdsH.GetLength(0);
                if (oIdsL != null) numLs += oIdsL.GetLength(0);
                if (oIdsP != null) numPs += oIdsP.GetLength(0);

                // Si no hay entidades, se lanza una expeción.
                if ((numHs + numLs + numPs) == 0) throw new ExcepcionGMLC();

                ObjectId[] oIds = new ObjectId[numHs + numLs + numPs];

                if (oIdsH != null)
                {
                    for (int i = 0; i < oIdsH.GetLength(0); i++)
                    {
                        oIds[i] = oIdsH[i];
                    }
                }
                if (oIdsL != null)
                {
                    for (int i = 0; i < oIdsL.GetLength(0); i++)
                    {
                        oIds[numHs + i] = oIdsL[i];
                    }
                }
                if (oIdsP != null)
                {
                    for (int i = 0; i < oIdsP.GetLength(0); i++)
                    {
                        oIds[numHs + numLs + i] = oIdsP[i];
                    }
                }
                foreach (ObjectId oid in oIds)
                {
                    Entidad ente = null;
                    ente = XData.GetXData(oid, "*");
                    if (ente != null)
                    {
                        Elemento elemento = new Elemento();
                        //Asignación del tipo de entidad.
                        elemento.CodigoEntidad = ente.Codigo;

                        //Recuperación de las coordenadas.
                        Entity entid = (Entity)oid.GetObject(OpenMode.ForRead);
                        switch (entid.GetRXClass().DxfName)
                        {
                            case "LINE":
                                {
                                    //Entidades lineales: "LINE", "LWPOLYLINE".
                                    Line lin = (Line)entid;
                                    elemento.Coordenadas.Add(new Coordenada(lin.StartPoint.X, lin.StartPoint.Y, lin.StartPoint.Z));
                                    elemento.Coordenadas.Add(new Coordenada(lin.EndPoint.X, lin.EndPoint.Y, lin.StartPoint.Z));
                                    break;
                                }
                            case "LWPOLYLINE":
                                //Entidades lineales: "LINE", "LWPOLYLINE".
                                Polyline poli2 = (Polyline)entid;
                                for (int i = 0; i < poli2.NumberOfVertices; i++)
                                {
                                    elemento.Coordenadas.Add(new Coordenada(poli2.GetPoint2dAt(i).X, poli2.GetPoint2dAt(i).Y, 0));
                                }
                                break;
                            case "POLYLINE":
                                {

                                    if (entid.GetRXClass().Name == "AcDb3dPolyline")
                                    {
                                        //Entidades lineales: "LINE", "LWPOLYLINE".
                                        Polyline3d poli = (Polyline3d)entid;
                                        // Use foreach to get each contained vertex
                                        foreach (ObjectId vId in poli)
                                        {
                                            PolylineVertex3d v3d = (PolylineVertex3d)tr.GetObject(vId, OpenMode.ForRead);
                                            elemento.Coordenadas.Add(new Coordenada(v3d.Position.X, v3d.Position.Y, v3d.Position.Z));
                                        }
                                    }
                                    else if (entid.GetRXClass().Name == "AcDb2dPolyline")
                                    {
                                        //Entidades lineales: "LINE", "LWPOLYLINE".
                                        Polyline2d poli = (Polyline2d)entid;
                                        /*for (int i = 0; i < poli..NumberOfVertices; i++)
                                        {
                                            elemento.coordenadas.Add(new Coordenada(poli.GetPoint2dAt(i).x, poli.GetPoint2dAt(i).y, 0));
                                        }*/
                                        // Use foreach to get each contained vertex
                                        foreach (ObjectId vId in poli)
                                        {
                                            Vertex2d v2d = (Vertex2d)tr.GetObject(vId, OpenMode.ForRead);
                                            elemento.Coordenadas.Add(new Coordenada(v2d.Position.X, v2d.Position.Y, v2d.Position.Z));
                                        }

                                    }
                                    break;
                                }
                            case "POINT":
                                {
                                    //Entidades puntuales: "POINT".
                                    DBPoint punto = (DBPoint)entid;
                                    elemento.Coordenadas.Add(new Coordenada(punto.Position.X, punto.Position.Y, punto.Position.Z));
                                    break;
                                }
                            case "INSERT":
                                {
                                    //Entidades puntuales: "INSERT".
                                    BlockReference bloque = (BlockReference)entid;
                                    elemento.Coordenadas.Add(new Coordenada(bloque.Position.X, bloque.Position.Y, bloque.Position.Z));
                                    break;
                                }
                            case "HATCH":
                                {
                                    //Poligonos: "HATCH".
                                    Hatch htch = (Hatch)entid;
                                    double height = htch.Elevation;
                                    if (htch.GetLoopAt(0).Curves != null)
                                    {
                                        for (int i = 0; i < htch.GetLoopAt(0).Curves.Count; i++)
                                        {
                                            elemento.Coordenadas.Add(new Coordenada(htch.GetLoopAt(0).Curves[i].StartPoint.X, htch.GetLoopAt(0).Curves[i].StartPoint.Y, height));
                                        }
                                    }
                                    else
                                    {
                                        BulgeVertexCollection vertCol = htch.GetLoopAt(0).Polyline;
                                        foreach (BulgeVertex buVe in vertCol)
                                        {
                                            elemento.Coordenadas.Add(new Coordenada(buVe.Vertex.X, buVe.Vertex.Y, height));
                                        }
                                    }
                                    //Se agrega el punto si no coincide con el primero.
                                    if (elemento.Coordenadas[elemento.Coordenadas.Count - 1].CoordX != elemento.Coordenadas[0].CoordX || elemento.Coordenadas[elemento.Coordenadas.Count - 1].CoordY != elemento.Coordenadas[0].CoordY)
                                        elemento.Coordenadas.Add(new Coordenada(elemento.Coordenadas[0].CoordX, elemento.Coordenadas[0].CoordY, height));

                                    for (int j = 1; j < htch.NumberOfLoops; j++)
                                    {
                                        Ring anillo = new Ring();
                                        if (htch.GetLoopAt(j).Curves != null)
                                        {
                                            for (int i = 0; i < htch.GetLoopAt(j).Curves.Count; i++)
                                            {
                                                anillo.Coordenadas.Add(new Coordenada(htch.GetLoopAt(j).Curves[i].StartPoint.X, htch.GetLoopAt(j).Curves[i].StartPoint.Y, height));
                                            }
                                        }
                                        else
                                        {
                                            if (htch.GetLoopAt(j).LoopType.ToString().IndexOf(HatchLoopTypes.Outermost.ToString(),StringComparison.CurrentCulture) >= 0)
                                            {
                                                BulgeVertexCollection vertCol = htch.GetLoopAt(j).Polyline;
                                                foreach (BulgeVertex buVe in vertCol)
                                                {
                                                    anillo.Coordenadas.Add(new Coordenada(buVe.Vertex.X, buVe.Vertex.Y, height));
                                                }
                                            }
                                        }

                                        //Por si se tratase de un anillo descartado...
                                        if (anillo.Coordenadas.Count > 0) //htch.GetLoopAt(j).LoopType.ToString().IndexOf(HatchLoopTypes.Outermost.ToString()) >= 0)
                                        {
                                            //Se agrega el punto si no coincide con el primero.
                                            if (anillo.Coordenadas[anillo.Coordenadas.Count - 1].CoordX != anillo.Coordenadas[0].CoordX || anillo.Coordenadas[anillo.Coordenadas.Count - 1].CoordY != anillo.Coordenadas[0].CoordY)
                                                anillo.Coordenadas.Add(new Coordenada(anillo.Coordenadas[0].CoordX, anillo.Coordenadas[0].CoordY, height));

                                            elemento.Interiores.Add(anillo);
                                        }
                                    }
                                    break;
                                }
                            default:
                                {
                                    // Al desconocesrse el tipo de entidad, no se exportará.
                                    //System.Windows.Forms.MessageBox.Show(GetEntityType(oid, ref entid));
                                    break;
                                }
                        }

                        // Asignacion de los atributos de entidad.

                        foreach (Atributo atr in ente.Atributos)
                        {
                            if ((atr.Action + "").Equals(GMLC01HerramientasCad.Logica.Properties.Resources.abrirFichero, StringComparison.OrdinalIgnoreCase))
                            {
                                if (!string.IsNullOrEmpty(atr.Valor))
                                    elemento.Atributos[atr.Codigo].Valor = System.IO.Path.GetFileName(atr.Valor);
                                else
                                    elemento.Atributos[atr.Codigo].Valor = "";
                            }
                            else
                            {
                                elemento.Atributos[atr.Codigo].Valor = atr.Valor;
                            }
                        }

                        if (lista == null)
                            lista = new Collection<Elemento>();

                        lista.Add(elemento);
                    }
                }
                tr.Commit();
            }
            return lista;
        }
        #endregion

        #region "Importado de entidades"
        /// <summary>
        /// PARA IMPORTAR ENTIDADES
        /// </summary>
        /// <param name="elementos"></param>
        public void GrabarElementos(Collection<Elemento> elementos)
        {
            foreach (Elemento elem in elementos)
                GrabarElemento(elem);

            object oldCmdEcho = Autodesk.AutoCAD.ApplicationServices.Application.GetSystemVariable("NOMUTT");

            // Zoom a la extensión (para que se vea lo recien importado).
            Document doc = Application.DocumentManager.MdiActiveDocument;
            Application.SetSystemVariable("NOMUTT", 1);
            doc.SendStringToExecute("_.z _e ", false, false, false);
            Application.SetSystemVariable("NOMUTT", oldCmdEcho);

        }

        private static void GrabarElemento(Elemento elem)
        {
            ObjectId inserted = new ObjectId(new System.IntPtr(0));
            LayerManagement.PrepareLayer(TemplateACad(elem));
            switch (elem.Entidad.TipoGeometria)
            {
                case TiposGeometria.Punto:
                    inserted = GrabarPuntual(elem);
                    break;
                case TiposGeometria.Linea:
                    inserted = GrabarLineal(elem);
                    break;
                case TiposGeometria.Area:
                    inserted = GrabarPoligonal(elem);
                    break;
            }

            if (long.Parse(inserted.OldIdPtr.ToString(), System.Globalization.CultureInfo.InvariantCulture) != 0)
            {
                // Si se insertó la geometría, se puede proceder a cargar sus atributos.
                XData.SetXData(inserted, elem);
            }
        }

        private static ObjectId GrabarPuntual(Elemento elem)
        {
            DibujarEnts blocker = new DibujarEnts();
            blocker.BlockName = TemplateACad(elem);   
            return blocker.InsertBlockOrPoint(elem.Coordenadas[0].CoordX, elem.Coordenadas[0].CoordY, elem.Coordenadas[0].CoordZ);
        }

        private static ObjectId GrabarLineal(Elemento elem)
        {
            return AddPolyline3D(elem.Coordenadas, false);
        }

        private static ObjectId GrabarPoligonal(Elemento elem)
        {
            ObjectId retVal = new ObjectId(new System.IntPtr(0));
            double height = 0;
            double height2 = 0;
            ObjectId boundary = AddPolyline2D(elem.Coordenadas, true, ref height);
            if (long.Parse(boundary.OldIdPtr.ToString(), System.Globalization.CultureInfo.InvariantCulture) != 0)
            {
                ObjectIdCollection elements = new ObjectIdCollection();
                for (int i = 0; i < elem.Interiores.Count; i++)
                {
                    ObjectId nuevo = AddPolyline2D(elem.Interiores[i].Coordenadas, true, ref height2);
                    if (long.Parse(nuevo.OldIdPtr.ToString(), System.Globalization.CultureInfo.InvariantCulture) != 0) elements.Add(nuevo);
                }

                //Al dibujar el Hatch, sólo se tiene en cuenta la altura media del contorno.
                retVal = DibujarEnts.MakeHatch(boundary, elements, height);

            }

            return retVal;
        }

        private static String TemplateACad(Elemento elem)
        {

            Entidad ent = ModComun.XSD.Entidades[elem.CodigoEntidad];

            if (ent.CampoSubTipo != null && !String.IsNullOrEmpty(ent.CampoSubTipo))
            {
                if (!String.IsNullOrEmpty(elem.Atributos[ent.CampoSubTipo].Valor))
                {
                    return ent.Codigo + "." + ent.CampoSubTipo + "." + elem.Atributos[ent.CampoSubTipo].Valor; // GetName4DomainValue(elem.Atributos[ent.CampoSubTipo].NombreDominio, elem.Atributos[ent.CampoSubTipo].Valor);
                }
                else
                {
                    return ent.Codigo;
                }
            }
            else
            {
                return ent.Codigo;
            }

        }

        //private static string GetName4DomainValue(string domainName, string domainValue)
        //{
        //    foreach (ValorDominio dl in ModComun.XSD.Dominios[domainName].Valores)
        //    {
        //        if (dl.Codigo == domainValue) return dl.Texto;
        //    }
        //    return "";
        //}

        private static ObjectId AddPolyline2D(Collection<Coordenada> coordenadas, bool closed, ref double height)
        {
            ObjectId retVal = new ObjectId(new System.IntPtr(0));
            height = 0;
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Autodesk.AutoCAD.ApplicationServices.TransactionManager tm = doc.TransactionManager;
            Database db = doc.Database;
            using (Transaction tr = doc.TransactionManager.StartTransaction())
            {
                //Editor ed = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Editor;
                BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                Polyline poli = new Polyline();

                for (int i = 0; i < coordenadas.Count; i++)
                {
                    height += coordenadas[i].CoordZ;
                    poli.AddVertexAt(i, new Point2d(coordenadas[i].CoordX, coordenadas[i].CoordY), 0, 0, 0);
                }

                height = height / coordenadas.Count;

                if (closed) poli.Closed = true;

                btr.AppendEntity(poli);
                tr.AddNewlyCreatedDBObject(poli, true);

                retVal = poli.ObjectId;

                tm.QueueForGraphicsFlush();
                tm.FlushGraphics();
                doc.Editor.UpdateScreen();
                tr.Commit();
            }
            return retVal;

        }

        private static ObjectId AddPolyline3D(Collection<Coordenada> coordenadas, bool closed)
        {
            ObjectId retVal = new ObjectId(new System.IntPtr(0));
            //Database db = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.Database;
            Point3dCollection vertices = new Point3dCollection();

            for (int i = 0; i < coordenadas.Count; i++)
            {
                vertices.Add(new Point3d(coordenadas[i].CoordX, coordenadas[i].CoordY, coordenadas[i].CoordZ));
            }
            retVal = DibujarEnts.Insert3DPolyline(vertices, closed);
            return retVal;
        }

        #endregion

        public void Importar()
        {

            ModComun.ActivarImportarGML(this);
        }

        public void Exportar()
        {

            ModComun.ActivarExportarGML(this);
        }

    }

    [Serializable]
    public class GMLCGmlException : System.Exception
    {
        public GMLCGmlException()
        {
        }

        public GMLCGmlException(string message)
            : base(message)
        {
        }

        public GMLCGmlException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        protected GMLCGmlException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
    }

}
