#!/usr/bin/env python
"""
概要:
    Windows版PowerPointを使って、PPTXファイルをMP4へ変換します。
詳細説明:
    必要条件として pip install pywin32 を実行してモジュールをインストールしてください。
関連リンク:
    pptx2mp4_usage
"""

from __future__ import annotations

import argparse
import sys
import time
from pathlib import Path

import pythoncom
import win32com.client


# PowerPoint.PpMediaTaskStatus
STATUS_NONE = 0
STATUS_IN_PROGRESS = 1
STATUS_QUEUED = 2
STATUS_DONE = 3
STATUS_FAILED = 4

STATUS_NAMES = {
    STATUS_NONE: "準備中",
    STATUS_IN_PROGRESS: "変換中",
    STATUS_QUEUED: "待機中",
    STATUS_DONE: "完了",
    STATUS_FAILED: "失敗",
}


def convert_pptx_to_mp4(
    input_path: Path,
    output_path: Path,
    *,
    resolution: int = 720,
    fps: int = 30,
    quality: int = 100,
    default_slide_duration: int = 5,
    use_timings_and_narrations: bool = True,
    overwrite: bool = False,
) -> None:
    """
    概要:
        PowerPointのCreateVideoを使ってPPTXをMP4に変換します。
    詳細説明:
        入力されたPowerPointファイルを指定された設定でMP4動画ファイルに変換します。
        変換の進捗状況を標準出力に表示し、完了するまで待機します。
    引数:
        :param input_path: 入力となるPowerPointファイルのパス。
        :type input_path: pathlib.Path
        :param output_path: 出力するMP4ファイルのパス。
        :type output_path: pathlib.Path
        :param resolution: 動画の縦解像度。
        :type resolution: int
        :param fps: 動画のフレームレート。
        :type fps: int
        :param quality: 動画の品質設定。
        :type quality: int
        :param default_slide_duration: タイミング未設定時の1スライドの秒数。
        :type default_slide_duration: int
        :param use_timings_and_narrations: 記録済みのタイミングとナレーションを使用するかどうか。
        :type use_timings_and_narrations: bool
        :param overwrite: 既存の出力ファイルがある場合に上書きするかどうか。
        :type overwrite: bool
    戻り値:
        :returns: なし。
        :rtype: None
    例外:
        :raises FileNotFoundError: 入力ファイルが存在しない場合に発生します。
        :raises ValueError: 入出力ファイルの拡張子が適切でない場合に発生します。
        :raises FileExistsError: 出力ファイルがすでに存在し、overwriteがFalseの場合に発生します。
        :raises RuntimeError: PowerPointのビデオ作成処理に失敗した場合、またはMP4ファイルが作成されなかった場合に発生します。
    """

    input_path = input_path.resolve()
    output_path = output_path.resolve()

    if not input_path.is_file():
        raise FileNotFoundError(f"入力ファイルが見つかりません: {input_path}")

    if input_path.suffix.lower() not in {".pptx", ".pptm", ".ppt"}:
        raise ValueError(f"PowerPointファイルではありません: {input_path}")

    if output_path.suffix.lower() != ".mp4":
        raise ValueError("出力ファイルの拡張子は .mp4 にしてください。")

    output_path.parent.mkdir(parents=True, exist_ok=True)

    if output_path.exists():
        if not overwrite:
            raise FileExistsError(
                f"出力ファイルがすでに存在します: {output_path}\n"
                "--overwrite を指定すると上書きできます。"
            )
        output_path.unlink()

    pythoncom.CoInitialize()

    powerpoint = None
    presentation = None

    try:
        # 独立したPowerPointプロセスを起動
        powerpoint = win32com.client.DispatchEx("PowerPoint.Application")
        powerpoint.Visible = True

        print(f"入力: {input_path}")
        print(f"出力: {output_path}")
        print(
            f"設定: {resolution}p, {fps} fps, "
            f"quality={quality}, timings={use_timings_and_narrations}"
        )

        # Open(FileName, ReadOnly, Untitled, WithWindow)
        presentation = powerpoint.Presentations.Open(
            str(input_path),
            True,   # ReadOnly
            False,  # Untitled
            True,   # WithWindow
        )

        # CreateVideo(
        #     FileName,
        #     UseTimingsAndNarrations,
        #     DefaultSlideDuration,
        #     VertResolution,
        #     FramesPerSecond,
        #     Quality
        # )
        presentation.CreateVideo(
            str(output_path),
            use_timings_and_narrations,
            default_slide_duration,
            resolution,
            fps,
            quality,
        )

        print("ビデオの作成を開始しました。")

        previous_status = None

        while True:
            status = int(presentation.CreateVideoStatus)

            if status != previous_status:
                print(f"状態: {STATUS_NAMES.get(status, f'不明 ({status})')}")
                previous_status = status

            if status == STATUS_DONE:
                break

            if status == STATUS_FAILED:
                raise RuntimeError("PowerPointによるビデオ作成に失敗しました。")

            time.sleep(2)

        if not output_path.exists():
            raise RuntimeError(
                "PowerPointは完了を返しましたが、MP4ファイルが見つかりません。"
            )

        size_mb = output_path.stat().st_size / (1024 * 1024)

        print("変換が完了しました。")
        print(f"MP4: {output_path}")
        print(f"サイズ: {size_mb:.1f} MB")

    finally:
        if presentation is not None:
            try:
                presentation.Close()
            except Exception:
                pass

        if powerpoint is not None:
            try:
                powerpoint.Quit()
            except Exception:
                pass

        pythoncom.CoUninitialize()


def parse_args() -> argparse.Namespace:
    """
    概要:
        コマンドライン引数を解析します。
    詳細説明:
        argparseを使って、入力ファイル、出力ファイル、解像度などの変換オプションを読み取ります。
    戻り値:
        :returns: 解析されたコマンドライン引数を格納した名前空間オブジェクト。
        :rtype: argparse.Namespace
    """
    parser = argparse.ArgumentParser(
        description="Windows版PowerPointを使ってPPTXをMP4に変換します。"
    )

    parser.add_argument(
        "input",
        type=Path,
        help="入力するPowerPointファイル",
    )

    parser.add_argument(
        "-o",
        "--output",
        type=Path,
        help="出力MP4ファイル。省略時は入力ファイルと同じ場所に作成",
    )

    parser.add_argument(
        "--resolution",
        type=int,
        default=720,
        choices=(480, 720, 1080, 2160),
        help="動画の縦解像度。デフォルト: 720",
    )

    parser.add_argument(
        "--fps",
        type=int,
        default=30,
        help="フレームレート。デフォルト: 30",
    )

    parser.add_argument(
        "--quality",
        type=int,
        default=100,
        choices=range(1, 101),
        metavar="1-100",
        help="動画品質。デフォルト: 100",
    )

    parser.add_argument(
        "--default-duration",
        type=int,
        default=5,
        help="タイミング未設定時の1スライドの秒数。デフォルト: 5",
    )

    parser.add_argument(
        "--ignore-timings",
        action="store_true",
        help="記録済みタイミングとナレーションを使わない",
    )

    parser.add_argument(
        "--overwrite",
        type=int,
        default=0,
        help="既存のMP4ファイルを上書きするか [0|1]",
    )

    parser.add_argument(
        "--pause",
        type=int,
        default=0,
        help="既存のMP4ファイルを上書きするか [0|1]",
    )

    return parser.parse_args()


def main() -> int:
    """
    概要:
        スクリプトのメイン処理を実行します。
    詳細説明:
        コマンドライン引数を取得し、変換関数を呼び出します。
        キーボード割り込みや予期せぬエラーが発生した場合はキャッチして終了コードを返します。
    戻り値:
        :returns: 終了コードとコマンドライン引数のタプル。
        :rtype: tuple
    """
    args = parse_args()

    output = args.output
    if output is None:
        output = args.input.with_suffix(".mp4")

    try:
        convert_pptx_to_mp4(
            args.input,
            output,
            resolution=args.resolution,
            fps=args.fps,
            quality=args.quality,
            default_slide_duration=args.default_duration,
            use_timings_and_narrations=not args.ignore_timings,
            overwrite=args.overwrite,
        )
        return 0, args

    except KeyboardInterrupt:
        print("\n中断しました。", file=sys.stderr)
        return 130, args

    except Exception as exc:
        print(f"エラー: {exc}", file=sys.stderr)
        return 1, args


if __name__ == "__main__":
    ret, args = main()
    if args.pause: input("\nPress ENTER to terminate>>\n")
    raise SystemExit(ret)