import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Sphere;
import javafx.scene.image.Image;
import javafx.scene.shape.Cylinder;
import javafx.scene.transform.Rotate;
import javafx.geometry.Point3D;
import java.io.InputStream;

/**
 * EllipticalBody: 楕円軌道および軌道傾斜を持つ天体クラス。
 * * 【設計思想】
 * 冥王星やエリス、セドナといった、単純な円運動では表現できない
 * 離心率と軌道傾斜角を持つ天体をシミュレートします。
 * 標準の Line ノードの代わりに、極細の Cylinder を使用することで、
 * 3D空間における線の震えを抑え、滑らかな軌道ラインを実現します。
 * @author きん ＆ Gemini
 */
public class EllipticalBody extends CelestialBody {

	// --- 楕円幾何学パラメータ ---
	protected double a;			// 長半径
	protected double b;			// 短半径
	protected double offset;	   // 焦点ズレ（太陽を焦点に置くためのオフセット）

	// --- 空間配置パラメータ ---
	protected double rotation;	 // 公転面全体の垂直軸回転（Y軸）
	protected double pitch;		// 軌道傾斜角（X軸周りの傾き）

	// --- 外見パラメータ ---
	private double radius;
	private String texturePath;
	private Color diffuseColor;

	/**
	 * EllipticalBody コンストラクタ
	 * 楕円軌道の幾何学形状、空間的な配置、および天体の外見を定義します。
	 * @param nameJa	   天体の日本語名（表示用）
	 * @param nameEn	   天体の英語名（表示用）
	 * @param a			長半径（軌道の横幅の半分。天体の遠日点・近日点の基準となる距離）
	 * @param b			短半径（軌道の縦幅の半分。aとの差が大きいほど細長い楕円になる）
	 * @param speed		公転速度（1フレームあたりの角度更新量。角速度）
	 * @param offset	   焦点オフセット（太陽を楕円の中心ではなく「焦点」に置くためのズレ量）
	 * @param rotation	 公転面全体の垂直軸回転（Y軸周りの向き。天体がどの方向から飛来するかを定義）
	 * @param pitch		軌道傾斜角（黄道面に対する傾き。プラス・マイナスの値で立体的な傾斜を表現）
	 * @param radius	   天体自体の物理的な半径（3Dモデルのサイズ）
	 * @param texturePath  天体表面に使用するテクスチャ画像のパス（nullの場合は単色表示）
	 * @param diffuseColor 天体の基本色（テクスチャがない場合や、ラベル・軌道ラインのベース色）
	 */
	public EllipticalBody(String nameJa, String nameEn, double a, double b, double speed, 
						  double offset, double rotation, double pitch,
						  double radius, String texturePath, Color diffuseColor) {

		super(nameJa, nameEn, speed);

		this.a = a;
		this.b = b;
		this.offset = offset;
		this.rotation = rotation;
		this.pitch = pitch;

		this.radius = radius;
		this.texturePath = texturePath;
		this.diffuseColor = diffuseColor;

		// カテゴリを「楕円・傾斜天体」に設定
		this.category = Category.ELLIPTICAL;

		// 初期角度をランダムに設定（全天体が重ならないように）
		this.currentAngle = Math.random() * 360;

		// モデル構築
		this.setupAppearance();

		// 軌道ライン構築（Cylinder連結方式）
		this.buildOrbitLine();

	} // end EllipticalBody constructor

	/**
	 * setupAppearance: 天体の外見（3Dモデルおよび質感）を構築します。
	 * リソースからのテクスチャ読み込みを優先し、失敗した場合は指定の色による
	 * フォールバック（代替表示）を行うことで、実行時のエラーを最小限に抑える設計です。
	 */
	private void setupAppearance() {

		PhongMaterial material = new PhongMaterial();
		boolean textureLoaded = false;

		// 1. テクスチャ画像のロード試行
		if (texturePath != null && !texturePath.isEmpty()) {

			try {
				// JARファイル内やプロジェクトリソースから安全にファイルを読み込むため InputStream を使用
				InputStream is = getClass().getResourceAsStream(texturePath);

				if (is != null) {
					Image image = new Image(is);

					// 画像の読み込みに成功（ファイル破損やフォーマットエラーがない）かを確認
					if (!image.isError()) {
						// 天体表面にテクスチャを貼り付け
						material.setDiffuseMap(image);
						textureLoaded = true;
					}
				}

			} catch (Exception e) {
				// ロード失敗時は標準エラー出力にログを残し、プログラムの強制終了を回避
				System.err.println("Texture load failed for " + name + ": " + texturePath);
			}
		}

		// 2. テクスチャがロードできなかった場合のフォールバック処理
		if (!textureLoaded) {
			// 指定された基本色を適用。指定がない場合はデフォルトの「白」とする
			material.setDiffuseColor(diffuseColor != null ? diffuseColor : Color.WHITE);
		}

		// 3. 3D形状（球体）の生成と質感の適用
		// 指定された物理半径に基づき Sphere 形状を生成
		Sphere sphere = new Sphere(radius);
		sphere.setMaterial(material);

		// 4. 生成したモデルを基底クラスの node フィールドに保持
		// 共通メソッド update() を通じて、この node が宇宙空間を移動します
		this.node = sphere;

	} // end setupAppearance

	/**
	 * buildOrbitLine: 3D空間における「線の震え（ジャギー）」を排除した高品質な軌道ラインを構築します。
	 * * 【技術的アプローチ】
	 * 1. 標準の Line ノードは 3D 空間での描画が不安定なため、極細の Cylinder（円柱）を連結する方式を採用。
	 * 2. 各線分（セグメント）の端点は、天体の物理移動ロジック（computePosition）と数学的に
	 * 完全に同一の計算パスを通すことで、天体の動きと軌道ラインの完璧な一致を保証します。
	 * 3. 透過率を極限まで下げる（1%）ことで、重なり合うラインを上品に可視化し、主役の天体を際立たせます。
	 */
	private void buildOrbitLine() {

		// 名前がない天体（背景塵など）や、物理的に成立しない軌道半径の場合は生成をスキップ
		if (this.name == null || this.name.isEmpty() || a <= 0) return;

		// // 分割数：円弧をどれだけの直線で近似するか。64分割は滑らかさと描画負荷のバランスが最適
		int segments = 64; 
		Color orbitBaseColor = (diffuseColor != null) ? diffuseColor : Color.WHITE;

		// 軌道ラインの色彩設定：天体の色を継承しつつ、透過率 0.01（1%）に設定。
		// これにより、カメラが引いた際にラインが「面」のように滑らかに見える視覚効果を生みます。
		Color lineColor = Color.color(
			orbitBaseColor.getRed(), 
			orbitBaseColor.getGreen(), 
			orbitBaseColor.getBlue(), 
			0.01 
		);
		PhongMaterial lineMaterial = new PhongMaterial(lineColor);

		// 数学計算用の事前ラジアン変換（ループ内での重複計算を避け、効率化を図る）
		double pRad = Math.toRadians(pitch);
		double rotRad = Math.toRadians(rotation);

		for (int i = 0; i < segments; i++) {

			// セグメントの開始角度と終了角度を算出
			double ang1 = Math.toRadians((360.0 / segments) * i);
			double ang2 = Math.toRadians((360.0 / segments) * (i + 1));

			// 点1の算出：数学座標 -> 軌道傾斜 -> 空間回転 の全工程を実行
			Point3D p1 = calculateOrbitPoint(ang1, pRad, rotRad);
			// 点2の算出：次のセグメントとの連続性を確保
			Point3D p2 = calculateOrbitPoint(ang2, pRad, rotRad);

			// 算出した2点間を、ベクトル演算に基づいた回転・伸縮を施した極細シリンダーで接続。
			// これにより、どの角度から見ても太さが一定の「真の3Dライン」を形成します。
			createOrbitSegment(p1, p2, lineMaterial);
		}

	} // end buildOrbitLine

	/**
	 * calculateOrbitPoint: 角度から空間上の座標を算出（物理計算と完全同期）
	 */
	private Point3D calculateOrbitPoint(double rad, double pRad, double rotRad) {

		// 1. 素の楕円平面座標
		double rx = Math.cos(rad) * a + offset;
		double rz = Math.sin(rad) * b;

		// 2. 軌道傾斜の適用
		double my = rz * Math.sin(pRad);
		double mz = rz * Math.cos(pRad);

		// 3. 公転面全体の回転適用
		double fx = rx * Math.cos(rotRad) - mz * Math.sin(rotRad);
		double fz = rx * Math.sin(rotRad) + mz * Math.cos(rotRad);
		double fy = my;

		return new Point3D(fx, fy, fz);

	} // end calculateOrbitPoint

	/**
	 * createOrbitSegment: 2点間に幾何学的に最適化された極細の Cylinder（円柱）を配置します。
	 * * 【数学的アルゴリズムの解説】
	 * 1. 2点間の距離を「高さ」とし、中間点を「配置座標」として算出します。
	 * 2. JavaFXの標準Cylinderは垂直（Y軸方向）に生成されるため、これを2点の成すベクトルへ
	 * 正確に向けるための「回転軸」と「回転角」をベクトル演算で導き出します。
	 * 3. 外積（crossProduct）により2つのベクトルに直交する「回転軸」を特定し、
	 * 内積（dotProduct）から導かれる「なす角」を用いて、空間上の正確な向きを確定させます。
	 */
	private void createOrbitSegment(Point3D p1, Point3D p2, PhongMaterial material) {

		// 1. 2点間のベクトル、距離（円柱の長さ）、および中間点（配置位置）を算出
		Point3D diff = p2.subtract(p1);
		// ベクトルの大きさ ＝ シリンダーの高さ
		double height = diff.magnitude();
		// 2点の中間座標
		Point3D mid = p1.midpoint(p2); 

		// 2. 回転軸の算出（外積）
		// 標準の垂直方向（Y_AXIS）と、結びたい方向（diff）の両方に垂直なベクトルを回転の軸とします
		Point3D axis = diff.crossProduct(Rotate.Y_AXIS);

		// 3. 回転角度の算出（内積）
		// 二つのベクトルの内積から、それらがなす角度（ラジアン）を逆余弦(acos)で求めます
		double angle = Math.acos(diff.normalize().dotProduct(Rotate.Y_AXIS));

		// 4. シリンダーの生成
		// 太さ 0.25。これにより、ズームアウトしても「消えない線」を実現。
		Cylinder segment = new Cylinder(0.25, height);
		segment.setMaterial(material);

		// 5. 3D空間への配置
		// 中間点に移動させることで、両端点 (p1, p2) を繋ぐ位置にセットされます
		segment.setTranslateX(mid.getX());
		segment.setTranslateY(mid.getY());
		segment.setTranslateZ(mid.getZ());

		// 6. 姿勢の制御
		// 算出した回転軸(axis)を中心に、なす角分だけ回転。
		// ※JavaFXの座標系とCylinderの初期向きを考慮し、角度を度数法に変換して適用します
		Rotate rotate = new Rotate(-Math.toDegrees(angle), 0, 0, 0, axis);
		segment.getTransforms().add(rotate);

		// 軌道グループに追加（CelestialBodyの共通管理下へ）
		this.orbitGroup.getChildren().add(segment);

	} // end createOrbitSegment

	/**
	 * computePosition: 楕円軌道および軌道傾斜、空間回転を統合した数学的座標算出。
	 * 毎フレームの AnimationTimer から呼び出され、天体の次なる「一歩」を確定させる基盤メソッドです。
	 * * 【数学的プロセス】
	 * 1. ケプラー運動の近似としての楕円座標算出。
	 * 2. 黄道面（XZ平面）に対する軌道傾斜角（Pitch）の適用。
	 * 3. 太陽系全体における公転面の向き（Rotation）の適用。
	 */
	@Override
	protected void computePosition() {

		// 1. 公転角度の更新
		// 1フレームあたりの角速度(speed)を加算し、ラジアン単位に変換します
		currentAngle += (speed * timeScale);
		double rad = Math.toRadians(currentAngle);

		// 2. 基本となる楕円平面座標の算出 (XZ平面)
		// a（長半径）、b（短半径）、および太陽を焦点に据えるための offset を使用します
		// この時点では、まだ「傾きのない水平な楕円」の状態です
		double rawX = Math.cos(rad) * a + offset;
		double rawZ = Math.sin(rad) * b;

		// 3. 軌道傾斜（Pitch）の適用
		// 平面上の Z 座標を高さ（Y軸）へと射影し、立体的な「傾き」を生み出します
		// これにより、冥王星のような黄道面を外れる天体の動きが再現されます
		double pRad = Math.toRadians(pitch);
		double midY = rawZ * Math.sin(pRad);
		double midZ = rawZ * Math.cos(pRad);

		// 4. 空間回転（Rotation）の適用
		// 算出された (rawX, midY, midZ) を、さらに宇宙の垂直軸（Y軸）を中心に回転させます
		// 行列回転の公式（x' = x*cosθ - z*sinθ, z' = x*sinθ + z*cosθ）を用いて、
		// 公転面そのものが宇宙のどの方向を向いているかを確定させます
		double rotRad = Math.toRadians(rotation);

		// 最終的な 3D 空間座標 (x, y, z) の確定
		this.x = rawX * Math.cos(rotRad) - midZ * Math.sin(rotRad);
		this.z = rawX * Math.sin(rotRad) + midZ * Math.cos(rotRad);
		this.y = midY;

	} // end computePosition

} // end class EllipticalBody
