"""
DWG_Comparison Tool
====================
Overlays two AutoCAD/Civil 3D drawings and produces a PDF highlighting
geometric differences between them.

Usage:
    python dwg_comparison.py "Drawing1.dwg" "Drawing2.dwg" [output.pdf] [--buffer N]

Arguments:
    drawing1    Name of first open drawing (as shown in AutoCAD title bar)
    drawing2    Name of second open drawing
    output      Optional output PDF path (default: C:/Tools/Civil3D/Projects/dwg_comparison.pdf)
    --buffer N  Tolerance in feet for "same" geometry (default: 2.0)

Both drawings must already be open in AutoCAD/Civil 3D.

Dependencies:
    pip install pywin32 matplotlib shapely reportlab
"""

import sys
import math
import json
import argparse
import warnings
warnings.filterwarnings("ignore")

import win32com.client
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.collections import LineCollection
from matplotlib.lines import Line2D
from shapely.geometry import LineString
from shapely.ops import unary_union

# ── Colors ────────────────────────────────────────────────────────────────────
C1    = "#1565C0"   # blue  – Drawing 1
C2    = "#B71C1C"   # red   – Drawing 2
BG    = "#1A1A2E"   # dark background
CDIFF1 = "#00E5FF"  # cyan  – only in Drawing 1
CDIFF2 = "#FF4081"  # pink  – only in Drawing 2


def connect_autocad():
    acad = win32com.client.GetActiveObject("AutoCAD.Application")
    return acad


def list_open_drawings(acad):
    names = []
    for i in range(acad.Documents.Count):
        names.append(acad.Documents.Item(i).Name)
    return names


def get_document(acad, name):
    for i in range(acad.Documents.Count):
        d = acad.Documents.Item(i)
        if d.Name.lower() == name.lower():
            return d
    raise ValueError(f"Drawing '{name}' not found. Open drawings: {list_open_drawings(acad)}")


def extract_geometry(doc):
    """Extract lines, polylines, points, and arcs from a drawing's ModelSpace."""
    ms = doc.ModelSpace
    data = {"polylines": [], "lines": [], "points": [], "arcs": []}

    for i in range(ms.Count):
        try:
            ent = ms.Item(i)
            ename = ent.EntityName

            if ename in ("AcDbPolyline", "AcDb2dPolyline"):
                coords = list(ent.Coordinates)
                pts = [(coords[j], coords[j+1]) for j in range(0, len(coords)-1, 2)]
                if pts:
                    data["polylines"].append(pts)

            elif ename == "AcDb3dPolyline":
                coords = list(ent.Coordinates)
                pts = [(coords[j], coords[j+1]) for j in range(0, len(coords)-1, 3)]
                if pts:
                    data["polylines"].append(pts)

            elif ename == "AcDbLine":
                sp = ent.StartPoint
                ep = ent.EndPoint
                data["lines"].append([(sp[0], sp[1]), (ep[0], ep[1])])

            elif ename in ("AcDbPoint", "AeccDbCogoPoint"):
                try:
                    c = ent.Coordinates
                except Exception:
                    c = ent.Location
                data["points"].append((c[0], c[1]))

            elif ename == "AcDbArc":
                c = ent.Center
                r = ent.Radius
                data["arcs"].append({
                    "cx": c[0], "cy": c[1], "r": r,
                    "sa": ent.StartAngle, "ea": ent.EndAngle
                })

        except Exception:
            pass

    return data


def draw_polylines(ax, data, color, alpha, lw, zorder):
    segs = [pl for pl in data["polylines"] if len(pl) >= 2]
    segs += [l for l in data["lines"] if len(l) >= 2]
    if segs:
        lc = LineCollection(segs, colors=color, linewidths=lw, alpha=alpha, zorder=zorder)
        ax.add_collection(lc)


def draw_arcs(ax, data, color, alpha, lw, zorder):
    for a in data["arcs"]:
        arc = mpatches.Arc(
            (a["cx"], a["cy"]), 2*a["r"], 2*a["r"],
            angle=0,
            theta1=math.degrees(a["sa"]),
            theta2=math.degrees(a["ea"]),
            color=color, lw=lw, alpha=alpha, zorder=zorder
        )
        ax.add_patch(arc)


def draw_points(ax, data, color, alpha, size, zorder):
    if data["points"]:
        xs = [p[0] for p in data["points"]]
        ys = [p[1] for p in data["points"]]
        ax.scatter(xs, ys, c=color, s=size, alpha=alpha, zorder=zorder, linewidths=0)


def build_union(data, buffer_dist):
    geoms = []
    for pl in data["polylines"]:
        if len(pl) >= 2:
            try:
                geoms.append(LineString(pl).buffer(buffer_dist))
            except Exception:
                pass
    for l in data["lines"]:
        if len(l) >= 2:
            try:
                geoms.append(LineString(l).buffer(buffer_dist))
            except Exception:
                pass
    return unary_union(geoms) if geoms else None


def plot_difference_shape(ax, geom, color, alpha):
    if geom is None or geom.is_empty:
        return
    polys = []
    if geom.geom_type == "Polygon":
        polys = [geom]
    elif geom.geom_type == "MultiPolygon":
        polys = list(geom.geoms)
    elif hasattr(geom, "geoms"):
        for g in geom.geoms:
            if g.geom_type == "Polygon":
                polys.append(g)
            elif g.geom_type == "MultiPolygon":
                polys.extend(g.geoms)
    for poly in polys:
        try:
            x, y = poly.exterior.xy
            ax.fill(x, y, color=color, alpha=alpha, zorder=4)
        except Exception:
            pass


def compute_extents(d1, d2):
    all_x, all_y = [], []
    for data in [d1, d2]:
        for pl in data["polylines"]:
            all_x += [p[0] for p in pl]; all_y += [p[1] for p in pl]
        for l in data["lines"]:
            all_x += [p[0] for p in l]; all_y += [p[1] for p in l]
        all_x += [p[0] for p in data["points"]]
        all_y += [p[1] for p in data["points"]]
    return min(all_x), max(all_x), min(all_y), max(all_y)


def generate_comparison_pdf(name1, name2, output_path, buffer_dist=2.0):
    print(f"Connecting to AutoCAD...")
    acad = connect_autocad()

    print(f"Extracting geometry from '{name1}'...")
    d1 = extract_geometry(get_document(acad, name1))
    print(f"  polylines/lines: {len(d1['polylines'])+len(d1['lines'])}  points: {len(d1['points'])}  arcs: {len(d1['arcs'])}")

    print(f"Extracting geometry from '{name2}'...")
    d2 = extract_geometry(get_document(acad, name2))
    print(f"  polylines/lines: {len(d2['polylines'])+len(d2['lines'])}  points: {len(d2['points'])}  arcs: {len(d2['arcs'])}")

    # ── Figure ────────────────────────────────────────────────────────────────
    fig, ax = plt.subplots(figsize=(24, 16), dpi=150)
    fig.patch.set_facecolor(BG)
    ax.set_facecolor(BG)

    # Drawing 2 behind, Drawing 1 in front
    draw_polylines(ax, d2, C2, 0.75, 0.8, 2)
    draw_points(ax, d2, C2, 0.6, 4, 2)
    draw_polylines(ax, d1, C1, 0.75, 0.8, 3)
    draw_arcs(ax, d1, C1, 0.75, 0.8, 3)
    draw_points(ax, d1, C1, 0.6, 4, 3)

    # ── Difference highlighting ───────────────────────────────────────────────
    print(f"Computing difference zones (buffer={buffer_dist} ft)...")
    u1 = build_union(d1, buffer_dist)
    u2 = build_union(d2, buffer_dist)
    if u1 and u2:
        plot_difference_shape(ax, u1.difference(u2), CDIFF1, 0.25)
        plot_difference_shape(ax, u2.difference(u1), CDIFF2, 0.25)

    # ── Extents / axes ────────────────────────────────────────────────────────
    xmin, xmax, ymin, ymax = compute_extents(d1, d2)
    xpad = (xmax - xmin) * 0.04
    ypad = (ymax - ymin) * 0.04
    ax.set_xlim(xmin - xpad, xmax + xpad)
    ax.set_ylim(ymin - ypad, ymax + ypad)
    ax.set_aspect("equal")
    ax.tick_params(colors="white", labelsize=7)
    for spine in ax.spines.values():
        spine.set_edgecolor("#444444")
    ax.set_xlabel("Easting (ft)", color="white", fontsize=9)
    ax.set_ylabel("Northing (ft)", color="white", fontsize=9)
    ax.grid(True, color="#333355", linewidth=0.4, alpha=0.6)

    # ── Title ─────────────────────────────────────────────────────────────────
    ax.set_title(
        f"Drawing Overlay Comparison\n{name1}  vs  {name2}",
        color="white", fontsize=14, fontweight="bold", pad=12
    )

    # ── Legend ────────────────────────────────────────────────────────────────
    legend_elements = [
        Line2D([0],[0], color=C1,     lw=2, label=f"{name1}"),
        Line2D([0],[0], color=C2,     lw=2, label=f"{name2}"),
        mpatches.Patch(facecolor=CDIFF1, alpha=0.5, label=f"Only in {name1}"),
        mpatches.Patch(facecolor=CDIFF2, alpha=0.5, label=f"Only in {name2}"),
    ]
    legend = ax.legend(
        handles=legend_elements,
        loc="lower right",
        facecolor="#0D0D1A", edgecolor="#555577",
        labelcolor="white", fontsize=9,
        title="LEGEND", title_fontsize=10
    )
    legend.get_title().set_color("white")

    # ── Stats box ─────────────────────────────────────────────────────────────
    stats = (
        f"{name1}:  {len(d1['polylines'])+len(d1['lines'])} segments | {len(d1['points'])} points | {len(d1['arcs'])} arcs\n"
        f"{name2}:  {len(d2['polylines'])+len(d2['lines'])} segments | {len(d2['points'])} points | {len(d2['arcs'])} arcs\n"
        f"Difference tolerance: {buffer_dist} ft"
    )
    ax.text(0.01, 0.01, stats,
            transform=ax.transAxes,
            color="#AAAACC", fontsize=8,
            verticalalignment="bottom",
            bbox=dict(boxstyle="round,pad=0.4", facecolor="#0D0D1A",
                      edgecolor="#555577", alpha=0.8))

    plt.tight_layout()
    plt.savefig(output_path, format="pdf", bbox_inches="tight",
                facecolor=fig.get_facecolor())
    plt.close()
    print(f"Saved: {output_path}")
    return output_path


# ── CLI entry point ───────────────────────────────────────────────────────────
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="DWG_Comparison — overlay two drawings and highlight differences")
    parser.add_argument("drawing1", help="Name of first open drawing (e.g. MyFile.dwg)")
    parser.add_argument("drawing2", help="Name of second open drawing")
    parser.add_argument("output",   nargs="?",
                        default="C:/Tools/Civil3D/Projects/dwg_comparison.pdf",
                        help="Output PDF path")
    parser.add_argument("--buffer", type=float, default=2.0,
                        help="Tolerance in feet for matching geometry (default: 2.0)")
    args = parser.parse_args()

    result = generate_comparison_pdf(args.drawing1, args.drawing2, args.output, args.buffer)
    import subprocess
    subprocess.Popen(["start", "", result], shell=True)
