import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.geometry.Pos;
import javafx.geometry.Insets;
import javafx.scene.CacheHint;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;

import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import java.io.File;

/**
 * Solar System Rider 3D - Object Oriented Edition
 * * 【設計思想】
 * 3D空間(SubScene3D)と2D UI(SubScene2D)をStackPaneで物理的に分離。
 * これにより、3D空間の回転やズームの影響をUIが直接受けないようにし、
 * 座標変換(localToScene)を用いた精密なラベル追従を実現しています。
 * 各天体は CelestialBody を継承し、物理計算と描画制御を自己完結させています。
 * @author きん ＆ Gemini
 */
public class InfinitySpace3D extends Application {

	// --- ウィンドウおよび表示設定 ---
	public static int WIDTH = 1024;
	public static int HEIGHT = 768;

	// --- レイヤー構造管理フィールド ---
	private StackPane paneMain = null;	  // 基盤となるスタックペイン（全レイヤーを重ねる）
	private SubScene subScene3D = null;	 // 3Dレンダリング専用（深度テスト、アンチエイリアス有効）
	private SubScene subScene2D = null;	 // 2Dラベル・UI専用（背景透過）
	private Group pane3D = null;			// 3D空間のルートノード
	private javafx.scene.layout.Pane pane2D = null; // 2Dラベルを配置する描画パネル

	// --- カメラおよび宇宙全体の回転制御 ---
	private PerspectiveCamera camera;
	private Rotate worldRotateX = new Rotate(0, Rotate.X_AXIS); // 宇宙全体の俯瞰（ピッチ）操作用
	private Rotate worldRotateY = new Rotate(0, Rotate.Y_AXIS); // 宇宙全体の水平回転（ヨー）操作用
	private double camDist = 2000;	  // カメラの原点（太陽）からの距離。ズーム操作の基準値
	private double currentRotX = 15;	// 初期の俯瞰角度。少し斜め上から見下ろす設定
	private double currentRotY = 0;	 // 初期の水平回転角度
	private double timeScale = 1.0; // 速度倍率（デフォルトは 1.0）

	// --- HUD（操作ガイド）用テキストオブジェクト ---
	private VBox keyGuide = null;
	private Text textTitle = new Text();
	private Text textMove = new Text();
	private Text textZoom = new Text();
	private Text textSpeedInfo = new Text();
	private Text textLabel = new Text();
	private Text textHUDHidden = new Text();
	private Text textOrbitHidden = new Text();
	private Text textOrbitPlanet = new Text();
	private Text textOrbitElliptic = new Text();
	private Text textOrbitComet = new Text();
	private Text textLang = new Text();

	// --- 天体群および環境エフェクト管理 ---
	private List<CelestialBody> bodies = new ArrayList<>(); // 生成した全天体を一括管理するリスト
	private boolean showLabels = false;					  // スペースキー連動：天体名表示フラグ
	private boolean showOrbits = false;					   // 0キー連動：全軌道の表示・非表示フラグ
	private String lang = "ja";							  // 言語管理：初期値は日本語
	private OortCloud oortCloud = null;					  // 太陽系を包む最外縁の微細天体群
	private SpaceWind spaceWind = null;					  // 宇宙の広がりを演出する星間物質の流れ
	private MilkyWayImage milkyWayImage = null;			  // 全天を包み込む銀河系背景画像

	private MediaPlayer backgroundMusic;

	@Override
	public void start(Stage stage) {

		// 1. 描画レイヤーとHUD、カメラの物理構築
		setupScene(stage);

		// 2. 太陽系天体群（恒星、惑星、小惑星、彗星、カイパーベルト）の生成と配置
		createUniverse();

		// 3. キーボード入力イベントの登録
		stage.getScene().addEventHandler(KeyEvent.KEY_PRESSED, event -> {

			switch (event.getCode()) {

				// ラベル表示のトグル切り替え
				case SPACE -> { 
					showLabels = !showLabels; 
					bodies.forEach(b -> b.setLabelVisible(showLabels)); 
				}

				// カテゴリ別の軌道表示制御（123キーによる動的な解説演出用）
				case DIGIT1, NUMPAD1 -> toggleOrbitByCategory(CelestialBody.Category.PLANET);
				case DIGIT2, NUMPAD2 -> toggleOrbitByCategory(CelestialBody.Category.ELLIPTICAL);
				case DIGIT3, NUMPAD3 -> toggleOrbitByCategory(CelestialBody.Category.COMET);

				// 全軌道の表示リセット（撮影開始時のクリーンアップ用）
				case DIGIT0, NUMPAD0 -> {
					showOrbits = !showOrbits;
					bodies.forEach(b -> b.setOrbitVisible(showOrbits));
				}

				// カメラ制御：上下左右で宇宙を回転
				case UP	  -> currentRotX -= 2;
				case DOWN	-> currentRotX += 2;
				case LEFT	-> currentRotY -= 2;
				case RIGHT   -> currentRotY += 2;

				// カメラ制御：ズームイン・アウト（極端な値にならないよう制限）
				case PAGE_UP -> camDist = Math.max(50, camDist - 100);
				case PAGE_DOWN -> camDist = Math.min(2000000, camDist + 100);

				// 言語切り替え：日英ガイドの動的置換
				case L	   -> this.changeLang();

				// HUDの表示・非表示
				case H	   -> keyGuide.setVisible(!keyGuide.isVisible());

				// スピードアップ
				case A  -> {
					timeScale *= 1.2; // 20%ずつ加速
					if (timeScale > 50.0) timeScale = 50.0; // 上限
					applyTimeScale();
				}

				// スピードダウン
				case Z  -> {
					timeScale /= 1.2; // 20%ずつ減速
					if (timeScale < 0.01) timeScale = 0.01; // 下限（停止回避）
					applyTimeScale();
				}

			} // end switch

		}); // end KEY_PRESSED

		// 4. メイン・アップデートループの起動（AnimationTimerは画面リフレッシュに同期）
		new AnimationTimer() {
			@Override
			public void handle(long now) { updateUniverse(); }
		}.start();

		stage.setTitle("Infinity Space 3D");
		stage.show();

		try {
			// ファイルパスは環境に合わせて調整してください
			String path = getClass().getResource("Music_fx_deep_space_ambient_delicate_myste.wav").toExternalForm();
			Media media = new Media(path);
			backgroundMusic = new MediaPlayer(media);

			// 無限ループ設定
			backgroundMusic.setCycleCount(MediaPlayer.INDEFINITE);

			// 「静寂」を壊さないよう、ボリュームは控えめに（0.1〜0.3推奨）
			backgroundMusic.setVolume(0.2); 

			backgroundMusic.play();

		} catch (Exception e) {
			System.err.println("BGMの読み込みに失敗しました: " + e.getMessage());
		}

	} // end start

	/**
	 * setupScene: 3D宇宙と2Dラベル、HUDのレイヤー構造を物理的に構築。
	 * * 【重要】3D SubSceneの上に透過の2D SubSceneを重ねることで、
	 * 3D空間の幾何学的な歪みに左右されない正確なラベル表示とUIを実現しています。
	 */
	private void setupScene(Stage stage) {

		// 全レイヤーを重ねるコンテナ
		this.paneMain = new StackPane();
		this.pane3D = new Group();

		// 宇宙全体の回転軸を定義：Y軸回転（水平）を先に適用することで操作性を向上
		pane3D.getTransforms().setAll(worldRotateY, worldRotateX);

		// 3D描画領域の設定：深度バッファとアンチエイリアスを有効にして高品質な出力を目指す
		this.subScene3D = new SubScene(pane3D, WIDTH, HEIGHT, true, SceneAntialiasing.BALANCED);
		subScene3D.setFill(Color.BLACK);

		// ウィンドウのリサイズにSubSceneを追従させるバインド設定
		subScene3D.widthProperty().bind(paneMain.widthProperty());
		subScene3D.heightProperty().bind(paneMain.heightProperty());

		// 2Dラベル描画用パネルの構築
		this.pane2D = new javafx.scene.layout.Pane();
		this.pane2D.prefWidthProperty().bind(paneMain.widthProperty());
		this.pane2D.prefHeightProperty().bind(paneMain.heightProperty());

		// 2Dオーバーレイサブシーン：背景を透明(TRANSPARENT)にして下の3Dを見せる
		this.subScene2D = new SubScene(pane2D, WIDTH, HEIGHT);
		subScene2D.setFill(Color.TRANSPARENT);
		subScene2D.widthProperty().bind(paneMain.widthProperty());
		subScene2D.heightProperty().bind(paneMain.heightProperty());

		// HUD（操作ガイド）VBoxの構築：画面左上に配置される計器のようなパネル
		keyGuide = new VBox();
		keyGuide.setPadding(new Insets(20));
		keyGuide.setMouseTransparent(true); // マウス入力を透過させ、下の3D空間の回転ドラッグを邪魔しない
		keyGuide.setAlignment(Pos.TOP_LEFT);
		keyGuide.setCache(true);			// 描画キャッシュによりパフォーマンスを維持
		keyGuide.setCacheHint(CacheHint.QUALITY);

		// サイバー感のある計器風フォントスタイル
		String textStyle = "-fx-fill: #C0C0C0; -fx-font-family: 'Courier New'; -fx-font-size: 11px; -fx-font-weight: bold;";
		textTitle.setStyle(textStyle + "-fx-fill: #00FF00;");
		textTitle.setText("--- CONTROLS ---");

		// 全テキストオブジェクトに一括でスタイルを適用
		Stream.of(textLang, textMove, textZoom, textLabel, textHUDHidden,
				  textOrbitHidden, textOrbitPlanet, textOrbitElliptic, textOrbitComet,
				  textSpeedInfo).forEach(t -> t.setStyle(textStyle));

		// 初期言語セットアップ
		this.lang = "en"; // 初回のchangeLang呼び出しでjaになるようにセット
		this.changeLang();

		// HUDへの要素追加
		keyGuide.getChildren().addAll(textTitle, textLang, textMove, textZoom, textLabel, textHUDHidden,
									  textOrbitHidden, textOrbitPlanet, textOrbitElliptic,
									   textOrbitComet, textSpeedInfo);

		// レイヤーの重畳：下から 3D SubScene -> 2D SubScene -> HUDガイド
		paneMain.getChildren().addAll(subScene3D, subScene2D, keyGuide);
		StackPane.setAlignment(keyGuide, Pos.TOP_LEFT);

		// カメラ設定：ニア・ファークリッピングを極限まで広げ、巨大な銀河背景まで描画
		camera = new PerspectiveCamera(true);
		camera.setNearClip(0.1);
		camera.setFarClip(5000000.0);
		camera.setTranslateZ(-camDist);
		subScene3D.setCamera(camera);

		// シーンを構築しステージへセット
		Scene scene = new Scene(paneMain, WIDTH, HEIGHT, true, SceneAntialiasing.BALANCED);
		scene.setFill(Color.BLACK);
		stage.setScene(scene);

	} // end setupScene

	/**
	 * createUniverse: 太陽系の物理的初期配置と環境エフェクトの生成
	 * 主要惑星から、カイパーベルト、ラグランジュ点、背景銀河までを統合します。
	 */
	private void createUniverse() {

		// 正規分布用
		Random random = new Random();

		// 1. 光源天体（太陽）とシーン照明
		SunBody sun = new SunBody("太陽", "Sun", 15.0, "image/2k_sun.jpg", Color.ORANGE);
		addBody(sun);
		pane3D.getChildren().add(new PointLight(Color.WHITE)); 
		pane3D.getChildren().add(new AmbientLight(Color.rgb(50, 50, 50)));

		// 2. 内惑星（円軌道モデル）
		addBody(new PlanetBody("水星", "Mercury", 60,  0.32,  3.0, "image/2k_mercury.jpg", Color.GRAY));
		addBody(new PlanetBody("金星", "Venus", 90,  0.24,  5.0, "image/2k_venus_atmosphere.jpg", Color.GOLD));
		addBody(new PlanetBody("地球", "Earth", 130, 0.2,  5.5, "image/2k_earth_daymap.jpg", Color.SKYBLUE));
		addBody(new PlanetBody("火星", "Mars", 180, 0.16,  4.5, "image/2k_mars.jpg", Color.ORANGERED));

		// 2.1 外惑星：木星およびトロヤ群のラグランジュ点(L4, L5)のシミュレーション
		// トロヤ群と変数を共有するものは外出ししている
		double jupiterDist = 300.0;
		double jupiterSpeed = 0.1;
		double jupiterRadius = 12.0;
		double jupiterAngle = 12.0;
		PlanetBody planetJupiter = new PlanetBody("木星", "Jupiter", jupiterDist, jupiterSpeed, jupiterRadius, "image/2k_jupiter.jpg", Color.WHEAT, jupiterAngle);
		planetJupiter.addPlanetRing(1.2, 1.5, 3.1, null, 0.00005);
		addBody(planetJupiter);

		// 木星トロヤ群（各200個ずつ配置）
		double[] lagrangeOffsets = {60.0, -60.0};
		for (double offset : lagrangeOffsets) {
			for (int i = 0; i < 200; i++) {
				double d = jupiterDist + (random.nextGaussian() * 6.0 - 3.0); 
				double s = jupiterSpeed; 
				double r = Math.random() * 0.2 + 0.1;
				double ang = jupiterAngle + offset + (random.nextGaussian() * 8.0 - 4.0);
				addBody(new PlanetBody("", "", d, s, r, null, Color.DARKGRAY, ang));
			}
		}

		// 2.2 外惑星：土星（地軸傾斜26.7度の再現）
		PlanetBody saturn = new PlanetBody("土星", "Saturn", 420, 0.06, 10.0, "image/Solarsystemscope_texture_2k_saturn.jpg", Color.KHAKI);
		saturn.addPlanetRing(1.1, 2.3, 0.0, "image/2k_saturn_ring_alpha.png", 0.1);
		saturn.getNode().getTransforms().add(new Rotate(26.7, Rotate.Z_AXIS)); 
		addBody(saturn);

		// 2.3 外惑星：天王星（極端な地軸倒れ）
		PlanetBody uranus = new PlanetBody("天王星", "Uranus", 510, 0.04, 8.0, null, Color.LIGHTBLUE);
		uranus.addPlanetRing(1.3, 2.0, 0.0, null, 0.00015);
		uranus.getNode().getTransforms().add(new Rotate(97.8, Rotate.Z_AXIS));
		addBody(uranus);

		// 2.4 外惑星：海王星
		PlanetBody neptune = new PlanetBody("海王星", "Neptune", 580, 0.03, 7.8, null, Color.CORNFLOWERBLUE);
		neptune.addPlanetRing(1.2, 1.6, 0.0, null, 0.0002);
		neptune.getNode().getTransforms().add(new Rotate(28.3, Rotate.Z_AXIS));
		addBody(neptune);

		// 3. 小惑星帯 (Main Belt)：正規分布密度 (300個)
		for (int i = 0; i < 300; i++) {
			double d = 225 + random.nextGaussian() * 10;
			addBody(new PlanetBody("", "", d, 0.1 + Math.random() * 0.005, 0.8, null, Color.GRAY));
		}
		addBody(new PlanetBody("ケレス", "Ceres",  235.0, 0.134, 1.5, null, Color.SILVER));
		addBody(new EllipticalBody("パラス", "Pallas", 240, 230, 0.106, 10, 0, 34.8, 1.2, null, Color.rgb(180, 180, 200)));

		// 4. 外縁天体（楕円・傾斜カテゴリ）
		addBody(new EllipticalBody("冥王星", "Pluto", 600, 550, 0.02, 100, 0, -17, 3.0, null, Color.LIGHTGRAY));
		addBody(new EllipticalBody("ハウメア", "Haumea", 640, 580, 0.018, 120, 200, -28, 2.5, null, Color.WHITE));
		addBody(new EllipticalBody("マケマケ", "Makemake", 680, 650, 0.016, 80, 45, -29, 2.6, null, Color.TOMATO));
		addBody(new EllipticalBody("エリス", "Eris", 800, 600, 0.014, 200, 150, -44, 2.8, null, Color.LIGHTYELLOW));
		addBody(new EllipticalBody("クワオアー", "Quaoar", 720, 700, 0.012, 50, 20, -8, 2.4, null, Color.CHOCOLATE));
		addBody(new EllipticalBody("オルクス", "Orcus", 610, 560, 0.02, 110, 180, -20, 2.3, null, Color.DARKGRAY));

		// 5. 彗星群（近日点加速モデル）
		addBody(new CometBody("ハレー彗星", "Halley", 700, 200, 0.15, 500, 45, 18, 1.2, Color.LIGHTBLUE));
		addBody(new CometBody("池谷・関彗星", "Ikeya-Seki", 900, 100, 0.1, 800, 30, 140, 1.5, Color.ALICEBLUE));
		addBody(new CometBody("６７Ｐ彗星", "67P/C-G", 500, 300, 0.1, 200, 180, 7, 1.4, Color.LIGHTGREY));
		addBody(new CometBody("西村彗星", "Nishimura", 600, 100, 0.125, 580, 10, 132, 1.1, Color.SPRINGGREEN));
		addBody(new CometBody("ＳＬ９彗星", "Shoemaker-Levy 9", 320, 150, 0.1, 100, 10, 5, 0.8, Color.MEDIUMPURPLE));
		addBody(new CometBody("百武彗星", "Hyakutake", 1200, 150, 0.2, 1100, 120, 124, 1.3, Color.AQUAMARINE));
		addBody(new CometBody("ヘール・ボップ彗星", "Hale-Bopp", 1000, 300, 0.125, 900, 280, 89, 2.5, Color.WHITE));
		addBody(new CometBody("ラヴジョイ彗星", "Lovejoy", 800, 120, 0.225, 790, 15, 101, 1.0, Color.LIMEGREEN));

		// 6. 特殊軌道・恒星間天体
		addBody(new CometBody("オウムアムア", "'Oumuamua", 20000, 200, 0.02, 10000, 60, 120, 0.5, Color.SADDLEBROWN));
		addBody(new EllipticalBody("セドナ", "Sedna", 1500, 800, 0.004, 1200, 90, -11, 2.5, null, Color.ORANGE));
		addBody(new EllipticalBody("ザ・ゴブリン", "The Goblin", 10000, 800, 0.004, 9000, 300, 11.6, 0.8, null, Color.DARKRED));
		addBody(new EllipticalBody("ドラック", "Drac", 400, 350, 0.08, 50, 0, 103, 0.8, null, Color.DARKRED));

		// 7. カイパーベルト：ケプラーの法則に基づく速度分布（500個）
		for (int i = 0; i < 500; i++) {
			double d = 600 + Math.random() * 200;
			double p = (Math.random() - 0.5) * 20;
			double s = 0.016 * Math.sqrt(580.0 / d); 
			addBody(new EllipticalBody("", "", d, d * 0.98, s, d * 0.02, Math.random()*360, p, 0.8, null, Color.WHITESMOKE));
		}

		// 8. 背景・環境エフェクトの構築
		this.oortCloud = new OortCloud(this.pane3D, 500);
		this.spaceWind = new SpaceWind(this.pane3D, 100);
		this.milkyWayImage = new MilkyWayImage(this.pane3D);

	} // end createUniverse

	/**
	 * addBody: マスターリストへの登録と3D/2Dシーンツリーへの天体要素追加。
	 */
	private void addBody(CelestialBody body) {

		// 全天体一括管理リストに追加
		bodies.add(body);

		// 天体本体の追加
		pane3D.getChildren().add(body.getNode());

		// 軌道ラインの追加（初期状態はON）
		if (body.getOrbitGroup() != null) {
			pane3D.getChildren().add(body.getOrbitGroup());
			body.setOrbitVisible(showOrbits);
		}

		// ラベルの追加（2Dレイヤー）
		if (body.getTextLabel() != null) {
			pane2D.getChildren().add(body.getTextLabel());
			body.setSubScene3D(this.subScene3D); // ビルボード計算用にコンテキスト注入
		}

	} // end addBody

	/**
	 * updateUniverse: 毎フレーム実行される全宇宙の同期処理。
	 * カメラの視点、ズーム、天体の物理計算、環境演出を一括で更新します。
	 */
	private void updateUniverse() {

		// 1. 宇宙全体の回転角を反映
		worldRotateX.setAngle(currentRotX);
		worldRotateY.setAngle(currentRotY);

		// 2. カメラのズーム位置（Z座標）反映
		camera.setTranslateZ(-camDist);

		// 3. ズーム倍率に応じたサイズ補正係数の算出
		// これにより、遠ざかってもラベルや天体が豆粒になりすぎないように制御可能
		double sizeFactor = Math.max(0.5, camDist / 1500.0);

		// 4. 全天体の物理計算と描画の更新
		for(CelestialBody body : bodies){
			// 座標更新、自転、近日点演出、ビルボード処理を一括実行
			body.update(currentRotX, currentRotY, camDist, sizeFactor);
		}

		// 5. 環境演出（星間物質の風）の進行
		if (spaceWind != null) spaceWind.updateSpaceWind();

	} // end updateUniverse

	/**
	 * toggleOrbitByCategory: 特定カテゴリに属する全天体の軌道表示を反転させます。
	 * @param cat 反転対象のカテゴリ（PLANET, ELLIPTICAL, COMET）
	 */
	private void toggleOrbitByCategory(CelestialBody.Category cat) {
		bodies.stream()
			.filter(b -> b.getCategory() == cat)
			.forEach(b -> b.setOrbitVisible(!b.getOrbitGroup().isVisible()));
	}

	/**
	 * changeLang: HUDテキストの多言語切り替え処理
	 * 日英の切り替えをします。
	 */
	private void changeLang(){

		// 言語変更
		this.lang = ("ja".equals(this.lang) ? "en" : "ja");

		// HUDの更新
		updateHUDTexts();

		// ★全天体の名前を一斉にスイッチ！
		bodies.forEach(b -> b.updateLabelLanguage(this.lang));

	} // end changeLang

	/**
	 * HUD（ヘッドアップディスプレイ）の更新
	 */
	private void updateHUDTexts(){

		textLang.setText(lang.equals("ja") ? "L：English／Japanese" : "L: English／Japanese");
		textMove.setText(lang.equals("ja") ? "カーソルキー：カメラ回転操作 （Ｘ／Ｙ）" : "Arrow Keys: Rotate Camera (X/Y)");
		textZoom.setText(lang.equals("ja") ? "PageUP/Down ：ズームイン／アウト" : "PageUP/Down: Zoom In/Out");
		textLabel.setText(lang.equals("ja") ? "スペースキー：天体ラベル表示切替" : "Space: Toggle Planet Labels");
		textHUDHidden.setText(lang.equals("ja") ? "Ｈキー：UI表示／非表示" : "H: Show/Hide UI");
		textOrbitHidden.setText(lang.equals("ja") ? "0：軌道ライン全天体非表示" : "0: Hide All Orbit Lines");
		textOrbitPlanet.setText(lang.equals("ja") ? "1：惑星軌道ライン表示" : "1: Show Planetary Orbit Lines");
		textOrbitElliptic.setText(lang.equals("ja") ? "2：楕円軌道天体 軌道ライン表示" : "2: Show Elliptical/Inclined Orbit Lines");
		textOrbitComet.setText(lang.equals("ja") ? "3：彗星軌道ライン表示" : "3: Show Comet Orbit Lines");

		// スピード倍率
		applyTimeScale();

	} // end updateHUDTexts

	/**
	 * 全天体に現在の倍率を適用する
	 */
	private void applyTimeScale() {
		bodies.forEach(b -> b.setTimeScale(timeScale));
		// HUD（テキスト）に現在の倍率を表示すると親切です
		textSpeedInfo.setText(String.format("Time Scale A-UP/Z-DOWN: %.2f x", timeScale));
	}

	public static void main(String[] args) {
		launch(args);
	}

}
