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.CullFace;
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;

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

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

	// 自転角
	private double tilt = 0;
	// 現在の自転角度
	private double selfRotationAngle = 0;
	// 自転速度（デフォルト値）
	private double selfRotationSpeed = 1;
	// 回転変換オブジェクト
	private Rotate selfRotate;

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

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

	} // end PlanetBody constructor

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

		super(nameJa, nameEn, speed, startAngle);

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

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

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

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

		// 自転用の設定
		this.selfRotate = new Rotate(0, Rotate.Y_AXIS);
		this.node.getTransforms().add(selfRotate);

	} // 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

	/**
	 * update: 毎フレームの更新処理。
	 * スーパークラスの処理を実行後、自転軸での回転を行います。
	 * 「位置計算」と「表示反映」を明確に分離し、数学的正確さを担保。
	 * @param rotX カメラ（世界）のX回転（逆行列用）
	 * @param rotY カメラ（世界）のY回転
	 * @param camDist カメラ距離
	 * @param sizeFactor ズーム倍率
	 * @param showLabels 天体ラベル表示
	 */
	@Override
	public void update(double rotX, double rotY, double camDist, double sizeFactor, boolean showLabels) {

		// スーパークラスのメソッド呼び出し
		super.update(rotX, rotY, camDist, sizeFactor, showLabels);

		// 自転
		// プラスすると、逆回転しているイメージになるので、マイナスする
		this.selfRotationAngle = this.selfRotationAngle - (this.selfRotationSpeed * timeScale);
		this.selfRotationAngle = (Math.abs(this.selfRotationAngle) > 360)?0: this.selfRotationAngle;

		// Rotate 変換
		this.node.getTransforms().clear();
		Rotate tiltRotateZ = new Rotate(this.tilt, Rotate.Z_AXIS);
		this.node.getTransforms().add(tiltRotateZ);
		Rotate tiltRotateY = new Rotate(this.selfRotationAngle, Rotate.Y_AXIS);
		this.node.getTransforms().add(tiltRotateY);

	} // end update

	/**
	 * 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には標準の円環形状がないため、高さ（厚み） 00.1 の
	 * 極薄 Cylinder を生成し、それを「円盤」として扱います。
	 * 2. 透過テクスチャの活用: 指定された画像（texturePath）に透明成分（α値）が含まれる場合、
	 * シリンダーの上面に円環状のテクスチャが美しくマッピングされます。
	 * 3. 複合オブジェクトの構築: 天体本体（Sphere）と環を一つの Group にまとめ直すことで、
	 * 公転やカメラ追従のロジックを変更することなく、一つの「惑星ユニット」として扱えるようにします。
	 * @param innerRatio  内径比率（本体半径に対する、環の内側の開始位置。将来の拡張用）
	 * @param outerRatio  外径比率（本体半径に対する、環の外側の広がり。例: 2.0 で本体の2倍の大きさ）
	 * @param texturePath 環に貼り付ける透過PNG等のリソースパス（nullの場合は単色表示）
	 * @param opacity     テクスチャがない、またはロード失敗時の環の透明度（0.0～1.0）	 */
	public void addPlanetRing(double innerRatio, double outerRatio, String texturePath, double opacity) {

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

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

		// 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() / 2, baseColor.getGreen() / 2, baseColor.getBlue() / 2, 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);

		}

	} // end addPlanetRing

} // end class PlanetBody
