import os
import sys
import toml
import tkinter as tk
from tkinter import ttk
import json

"""
JSONView.py

概要: JSONファイルをツリービューで表示するGUIアプリケーション。

詳細説明:
    Tkinterとttk.Treeviewを使用して、JSONデータの階層構造を視覚的に表示します。
    アプリケーションはコマンドライン引数またはINIファイルから初期のウィンドウサイズを読み込みます。
    終了時には、現在のウィンドウサイズがINIファイルに保存されます。

関連リンク: :doc:`JSONView_usage`
"""


def usage():
    print("Usage: python {} infile window_size".format(sys.argv[0]))


def load_json(filename):
    """
    概要: 指定されたJSONファイルからデータを読み込む。

    詳細説明:
        ファイルパスからJSONデータをパースし、Pythonの辞書またはリストとして返します。
        この関数は、JSON形式のデータを含むテキストファイルを読み込むために使用されます。

    :param filename: str: 読み込むJSONファイルのパス。
    :returns: dict | list: 読み込まれたJSONデータ。JSONのルート要素が辞書の場合はdict、リストの場合はlist。
    """
    with open(filename, "r") as file:
        return json.load(file)


def insert_item(tree, parent, key, value):
    """
    概要: TreeviewウィジェットにJSONデータ項目を挿入する。

    詳細説明:
        JSONの構造（辞書、リスト、プリミティブ値）に応じて、
        Treeviewに適切なノードを作成し、再帰的に子要素を挿入します。
        辞書はキーと値のペアとして展開され、リストはインデックス付きのアイテムとして表示されます。
        プリミティブ値（文字列、数値、真偽値など）はキーと値の列に直接表示されます。

    :param tree: ttk.Treeview: データを挿入するTreeviewウィジェットのインスタンス。
    :param parent: str: 親ノードのID。ルートレベルの項目を挿入する場合は空文字列 ''。
    :param key: str: 現在のアイテムのキー（辞書の場合）または表示名（リストのアイテムの場合）。
    :param value: Any: 現在のアイテムの値。これは辞書、リスト、またはプリミティブ値のいずれかです。
    :returns: None
    """
    if isinstance(value, dict):
        node = tree.insert(parent, 'end', text=str(key), open=False)
        for subkey, subvalue in value.items():
            insert_item(tree, node, subkey, subvalue)
    elif isinstance(value, list):
        node = tree.insert(parent, 'end', text=str(key) + '[]', open=False)
        for i, item in enumerate(value):
            insert_item(tree, node, f'Item {i}', item)
    else:
        tree.insert(parent, 'end', text=str(key), value=str(value))


def build_tree(tree, data):
    """
    概要: Treeviewウィジェットの列を設定し、JSONデータを読み込んで表示する。

    詳細説明:
        Treeviewウィジェットの列を「Key」と「Value」として設定し、それぞれの幅とヘッダーを定義します。
        その後、与えられたJSONデータのトップレベルのキーと値を使って
        `insert_item` 関数を呼び出し、ツリー構造を再帰的に構築します。

    :param tree: ttk.Treeview: データを表示するTreeviewウィジェットのインスタンス。
    :param data: dict: 表示するJSONデータ（ルートが辞書であると仮定）。
    :returns: None
    """
    tree["columns"] = ("one",)
    tree.column("#0", width=270, minwidth=270, stretch=tk.NO)
    tree.column("one", width=150, minwidth=150, stretch=tk.NO)

    tree.heading("#0", text="Key", anchor=tk.W)
    tree.heading("one", text="Value", anchor=tk.W)
    
    for key, value in data.items():
        insert_item(tree, '', key, value)


def window_close(root, inifile):
    """
    概要: ウィンドウを閉じる際の処理を実行する。

    詳細説明:
        アプリケーションが終了する際に呼び出されるコールバック関数です。
        現在のウィンドウのジオメトリ（サイズと位置）をINIファイルに保存します。
        これにより、次回のアプリケーション起動時に同じウィンドウサイズが復元されます。
        その後、Tkinterアプリケーションを正常に終了させます。

    :param root: tk.Tk: メインのTkinterウィンドウオブジェクトのインスタンス。
    :returns: None
    """
    with open(inifile, "w") as file:
        toml.dump({"geometry": root.geometry()}, file)    
    root.quit()


def main():
    """
    概要: JSONビューアアプリケーションのメイン処理を実行する。

    詳細説明:
        Tkinterウィンドウを初期化し、指定されたJSONファイルを読み込み、
        その内容をttk.Treeviewウィジェットに表示します。
        ウィンドウのタイトル、初期サイズ、およびウィンドウが閉じられた際の処理を設定します。
        最後に、Tkinterのイベントループを開始し、GUIの表示とユーザー操作を処理します。

    :param infile: str: 表示するJSONファイルのパス。
    :param geometry: str: ウィンドウの初期サイズと位置を示す文字列（例: "600x400"）。
    :returns: None
    """
    argv = sys.argv
    nargs = len(argv)

    if nargs <= 1:
        usage()
        sys.exit(1)

    geometry = "600x400"  # ウィンドウのデフォルトサイズ

    inifile = os.path.splitext(argv[0])[0] + ".ini"
    if os.path.isfile(inifile): 
        geometry = toml.load(inifile).get("geometry", geometry)

    infile = argv[1]
    if nargs >= 3:
        geometry = argv[2]

    root = tk.Tk()
    root.title(f"JSON Viewer - {infile}")
    root.geometry(geometry)
    root.protocol("WM_DELETE_WINDOW", lambda: window_close(root, inifile))

    tree = ttk.Treeview(root)
    data = load_json(infile)
    build_tree(tree, data)
    tree.pack(side='top', fill='both', expand=True)

    root.mainloop()


if __name__ == "__main__":
    main()