#!/usr/bin/env python3
"""
Preprocess PDF pages: Adaptive Threshold + Bilateral Filter + Morphology
Dùng cho scan giấy kém chất lượng (thấu quang, chữ mờ, nhiễu).

Usage:
    python3 preprocess_demo.py pdf/so-tay-mahavihara.pdf --pages 5
"""

import sys, os, argparse
from pathlib import Path
import subprocess

try:
    import cv2
    import numpy as np
except ImportError:
    print("❌ Cần opencv-python: pip install opencv-python")
    sys.exit(1)

try:
    from PIL import Image
except ImportError:
    print("❌ Cần Pillow: pip install Pillow")
    sys.exit(1)


def preprocess_page(input_path: str, output_path: str, params: dict):
    """Xử lý một ảnh với adaptive threshold + morphology."""
    img = cv2.imread(input_path, cv2.IMREAD_GRAYSCALE)
    if img is None:
        print(f"  ❌ Không đọc được: {input_path}")
        return False

    h, w = img.shape

    # 1. Bilateral filter — giữ nét chữ, làm mờ nhiễu nền
    bf = cv2.bilateralFilter(
        img,
        d=params['bilateral_d'],
        sigmaColor=params['bilateral_sigmaColor'],
        sigmaSpace=params['bilateral_sigmaSpace']
    )

    # 2. Adaptive threshold
    binary = cv2.adaptiveThreshold(
        bf, 255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY,
        params['adaptive_block_size'],
        params['adaptive_C']
    )

    # 3. Morphology — xóa đốm li ti
    kernel = np.ones(tuple(params['morph_kernel']), np.uint8)
    cleaned = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

    # 4. Optional: xóa đốm đen nhỏ hơn min_area (connected components)
    if params.get('min_area', 0) > 0:
        num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(~cleaned, 8, cv2.CV_32S)
        for i in range(1, num_labels):
            if stats[i, cv2.CC_STAT_AREA] < params['min_area']:
                cleaned[labels == i] = 255  # xóa đốm

    cv2.imwrite(output_path, cleaned)
    return True


def main():
    parser = argparse.ArgumentParser(description="Preprocess PDF với adaptive threshold")
    parser.add_argument("pdf", help="Đường dẫn file PDF")
    parser.add_argument("--pages", type=int, default=5, help="Số trang cần xử lý (mặc định: 5)")
    parser.add_argument("--dpi", type=int, default=300, help="DPI khi trích xuất PNG (mặc định: 300)")
    parser.add_argument("--block-size", type=int, default=21, help="Adaptive block size (mặc định: 21)")
    parser.add_argument("--C", type=int, default=25, help="Adaptive C value (mặc định: 25)")
    parser.add_argument("--bilateral-d", type=int, default=9, help="Bilateral filter diameter (mặc định: 9)")
    parser.add_argument("--min-area", type=int, default=10, help="Xóa đốm < min_area px² (mặc định: 10)")
    args = parser.parse_args()

    pdf_path = os.path.abspath(args.pdf)
    if not os.path.isfile(pdf_path):
        print(f"❌ Không tìm thấy PDF: {pdf_path}")
        sys.exit(1)

    base_dir = os.path.dirname(os.path.dirname(pdf_path))  # 011-so-tay-mahavihara
    raw_dir = os.path.join(base_dir, "raw_pages")
    cleaned_dir = os.path.join(base_dir, "cleaned")
    os.makedirs(raw_dir, exist_ok=True)
    os.makedirs(cleaned_dir, exist_ok=True)

    # ------------------------
    # 1. Extract PDF → PNG
    # ------------------------
    print("=" * 60)
    print("🔧 BƯỚC 1: Trích xuất PDF → PNG")
    print("=" * 60)
    pdf_name = Path(pdf_path).stem
    cmd = [
        "pdftoppm", "-r", str(args.dpi), "-png",
        pdf_path, os.path.join(raw_dir, "page")
    ]
    if args.pages:
        # pdftoppm -l = last page
        cmd.extend(["-f", "1", "-l", str(args.pages)])

    print(f"   pdftoppm -r {args.dpi} ... ({args.pages} trang)")
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"   ❌ Lỗi pdftoppm: {result.stderr}")
        sys.exit(1)

    png_files = sorted([f for f in os.listdir(raw_dir) if f.endswith(".png")])
    print(f"   ✅ Trích xuất {len(png_files)} trang vào raw_pages/")

    # ------------------------
    # 2. Preprocess
    # ------------------------
    print()
    print("=" * 60)
    print("🔧 BƯỚC 2: Adaptive Threshold + Morphology")
    print("=" * 60)
    params = {
        'bilateral_d': args.bilateral_d,
        'bilateral_sigmaColor': 75,
        'bilateral_sigmaSpace': 75,
        'adaptive_block_size': args.block_size,
        'adaptive_C': args.C,
        'morph_kernel': [3, 3],
        'min_area': args.min_area,
    }
    print(f"   Bilateral: d={params['bilateral_d']}, sigmaColor=75, sigmaSpace=75")
    print(f"   Adaptive threshold: block={params['adaptive_block_size']}, C={params['adaptive_C']}")
    print(f"   Morphology: kernel={params['morph_kernel']}, min_area={params['min_area']}px")
    print()

    success = 0
    for f in png_files:
        inp = os.path.join(raw_dir, f)
        out = os.path.join(cleaned_dir, f)
        print(f"   {f} ...", end=" ", flush=True)
        ok = preprocess_page(inp, out, params)
        if ok:
            orig_kb = os.path.getsize(inp) / 1024
            clean_kb = os.path.getsize(out) / 1024
            print(f"✅ {orig_kb:.0f}KB → {clean_kb:.0f}KB")
            success += 1
        else:
            print("❌")

    print(f"\n   ✅ {success}/{len(png_files)} trang đã xử lý vào cleaned/")

    # ------------------------
    # 3. Gộp thành PDF
    # ------------------------
    print()
    print("=" * 60)
    print("🔧 BƯỚC 3: Gộp PNG → PDF")
    print("=" * 60)
    output_pdf = os.path.join(base_dir, f"{pdf_name}_preprocessed_{args.pages}p.pdf")
    cleaned_pngs = sorted([os.path.join(cleaned_dir, f) for f in os.listdir(cleaned_dir) if f.endswith(".png")])
    cmd = ["img2pdf"] + cleaned_pngs + ["-o", output_pdf]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode == 0:
        pdf_kb = os.path.getsize(output_pdf) / 1024
        print(f"   ✅ Output: {output_pdf} ({pdf_kb:.0f}KB)")
    else:
        print(f"   ❌ Lỗi img2pdf: {result.stderr}")

    # ------------------------
    # 4. So sánh file size
    # ------------------------
    print()
    print("=" * 60)
    print("📊 SO SÁNH")
    print("=" * 60)
    orig_kb = os.path.getsize(pdf_path) / 1024
    if os.path.isfile(output_pdf):
        new_kb = os.path.getsize(output_pdf) / 1024
        pct = (1 - new_kb / (orig_kb * args.pages / 41)) * 100  # ước lượng
        print(f"   PDF gốc (41 trang):   {orig_kb:.0f} KB")
        print(f"   PDF sạch ({args.pages} trang): {new_kb:.0f} KB")
        print(f"   Dung lượng giảm ~{pct:.0f}% (ước lượng)")
    print()
    print("✅ Demo hoàn thành! Hãy kiểm tra ảnh trong cleaned/ và PDF output.")

    # Also show comparison images if possible
    print()
    print("📂 Các file đã tạo:")
    for f in sorted(os.listdir(cleaned_dir)):
        path = os.path.join(cleaned_dir, f)
        print(f"   cleaned/{f}  ({os.path.getsize(path)//1024}KB)")
    print(f"   {os.path.basename(output_pdf)}  ({new_kb:.0f}KB)")


if __name__ == "__main__":
    main()
