#pip install PyPDF2
#pip install reportlab

#次のpythonプログラムを作ってください
#argparseでwork_dirとnmergeをうけとる。それぞれのデフォルトを., 10にする
#ディレクトリ"merged"がなければつくる
#work_dirから*.pdfを検索、ソートし、nmergeファイルごとに連結したPDFファイルを作って
#mergedに保存する。この際、読み込み、連結に失敗したファイルは、そのままmergedにコピー

import argparse
import glob
import os
import shutil
import io
import re
from PyPDF2 import PdfMerger, PdfReader
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.enums import TA_CENTER

"""
複数のPDFファイルをマージする
・どのPDFファイルがマージされたかは、マージファイルの先頭ページに記入
・マージされたファイルが始まるページには、そのファイル名のページを挿入

起動時引数:
  --mode: "rename"の場合、ソースファイル名の先頭に4桁の連番をつけてrenameする。
          4桁連番+'_'をもつファイル名が存在する場合、その最大数の次から連番をつける
  --mode: "merge" (デフォルト)の場合、mergeを実行

  -work_dir: ソースPDFファイルのディレクトリ
             マージしたファイルは merged サブディレクトリに保存される
  --nmerge: 1つのPDFファイルのマージする最大ファイル数
"""

# ヘルパー関数：ファイル名をリストしたPDFページを作成する（チャンク全体用）
def create_chunk_summary_page(filenames):
    """
    与えられたファイル名のリストを含むPDFページを生成し、バイトストリームとして返します。
    このページは、チャンク内のすべてのファイルを要約します。
    """
    buffer = io.BytesIO()
    doc = SimpleDocTemplate(buffer, pagesize=letter)
    styles = getSampleStyleSheet()
    story = []

    # ページタイトルを追加
    story.append(Paragraph("--- このチャンクで連結されるファイル ---", styles['h1']))
    story.append(Spacer(1, 12))

    # 各ファイル名を段落として追加
    for filename in filenames:
        story.append(Paragraph(os.path.basename(filename), styles['Normal']))
        story.append(Spacer(1, 6))

    story.append(PageBreak()) # 次のコンテンツが新しいページから始まるようにページ区切りを追加
    doc.build(story)
    buffer.seek(0)
    return buffer

# ヘルパー関数：単一のファイル名を含むPDFページを作成する（各ファイル用）
def create_filename_page(filename):
    """
    単一のファイル名を含むPDFページを生成し、バイトストリームとして返します。
    """
    buffer = io.BytesIO()
    doc = SimpleDocTemplate(buffer, pagesize=letter)
    styles = getSampleStyleSheet()
    
    # ファイル名表示用のスタイルを定義（中央揃え、大きめフォント）
    filename_style = styles['Normal']
    filename_style.alignment = TA_CENTER
    filename_style.fontSize = 24
    
    story = []
    # ページの中央にファイル名を表示するためにスペースを追加
    story.append(Spacer(1, 200)) # 上部の余白
    story.append(Paragraph(os.path.basename(filename), filename_style))
    story.append(Spacer(1, 200)) # 下部の余白
    story.append(PageBreak()) # 次のコンテンツが新しいページから始まるようにページ区切りを追加

    doc.build(story)
    buffer.seek(0)
    return buffer

def merge_pdfs(work_dir, nmerge):
    """
    指定されたディレクトリ内のPDFを、nmergeファイルごとに連結して'merged'ディレクトリに保存します。
    連結前に、チャンク全体のファイルリストページと、各PDFファイル名のページを追加します。
    """
    print("\n--- PDF連結モードを開始 ---")

    merged_dir = os.path.join(work_dir, "merged")
    
    # 'merged'ディレクトリの存在チェックとユーザーへの警告
    if os.path.exists(merged_dir):
        existing_pdf_files_in_merged = glob.glob(os.path.join(merged_dir, "*.pdf"))
        if len(existing_pdf_files_in_merged) > 0:
            print(f"警告: '{merged_dir}' ディレクトリには既に {len(existing_pdf_files_in_merged)} 個のPDFファイルが存在します。")
            for f in existing_pdf_files_in_merged:
                print(f"  [{f}]")

            ret = input("\n  続行する場合は 'y' を入力してください >> ")
            if ret.lower() == 'y':
                pass
            else:
                print(f"  '{ret}' が入力されました。処理を終了します。")
                return False
        else:
            print(f"警告: '{merged_dir}' ディレクトリは存在しますが、PDFファイルはありません。続行します。")
    else:
        print(f"'{merged_dir}' ディレクトリを作成します。")
        os.makedirs(merged_dir, exist_ok=True)
    
    # work_dirからPDFファイルを検索し、ソート
    pdf_files = sorted(glob.glob(os.path.join(work_dir, "*.pdf")))
    
    if not pdf_files:
        print(f"警告: '{work_dir}' ディレクトリにPDFファイルが見つかりませんでした。")
        return

    # PDFファイルをnmerge個ずつのチャンクに分割
    chunks = [pdf_files[i:i + nmerge] for i in range(0, len(pdf_files), nmerge)]
    
    idx = 1 # 出力ファイル名のインデックス
    nmerged = 0
    for i, chunk in enumerate(chunks):
        print(f"\n--- チャンク {i+1} の処理を開始 ---")

        merger = PdfMerger()
        
        # 出力ファイル名が既存のものと重複しないようにインデックスを決定
        current_idx = idx
        while True:
            # ファイル名に連番範囲またはチャンク番号を追加
            range_str = ""
            if chunk:
                # ファイル名から数字4桁の連番を抽出
                numbers = []
                for f in chunk:
                    match = re.match(r'^(\d{4})_', os.path.basename(f))
                    if match:
                        numbers.append(int(match.group(1)))
                
                if numbers:
                    min_num = min(numbers)
                    max_num = max(numbers)
                    range_str = f"_{min_num:04d}-{max_num:04d}"
                else:
                    range_str = f"_{nmerged+1:03d}_{nmerged+1+len(chunk):03d}" # 連番がない場合はチャンク番号
            
            output_filename = f"merged_{current_idx:03d}{range_str}.pdf"
            output_path = os.path.join(merged_dir, output_filename)
            if not os.path.exists(output_path):
                break
            current_idx += 1
        idx = current_idx + 1 # 次のチャンクのためにグローバルインデックスを更新
        
        print(f"  出力パス: [{output_path}]")

        # チャンクにファイルがある場合のみ、チャンク全体のファイルリストのページを追加
        if chunk:
            filenames_only_for_summary = [os.path.basename(f) for f in chunk]
            chunk_summary_pdf_buffer = create_chunk_summary_page(filenames_only_for_summary)
            merger.append(PdfReader(chunk_summary_pdf_buffer))
            print(f"  チャンク全体のファイルリストページを追加しました。")

        failed_files = []
        # チャンク内のPDFを連結
        for pdf_file in chunk:
            print(f"  + 処理中: [{pdf_file}]")
            
            # 個々のファイル名ページを追加
            try:
                filename_page_buffer = create_filename_page(pdf_file)
                merger.append(PdfReader(filename_page_buffer))
                print(f"    ファイル名ページ '{os.path.basename(pdf_file)}' を追加しました。")
            except Exception as e:
                print(f"    警告: ファイル名ページ '{os.path.basename(pdf_file)}' の作成または追加に失敗しました。エラー: {e}")
                # ファイル名ページの失敗は、元のPDFの連結を妨げない

            # 元のPDFを連結
            try:
                merger.append(pdf_file)
            except Exception as e:
                print(f"    失敗: '{pdf_file}' の読み込みに失敗しました。このファイルは連結対象外です。エラー: {e}")
                failed_files.append(pdf_file)
        
        # 連結できたPDFがある場合のみ保存
        # (ファイルリストページのみの場合も保存されるように、失敗ファイルがチャンクの全ファイルでないことを確認)
        if len(chunk) > len(failed_files) or (len(chunk) == 0 and len(failed_files) == 0):
            try:
                merger.write(output_path)
                print(f"  '{output_path}' を作成しました。")
            except Exception as e:
                print(f"  エラー: '{output_path}' の書き込みに失敗しました。エラー: {e}")
            finally:
                merger.close()
        else:
            print(f"  このチャンクでは連結可能なPDFがありませんでした。'{output_path}' は作成されません。")
        
        # 読み込みに失敗したPDFを'merged'ディレクトリにコピー
        for failed_file in failed_files:
            try:
                shutil.copy(failed_file, merged_dir)
                print(f"  読み込みに失敗したファイル '{os.path.basename(failed_file)}' を'merged'ディレクトリにコピーしました。")
            except Exception as e:
                print(f"  エラー: ファイル '{failed_file}' のコピーに失敗しました。エラー: {e}")
        
        print(f"--- チャンク {i+1} の処理を終了 ---\n")
        
        nmerged += len(chunk)

    return True

def rename_pdfs(work_dir):
    """
    指定されたディレクトリ内のPDFファイルをリネームします。
    ファイル名が数字4桁+'_'で始まるものから最大の数字を抽出し、
    それ以外のファイルをソートして、その次の連番から4桁+'_'を付けてリネームします。
    """
    print("\n--- PDFリネームモードを開始 ---")

    pdf_files = sorted(glob.glob(os.path.join(work_dir, "*.pdf")))
    
    if not pdf_files:
        print(f"警告: '{work_dir}' ディレクトリにPDFファイルが見つかりませんでした。")
        return

    max_idx = -1
    unprefixed_files = [] # プレフィックスのないファイル

    # 既存の連番プレフィックスを持つファイルから最大インデックスを抽出
    for f in pdf_files:
        basename = os.path.basename(f)
        match = re.match(r'^(\d{4})_', basename)
        if match:
            current_idx = int(match.group(1))
            if current_idx > max_idx:
                max_idx = current_idx
            print(f"  既存の連番ファイル: [{basename}] (インデックス: {current_idx})")
        else:
            unprefixed_files.append(f)
            print(f"  プレフィックスなしファイル: [{basename}]")

    start_idx = max_idx + 1
    if start_idx == 0: # 既存の連番ファイルが一つもなかった場合
        start_idx = 1
    
    print(f"\n  リネーム開始インデックス: {start_idx:04d}")

    # プレフィックスのないファイルをソートしてリネーム
    for i, old_path in enumerate(sorted(unprefixed_files)): # ファイル名をソートしてから処理
        basename = os.path.basename(old_path)
        new_prefix = f"{start_idx + i:04d}_"
        new_basename = new_prefix + basename
        new_path = os.path.join(work_dir, new_basename)

        if os.path.exists(new_path):
            print(f"  警告: リネーム先のファイル '{new_path}' が既に存在します。スキップします。")
            continue
        
        try:
            os.rename(old_path, new_path)
            print(f"  リネーム: '{basename}' -> '{new_basename}'")
        except Exception as e:
            print(f"  エラー: '{old_path}' のリネームに失敗しました。エラー: {e}")
    
    print("\n--- PDFリネームモードを終了 ---")
    return True

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="PDFファイルの連結またはリネームを行います。")
    parser.add_argument(
        "work_dir",
        nargs="?",
        default=".",
        help="PDFを検索するディレクトリ。デフォルト: '.' (カレントディレクトリ)"
    )
    parser.add_argument(
        "-n",
        "--nmerge",
        type=int,
        default=10,
        help="連結するPDFファイルの数（mergeモードのみ有効）。デフォルト: 10"
    )
    parser.add_argument(
        "-m",
        "--mode",
        choices=['merge', 'rename'],
        default="merge",
        help="実行モードを選択します: 'merge' (PDF連結) または 'rename' (PDFリネーム)。デフォルト: 'merge'"
    )

    args = parser.parse_args()
    
    if not os.path.isdir(args.work_dir):
        print(f"エラー: 指定されたディレクトリ '{args.work_dir}' が見つかりません。")
    else:
        if args.mode == "merge":
            merge_pdfs(args.work_dir, args.nmerge)
        elif args.mode == "rename":
            rename_pdfs(args.work_dir)
