# -*- coding: utf-8 -*-
"""형강 부재용 공통 모듈: pass6_multi.py 의 '끝점 연결 덩어리(connected component)' 추출을
   그대로 재사용한다. (같은 로직을 두 번 쓰지 않기 위해 분리 — 새 알고리즘이 아니다.)
   pass6 은 스크립트라 import 하면 전체가 실행되므로, 덩어리 추출 부분만 여기로 옮겨 왔다.
"""
import json, math, collections, os
import numpy as np

import product          # 제품 고유값(경로·도번규식·규격) 모음 — 기본값 500pro
SCR = product.SCR
FAB = product.FAB
TOL = 0.5


def load_index():
    d1 = json.load(open(SCR + r'\index.json', encoding='utf-8'))
    d2 = json.load(open(SCR + r'\index2.json', encoding='utf-8'))
    return d1, d2


def build_segments(d2):
    segs = []
    for l in d2['lines']:
        segs.append({'p0': (l[0], l[1]), 'p1': (l[2], l[3]), 'kind': 'L'})
    for a in d2['arcs']:
        cx, cy, r, s, e = a[:5]
        segs.append({'p0': (cx + r*math.cos(math.radians(s)), cy + r*math.sin(math.radians(s))),
                     'p1': (cx + r*math.cos(math.radians(e)), cy + r*math.sin(math.radians(e))),
                     'kind': 'A', 'arc': (cx, cy, r, s, e)})
    return segs


def components(segs, min_seg=2, max_seg=2000, min_dim=5.0, max_dim=20000.0):
    """끝점이 만나는 선분들을 한 덩어리로 묶는다. 필터는 호출자가 정한다
       (형강은 얇은 단면도라 pass6 의 판재용 필터보다 작은 것도 봐야 한다)."""
    node = {}

    def nid(p):
        k = (round(p[0]/TOL), round(p[1]/TOL))
        for dx in (-1, 0, 1):
            for dy in (-1, 0, 1):
                kk = (k[0]+dx, k[1]+dy)
                if kk in node:
                    return node[kk]
        v = len(node); node[k] = v; return v

    par = {}

    def find(x):
        while par.get(x, x) != x:
            par[x] = par.get(par[x], par[x]); x = par[x]
        return x

    def uni(a, b):
        ra, rb = find(a), find(b)
        if ra != rb:
            par[ra] = rb

    for s in segs:
        a, b = nid(s['p0']), nid(s['p1'])
        par.setdefault(a, a); par.setdefault(b, b)
        s['a'], s['b'] = a, b
        uni(a, b)

    comp = collections.defaultdict(list)
    for i, s in enumerate(segs):
        comp[find(s['a'])].append(i)

    out = []
    for root, idxs in comp.items():
        if not (min_seg <= len(idxs) <= max_seg):
            continue
        # 원호(ARC)는 끝점만으로는 크기를 못 잰다 — 불룩한 부분이 bbox 밖으로 빠져
        # 돔형·D형 부재의 덩어리 크기가 실제보다 작게 나온다(pass6_multi 와 같은 교정).
        _pp = []
        for i in idxs:
            s = segs[i]
            if s['kind'] == 'A':
                cx, cy, r, a0, a1 = s['arc']
                if a1 <= a0:
                    a1 += 360
                t = np.radians(np.linspace(a0, a1, max(6, int(abs(a1 - a0) / 4))))
                _pp.extend(zip(cx + r * np.cos(t), cy + r * np.sin(t)))
            else:
                _pp.extend((s['p0'], s['p1']))
        pts = np.array(_pp, float)
        x0, y0 = pts.min(0); x1, y1 = pts.max(0)
        w, h = x1-x0, y1-y0
        if not (min_dim < max(w, h) < max_dim):
            continue
        narc = sum(1 for i in idxs if segs[i]['kind'] == 'A')
        out.append(dict(root=root, idxs=idxs, bbox=(float(x0), float(y0), float(x1), float(y1)),
                        w=float(w), h=float(h), narc=narc, n=len(idxs)))
    return out


def dist_to_box(px, py, bb):
    x0, y0, x1, y1 = bb
    dx = max(x0-px, 0, px-x1); dy = max(y0-py, 0, py-y1)
    return math.hypot(dx, dy)


def callout_positions(d1):
    """도번 -> '단품도 옆' 좌표 목록(가까운 순 아님, 도면 등장 순).

    왜 필요한가: 도번은 '부품표(BOM) 열' 과 '단품도 옆' 두 곳에 적힌다. 먼저 만난 것을 쓰면
    부품표 좌표가 잡혀 엉뚱한 위치의 덩어리를 부재로 오인한다(500PRO 0031-004 가 RB702 보스
    그림으로 나왔던 사고, 400Q 채널 10개가 전부 같은 3331mm 조립도를 옆면도로 붙잡은 사고).
    부품표 열은 같은 x 에 도번이 여러 개 쌓이므로 그것으로 걸러낸다.
    사본 시트(주영역 밖)도 같이 뺀다 — 제품에 주영역이 없으면(500PRO) 이 조건은 무시된다.
    걸러서 하나도 안 남으면 원본 목록을 그대로 돌려준다(근거를 없애지 않는다).
    """
    import collections as _c
    loc = _c.defaultdict(list)
    for t in d1['texts']:
        m = product.PN.search(t[4].replace(' ', ''))
        if m:
            loc[m.group()].append((t[2], t[3]))
    xc = _c.Counter(round(x) for p in loc for x, y in loc[p])
    out = {}
    for p, ps in loc.items():
        q = [xy for xy in ps
             if xc[round(xy[0])] < 5
             and (product.Y_MAIN is None or xy[1] > product.Y_MAIN)]
        out[p] = q or ps
    return out
