using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections;
using System.Linq;
 
namespace AcadTest
{
    public class FlatSolidTest
    {
        [CommandMethod("FlatSolidTestRun")]
        public void RunCmd()
        {
            SupportMethods.InitializeVars(out Document adoc, out Editor ed, out Database db);
            if (SupportMethods.SelectEntity<Solid3d>(ed, out ObjectId id)
                && SupportMethods.GetPoint(ed, out Point3d pt, "\nSection base point: ")
                && SupportMethods.GetNextPoint(ed, pt, out Point3d secondPt, "\nSecond section point: ")
                && SupportMethods.GetPoint(ed, out Point3d forInsPt, "\nForeground objects position: ")
                && SupportMethods.GetNextPoint(ed, forInsPt, out Point3d viewInsPt, "\nBackground objects position: ")
                && SupportMethods.GetNextPoint(ed, viewInsPt, out Point3d intersInsPt, "\nIntersection objects position: "))
            {
                Vector3d viewDir;
 
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    BlockTableRecord space = tr.GetObject
                            (db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord;
                    Solid3d solid = tr.GetObject(id, OpenMode.ForRead) as Solid3d;
 
                    GetSectionEntities
                        (tr,
                        solid,
                        pt,
                        secondPt,
                        Vector3d.ZAxis,
                        true,
                        0.5,
                        out Entity[] forEnts,
                        out Entity[] backEnts,
                        out Entity[] intersEnts,
                        out viewDir);
 
                    void AddCol(Entity[] col, short colorIndex, Point3d pos)
                    {
                        Matrix3d move = Matrix3d.Displacement(pt.GetVectorTo(pos));
                        foreach (Entity ent in col)
                        {
                            ent.TransformBy(move);
                            ent.ColorIndex = colorIndex;
                            space.AppendEntity(ent);
                            tr.AddNewlyCreatedDBObject(ent, true);
                        }
                    }
 
                    AddCol(forEnts, 3, forInsPt);
                    AddCol(backEnts, 4, viewInsPt);
                    AddCol(intersEnts, 1, intersInsPt);
 
                    tr.Commit();
                }
 
                ed.WriteMessage("\nSection view direction: {0:0.00}, {1:0.00}, {2:0.00}", viewDir.X, viewDir.Y, viewDir.Z);
            }
        }
 
        /// <summary>
        /// Получение объектов сечения солида
        /// </summary>
        /// <param name="solid"></param>
        /// <param name="basePoint"></param>
        /// <param name="vertDir"></param>
        /// <param name="viewDir"></param>
        /// <param name="backEnts"></param>
        /// <param name="intersEnts"></param>
        public static void GetSectionEntities
            (Transaction tr,
            Solid3d solid,
            Point3d basePoint,
            Point3d secondPoint,
            Vector3d vertDir, bool convertSplines,
            double maxSegmentLen,
            out Entity[] forEnts,
            out Entity[] backEnts,
            out Entity[] intersEnts,
            out Vector3d viewDirection)
        {
            Point3dCollection pts = new Point3dCollection
                {
                    basePoint,
                    secondPoint
                };
 
            Section sec = new Section(pts, vertDir);           
            viewDirection = sec.ViewingDirection;
            BlockTableRecord space = tr.GetObject
                (solid.Database.CurrentSpaceId, OpenMode.ForWrite)
                as BlockTableRecord;
            space.AppendEntity(sec);
            tr.AddNewlyCreatedDBObject(sec, true);
            sec.State = SectionState.Plane;
            sec.GenerateSectionGeometry
                (solid,
                out Array intBoundaryEnts,
                out Array intFillEnts,
                out Array backgroundEnts,
                out Array foregroundEnts,
                out Array curveTangencyEnts);
 
            void DisposeCollection(IEnumerable col)
            {
                foreach (Entity ent in col)
                {
                    ent.Dispose();
                }
            }
           
            // Уничтожаем штриховки пересечения
            DisposeCollection(intFillEnts);
            // Уничтожаем линии плавного перехода кривых
            DisposeCollection(curveTangencyEnts);
 
            //// По идее, выходной нужна именно эта коллекция
            //// объектов-силуэтов перед сечением
            //// Но в ней почему-то либо вовсе нет объектов,
            //// либо их совсем мало - недостаточно для построения
            //// вида.
            forEnts = foregroundEnts.Cast<Entity>().ToArray();
            // Получаем выходную коллекцию - силуэты за сечением.
            backEnts = backgroundEnts.Cast<Entity>().ToArray();
            // Получаем выходную коллецию - объекты, полученные
            // непосредственно пересечением плоскости сечения
            // с 3D-телом
            intersEnts = intBoundaryEnts.Cast<Entity>().ToArray();
 
            if (convertSplines)
            {
                // Преобразуем сплайны в полилинии
                void ConvertSplines(Entity[] col)
                {
                    for (int i = 0; i < col.Length; i++)
                    {
                        Entity ent = col[i];
                        if (ent is Spline spline)
                        {
                            double length
                                = spline.GetDistanceAtParameter(spline.EndParam)
                                - spline.GetDistanceAtParameter(spline.StartParam);
                            uint pointsCount = (uint)Math.Max
                                (length / maxSegmentLen,
                                spline.Type == SplineType.FitPoints
                                ? spline.NumFitPoints
                                : spline.NumControlPoints);
                            Curve newEnt = spline.ToPolyline(pointsCount);
                            col[i] = newEnt;
                            ent.Dispose();
                        }
                    }
                }
 
                ConvertSplines(forEnts);
                ConvertSplines(backEnts);
                ConvertSplines(intersEnts);               
            }
        }
    }
}