#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
概要:
    2つのテキストファイルの差分を比較して表示するプログラムです。

詳細説明:
    compare_update_diff.py から、2つのテキストファイルのdiff機能だけを抜き出しています。
    第1位置引数に old_file、第2位置引数に new_file を指定します。
    例として python compare_file_diff.py old.py new.py や
    python compare_file_diff.py old.py new.py --context 3 のように実行します。
"""

import argparse
import difflib
from pathlib import Path


def read_text_lines(path):
    """
    概要:
        テキストファイルを読み込み、行リストと文字コードを返します。

    詳細説明:
        UTF-8を優先し、日本語Windows環境を考慮してcp932も試します。
        改行コードは universal newline で正規化するため、
        CRLFとLFの違いだけではdiffになりません。

    引数:
        :param path: 読み込むテキストファイルのパス。
        :type path: str または pathlib.Path

    戻り値:
        :returns: ファイルの行リストと判定された文字コードのタプル。
        :rtype: tuple
    """
    encodings = ("utf-8-sig", "utf-8", "cp932")

    for enc in encodings:
        try:
            with open(path, "r", encoding=enc) as f:
                return f.readlines(), enc
        except UnicodeDecodeError:
            pass

    with open(path, "r", encoding="utf-8", errors="replace") as f:
        return f.readlines(), "utf-8(replace)"


def make_compact_diff(old_path, new_path, context=2):
    """
    概要:
        コンパクトな差分リストを作成します。

    詳細説明:
        実際の変更行とその前後のコンテキスト行だけを出力します。
        unified diffのヘッダや行番号情報などは表示しません。
        離れた変更箇所はドット3つで区切ります。

    引数:
        :param old_path: 比較元のファイルパス。
        :type old_path: str または pathlib.Path
        :param new_path: 比較先のファイルパス。
        :type new_path: str または pathlib.Path
        :param context: 変更行の前後に表示するコンテキスト行数。
        :type context: int

    戻り値:
        :returns: 差分行のリスト、比較元ファイルの文字コード、比較先ファイルの文字コードのタプル。
        :rtype: tuple
    """
    old_lines, old_enc = read_text_lines(old_path)
    new_lines, new_enc = read_text_lines(new_path)

    diff_lines = list(
        difflib.unified_diff(
            old_lines,
            new_lines,
            fromfile=str(old_path),
            tofile=str(new_path),
            n=max(0, int(context)),
            lineterm="\n",
        )
    )

    compact = []
    seen_hunk = False

    for line in diff_lines:
        if line.startswith("--- ") or line.startswith("+++ "):
            continue

        if line.startswith("@@ "):
            if seen_hunk and compact and compact[-1] != "...":
                compact.append("...")
            seen_hunk = True
            continue

        if line.startswith("\\ No newline at end of file"):
            continue

        if line.endswith("\n"):
            line = line[:-1]

        compact.append(line)

    while compact and compact[0] == "...":
        compact.pop(0)
    while compact and compact[-1] == "...":
        compact.pop()

    return compact, old_enc, new_enc


def main():
    """
    概要:
        コマンドライン引数を解析し、差分比較処理を実行します。

    詳細説明:
        argparseを用いて old_file と new_file、および context オプションを受け取ります。
        指定されたファイルが存在するか確認し、make_compact_diff関数を呼び出して結果を標準出力に表示します。
    """
    parser = argparse.ArgumentParser(
        description="Show a compact text diff between two files."
    )
    parser.add_argument(
        "old_file",
        help="Old/reference file",
    )
    parser.add_argument(
        "new_file",
        help="New/current file",
    )
    parser.add_argument(
        "--context", "-c",
        type=int,
        default=2,
        help="Number of unchanged context lines around each change (default: 2)",
    )

    args = parser.parse_args()

    old_path = Path(args.old_file).expanduser().resolve()
    new_path = Path(args.new_file).expanduser().resolve()

    if not old_path.is_file():
        parser.error(f"old_file is not found: {old_path}")

    if not new_path.is_file():
        parser.error(f"new_file is not found: {new_path}")

    diff_lines, old_enc, new_enc = make_compact_diff(
        old_path,
        new_path,
        context=max(0, args.context),
    )

    print(f"old: {old_path} ({old_enc})")
    print(f"new: {new_path} ({new_enc})")
    print(f"context: {max(0, args.context)}")
    print("---")

    if diff_lines:
        print("\n".join(diff_lines))
    else:
        print("(no text difference found)")


if __name__ == "__main__":
    main()