import java.io.InputStream;
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.shape.Line;
import javafx.scene.transform.Rotate;

/**
 * PlanetBody: 円軌道を描く天体クラス（主要惑星、小惑星帯など）。
 * * 【設計のポイント】
 * 基底クラスで定義された computePosition を実装し、
 * 太陽を中心とした数学的な円運動（x = r*cosθ, z = r*sinθ）を行います。
 * 自身の軌道半径に基づいた可視化ラインの生成機能を搭載しています。
 * @author きん ＆ Gemini
 */
public class PlanetBody extends CelestialBody {

	// 太陽（原点）からの公転半径
	private double orbitDistance;

	// 天体自体の大きさ（半径）
	private double radius;

	// 表面テクスチャのパス
	private String texturePath;

	// 天体の基本色（テクスチャがない場合の代替色）
	private Color diffuseColor;

	/**
	 * コンストラクタ（角度ランダム生成用）
	 * @param nameJa		天体名（日）
	 * @param nameEn		天体名（英）
	 * @param dist			公転半径
	 * @param speed			公転速度（1フレームあたりの移動角度）
	 * @param radius		天体自体の半径
	 * @param texturePath	テクスチャ画像のパス
	 * @param diffuseColor	天体の基本色
	 */
	public PlanetBody(String nameJa, String nameEn, double dist, double speed, double radius,
						String texturePath, Color diffuseColor) {

		// 開始角度に -1 を指定し、ランダム初期化コンストラクタへ委譲
		this(nameJa, nameEn, dist, speed, radius, texturePath, diffuseColor, -1);

	} // end PlanetBody constructor

	/**
	 * コンストラクタ（開始角度指定用）
	 * @param nameJa	   天体名（日）
	 * @param nameEn	   天体名（英）
	 * @param dist		 公転半径
	 * @param speed		公転速度
	 * @param radius	   天体自体の半径
	 * @param texturePath  テクスチャ画像のパス
	 * @param diffuseColor 天体の基本色
	 * @param startAngle   初期の角度（-1 の場合は基底クラスでランダム値を生成）
	 */
	public PlanetBody(String nameJa, String nameEn, double dist, double speed, double radius,
						String texturePath, Color diffuseColor, double startAngle) {

		super(nameJa, nameEn, speed, startAngle);

		this.orbitDistance = dist;
		this.radius = radius;
		this.texturePath = texturePath;
		this.diffuseColor = diffuseColor;

		// カテゴリを惑星に設定
		this.category = Category.PLANET;

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

		// 軌道ラインの構築
		this.buildOrbitLine();

	} // end PlanetBody constructor

	/**
	 * setupAppearance: 天体の物理的形状（Sphere）と視覚的質感（Material）を構築します。
	 * * 【実装のこだわり】
	 * 1. リソース透過性: JARパッケージ化後も動作するよう、InputStream経由での読み込みを徹底。
	 * 2. 堅牢性: テクスチャの欠落や読み込みエラーが発生しても、プログラムを停止させず
	 * 基本色（diffuseColor）による描画へ自動的に切り替えるフォールバック機能を搭載。
	 * 3. 拡張性: 生成した Sphere を基底クラスの node に格納することで、
	 * 共通の物理更新ロジックによる制御を可能にしています。
	 */
	private void setupAppearance() {

		// JavaFX 3Dの基本マテリアル。光の拡散反射（Diffuse）を制御します。
		PhongMaterial material = new PhongMaterial();
		boolean textureLoaded = false;

		// 1. テクスチャのロード試行（外部リソースへのアクセス）
		if (texturePath != null && !texturePath.isEmpty()) {

			try {

				// クラスパス内からリソースを取得。
				// getResourceAsStream を使用することで、ファイルパスの差異を吸収し安全にロードします。
				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);
			}

		} // end if

		// 2. テクスチャ未ロード時のフォールバック処理
		// 画像がない、あるいはロードに失敗した場合は、設定された diffuseColor で塗りつぶします。
		if (!textureLoaded) {
			material.setDiffuseColor(diffuseColor != null ? diffuseColor : Color.WHITE);
		}

		// 3. 3Dモデル（Sphere）の生成
		// 引数の radius（半径）に基づき、JavaFX標準の球体ジオメトリを生成。
		Sphere sphere = new Sphere(radius);

		// 構築したマテリアル（質感）をモデルにバインド。
		sphere.setMaterial(material);

		// 4. 共通ノードへの登録
		// この sphere が CelestialBody クラスの node フィールドにセットされることで、
		// 外部（InfinitySpace3D）からの座標更新や回転制御の対象となります。
		this.node = sphere;

	} // end setupAppearance

	/**
	 * buildOrbitLine: 惑星の公転軌道（円軌道）を可視化するための3Dライン群を構築します。
	 * * 【実装のポイント】
	 * 1. 軽量化設計: 主要惑星は完全な円軌道として扱うため、2Dの Line ノードを 64個 連結して円を近似します。
	 * 2. 座標系の変換: JavaFX の Line は本来 XY平面（画面と平行）に描画されます。これを
	 * Rotate(90, X_AXIS) を用いて XZ平面（宇宙の床面）へと倒し、公転面と一致させています。
	 * 3. 視認性: 惑星固有の色（diffuseColor）を継承しつつ、透過率 25% (0.25) を適用。
	 * これにより、軌道が主張しすぎず、かつ遠くからでも存在を感じられる絶妙な視覚効果を生んでいます。
	 */
	private void buildOrbitLine() {

		// 名前がない、または軌道半径が 0 以下の場合は生成をスキップ（安全策）
		if (name == null || name.isEmpty() || orbitDistance <= 0) return;

		// 解像度の定義：64分割。円としての滑らかさを保ちつつ、描画負荷を最小限に抑える黄金比。
		int segments = 64;
		Color orbitBaseColor = (diffuseColor != null) ? diffuseColor : Color.WHITE;

		// 軌道ラインの色彩設定：天体色を継承。
		// 透過率 0.25 は、複数の軌道が重なった際にも「重なりによる輝き」を美しく表現するための値です。
		Color lineColor = Color.color(
			orbitBaseColor.getRed(), 
			orbitBaseColor.getGreen(), 
			orbitBaseColor.getBlue(), 
			0.25 
		);

		// 360度を segments 分割してループ処理
		for (int i = 0; i < segments; i++) {

			// 開始角と終了角をラジアンに変換
			double angle1 = Math.toRadians((360.0 / segments) * i);
			double angle2 = Math.toRadians((360.0 / segments) * (i + 1));

			// 1. 円周上の座標を算出（この時点ではまだ平面的な計算）
			// 数学的な XZ平面上の座標 (cos, sin) に軌道半径を乗算します。
			double x1 = Math.cos(angle1) * orbitDistance;
			double z1 = Math.sin(angle1) * orbitDistance;
			double x2 = Math.cos(angle2) * orbitDistance;
			double z2 = Math.sin(angle2) * orbitDistance;

			// 2. Lineノードの生成
			// 本来 JavaFX の Line(x1, y1, x2, y2) は「垂直な壁」に描く命令ですが、
			// ここではあえて Y座標の代わりに Z座標の値を流し込み、形を作ります。
			Line segment = new Line(x1, z1, x2, z2);
			segment.setStroke(lineColor);

			// 3. 空間配置の魔術（重要）
			// 生成された Line は XY平面に立っている状態のため、X軸を中心に 90度回転。
			// これにより、Line の Y軸成分が 3D空間の Z軸（奥行き）へと変換され、
			// 太陽を中心とした水平な「公転面（XZ平面）」にピッタリと重なります。
			segment.getTransforms().add(new Rotate(90, Rotate.X_AXIS));

			// 4. 軌道グループへの追加
			// CelestialBody の管理下にある orbitGroup に集約し、一括での表示・非表示制御を可能にします。
			this.orbitGroup.getChildren().add(segment);

		} // end for

	} // end buildOrbitLine

	/**
	 * computePosition: 惑星の基本となる円軌道の数学的座標を算出します。
	 * * 【幾何学的設計】
	 * 1. 太陽系の主平面を XZ平面（床面）として定義し、Y座標（高さ）を 0 に固定することで、
	 * すべての主要惑星が同一平面上を整然と公転する構造を作ります。
	 * 2. 三角関数（sin/cos）を用いた極座標変換により、滑らかな円運動を実現します。
	 */
	@Override
	protected void computePosition() {

		// 1. 公転角度の更新
		// フレームごとに設定された角速度（speed）を加算し、天体の位置を進めます。
		currentAngle += (speed * timeScale);

		// 三角関数の計算に必要なラジアン単位に変換（360度法 -> 弧度法）
		double rad = Math.toRadians(currentAngle);

		// 2. 極座標変換による 3D 座標の確定
		// 太陽（原点 0,0,0）を中心とした円運動を算出します。
		// x = r * cos(θ) : X軸方向の距離
		// z = r * sin(θ) : Z軸（奥行き）方向の距離
		// y = 0          : 黄道面（主平面）からのズレがないことを保証
		this.x = Math.cos(rad) * orbitDistance;
		this.y = 0; 
		this.z = Math.sin(rad) * orbitDistance;

	} // end computePosition

	/**
	 * addPlanetRing: 惑星に対して、土星のような美しい「環」を追加します。
	 * * 【実装の工夫：円環のシミュレーション】
	 * 1. 形状の代用: JavaFXには標準の円環形状がないため、高さ（厚み） 0.1 の
	 * 極薄 Cylinder を生成し、それを「円盤」として扱います。
	 * 2. 透過テクスチャの活用: 指定された画像（texturePath）に透明成分（α値）が含まれる場合、
	 * シリンダーの上面に円環状のテクスチャが美しくマッピングされます。
	 * 3. 複合オブジェクトの構築: 天体本体（Sphere）と環を一つの Group にまとめ直すことで、
	 * 公転やカメラ追従のロジックを変更することなく、一つの「惑星ユニット」として扱えるようにします。
	 */
	public void addPlanetRing(double innerRatio, double outerRatio, double tilt, String texturePath, double opacity) {

		// 1. 環のサイズ算出
		// 天体本体の半径(radius)に外径比率(outerRatio)を乗じ、全体の広がりを決定します。
		double ringRadius = this.radius * outerRatio;

		// 厚み 0.1 の極薄シリンダー。これが惑星を囲む「円盤」のベースとなります。
		Cylinder ring = new Cylinder(ringRadius, 0.1); 

		// 2. マテリアル（質感）の設定
		PhongMaterial material = new PhongMaterial();
		boolean ringTexLoaded = false;

		if (texturePath != null && !texturePath.isEmpty()) {

			try {
				// 環専用のテクスチャ（通常はドーナツ状の透過PNG等）をロード
				InputStream is = getClass().getResourceAsStream(texturePath);

				if (is != null) {
					material.setDiffuseMap(new Image(is));
					ringTexLoaded = true;
				}

			} catch (Exception e) {
				// ロード失敗時はログを出力し、フォールバック（色指定）へ移行
				System.err.println("Ring texture load failed: " + texturePath);
			}

		} // end if

		// テクスチャがない場合、またはロード失敗時は天体色をベースにした単色塗りつぶし
		if (!ringTexLoaded) {
			Color baseColor = (this.diffuseColor != null) ? this.diffuseColor : Color.LIGHTGRAY;
			// 指定された透明度（opacity）を適用。これにより、下の宇宙空間が透けて見えるようになります。
			material.setDiffuseColor(new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), opacity));
		}

		ring.setMaterial(material);

		// 3. ノードの親子関係を再構築（シーングラフの最適化）
		// 単一の Sphere だった node を、Sphere + Ring の Group 構成へアップデートします。
		// これにより、update() メソッドを書き換えることなく「環を持つ天体」を一括操作可能です。
		if (this.node instanceof Sphere) {

			Sphere bodySphere = (Sphere) this.node;
			Group group = new Group();

			// 本体と環を同一の座標系（Group内）に配置
			group.getChildren().addAll(bodySphere, ring);
			this.node = group;

		} else if (this.node instanceof Group) {

			// すでに Group 化されている場合（二重の環など）は、既存のグループに追加
			((Group) this.node).getChildren().add(ring);

		}

		// 4. 環の傾斜角を適用
		// 惑星の自転軸とは独立して、環の独自の傾き（tilt）を Z軸回転で表現します。
		ring.getTransforms().clear();
		Rotate tiltRotate = new Rotate(tilt, Rotate.Z_AXIS);
		ring.getTransforms().add(tiltRotate);

	} // end addPlanetRing

} // end class PlanetBody
