// © Andrey Bushman, 2014
// ExtensionMethods.cs
// This code partially based on Alexander Rivilis's code (ARX version):
// http://adn-cis.org/forum/index.php?topic=1060.msg4983#msg4983
 
using System;
using System.Collections.Generic;
using System.Linq;
 
#if AUTOCAD
using cad = Autodesk.AutoCAD.ApplicationServices.Application;
using Ap = Autodesk.AutoCAD.ApplicationServices;
using Db = Autodesk.AutoCAD.DatabaseServices;
using Ed = Autodesk.AutoCAD.EditorInput;
using Rt = Autodesk.AutoCAD.Runtime;
using Gm = Autodesk.AutoCAD.Geometry;
#endif
 
namespace Bushman.CAD {
    public static class ExtensionMethods {
 
        static Rt.RXClass proxyObjectRXType = Rt.RXClass.GetClass(typeof(
            Db.ProxyObject));
 
        static Rt.RXClass proxyEntityRXType = Rt.RXClass.GetClass(typeof(
            Db.ProxyEntity));
 
        public static Db.ObjectId[] GetDBObjectIds(this Db.Database db) {
            Db.ObjectId[] ids = GetDBObjectIds(db, (n => true));
            return ids;
        }
 
        public static Db.ObjectId[] GetDBObjectIds(this Db.Database db,
            Func<Db.ObjectId, Boolean> filter) {
 
            // Check arguments
            if (null == db)
                throw new ArgumentNullException("null == db");
            if (null == filter)
                throw new ArgumentNullException("null == filter");
            if (db.IsDisposed)
                throw new ArgumentException("true == db.IsDisposed");
 
            // -------------------
            Int32 approxNum = db.ApproxNumObjects;
            List<Db.ObjectId> ids = new List<Db.ObjectId>();
 
            for (Int64 i = db.BlockTableId.Handle.Value; i < db.Handseed.Value
                && approxNum > 0; ++i) {
 
                Db.Handle h = new Db.Handle(i);
                Db.ObjectId id = Db.ObjectId.Null;
 
                Boolean parseResult = db.TryGetObjectId(h, out id);
 
                if (parseResult) {
                    --approxNum;
                    if (filter(id)) {
                        ids.Add(id);
                    }
                }
            }
            return ids.ToArray();
        }
 
        public static Db.ObjectId[] GetEntityIds(this Db.Database db) {
            Db.ObjectId[] ids = GetEntityIds(db, (n => true));
            return ids;
        }
 
        public static Db.ObjectId[] GetEntityIds(this Db.Database db,
            Func<Db.ObjectId, Boolean> filter) {
 
            // Check arguments
            if (null == db)
                throw new ArgumentNullException("null == db");
            if (null == filter)
                throw new ArgumentNullException("null == filter");
            if (db.IsDisposed)
                throw new ArgumentException("true == db.IsDisposed");
 
            // -------------------
            List<Db.ObjectId> ids = new List<Db.ObjectId>();
            // Entity items can be located in the BlockTableRecord instances 
            // only.
            using (Db.Transaction tr = db.TransactionManager.StartTransaction()
                ) {
                Db.BlockTable bt = tr.GetObject(db.BlockTableId,
                    Db.OpenMode.ForRead) as Db.BlockTable;
 
                // Skip erased BlockTableRecord instances.
                IEnumerable<Db.ObjectId> btrIds = bt.Cast<Db.ObjectId>()
                    .Where(n => !n.IsErased);
 
                foreach (Db.ObjectId btrId in btrIds) {
                    Db.BlockTableRecord btr = tr.GetObject(btrId,
                        Db.OpenMode.ForRead) as Db.BlockTableRecord;
                    foreach (Db.ObjectId id in btr) {
                        if (filter(id)) {
                            ids.Add(id);
                        }
                    }
                }
                tr.Commit();
            }
            return ids.ToArray();
        }
 
        public static Db.ObjectId[] ExplodeProxyEntity(
            Db.ObjectId proxyEntityId, out Boolean handOverTo_called) {
 
            // Check arguments
            if (null == proxyEntityId)
                throw new ArgumentException("null == proxyEntityId");
            if (proxyEntityId.IsErased)
                throw new ArgumentException("true == proxyEntityId.IsErased");
            if (null == proxyEntityId.Database)
                throw new ArgumentException("null == proxyEntityId.Database");
            if (proxyEntityId.Database.IsDisposed)
                throw new ArgumentException(
                    "true == proxyEntityId.Database.IsDisposed");
            if (!proxyEntityId.ObjectClass.IsDerivedFrom(proxyEntityRXType))
                throw new ArgumentException("false == proxyEntityId." +
                    "ObjectClass.IsDerivedFrom(proxyEntityRXType)");
 
            // -------------------
            using (Db.DBObjectCollection newDBObjects =
                new Db.DBObjectCollection()) {
                Db.Database db = proxyEntityId.Database;
 
                using (Db.Transaction tr = db.TransactionManager
                    .StartOpenCloseTransaction()) {
                    Db.ProxyEntity proxyEntity = tr.GetObject(
                        proxyEntityId, Db.OpenMode.ForWrite, false, true)
                        as Db.ProxyEntity;
 
                    if (proxyEntity.GraphicsMetafileType ==
                        Db.GraphicsMetafileType.FullGraphics) {
                        try {
                            proxyEntity.Explode(newDBObjects);
                        }
                        catch {
                        }
                    }
                    else if (proxyEntity.GraphicsMetafileType ==
                        Db.GraphicsMetafileType.BoundingBox) {
 
                        Db.Extents3d ext = proxyEntity.GeometricExtents;
                        Gm.Point3dCollection arr =
                            new Gm.Point3dCollection();
 
                        arr.Add(ext.MinPoint);
                        arr.Add(new Gm.Point3d(ext.MinPoint.X,
                            ext.MaxPoint.Y, ext.MinPoint.Z));
                        arr.Add(new Gm.Point3d(ext.MaxPoint.X,
                            ext.MaxPoint.Y, ext.MinPoint.Z));
                        arr.Add(new Gm.Point3d(ext.MaxPoint.X,
                            ext.MinPoint.Y, ext.MinPoint.Z));
 
                        Db.Polyline3d pline = new Db.Polyline3d(
                            Db.Poly3dType.SimplePoly, arr, true);
 
                        pline.LayerId = proxyEntity.LayerId;
                        pline.LinetypeId = proxyEntity.LinetypeId;
                        pline.Color = proxyEntity.Color;
 
                        newDBObjects.Add(pline);
                    }
 
                    Db.BlockTableRecord btr = tr.GetObject(
                        proxyEntity.BlockId, Db.OpenMode.ForWrite, false)
                        as Db.BlockTableRecord;
 
                    // ----------------
                    Boolean canBeErased = (proxyEntity.ProxyFlags & 0x1) != 0;
 
                    if (canBeErased) {
                        proxyEntity.Erase();
                        handOverTo_called = false;
                    }
                    else {
                        using (Db.Line tmp = new Db.Line()) {
                            proxyEntity.HandOverTo(tmp, false, false);
                            tmp.Erase();
                            proxyEntity.Dispose();
                            handOverTo_called = true;
                        }
                    }
 
                    if (newDBObjects.Count > 0) {
                        foreach (Db.DBObject item in newDBObjects) {
                            if (item is Db.Entity) {
                                Db.Entity _ent = item as Db.Entity;
                                btr.AppendEntity(_ent);
                                tr.AddNewlyCreatedDBObject(item, true);
                            }
                        }
                    }
                    Db.ObjectIdCollection idsRef =
                        btr.GetBlockReferenceIds(true, true);
                    foreach (Db.ObjectId id in idsRef) {
                        Db.BlockReference br = tr.GetObject(id,
                            Db.OpenMode.ForWrite, false)
                            as Db.BlockReference;
                        br.RecordGraphicsModified(true);
                    }
                    tr.Commit();
                }
                return newDBObjects.Cast<Db.DBObject>().Select(n => n.ObjectId)
                    .ToArray();
            }
        }
 
        public static void RemoveDBObject(Db.ObjectId id,
            out Boolean handOverTo_called) {
 
            // Check arguments
            if (null == id)
                throw new ArgumentException("null == id");
            if (id.IsErased)
                throw new ArgumentException("true == id.IsErased");
            if (null == id.Database)
                throw new ArgumentException("null == id.Database");
            if (id.Database.IsDisposed)
                throw new ArgumentException("true == id.Database.IsDisposed");
 
            // -------------------
            Db.Database db = id.Database;
 
            using (Db.Transaction tr = db.TransactionManager
                .StartOpenCloseTransaction()) {
                Db.DBObject obj = tr.GetObject(id, Db.OpenMode.ForWrite, false,
                    true);
                EraseDBObject(obj, out handOverTo_called);
                tr.Commit();
            }
        }
 
        private static void EraseDBObject(Db.DBObject obj,
            out Boolean handOverTo_called) {
 
            // Check argument
            if (null == obj)
                throw new ArgumentNullException("null == obj");
            if (obj.IsErased)
                throw new ArgumentException("true == obj.IsErased");
            if (obj.IsDisposed)
                throw new ArgumentException("true == obj.IsDisposed");
            if (null == obj.Database)
                throw new ArgumentException("null == obj.Database");
            if (obj.Database.IsDisposed)
                throw new ArgumentException("true == obj.Database.IsDisposed");
 
            // ----------------
            Boolean canBeErased = true;
 
            // AcDbProxyEntity::kEraseAllowed = 0x1
            if (obj is Db.ProxyObject) {
                Db.ProxyObject proxy = obj as Db.ProxyObject;
                canBeErased = (proxy.ProxyFlags & 0x1) != 0;
            }
            else if (obj is Db.ProxyEntity) {
                Db.ProxyEntity proxy = obj as Db.ProxyEntity;
                canBeErased = (proxy.ProxyFlags & 0x1) != 0;
            }
 
            if (canBeErased) {
                obj.Erase(true);
                handOverTo_called = false;
            }
            else {
                using (Db.DBObject tmp = obj is Db.Entity ?
                    (Db.DBObject)new Db.Line() : new Db.DBDictionary()) {
                    obj.HandOverTo(tmp, false, false);
                    tmp.Erase(true);
                    obj.Dispose();
                    handOverTo_called = true;
                }
            }
        }
 
        public static Db.ObjectId[] GetFreeAnnotativeScaleIds(
            this Db.Database db) {
 
            // Check argument
            if (null == db)
                throw new ArgumentNullException("null == db");
            if (db.IsDisposed)
                throw new ArgumentException("true == db.IsDisposed");
 
            // ----------------
            using (Db.ObjectIdCollection ids = new Db.ObjectIdCollection()) {
                Db.ObjectContextManager ocMng = db.ObjectContextManager;
                if (null != ocMng) {
                    Db.ObjectContextCollection ocItems =
                        ocMng.GetContextCollection("ACDB_ANNOTATIONSCALES");
                    if (null != ocItems) {
                        foreach (Db.ObjectContext item in ocItems) {
                            if (item is Db.AnnotationScale)
                                ids.Add(new Db.ObjectId(item.UniqueIdentifier)
                                    );
                        }
                        db.Purge(ids);
                    }
                }
                return ids.Cast<Db.ObjectId>().Where(n => !n.IsErased
                    && !n.IsEffectivelyErased).ToArray();
            }
        }
 
        public static void RemoveFreeAnnotativeScales(this Db.Database db) {
 
            // Check argument
            if (null == db)
                throw new ArgumentNullException("null == db");
            if (db.IsDisposed)
                throw new ArgumentException("true == db.IsDisposed");
 
            // -----------------
            Db.ObjectId[] ids = db.GetFreeAnnotativeScaleIds();
 
            using (Db.Transaction tr = db.TransactionManager.StartTransaction()
                ) {
                foreach (Db.ObjectId id in ids) {
                    try {
                        Db.DBObject obj = tr.GetObject(id, Db.OpenMode
                            .ForWrite, false);
                        obj.Erase();
                    }
                    catch { }
                }
                tr.Commit();
            }
        }
    }
}