using System.Drawing;
using System.Drawing.Drawing2D;
using InABox.Core;
using netDxf;
using netDxf.Entities;
using netDxf.Objects;
using netDxf.Tables;
using Svg;
using Syncfusion.Pdf;

namespace InABox.Dxf;

public class DxfImportSettings
{
    public Size ImageSize;

    public string[]? SupportFolders;

    public string? LayoutName { get; set; }

    public bool AutoFit { get; set; }

    public DxfImportSettings(Size? imageSize = null, string[]? supportFolders = null, string? layoutName = null, bool autoFit = true)
    {
        AutoFit = autoFit;
        ImageSize = imageSize ?? new(2048, 2048);
        SupportFolders = supportFolders;
        LayoutName = layoutName ?? "Model";
    }
}

public class DxfData
{
    public DxfDocument Document { get; set; }

    public DxfImportSettings Settings { get; set; }

    public SizeF Size { get; set; }

    public PointF Origin { get; set; }

    public Layout Layout { get; set; }

    public IEnumerable<string> LayoutNames => Document.Layouts.Select(x => x.Name);

    public HashSet<string>? Layers { get; set; }

    public bool HasLayer(Layer layer)
    {
        return Layers is null || Layers.Contains(layer.Name);
    }

    public void SetLayers(params string[] layers)
    {
        Layers = layers.ToHashSet();
    }
    public void SetLayers(IEnumerable<string> layers)
    {
        Layers = layers.ToHashSet();
    }

    public bool ShouldDraw(EntityObject obj)
    {
        return obj.IsVisible && HasLayer(obj.Layer);
    }

    public string LayoutName
    {
        get => Layout.Name;
        set
        {
            Layout = Document.Layouts.First(x => x.Name == value);
            UpdateSizeAndOrigin();
        }
    }

    public DxfData(DxfDocument document, DxfImportSettings settings)
    {
        Document = document;
        Settings = settings;

        Layout = settings.LayoutName is not null ? document.Layouts.First(x => x.Name == settings.LayoutName) : document.Layouts.First();
        UpdateSizeAndOrigin();
    }

    private void UpdateSizeAndOrigin()
    {
        if (Settings.AutoFit)
        {
            var bounds = DxfUtils.CalculateDxfSize(this) ?? new();
            var margin = 0.1f * Math.Min(bounds.Width, bounds.Height);
            if (bounds.IsEmpty)
            {
                bounds.Size = new(100, 100);
                bounds.Location = new(0, 0);
            }
            else
            {
                bounds.Size = new(bounds.Width + margin * 2, bounds.Height + margin * 2);
                bounds.Location = new(bounds.X - margin, bounds.Y - margin);
            }
            Size = bounds.Size;
            Origin = bounds.Location;
        }
        else
        {
            Size = new((float)(Layout.MaxLimit.X - Layout.MinLimit.X), (float)(Layout.MaxLimit.Y - Layout.MinLimit.Y));
            Origin = new((float)Layout.MinLimit.X, (float)Layout.MinLimit.Y);
        }
    }
}

public static class DxfUtils
{
    public static event ProcessError OnProcessError;
    public delegate void ProcessError(string message);

    internal static IDxfObject? ConvertEl(EntityObject el)
    {
        if(el is Line line)
        {
            return new DxfLine { Line = line };
        }
        else if(el is Insert insert)
        {
            return new DxfInsert(insert);
        }
        else if(el is Ellipse ellipse)
        {
            return new DxfEllipse(ellipse);
        }
        else if(el is MText text)
        {
            return new DxfMText(text);
        }
        else if(el is Polyline2D ln2D)
        {
            return new DxfPolyline2D(ln2D);
        }
        else if(el is Dimension dim)
        {
            return new DxfDimension(dim);
        }
        else if(el is Solid solid)
        {
            return new DxfSolid(solid);
        }
        else if (el is netDxf.Entities.Point point)
        {
            return null;
        }
        else if (el is netDxf.Entities.Viewport viewport)
        {
            return null;
        }
        else if (el is Arc a)
        {
            return new DxfArc(a);
        }
        else if(el is Text t)
        {
            return new DxfText(t);
        }
        else
        {
            return null;
        }
    }

    public static void DrawDxf(DxfData data, IGraphics graphics, float width, float height)
    {
        // Calculate the scaling factor to fit the image within the bounds
        float ratioX = (float)data.Settings.ImageSize.Width / data.Size.Width;
        float ratioY = (float)data.Settings.ImageSize.Height / data.Size.Height;

        var scale = Math.Min(ratioX, ratioY);

        var drawData = new DrawData() { Graphics = graphics, Data = data };

        graphics.Clear(Color.White);

        // drawData.Translate(graphics.VisibleClipBounds.Width / 2, graphics.VisibleClipBounds.Height / 2);

        drawData.Scale(scale, scale);
        drawData.Translate(-data.Origin.X, -data.Origin.Y);
        // drawData.Translate(-data.Origin.X - data.Size.Width / 2, -data.Origin.Y - data.Size.Height / 2);

        foreach(var el in data.Layout.AssociatedBlock.Entities)
        {
            var item = ConvertEl(el);
            item?.Draw(drawData);
        }
        graphics.Finish();
    }

    public static DxfData LoadDxf(string filename, DxfImportSettings? settings = null)
    {
        using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
        settings ??= new();

        var document = DxfDocument.Load(stream, settings.SupportFolders ?? Array.Empty<string>());
        document.BuildDimensionBlocks = true;
        return new(document, settings);
    }
    public static DxfData LoadDxf(Stream stream, DxfImportSettings? settings = null)
    {
        settings ??= new();

        var document = DxfDocument.Load(stream, settings.SupportFolders ?? Array.Empty<string>());
        return new(document, settings);
    }

    /// <summary>
    /// Returns <see langword="null"/> if the bounds are completely empty.
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    public static RectangleF? CalculateDxfSize(DxfData data)
    {
        var transformData = new TransformData { Data = data };

        RectangleF? bounds = null;
        foreach(var el in data.Document.Entities.All)
        {
            if(ConvertEl(el) is IDxfObject obj)
            {
                bounds = Utils.CombineBounds(bounds, obj.GetBounds(transformData));
            }
        }
        return bounds;
    }

    public static Bitmap ProcessImage(DxfData data)
    {
        var height = data.Size.Height;
        var width = data.Size.Width;
        
        // Calculate the scaling factor to fit the image within the bounds
        float ratioX = (float)data.Settings.ImageSize.Width / width;
        float ratioY = (float)data.Settings.ImageSize.Height / height;

        var scale = Math.Min(ratioX, ratioY);

        var _result = new Bitmap((int)(width * scale), (int)(height * scale));
        using (var _graphics = Graphics.FromImage(_result))
        {
            _graphics.SmoothingMode = SmoothingMode.AntiAlias;
            DrawDxf(data, new GdiGraphics(_graphics), _result.Width, _result.Height);
        }
        _result.RotateFlip(RotateFlipType.RotateNoneFlipY);

        return _result;
    }

    public static PdfDocument ProcessPdf(DxfData data)
    {
        var doc = new PdfDocument();

        doc.PageSettings.Size = data.Size;
        doc.PageSettings.SetMargins(0);

        var page = doc.Pages.Add();

        var graphics = page.Graphics;

        var drawData = new DrawData() { Graphics = new PdfGraphics(page.Graphics), Data = data };
        drawData.PushTransform();
        drawData.Translate(data.Size.Width / 2, data.Size.Height / 2);
        drawData.Scale(1, -1);
        drawData.Translate(-data.Size.Width / 2, -data.Size.Height / 2);
        drawData.Translate(-data.Origin.X, -data.Origin.Y);

        if (data.Layout.IsPaperSpace)
        {
            var modelSpace = data.Document.Layouts.First(x => x.Name == "Model");
            foreach(var el in modelSpace.AssociatedBlock.Entities)
            {
                var item = ConvertEl(el);
                item?.Draw(drawData);
            }
        }

        foreach(var el in data.Layout.AssociatedBlock.Entities)
        {
            var item = ConvertEl(el);
            item?.Draw(drawData);
        }
        drawData.PopTransform();
        drawData.Graphics.Finish();

        return doc;
    }
    // public static PdfDocument ProcessPdf(DxfData data)
    // {
    //     var doc = new PdfDocument();

    //     var size = data.Size;
    //     var origin = data.Origin;

    //     foreach(var layout in data.LayoutNames)
    //     {
    //         data.LayoutName = layout;

    //         Matrix4x4 transform;
    //         if (data.Layout.IsPaperSpace)
    //         {
    //             var viewport = data.Layout.Viewport;

    //             var scale = (float)(viewport.ViewHeight == 0 ? 0 : viewport.Height / viewport.ViewHeight);

    //             data.Origin = new((float)(viewport.ViewCenter.X - viewport.Width / 2), (float)(viewport.ViewCenter.Y - viewport.Height / 2));
    //             data.Size = new((float)viewport.Width, (float)viewport.Height);

    //             var normal = -System.Numerics.Vector3.Cross(viewport.UcsXAxis.ToVec3(), viewport.UcsYAxis.ToVec3());
    //             transform = Matrix4x4.CreateLookAt(viewport.UcsOrigin.ToVec3(), viewport.UcsOrigin.ToVec3() + normal, viewport.UcsYAxis.ToVec3())
    //                 * Matrix4x4.CreateScale(scale)
    //                 * Matrix4x4.CreateRotationZ((float)viewport.TwistAngle);
    //         }
    //         else
    //         {
    //             data.Size = size;
    //             data.Origin = origin;
    //             transform = Matrix4x4.Identity;
    //         }

    //         var section = doc.Sections.Add();

    //         section.PageSettings.Size = data.Size;
    //         section.PageSettings.SetMargins(0);

    //         var page = section.Pages.Add();

    //         var graphics = page.Graphics;

    //         var drawData = new DrawData() { Graphics = new PdfGraphics(page.Graphics), Data = data };
    //         drawData.PushTransform();
    //         drawData.Translate(data.Size.Width / 2, data.Size.Height / 2);
    //         drawData.Scale(1, -1);
    //         drawData.Translate(-data.Size.Width / 2, -data.Size.Height / 2);
    //         drawData.Translate(-data.Origin.X, -data.Origin.Y);

    //         drawData.TransformBy(transform);

    //         if (data.Layout.IsPaperSpace)
    //         {
    //             var modelSpace = data.Document.Layouts.First(x => x.Name == "Model");
    //             foreach(var el in modelSpace.AssociatedBlock.Entities)
    //             {
    //                 var item = ConvertEl(el);
    //                 item?.Draw(drawData);
    //             }
    //         }

    //         foreach(var el in data.Layout.AssociatedBlock.Entities)
    //         {
    //             var item = ConvertEl(el);
    //             item?.Draw(drawData);
    //         }
    //         drawData.PopTransform();
    //         drawData.Graphics.Finish();
    //     }

    //     return doc;
    // }

    public static SvgDocument ProcessSvg(DxfData data)
    {
        var doc = new SvgDocument
        {
            ViewBox = new(data.Origin.X, data.Origin.Y, data.Size.Width, data.Size.Height)
        };
        var drawData = new DrawData() { Graphics = new SvgGraphics(doc), Data = data };
        foreach(var el in data.Layout.AssociatedBlock.Entities)
        {
            var item = ConvertEl(el);
            item?.Draw(drawData);
        }
        drawData.Graphics.Finish();
        return doc;
    }

    public static Bitmap ProcessImage(Stream stream, DxfImportSettings? settings = null)
    {
        return ProcessImage(LoadDxf(stream, settings));
    }

    public static Result<Bitmap, Exception> DXFToBitmap(string filename, DxfImportSettings? settings = null)
    {
        using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
        try
        {
            return Result.Ok(ProcessImage(stream, settings: settings));
        }
        catch (Exception e)
        {
            OnProcessError?.Invoke(e.Message);
            return Result.Error(e);
        }
    }
}