import base64
import html
import re
from io import BytesIO
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont


# ============================================================
# 설정
# ============================================================

INPUT_HTML = "input.html"
WORD_FILE = "delete_word.txt"
OUTPUT_HTML = "output.html"

# Windows 맑은 고딕
FONT_PATH = r"C:\Windows\Fonts\malgun.ttf"

# 기본 글자 크기
DEFAULT_FONT_SIZE = 16


# ============================================================
# 1. del.txt 읽기
# ============================================================

def load_words():
    """
    del.txt에서 이미지로 변경할 단어를 읽는다.

    예:
        전자문서
        발송기관
        확인
        인증
    """

    path = Path(WORD_FILE)

    if not path.exists():
        print(f"[ERROR] {WORD_FILE} 파일이 없습니다.")
        return []

    words = []

    with open(path, "r", encoding="utf-8-sig") as f:

        for line in f:

            word = line.strip()

            if word:
                words.append(word)

    # 긴 단어부터 처리
    # 예:
    # 전자
    # 전자문서
    #
    # 이 경우 전자문서를 먼저 처리
    words.sort(
        key=len,
        reverse=True
    )

    return words


# ============================================================
# 2. display:none 요소 제거
# ============================================================

def remove_display_none(html_text):
    """
    style 속성에 display:none이 있는 요소를
    HTML에서 완전히 제거한다.

    예:

    전<b style="display:none"></b>자

        ↓

    전자


    <span style="display:none">
        ABC
    </span>

        ↓

    완전히 삭제
    """

    total_removed = 0

    # 중첩된 display:none 요소도 처리하기 위해 반복
    while True:

        before = html_text

        # ----------------------------------------------------
        # 일반적인 시작/종료 태그
        #
        # <b style="display:none">...</b>
        # <span style="display:none">...</span>
        # <mark style="display:none"></mark>
        # <ins style="display:none"></ins>
        # <table style="display:none">...</table>
        # ----------------------------------------------------

        pattern = re.compile(
            r'''
            <
            (?P<tag>[a-zA-Z][a-zA-Z0-9]*)
            \b

            (?=
                [^>]*
                \bstyle\s*=\s*
                ["']
                [^"']*
                display\s*:\s*none
                [^"']*
                ["']
            )

            [^>]*
            >

            .*?

            </(?P=tag)\s*>
            ''',

            re.IGNORECASE |
            re.DOTALL |
            re.VERBOSE
        )

        html_text, count = pattern.subn(
            "",
            html_text
        )

        total_removed += count

        # ----------------------------------------------------
        # img / input / br 등 종료태그가 없는 요소
        # ----------------------------------------------------

        void_pattern = re.compile(
            r'''
            <
            (?P<tag>
                img|
                input|
                br|
                hr|
                meta|
                link|
                source|
                area|
                base|
                embed|
                param|
                track|
                wbr
            )

            \b

            (?=
                [^>]*
                \bstyle\s*=\s*
                ["']
                [^"']*
                display\s*:\s*none
                [^"']*
                ["']
            )

            [^>]*
            /?
            >
            ''',

            re.IGNORECASE |
            re.VERBOSE
        )

        html_text, count = void_pattern.subn(
            "",
            html_text
        )

        total_removed += count

        # 더 이상 변화가 없으면 종료
        if html_text == before:
            break

    return html_text, total_removed


# ============================================================
# 3. 글자 이미지 생성
# ============================================================

def text_to_base64_image(text, font_size=DEFAULT_FONT_SIZE):
    """
    문자열을 투명 PNG 이미지로 만든 후
    Base64 문자열로 반환한다.
    """

    try:

        font = ImageFont.truetype(
            FONT_PATH,
            font_size
        )

    except Exception as e:

        print("[WARNING] 폰트 로딩 실패:", e)

        font = ImageFont.load_default()

    # --------------------------------------------------------
    # 글자 실제 크기 계산
    # --------------------------------------------------------

    temp_image = Image.new(
        "RGBA",
        (10, 10),
        (255, 255, 255, 0)
    )

    draw = ImageDraw.Draw(temp_image)

    bbox = draw.textbbox(
        (0, 0),
        text,
        font=font
    )

    width = bbox[2] - bbox[0]
    height = bbox[3] - bbox[1]

    # 여유 공간
    padding_x = 2
    padding_y = 2

    image_width = max(
        width + padding_x * 2,
        1
    )

    image_height = max(
        height + padding_y * 2,
        1
    )

    # --------------------------------------------------------
    # 투명 이미지 생성
    # --------------------------------------------------------

    image = Image.new(
        "RGBA",
        (image_width, image_height),
        (255, 255, 255, 0)
    )

    draw = ImageDraw.Draw(image)

    draw.text(
        (
            padding_x - bbox[0],
            padding_y - bbox[1]
        ),
        text,
        font=font,
        fill=(0, 0, 0, 255)
    )

    # --------------------------------------------------------
    # PNG → Base64
    # --------------------------------------------------------

    buffer = BytesIO()

    image.save(
        buffer,
        format="PNG"
    )

    base64_data = base64.b64encode(
        buffer.getvalue()
    ).decode("ascii")

    return (
        base64_data,
        image_width,
        image_height
    )


# ============================================================
# 4. <img> 태그 만들기
# ============================================================

def make_image_tag(word):

    base64_data, width, height = text_to_base64_image(
        word
    )

    safe_word = html.escape(
        word,
        quote=True
    )

    return (
        '<img '
        f'src="data:image/png;base64,{base64_data}" '
        f'alt="{safe_word}" '
        f'width="{width}" '
        f'height="{height}" '
        'style="'
        'display:inline-block;'
        'vertical-align:middle;'
        'margin:0;'
        'padding:0;'
        'border:0;'
        'max-width:none;'
        '">'
    )


# ============================================================
# 5. 텍스트에서 del.txt 단어 찾기
# ============================================================

def replace_words(text, words):

    result = text
    count = 0

    for word in words:

        if word not in result:
            continue

        found_count = result.count(word)

        if found_count == 0:
            continue

        image_tag = make_image_tag(
            word
        )

        result = result.replace(
            word,
            image_tag
        )

        count += found_count

    return result, count


# ============================================================
# 6. HTML의 표시되는 텍스트만 처리
# ============================================================

def process_html(html_text, words):

    """
    HTML 태그 자체는 건드리지 않고

        <div>전자문서 확인</div>

    에서

        전자문서
        확인

    같은 실제 텍스트만 처리한다.
    """

    # HTML 태그와 텍스트를 분리
    parts = re.split(
        r'(<[^>]+>)',
        html_text
    )

    result = []

    total_count = 0

    inside_script = False
    inside_style = False

    for part in parts:

        lower = part.lower()

        # ----------------------------------------------------
        # script
        # ----------------------------------------------------

        if lower.startswith("<script"):
            inside_script = True
            result.append(part)
            continue

        if lower.startswith("</script"):
            inside_script = False
            result.append(part)
            continue

        # ----------------------------------------------------
        # style
        # ----------------------------------------------------

        if lower.startswith("<style"):
            inside_style = True
            result.append(part)
            continue

        if lower.startswith("</style"):
            inside_style = False
            result.append(part)
            continue

        # ----------------------------------------------------
        # HTML 태그
        # ----------------------------------------------------

        if part.startswith("<"):
            result.append(part)
            continue

        # ----------------------------------------------------
        # JavaScript/CSS 내용은 건드리지 않는다
        # ----------------------------------------------------

        if inside_script or inside_style:
            result.append(part)
            continue

        # ----------------------------------------------------
        # 실제 표시 텍스트
        # ----------------------------------------------------

        new_text, count = replace_words(
            part,
            words
        )

        result.append(
            new_text
        )

        total_count += count

    return "".join(result), total_count


# ============================================================
# 7. MAIN
# ============================================================

def main():

    print("--------------------------------")
    print("HTML TEXT -> IMAGE")
    print("--------------------------------")

    # --------------------------------------------------------
    # input.html 확인
    # --------------------------------------------------------

    input_path = Path(
        INPUT_HTML
    )

    if not input_path.exists():

        print(
            f"[ERROR] {INPUT_HTML} 파일이 없습니다."
        )

        return

    # --------------------------------------------------------
    # HTML 읽기
    # --------------------------------------------------------

    with open(
        input_path,
        "r",
        encoding="utf-8-sig"
    ) as f:

        html_text = f.read()

    # --------------------------------------------------------
    # 1단계
    # display:none 제거
    # --------------------------------------------------------

    html_text, removed_count = remove_display_none(
        html_text
    )

    print(
        f"[SUCCESS] display:none 제거 : "
        f"{removed_count}개"
    )

    # --------------------------------------------------------
    # 2단계
    # del.txt 읽기
    # --------------------------------------------------------

    words = load_words()

    if not words:

        print(
            "[ERROR] del.txt에 단어가 없습니다."
        )

        return

    print(
        f"[SUCCESS] del.txt 단어 : "
        f"{len(words)}개"
    )

    # --------------------------------------------------------
    # 3단계
    # 단어 → 이미지
    # --------------------------------------------------------

    new_html, changed_count = process_html(
        html_text,
        words
    )

    # --------------------------------------------------------
    # 4단계
    # output.html 저장
    # --------------------------------------------------------

    with open(
        OUTPUT_HTML,
        "w",
        encoding="utf-8"
    ) as f:

        f.write(
            new_html
        )

    print(
        f"[SUCCESS] 이미지 변환 : "
        f"{changed_count}개"
    )

    print(
        f"[SUCCESS] 저장 : "
        f"{OUTPUT_HTML}"
    )


# ============================================================
# 실행
# ============================================================

if __name__ == "__main__":
    main()