import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import savgol_filter
import matplotlib
import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
    QGroupBox, QLabel, QLineEdit, QPushButton, QFileDialog, 
    QMessageBox, QScrollArea, QTextEdit, QGridLayout # 💡 修正: QGridLayoutを追加
)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QFont

# MatplotlibのバックエンドをQtに変更
# 注意：使用環境にMS Gothicがない場合は、適宜変更してください
try:
    matplotlib.rcParams['font.family'] = 'MS Gothic'
except:
    pass
matplotlib.use('QtAgg') 

# --- 解析機能（クラス外で定義） ---

def read_xrd_data(filepath, xrange_analysis):
    x, y = [], []
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            for line in f:
                if line.strip():
                    parts = line.strip().split('\t')
                    if len(parts) == 2:
                        try:
                            x_val = float(parts[0])
                            y_val = float(parts[1])
                            if x_val < xrange_analysis[0] or x_val > xrange_analysis[1]:
                                continue
                            x.append(x_val)
                            y.append(y_val)
                        except ValueError:
                            continue
    except FileNotFoundError:
        raise FileNotFoundError(f"ファイルが見つかりません: {filepath}")

    if not x:
        raise ValueError(f"ファイル {filepath} に有効なデータがありません。")
        
    return np.array(x), np.array(y)

def fit_background(x_bg, y_bg, x_cal, norder):
    if len(x_bg) < norder + 1:
        raise ValueError(f"BGフィットのデータ点数が多項式次数({norder}+1)より少ないです。範囲設定を見直してください。")
        
    coeffs = np.polyfit(x_bg, y_bg, deg=norder)
    return np.polyval(coeffs, x_cal)

def extract(x, y, ranges):
    mask = np.zeros_like(x, dtype=bool)
    for r in ranges:
        mask |= (x >= r[0]) & (x <= r[1])
    return x[mask], y[mask]


class XRDAnalyzerApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("XRDスペクトル解析 GUI (PyQt6)")
        self.setGeometry(100, 100, 800, 600)
        self.data = {}
        
        # 中央ウィジェットの設定
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # メインレイアウト (垂直方向)
        main_layout = QVBoxLayout(central_widget)
        
        # ログエリア
        self.log_text = QTextEdit()
        self.log_text.setFont(QFont("Consolas", 10))
        self.log_text.setReadOnly(True)
        self.log_text.setMaximumHeight(100)
        
        # ⚙️ 設定パネル
        settings_group = self._create_settings_group()
        
        # 🏃 実行ボタン
        process_button = QPushButton("🚀 データ処理実行")
        process_button.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 10px;")
        process_button.clicked.connect(self.process_data)
        
        # 📊 結果表示ボタン
        result_group = self._create_result_group()

        # レイアウトに追加
        main_layout.addWidget(settings_group)
        main_layout.addWidget(process_button)
        main_layout.addWidget(result_group)
        main_layout.addWidget(QLabel("ログ:"))
        main_layout.addWidget(self.log_text)
        
        self.log("初期化完了。解析設定を確認してください。")

    def _create_settings_group(self):
        group = QGroupBox("⚙️ 解析設定")
        layout = QVBoxLayout()
        grid = QGridLayout() # 💡 修正: QGridLayoutが利用可能

        # 1. ファイル設定 (LineEditはクラスの属性として保持)
        self.infile1_edit = self._create_file_entry(grid, 0, "ファイル1 (基板):", "Eagle基板.TXT", self.browse_file1)
        self.infile2_edit = self._create_file_entry(grid, 1, "ファイル2 (試料):", "試料1_a-GeO2_eagle.TXT", self.browse_file2)
        
        # 2. 平滑化設定
        self.nsmooth_edit = self._create_param_entry(grid, 2, "平滑化点数 (nsmooth):", "15", "（奇数）")
        self.norder_smooth_edit = self._create_param_entry(grid, 3, "平滑化次数:", "2")

        # 3. 分析範囲
        self.xrange_min_edit = self._create_param_entry(grid, 4, "分析範囲 (2θ min):", "10.0")
        self.xrange_max_edit = self._create_param_entry(grid, 5, "分析範囲 (2θ max):", "50.0")

        # 4. BGフィット設定
        self.norder_bg_edit = self._create_param_entry(grid, 6, "BG多項式次数:", "3")
        self.bg_ranges_edit = self._create_param_entry(grid, 7, "BGフィット範囲(str):", "(10, 11), (40, 50)")

        layout.addLayout(grid)
        group.setLayout(layout)
        return group
    
    def _create_file_entry(self, grid, row, label_text, default_text, connect_func):
        label = QLabel(label_text)
        line_edit = QLineEdit(default_text) # 💡 QLineEditの初期値はここで設定される
        button = QPushButton("参照")
        button.clicked.connect(connect_func)
        
        # PyQt6では、QLabel, QLineEdit, QPushButtonがQGridLayoutに追加される
        grid.addWidget(label, row, 0)
        grid.addWidget(line_edit, row, 1, 1, 3) # colspan=3
        grid.addWidget(button, row, 4)
        return line_edit

    def _create_param_entry(self, grid, row, label_text, default_text, hint=""):
        label = QLabel(label_text)
        line_edit = QLineEdit(default_text)
        hint_label = QLabel(hint)
        
        grid.addWidget(label, row, 0)
        grid.addWidget(line_edit, row, 1)
        grid.addWidget(hint_label, row, 2, alignment=Qt.AlignmentFlag.AlignLeft)
        return line_edit
    
    def _create_result_group(self):
        group = QGroupBox("📊 結果表示")
        layout = QHBoxLayout()

        # 生データとBGスペクトル
        raw_bg_button = QPushButton("生データとBGスペクトル")
        raw_bg_button.clicked.connect(lambda: self.plot_result("raw_bg"))
        layout.addWidget(raw_bg_button)

        # 規格化スペクトル
        normalized_button = QPushButton("規格化スペクトル")
        normalized_button.clicked.connect(lambda: self.plot_result("normalized"))
        layout.addWidget(normalized_button)

        # 差スペクトル
        diff_button = QPushButton("差スペクトル")
        diff_button.clicked.connect(lambda: self.plot_result("difference"))
        layout.addWidget(diff_button)

        group.setLayout(layout)
        return group
    
    # --- ファイル参照メソッド ---
    def browse_file1(self):
        filepath, _ = QFileDialog.getOpenFileName(self, "ファイル1 (基板) を選択", "", "TXT files (*.TXT);;Text files (*.txt);;All files (*.*)")
        if filepath:
            self.infile1_edit.setText(filepath) # 💡 QLineEdit.setText() で即座に表示が更新される
            self.log(f"ファイル1設定: {filepath}")

    def browse_file2(self):
        filepath, _ = QFileDialog.getOpenFileName(self, "ファイル2 (試料) を選択", "", "TXT files (*.TXT);;Text files (*.txt);;All files (*.*)")
        if filepath:
            self.infile2_edit.setText(filepath) # 💡 QLineEdit.setText() で即座に表示が更新される
            self.log(f"ファイル2設定: {filepath}")
            
    # --- ログ出力メソッド ---
    def log(self, message):
        self.log_text.append(message)
        
    # --- 設定値のパース ---
    def parse_config(self):
        try:
            # QLineEdit.text() から直接値を取得
            cfg = {
                "infile1": self.infile1_edit.text(),
                "infile2": self.infile2_edit.text(),
                "nsmooth": int(self.nsmooth_edit.text()),
                "norder_smooth": int(self.norder_smooth_edit.text()),
                "xrange_analysis": [float(self.xrange_min_edit.text()), float(self.xrange_max_edit.text())],
                "norder_bg": int(self.norder_bg_edit.text()),
            }
            
            # nsmoothは奇数チェック
            if cfg["nsmooth"] % 2 == 0:
                raise ValueError("平滑化点数 (nsmooth) は奇数である必要があります。")
            # x範囲のチェック
            if cfg["xrange_analysis"][0] >= cfg["xrange_analysis"][1]:
                 raise ValueError("分析範囲の下限が上限以上になっています。")
                 
            # BG範囲文字列をパース
            bg_ranges_str = self.bg_ranges_edit.text()
            bg_ranges = []
            for item in bg_ranges_str.replace(" ", "").split('),'):
                if item:
                    clean_item = item.strip('()')
                    if ',' in clean_item:
                        parts = clean_item.split(',')
                        bg_ranges.append((float(parts[0]), float(parts[1])))
            
            if not bg_ranges:
                 raise ValueError("BGフィット範囲の形式が不正です。例: (10, 11), (40, 50)")

            cfg["bg_ranges"] = bg_ranges
            
            self.log(f"解析設定読み込み完了。")
            return cfg

        except Exception as e:
            QMessageBox.critical(self, "設定エラー", f"設定値の読み込みまたはパース中にエラーが発生しました: {e}")
            self.log(f"エラー: 設定パース失敗 - {e}")
            return None

    # --- データ処理実行 ---
    def process_data(self):
        self.data = {}
        cfg = self.parse_config()
        if cfg is None:
            return

        try:
            self.log("データ読み込み中...")
            x1, y1_raw = read_xrd_data(cfg["infile1"], cfg["xrange_analysis"])
            x2, y2_raw = read_xrd_data(cfg["infile2"], cfg["xrange_analysis"])
            
            # 平滑化
            y1 = savgol_filter(y1_raw, cfg["nsmooth"], cfg["norder_smooth"])
            y2 = savgol_filter(y2_raw, cfg["nsmooth"], cfg["norder_smooth"])
            
            # バックグラウンドフィット
            x1fit, y1fit = extract(x1, y1, cfg["bg_ranges"])
            x2fit, y2fit = extract(x2, y2, cfg["bg_ranges"])
            bg1 = fit_background(x1fit, y1fit, x1, norder=cfg["norder_bg"])
            bg2 = fit_background(x2fit, y2fit, x2, norder=cfg["norder_bg"])

            # バックグラウンド除去
            y1_corr = y1 - bg1
            y2_corr = y2 - bg2

            # 規格化
            ymax1 = np.max(y1_corr)
            xmax = x1[np.argmax(y1_corr)]
            x2_index = np.argmin(np.abs(x2 - xmax))
            y2_at_xmax = y2_corr[x2_index]

            y1_norm = y1_corr
            y2_norm = y2_corr * ymax1 / y2_at_xmax
            self.log("データ処理と規格化が完了しました。")

            # 結果を保存
            self.data = {
                "x1": x1, "y1": y1, "bg1": bg1, "y1_norm": y1_norm,
                "x2": x2, "y2": y2, "bg2": bg2, "y2_norm": y2_norm,
                "difference": y2_norm - y1_norm,
                "infile1": cfg["infile1"], "infile2": cfg["infile2"]
            }

        except Exception as e:
            QMessageBox.critical(self, "解析エラー", f"データ処理中にエラーが発生しました: {e}")
            self.log(f"エラー: データ処理失敗 - {e}")
            
    # --- グラフ描画機能 ---
    def plot_result(self, plot_type):
        if not self.data:
            QMessageBox.warning(self, "データなし", "先に「データ処理実行」ボタンを押してください。")
            return

        plt.figure(figsize=(10, 6))
        
        file1_name = self.data["infile1"].split('/')[-1].split('\\')[-1]
        file2_name = self.data["infile2"].split('/')[-1].split('\\')[-1]

        if plot_type == "raw_bg":
            # 生データとBGスペクトル
            plt.plot(self.data["x1"], self.data["y1"], label=f"{file1_name} (平滑化)", linestyle='-')
            plt.plot(self.data["x1"], self.data["bg1"], label=f"{file1_name} BG", linestyle='--')
            plt.plot(self.data["x2"], self.data["y2"], label=f"{file2_name} (平滑化)", linestyle='-')
            plt.plot(self.data["x2"], self.data["bg2"], label=f"{file2_name} BG", linestyle='--')
            plt.title("XRDスペクトルとバックグラウンド（平滑化済）")
            plt.ylabel("Intensity (a.u.)")
        
        elif plot_type == "normalized":
            # 規格化スペクトル
            plt.plot(self.data["x1"], self.data["y1_norm"], label=f"{file1_name} (BG除去・規格化)", linestyle='--')
            plt.plot(self.data["x2"], self.data["y2_norm"], label=f"{file2_name} (BG除去・規格化)", linewidth=2)
            plt.title("規格化スペクトル比較（バックグラウンド除去・規格化済）")
            plt.ylabel("Intensity (a.u.)")
        
        elif plot_type == "difference":
            # 差スペクトル
            plt.plot(self.data["x1"], self.data["difference"], label="差スペクトル", linestyle='-', color='red', linewidth=2)
            plt.plot(self.data["x1"], np.zeros_like(self.data["x1"]), 'k:', linewidth=0.5)
            plt.title("差スペクトル (試料 - 基板)")
            plt.ylabel("delta I (a.u.)")

        plt.xlabel("2θ (deg)")
        plt.legend()
        plt.grid(True)
        plt.tight_layout()
        plt.show()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = XRDAnalyzerApp()
    window.show()
    sys.exit(app.exec())