#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import shutil
import subprocess
import sys
import tempfile
import gtk
import pygtk
import gobject

pygtk.require("2.0")

from gimpfu import *

CJPEGLI_PATH = os.path.join(
    os.path.dirname(os.path.abspath(__file__)), "cjpegli.exe"
)


def get_desktop_dir():
    return os.path.join(os.path.expanduser("~"), "Desktop")


def get_jpegli_temp_dir():
    temp_dir = os.path.join(tempfile.gettempdir(), "export-jpegli")
    if not os.path.exists(temp_dir):
        os.makedirs(temp_dir)
    return temp_dir


def clear_jpegli_temp_dir():
    temp_dir = get_jpegli_temp_dir()
    deleted_count = 0
    if os.path.exists(temp_dir):
        for filename in os.listdir(temp_dir):
            file_path = os.path.join(temp_dir, filename)
            try:
                if os.path.isfile(file_path) or os.path.islink(file_path):
                    os.unlink(file_path)
                    deleted_count += 1
                elif os.path.isdir(file_path):
                    shutil.rmtree(file_path)
                    deleted_count += 1
            except Exception as e:
                print("削除失敗: %s - %s" % (file_path, e))
    return deleted_count


def copy_image_file_to_clipboard(file_path):
    try:
        escaped_path = file_path.replace("\\", "\\\\")
        system_root = os.environ.get("SystemRoot", r"C:\Windows")
        powershell_exe = os.path.join(
            system_root, r"System32\WindowsPowerShell\v1.0\powershell.exe"
        )

        if not os.path.exists(powershell_exe):
            powershell_exe = "powershell.exe"

        ps_script = """
        Add-Type -AssemblyName System.Windows.Forms
        $filePath = '%s'
        $files = New-Object System.Collections.Specialized.StringCollection
        $files.Add($filePath) | Out-Null
        [System.Windows.Forms.Clipboard]::SetFileDropList($files)
        """ % escaped_path

        startupinfo = None
        if os.name == "nt":
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

        proc = subprocess.Popen(
            [powershell_exe, "-NoProfile", "-Command", ps_script],
            startupinfo=startupinfo,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        proc.communicate()
        return proc.returncode == 0
    except Exception as e:
        gimp.message("クリップボード書き込みエラー:\n" + str(e))
        return False


def resolve_output_path(target_path, allow_overwrite):
    """上書きが許可されていない場合、ファイル名に数字(_1, _2...)を追加して重複を避ける"""
    # Unicode型のパスで確実にチェックを行う
    if isinstance(target_path, str):
        target_path = target_path.decode("utf-8")

    if allow_overwrite or not os.path.exists(target_path):
        return target_path

    dir_name, full_name = os.path.split(target_path)
    base_name, ext = os.path.splitext(full_name)

    counter = 1
    new_path = os.path.join(dir_name, "%s_%d%s" % (base_name, counter, ext))
    while os.path.exists(new_path):
        counter += 1
        new_path = os.path.join(dir_name, "%s_%d%s" % (base_name, counter, ext))

    return new_path


class JpegliDialog(gtk.Dialog):

    def apply_white_text(self, widget):
        """指定したウィジェット（およびその子要素）の文字色を確実に白に設定する"""
        white = gtk.gdk.Color(65535, 65535, 65535)
        for state in [gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE]:
            widget.modify_fg(state, white)
        
        if hasattr(widget, "get_child") and widget.get_child():
            child = widget.get_child()
            if isinstance(child, gtk.Label):
                for state in [gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE]:
                    child.modify_fg(state, white)

    gtk.rc_parse_string("""
    style "jpegli_mode_frame_style"
    {
        light[NORMAL] = "#fffa7b"
        dark[NORMAL]  = "#fffa7b"
    }
    widget "*.jpegli_mode_frame" style "jpegli_mode_frame_style"
    """)

    def __init__(self, default_custom_dir):
        super(JpegliDialog, self).__init__(
            "JPEG書き出し with jpegli",
            None,
            gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
            (
                gtk.STOCK_CANCEL,
                gtk.RESPONSE_CANCEL,
                gtk.STOCK_OK,
                gtk.RESPONSE_OK,
            ),
        )
        self.set_size_request(480, -1)

        # 背景色（ダークグレー #484848）を設定
        bg_color = gtk.gdk.Color(72 * 257, 72 * 257, 72 * 257)
        self.modify_bg(gtk.STATE_NORMAL, bg_color)
        self.vbox.modify_bg(gtk.STATE_NORMAL, bg_color)
        self.action_area.modify_bg(gtk.STATE_NORMAL, bg_color)

        # 外枠余白(20px)を確保
        align = gtk.Alignment(0, 0, 1, 1)
        align.set_padding(20, 20, 20, 20)
        self.vbox.pack_start(align, False, False, 0)

        # 内部レイアウト用のメインVBox
        vbox = gtk.VBox(spacing=16)
        align.add(vbox)

        # 圧縮モード選択フレーム
        mode_frame = gtk.Frame()
        mode_frame.set_name("jpegli_mode_frame")
        mode_frame.modify_bg(gtk.STATE_NORMAL, bg_color)
        frame_label = gtk.Label()
        frame_label.set_markup(" <b><span foreground='#fffa7b'>画質圧縮モード</span></b> ")
        mode_frame.set_label_widget(frame_label)

        frame_align = gtk.Alignment(0, 0, 1, 1)
        frame_align.set_padding(15, 15, 15, 15)
        
        mode_box = gtk.VBox(spacing=12)
        frame_align.add(mode_box)

        box_combo = gtk.HBox(spacing=12)
        self.combo_mode = gtk.combo_box_new_text()
        self.combo_mode.append_text("Distance")
        self.combo_mode.append_text("品質")
        self.combo_mode.append_text("ファイルサイズ指定 (KB)")
        self.combo_mode.set_active(0)
        box_combo.pack_start(self.combo_mode, False, False, 0)

        # Distance用
        self.adj_dist = gtk.Adjustment(1.0, 0.0, 25.0, 0.1, 1.0)
        self.box_dist_input = gtk.HBox(spacing=10)
        self.scale_dist = gtk.HScale(self.adj_dist)
        self.scale_dist.set_draw_value(False)
        self.spin_dist = gtk.SpinButton(self.adj_dist, 0.1, 1)
        self.box_dist_input.pack_start(self.scale_dist, True, True, 0)
        self.box_dist_input.pack_start(self.spin_dist, False, False, 0)

        # Quality用
        self.adj_qual = gtk.Adjustment(90, 1, 100, 1, 10)
        self.box_qual_input = gtk.HBox(spacing=10)
        self.scale_qual = gtk.HScale(self.adj_qual)
        self.scale_qual.set_draw_value(False)
        self.spin_qual = gtk.SpinButton(self.adj_qual, 1.0, 0)
        self.box_qual_input.pack_start(self.scale_qual, True, True, 0)
        self.box_qual_input.pack_start(self.spin_qual, False, False, 0)

        # Size指定用
        self.adj_size = gtk.Adjustment(100, 1, 100000, 1, 100)
        self.spin_size = gtk.SpinButton(self.adj_size, 1.0, 0)
        self.lbl_unit = gtk.Label("KB")
        self.apply_white_text(self.lbl_unit)
        
        self.box_size = gtk.HBox(spacing=10)
        self.box_size.pack_start(self.spin_size, False, False, 0)
        self.box_size.pack_start(self.lbl_unit, False, False, 0)

        box_combo.pack_start(self.box_dist_input, True, True, 0)
        box_combo.pack_start(self.box_qual_input, True, True, 0)
        box_combo.pack_start(self.box_size, True, True, 0)
        mode_box.pack_start(box_combo, False, False, 0)

        # 説明文
        self.lbl_dist_desc = gtk.Label()
        self.lbl_dist_desc.set_markup(
            "--------------------------------------------\n"
            "  0.0: 最高画質\n"
            "  1.0: 高画質（視覚的ロスレス/デフォルト）\n"
            " 25.0: 低画質\n"
            "<b><span foreground='#b3fef1'>※推奨値：0.5〜3.0</span></b>\n"
            "--------------------------------------------"
        )
        self.lbl_dist_desc.set_alignment(0.0, 0.5)
        self.apply_white_text(self.lbl_dist_desc)

        self.lbl_qual_desc = gtk.Label()
        self.lbl_qual_desc.set_markup(
            "--------------------------------------------\n"
            " 100: 最高画質\n"
            "  90: 高画質（視覚的ロスレス/デフォルト）\n"
            "    0: 低画質\n"
            "<b><span foreground='#b3fef1'>※推奨値：68〜96</span></b>\n"
            "--------------------------------------------"
        )
        self.lbl_qual_desc.set_alignment(0.0, 0.5)
        self.apply_white_text(self.lbl_qual_desc)

        self.lbl_size_desc = gtk.Label()
        self.lbl_size_desc.set_markup("<b><span foreground='#b3fef1'>※注意: エンコード処理速度が非常に遅くなります</span></b>")
        self.lbl_size_desc.set_alignment(0.0, 0.5)

        mode_box.pack_start(self.lbl_dist_desc, False, False, 0)
        mode_box.pack_start(self.lbl_qual_desc, False, False, 0)
        mode_box.pack_start(self.lbl_size_desc, False, False, 0)

        mode_frame.add(frame_align)
        vbox.pack_start(mode_frame, False, False, 0)

        # 保存先指定UI
        box_dir = gtk.HBox(spacing=12)
        self.chk_custom_dir = gtk.CheckButton("指定フォルダに保存")
        self.apply_white_text(self.chk_custom_dir)

        self.btn_chooser = gtk.FileChooserButton("保存先フォルダを選択")
        self.btn_chooser.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)

        if default_custom_dir and os.path.exists(default_custom_dir):
            self.btn_chooser.set_filename(default_custom_dir)
        else:
            self.btn_chooser.set_filename(get_desktop_dir())

        box_dir.pack_start(self.chk_custom_dir, False, False, 0)
        box_dir.pack_start(self.btn_chooser, True, True, 0)
        vbox.pack_start(box_dir, False, False, 0)

        # オプション設定
        self.chk_overwrite = gtk.CheckButton("ファイルを上書き")
        self.apply_white_text(self.chk_overwrite)
        vbox.pack_start(self.chk_overwrite, False, False, 0)

        box_cb_row = gtk.HBox(spacing=12)
        self.chk_clipboard = gtk.CheckButton("クリップボードにコピー")
        self.apply_white_text(self.chk_clipboard)

        btn_clear_temp = gtk.Button()
        
        btn_align = gtk.Alignment(0.5, 0.5, 0, 0)
        btn_align.set_padding(5, 5, 5, 5)

        btn_clear_temp_label = gtk.Label()
        btn_clear_temp_label.set_markup("<span foreground='#000000'>一時ファイルを消去</span>")

        btn_align.add(btn_clear_temp_label)
        btn_clear_temp.add(btn_align)
        
        def on_clear_temp_clicked(widget):
            count = clear_jpegli_temp_dir()
            gimp.message("一時フォルダ(%temp%\\export-jpegli)から %d 件のファイルを削除しました。" % count)

        btn_clear_temp.connect("clicked", on_clear_temp_clicked)
        box_cb_row.pack_start(self.chk_clipboard, False, False, 0)
        box_cb_row.pack_start(btn_clear_temp, False, False, 0)
        vbox.pack_start(box_cb_row, False, False, 0)

        note_label = gtk.Label(" ※クリップボードの一時ファイルは %temp%\\export-jpegli に保存")
        note_label.set_alignment(0.0, 0.5)
        self.apply_white_text(note_label)
        vbox.pack_start(note_label, False, False, 0)

        # シグナル設定
        def on_mode_changed(widget):
            idx = self.combo_mode.get_active()
            self.box_dist_input.hide()
            self.box_qual_input.hide()
            self.box_size.hide()
            self.lbl_dist_desc.hide()
            self.lbl_qual_desc.hide()
            self.lbl_size_desc.hide()

            if idx == 0:
                self.box_dist_input.show()
                self.lbl_dist_desc.show()
            elif idx == 1:
                self.box_qual_input.show()
                self.lbl_qual_desc.show()
            elif idx == 2:
                self.box_size.show()
                self.lbl_size_desc.show()

        def on_custom_dir_toggled(widget):
            active = self.chk_custom_dir.get_active()
            self.btn_chooser.set_sensitive(active)

        self.combo_mode.connect("changed", on_mode_changed)
        self.chk_custom_dir.connect("toggled", on_custom_dir_toggled)

        # 詳細設定 (Expander)
        expander = gtk.Expander()
        expander.set_use_markup(True)
        expander.set_label("<b><span foreground='#ffffff'>詳細設定</span></b> <span foreground='#ffffff'>※基本的には初期設定でOK</span>")
        
        expander.modify_bg(gtk.STATE_NORMAL, bg_color)
        expander.modify_bg(gtk.STATE_PRELIGHT, bg_color)
        expander.modify_bg(gtk.STATE_ACTIVE, bg_color)

        expander_align = gtk.Alignment(0, 0, 1, 1)
        expander_align.set_padding(12, 12, 12, 12)

        expander_box = gtk.VBox(spacing=10)
        expander_align.add(expander_box)

        box_sub = gtk.HBox(spacing=12)
        lbl_sub = gtk.Label("色情報:")
        self.apply_white_text(lbl_sub)

        self.combo_sub = gtk.combo_box_new_text()
        for opt in ["Auto (デフォルト)", "444 (色情報を維持 / イラスト・文字向け)", "440 (色情報を圧縮 / 写真・軽量化向け)", "422", "420"]:
            self.combo_sub.append_text(opt)
        self.combo_sub.set_active(0)
        box_sub.pack_start(lbl_sub, False, False, 0)
        box_sub.pack_start(self.combo_sub, False, False, 0)
        expander_box.pack_start(box_sub, False, False, 0)

        box_prog = gtk.HBox(spacing=12)
        lbl_prog = gtk.Label("プログレッシブレベル:")
        self.apply_white_text(lbl_prog)

        self.adj_prog = gtk.Adjustment(2, 0, 2, 1, 1)
        scale_prog = gtk.HScale(self.adj_prog)
        scale_prog.set_digits(0)
        scale_prog.set_size_request(80, -1)

        white = gtk.gdk.Color(65535, 65535, 65535)
        for state in [gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE]:
            scale_prog.modify_fg(state, white)

        box_prog.pack_start(lbl_prog, False, False, 0)
        box_prog.pack_start(scale_prog, True, True, 0)
        expander_box.pack_start(box_prog, False, False, 0)

        self.chk_xyb = gtk.CheckButton("XYB色空間に変換 (--xyb)")
        self.chk_std_quant = gtk.CheckButton("JPEG標準量子化を有効 (--std_quant)")
        self.chk_no_adaptive = gtk.CheckButton("適応量子化を無効 (--noadaptive_quantization)")
        self.chk_fixed_code = gtk.CheckButton("ハフマン符号最適化を無効 (--fixed_code ※-p 0強制)")

        for chk in [self.chk_xyb, self.chk_std_quant, self.chk_no_adaptive, self.chk_fixed_code]:
            self.apply_white_text(chk)
            expander_box.pack_start(chk, False, False, 0)

        expander.add(expander_align)
        vbox.pack_start(expander, False, False, 0)

        def on_expander_notify(widget, param):
            self.resize(1, 1)
        expander.connect("notify::expanded", on_expander_notify)

        self.show_all()
        on_mode_changed(None)
        on_custom_dir_toggled(None)

        def shrink_to_initial_size():
            self.resize(1, 1)
            return False
        gobject.idle_add(shrink_to_initial_size)

    def get_settings(self):
        modes = ["distance", "quality", "target_size"]
        subsamplings = ["auto", "444", "440", "422", "420"]

        return {
            "mode": modes[self.combo_mode.get_active()],
            "distance": self.adj_dist.get_value(),
            "quality": int(self.adj_qual.get_value()),
            "target_size_kb": int(self.adj_size.get_value()),
            "use_custom_dir": self.chk_custom_dir.get_active(),
            "custom_dir": self.btn_chooser.get_filename(),
            "overwrite": self.chk_overwrite.get_active(),
            "copy_to_clipboard": self.chk_clipboard.get_active(),
            "subsampling": subsamplings[self.combo_sub.get_active()],
            "progressive": int(self.adj_prog.get_value()),
            "xyb": self.chk_xyb.get_active(),
            "std_quant": self.chk_std_quant.get_active(),
            "no_adaptive": self.chk_no_adaptive.get_active(),
            "fixed_code": self.chk_fixed_code.get_active(),
        }


def export_flat_png(image, drawable, path):
    pdb.file_png_save2(
        image,
        drawable,
        path,
        path,
        0,
        9,
        0,
        0,
        0,
        0,
        0,
        0,
        0
    )


def export_jpegli_main(image, drawable):
    if not os.path.isfile(CJPEGLI_PATH):
        gimp.message("cjpegli.exe が見つかりません:\n" + CJPEGLI_PATH)
        return

    dialog = JpegliDialog(get_desktop_dir())
    response = dialog.run()
    settings = dialog.get_settings()
    dialog.destroy()

    if response != gtk.RESPONSE_OK:
        return

    gimp.progress_init("jpegliで書き出し中...")
    gimp.progress_update(0.0)

    gimp.tile_cache_ntuples = 10
    image.undo_group_start()

    tmp_png = None

    try:
        tmp_dir = tempfile.gettempdir()
        tmp_png = os.path.join(tmp_dir, "gimp_jpegli_tmp.png")
        dup = image.duplicate()
        merged_drawable = pdb.gimp_image_merge_visible_layers(dup, CLIP_TO_IMAGE)
        export_flat_png(dup, merged_drawable, tmp_png)
        gimp.delete(dup)

        gimp.progress_update(0.4)

        src_filename = image.filename
        if src_filename:
            base_name = os.path.splitext(os.path.basename(src_filename))[0]
            src_dir = os.path.dirname(src_filename)
        else:
            base_name = "gimp_jpegli_output"
            src_dir = None

        if settings["copy_to_clipboard"]:
            output_dir = get_jpegli_temp_dir()
        elif settings["use_custom_dir"] and settings["custom_dir"] and os.path.exists(settings["custom_dir"]):
            output_dir = settings["custom_dir"]
        elif src_dir:
            output_dir = src_dir
        else:
            output_dir = get_desktop_dir()

        # パス文字列をUnicode型に整える
        if isinstance(output_dir, str):
            output_dir = output_dir.decode("utf-8")
        if isinstance(base_name, str):
            base_name = base_name.decode("utf-8")

        target_path = os.path.join(output_dir, base_name + ".jpg")
        
        # ★ ここで確実に連番処理（_1, _2...）を行う
        output_path_unicode = resolve_output_path(target_path, settings["overwrite"])

        # 日本語・特殊文字パス対策: 一時ファイル(英数字パス)に出力させてから移動する
        tmp_jpeg = os.path.join(tempfile.gettempdir(), "jpegli_out_tmp.jpg")
        if os.path.exists(tmp_jpeg):
            try:
                os.remove(tmp_jpeg)
            except:
                pass

        command_args = [CJPEGLI_PATH, tmp_png, tmp_jpeg]

        mode_val = settings["mode"]
        if mode_val == "distance":
            command_args.extend(["-d", str(settings["distance"])])
        elif mode_val == "quality":
            command_args.extend(["-q", str(settings["quality"])])
        elif mode_val == "target_size":
            target_bytes = settings["target_size_kb"] * 1024
            command_args.extend(["--target_size", str(target_bytes)])

        if settings["subsampling"] != "auto":
            command_args.append("--chroma_subsampling=%s" % settings["subsampling"])

        if settings["fixed_code"]:
            command_args.append("--fixed_code")
            command_args.extend(["-p", "0"])
        else:
            prog_val = settings["progressive"]
            if prog_val != 2:
                command_args.extend(["-p", str(prog_val)])

        if settings["xyb"]:
            command_args.append("--xyb")
        if settings["std_quant"]:
            command_args.append("--std_quant")
        if settings["no_adaptive"]:
            command_args.append("--noadaptive_quantization")

        gimp.progress_update(0.6)

        proc = subprocess.Popen(
            command_args,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        stdout, stderr = proc.communicate()
        
        # 実行コマンドの文字列（CLI表示用）を構築
        cmd_str = subprocess.list2cmdline(command_args)

        if proc.returncode != 0:
            raise Exception("cjpegli 実行エラー:\n" + str(stderr))

        # 上書き許可かつ同名ファイルが存在する場合のみ既存ファイルを削除
        if settings["overwrite"] and os.path.exists(output_path_unicode):
            os.remove(output_path_unicode)
            
        shutil.move(tmp_jpeg, output_path_unicode)

        gimp.progress_update(0.8)

        if settings["copy_to_clipboard"]:
            success = copy_image_file_to_clipboard(output_path_unicode)
            if success:
                gimp.message("クリップボードに画像をコピーしました！\n\n※一時ファイル保存先：\n" + output_path_unicode + "\n\n【実行コマンド】\n" + cmd_str)
            else:
                gimp.message("クリップボードへのコピーに失敗しました:\n" + output_path_unicode + "\n\n【実行コマンド】\n" + cmd_str)
        else:
            gimp.message("jpegliでの書き出しが完了しました:\n" + output_path_unicode + "\n\n【実行コマンド】\n" + cmd_str)

        gimp.progress_update(0.9)

    except Exception as e:
        import traceback
        gimp.message("エラーが発生しました:\n%s\n\n%s" % (e, traceback.format_exc()))

    finally:
        if tmp_png and os.path.exists(tmp_png):
            os.remove(tmp_png)
        image.undo_group_end()
        gimp.progress_update(1.0)
        gimp.progress_end()


register(
    "python_fu_export_jpegli",
    "jpegliでJPEG書き出し",
    "jpegliエンコーダー(cjpegli.exe)を使って高効率なJPEGを書き出します。",
    "ぐうてる",
    "ぐうてる",
    "2026",
    "JPEG書き出し with jpegli...",
    "*",
    [
        (PF_IMAGE, "image", "Input image", None),
        (PF_DRAWABLE, "drawable", "Input drawable", None),
    ],
    [],
    export_jpegli_main,
    menu="<Image>/プラグイン/"
)

main()