#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Layout Detector — разделение страницы чертежа на зоны.

Зоны:
- "drawing" — схемы, виды, разрезы (линии + текст, разрежено)
- "table" — таблицы (плотные линии в сетке)
- "title_block" — штамп (нижний правый угол или низ страницы)
- "notes" — примечания, текстовые блоки
- "legend" — легенда/условные обозначения

Алгоритм:
1. Находит все линии на странице
2. Находит прямоугольники = таблицы
3. Анализирует плотность OCR текста
4. Классифицирует регионы
"""

import sys
import json
from pathlib import Path
from typing import List, Dict, Tuple
import cv2
import numpy as np
from PIL import Image


def find_all_lines(img_gray: np.ndarray, min_length: int = 40):
    """Находит все прямые линии (горизонтальные и вертикальные)."""
    _, binary = cv2.threshold(img_gray, 180, 255, cv2.THRESH_BINARY_INV)
    h, w = binary.shape
    lines = []
    
    # Горизонтальные
    for y in range(h):
        row = binary[y, :]
        in_line = False
        start = 0
        for x in range(w):
            if row[x] > 128:
                if not in_line:
                    in_line = True
                    start = x
            else:
                if in_line:
                    length = x - start
                    if length >= min_length:
                        lines.append(("h", start, y, x-1, y))
                    in_line = False
        if in_line:
            length = w - start
            if length >= min_length:
                lines.append(("h", start, y, w-1, y))
    
    # Вертикальные
    for x in range(w):
        col = binary[:, x]
        in_line = False
        start = 0
        for y in range(h):
            if col[y] > 128:
                if not in_line:
                    in_line = True
                    start = y
            else:
                if in_line:
                    length = y - start
                    if length >= min_length:
                        lines.append(("v", x, start, x, y-1))
                    in_line = False
        if in_line:
            length = h - start
            if length >= min_length:
                lines.append(("v", x, start, x, h-1))
    
    return lines


def find_rectangles(lines: List[Tuple], min_size: int = 100) -> List[Dict]:
    """Находит прямоугольники, образованные пересечением линий."""
    horiz = [(l[1], l[2], l[3], l[4]) for l in lines if l[0] == "h"]
    vert = [(l[1], l[2], l[3], l[4]) for l in lines if l[0] == "v"]
    
    # Группируем горизонтальные по Y
    from collections import defaultdict
    h_by_y = defaultdict(list)
    for x1, y1, x2, y2 in horiz:
        h_by_y[y1].append((x1, x2))
    
    # Группируем вертикальные по X
    v_by_x = defaultdict(list)
    for x1, y1, x2, y2 in vert:
        v_by_x[x1].append((y1, y2))
    
    rects = []
    # Ищем пары горизонтальных линий с общими вертикальными
    y_vals = sorted(h_by_y.keys())
    for i in range(len(y_vals)):
        for j in range(i+1, len(y_vals)):
            y_top = y_vals[i]
            y_bottom = y_vals[j]
            # Ищем общий X-интервал
            for x1_a, x2_a in h_by_y[y_top]:
                for x1_b, x2_b in h_by_y[y_bottom]:
                    x_left = max(x1_a, x1_b)
                    x_right = min(x2_a, x2_b)
                    if x_right - x_left < min_size:
                        continue
                    # Проверяем, есть ли вертикальные линии на x_left и x_right
                    has_left = any(y_top <= y_bottom and not (y2 < y_top or y1 > y_bottom) 
                                   for y1, y2 in v_by_x.get(x_left, []))
                    has_right = any(y_top <= y_bottom and not (y2 < y_top or y1 > y_bottom)
                                    for y1, y2 in v_by_x.get(x_right, []))
                    if has_left and has_right:
                        rects.append({
                            "x": x_left, "y": y_top,
                            "w": x_right - x_left, "h": y_bottom - y_top
                        })
    
    # Фильтруем вложенные прямоугольники (оставляем только внешние)
    filtered = []
    for r in rects:
        is_inner = False
        for other in rects:
            if r is other:
                continue
            if (r["x"] > other["x"] and r["y"] > other["y"] and
                r["x"] + r["w"] < other["x"] + other["w"] and
                r["y"] + r["h"] < other["y"] + other["h"]):
                is_inner = True
                break
        if not is_inner:
            filtered.append(r)
    
    return filtered


def classify_regions(rects: List[Dict], ocr_lines: List[Dict], img_w: int, img_h: int) -> List[Dict]:
    """Классифицирует регионы страницы."""
    regions = []
    
    # 1. Таблицы = большие прямоугольники с высокой плотностью линий
    for r in rects:
        area = r["w"] * r["h"]
        # Считаем OCR строки внутри
        texts_in = [t for t in ocr_lines
                    if r["x"] <= t["cx"] <= r["x"] + r["w"]
                    and r["y"] <= t["cy"] <= r["y"] + r["h"]]
        density = len(texts_in) / (area / 1000000)  # текстов на мегапиксель
        
        if density > 20:  # высокая плотность = таблица
            regions.append({
                "type": "table",
                "bbox": [r["x"], r["y"], r["x"]+r["w"], r["y"]+r["h"]],
                "density": density,
                "text_count": len(texts_in)
            })
    
    # 2. Определяем чертежи = области с линиями и текстом, но без плотной сетки
    # Для простоты: левая половина, не покрытая таблицами
    # Найдём ограничивающий bbox для всех "чертёжных" текстов
    drawing_texts = [t for t in ocr_lines if t["cy"] < img_h * 0.75 and t["cx"] < img_w * 0.6]
    if drawing_texts:
        xs = [t["cx"] for t in drawing_texts]
        ys = [t["cy"] for t in drawing_texts]
        # Расширяем на 200px
        dx = [t["cx"] - t["x1"] for t in drawing_texts if "x1" in t]
        max_w = max(dx) if dx else 100
        regions.append({
            "type": "drawing",
            "bbox": [max(0, min(xs)-max_w), max(0, min(ys)-100),
                     min(img_w, max(xs)+max_w), min(img_h, max(ys)+100)],
            "text_count": len(drawing_texts)
        })
    
    # 3. Штамп = низ страницы, мелкий текст
    title_texts = [t for t in ocr_lines if t["cy"] > img_h * 0.85]
    if title_texts:
        xs = [t["cx"] for t in title_texts]
        ys = [t["cy"] for t in title_texts]
        regions.append({
            "type": "title_block",
            "bbox": [min(xs)-50, min(ys)-50, max(xs)+50, max(ys)+50],
            "text_count": len(title_texts)
        })
    
    # 4. Примечания = текстовые блоки
    note_keywords = ["примечание", "общие указания", "границы", "размеры"]
    note_texts = [t for t in ocr_lines
                  if any(kw in t["text"].lower() for kw in note_keywords)]
    if note_texts:
        xs = [t["cx"] for t in note_texts]
        ys = [t["cy"] for t in note_texts]
        regions.append({
            "type": "notes",
            "bbox": [min(xs)-100, min(ys)-100, max(xs)+100, max(ys)+100],
            "text_count": len(note_texts)
        })
    
    return regions


def detect_layout(png_path: Path, ocr_path: Path) -> Dict:
    """Основная функция layout detection."""
    img = cv2.imread(str(png_path), cv2.IMREAD_GRAYSCALE)
    h, w = img.shape[:2]
    
    # Загрузить OCR
    ocr = json.loads(ocr_path.read_text(encoding="utf-8"))
    
    # Собрать все OCR lines с координатами
    all_texts = []
    for page in ocr.get("pages", []):
        for line in page.get("ocr_lines", []):
            bbox = line.get("bbox", [])
            if not bbox:
                continue
            if isinstance(bbox[0], list):
                xs = [p[0] for p in bbox]
                ys = [p[1] for p in bbox]
            else:
                xs = [bbox[0], bbox[2]]
                ys = [bbox[1], bbox[3]]
            all_texts.append({
                "text": line["text"],
                "cx": sum(xs)/len(xs),
                "cy": sum(ys)/len(ys),
                "x1": min(xs), "y1": min(ys),
                "x2": max(xs), "y2": max(ys),
                "bbox": bbox
            })
    
    # Найти линии
    lines = find_all_lines(img)
    print(f"[INFO] Найдено {len(lines)} линий")
    
    # Найти прямоугольники
    rects = find_rectangles(lines)
    print(f"[INFO] Найдено {len(rects)} прямоугольников")
    
    # Классифицировать
    regions = classify_regions(rects, all_texts, w, h)
    print(f"[INFO] Классифицировано {len(regions)} регионов")
    for r in regions:
        print(f"  {r['type']}: bbox={r['bbox']}, texts={r.get('text_count', 0)}")
    
    return {
        "image_size": [w, h],
        "regions": regions,
        "rectangles": rects,
        "line_count": len(lines)
    }


def visualize_layout(png_path: Path, layout: Dict, out_path: Path):
    """Рисует зоны на изображении."""
    img = Image.open(png_path)
    draw = ImageDraw.Draw(img)
    colors = {
        "table": "blue",
        "drawing": "green",
        "title_block": "purple",
        "notes": "orange"
    }
    
    for region in layout["regions"]:
        x1, y1, x2, y2 = region["bbox"]
        color = colors.get(region["type"], "red")
        draw.rectangle([x1, y1, x2, y2], outline=color, width=4)
        draw.text((x1+5, y1+5), region["type"], fill=color)
    
    img.save(out_path)
    print(f"[OK] Layout visualization: {out_path}")


def main():
    if len(sys.argv) < 3:
        print("Usage: python layout_detector.py <png> <ocr_json>")
        sys.exit(1)
    
    png = Path(sys.argv[1])
    ocr = Path(sys.argv[2])
    out_json = png.parent / "layout.json"
    out_png = png.parent / f"{png.stem}_layout.png"
    
    layout = detect_layout(png, ocr)
    
    with open(out_json, "w", encoding="utf-8") as f:
        json.dump(layout, f, ensure_ascii=False, indent=2)
    print(f"[OK] Layout JSON: {out_json}")
    
    visualize_layout(png, layout, out_png)


if __name__ == "__main__":
    from PIL import ImageDraw
    main()
