import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.transform.Rotate;

/**
 * CometBody: 独立した楕円軌道計算と、職人技によるシリンダー構成の核・尾を持つ天体クラス。
 * 太陽との距離（近日点への接近）に応じて、尾の伸長、透過度、および太陽回避挙動を
 * 動的に変化させる演出ロジックを内包する。
 * 本体のupdate()と同一の計算プロセスをループ内で再現し、近日点でのズレを極限まで抑制。
 * 著者：きん & Gemini
 */
public class CometBody extends CelestialBody {

	// --- 楕円軌道用幾何学パラメータ ---
	private double a;			// 長半径
	private double b;			// 短半径
	private double offset;	   // 焦点ズレ（太陽を焦点に置くための中心からの距離）
	private double rotation;	 // 公転面全体の垂直軸回転（Y軸）
	private double pitch;		// 軌道傾斜角（X軸周りの傾き）

	private double radius;	   // 彗星の核の半径
	private Color diffuseColor;  // 彗星の基本発光色

	// --- 動的演出用コンポーネント ---
	private Cylinder tail;			   // 尾（動的にスケールと表示を操作）
	private PhongMaterial tailMaterial;  // 尾のマテリアル（透過度を動的に操作）
	private double baseTailLength;	   // 構築時の基準となる尾の長さ

	/**
	 * CometBody コンストラクタ
	 * 彗星特有の極端な楕円軌道と、太陽接近時に動的に変化する「核と尾」を定義します。
	 * @param nameJa	   天体の日本語名
	 * @param nameEn	   天体の英語名
	 * @param a			長半径（彗星の非常に細長い軌道の主軸）
	 * @param b			短半径（彗星の軌道の幅。aに対して極端に小さい値に設定されることが多い）
	 * @param speed		公転速度（近日点付近での加速ロジックのベースとなる角速度）
	 * @param offset	   焦点オフセット（太陽を焦点に配置し、極端な近日点を実現するためのズレ量）
	 * @param rotation	 公転面全体の垂直軸回転（Y軸周りの向き）
	 * @param pitch		軌道傾斜角（黄道面に対する傾き。逆走彗星などの表現に重要）
	 * @param radius	   彗星の核（ヘッド）の半径。尾の太さの基準にもなります。
	 * @param diffuseColor 彗星の発光色。核の色および、尾のグラデーションのベース色に使用。
	 */
	public CometBody(String nameJa, String nameEn, double a, double b, double speed, 
					 double offset, double rotation, double pitch,
					 double radius, 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.diffuseColor = diffuseColor;

		// カテゴリを「彗星」に設定
		this.category = Category.COMET;

		// 3Dモデル（核と尾）の構築
		this.setupAppearance();

		// 軌道ラインの構築（物理更新クローンロジックによる完全同期）
		this.buildOrbitLine();

	} // end CometBody constructor

	/**
	 * setupAppearance: 核と尾をすべてシリンダーで構築し、中心軸の一致を保証する。
	 * 球体（Sphere）を使用せず、同径のシリンダーを重ねることで、回転時の接合部のブレを排除。
	 */
	private void setupAppearance() {

		// 核と尾を作成するため、Groupに載せる
		Group group = new Group();

		// 1. 核（Head）: 太くて短いシリンダー。彗星の本体。
		double headHeight = radius * 1.5;
		Cylinder head = new Cylinder(radius, headHeight);
		head.setMaterial(new PhongMaterial(diffuseColor));

		// 2. 尾（Tail）: 核から太陽と反対方向へ伸びるガス状の尾。
		this.baseTailLength = radius * 10; 
		this.tail = new Cylinder(radius * 0.7, baseTailLength);

		// 尾の質感を出すため、初期マテリアルに透過設定を適用
		Color tailColor = diffuseColor.deriveColor(0, 1, 1, 0.4);
		this.tailMaterial = new PhongMaterial(tailColor);
		tail.setMaterial(this.tailMaterial);

		// 【接合部調整】
		// JavaFXのCylinderは中心が原点となるため、高さの半分＋核のオフセット分を
		// ずらすことで、核の背面にジャストフィットさせる。
		tail.setTranslateY(baseTailLength / 2 + (radius * 0.75));

		// Groupに追加
		group.getChildren().addAll(head, tail);
		this.node = group;

	} // end setupAppearance

	/**
	 * buildOrbitLine: 彗星特有の激しい挙動を正確に可視化する、高精度な軌道ラインを構築します。
	 * * 【技術的こだわり：近日点完全同期ロジック】
	 * 1. 彗星は太陽に極めて接近するため、近日点での急激なカーブを滑らかに描画する必要があります。
	 * 通常の惑星（64分割）を凌駕する「256分割」という超高密度設計により、この曲率を克服しています。
	 * 2. 本クラス独自の「太陽回避（物理的な押し出し）」ロジックを、軌道ライン描画時にも完全にトレース。
	 * これにより、天体本体の移動軌跡とラインが1ミリのズレもなく重なる、極めて精緻なシミュレーションを実現。
	 * 3. 2DのLineノードでは不可能な、奥行きのある「宇宙空間に浮かぶ線」を Cylinder 連結によって構築します。
	 */
	private void buildOrbitLine() {

		// 名前がない場合や、不正な長半径 (a) の場合は生成を中止（安全策）
		if (this.name == null || this.name.isEmpty() || a <= 0) return;

		// カテゴリを「彗星」に指定。これにより、メインクラスからのカテゴリ別軌道制御（3キー）を可能にする
		this.category = Category.COMET;

		// 解像度の最大化：256セグメント。近日点付近の急激な加速と進路変更を、カクつきなく表現するための職人設定
		int segments = 256;
		Color orbitColor = (diffuseColor != null) ? diffuseColor : Color.WHITE;

		// 「極薄」演出設定：透過率 1%(0.01) と太さ 0.25 の組み合わせ。
		// これにより、ラインが重なり合う近日点付近でも「まぶしさ」を抑えつつ、深みのある光の筋を演出します。
		Color lineColor = Color.color(orbitColor.getRed(), orbitColor.getGreen(), orbitColor.getBlue(), 0.01);
		PhongMaterial lineMat = new PhongMaterial(lineColor);

		// 360度の軌道を segments 数に分割し、一節ずつラインを構築
		for (int i = 0; i < segments; i++) {

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

			// update() メソッド内で行われる「楕円計算 → 3D回転 → 太陽回避補正」の全工程を
			// そっくりそのまま再現（クローン計算）した座標を取得します。
			// これにより、天体本体の移動パスと、描画された軌道ラインの「完全一致」を保証します。
			Point3D p1 = getFinalVisualPosition(ang1);
			Point3D p2 = getFinalVisualPosition(ang2);

			// 算出した2点間を極細のシリンダーで接続
			createOrbitSegment(p1, p2, lineMat);

		} // end for

	} // end buildOrbitLine

	/**
	 * getFinalVisualPosition: 物理シミュレーションと視覚的演出を完全に同期させるための座標算出メソッド。
	 * * 【このメソッドの存在意義】
	 * 彗星は近日点付近で「太陽にぶつからないように回避する」という独自の演出ロジック（updateTailVisuals）
	 * を持っています。軌道ラインを描画する際にもこの「回避後の座標」を計算しないと、
	 * 本体と軌道がズレてしまいます。そのため、移動ロジックを完全にクローン（再現）しています。
	 * @param rad 算出対象となる軌道上の角度（ラジアン）
	 * @return 演出補正まで適用された、最終的な 3D 空間上の座標点
	 */
	private Point3D getFinalVisualPosition(double rad) {

		// --- Step 1: 基礎楕円の算出 (computePosition と同一) ---
		// ケプラーの法則に基づき、太陽を焦点(offset)に置いた平面上の楕円座標を求めます。
		double flatX = Math.cos(rad) * a + offset; 
		double flatZ = Math.sin(rad) * b;

		// --- Step 2: 三次元空間への投影と回転 (computePosition と同一) ---
		double pRad = Math.toRadians(pitch);
		double rRad = Math.toRadians(rotation);

		// 1. X軸周りの回転（Pitch）により、平面に高低差(Y)をつけます。
		double pY = -flatZ * Math.sin(pRad); 
		double pZ =  flatZ * Math.cos(pRad);

		// 2. Y軸周りの回転（Rotation）により、太陽系内での公転面の向きを決定します。
		// 回転行列を用いた座標変換 (x', z')
		double fX = flatX * Math.cos(rRad) - pZ * Math.sin(rRad);
		double fZ = flatX * Math.sin(rRad) + pZ * Math.cos(rRad);
		double fY = pY;

		// --- Step 3: 太陽回避補正の適用 (updateTailVisuals のロジックを再現) ---
		// 彗星が太陽（原点）に近すぎる場合、視覚的に「太陽の中を突き抜けない」ように
		// 外側へ押し出す補正を行います。これを軌道ラインにも適用することで、ラインと本体を密着させます。
		double dist = Math.sqrt(fX * fX + fY * fY + fZ * fZ);
		double threshold = 1200;

		if (dist < threshold && dist > 0) {

			// 距離が近いほど強くなる補正強度 (0.0 ～ 1.0)
			double intensity = (threshold - dist) / threshold;

			// 太陽半径(25) + 彗星半径 + 安全マージン(5) を考慮した最大押し出し量
			double maxPushOut = 25 + radius + 5.0;

			// 距離に応じた比率を計算し、放射状（太陽から離れる方向）に座標を補正
			double ratio = (dist + (maxPushOut * intensity)) / dist;

			// 演出補正済みの座標を返す
			return new Point3D(fX * ratio, fY * ratio, fZ * ratio);

		} // end if

		// 補正範囲外（遠方）の場合は、純粋な数学的楕円座標を返す
		return new Point3D(fX, fY, fZ);

	} // end getFinalVisualPosition

	/**
	 * createOrbitSegment: 算出された2点間を、ベクトル演算に基づいた極細シリンダーで精密に接続します。
	 * * 【3D幾何学ロジックの解説】
	 * 1. 2点間の差分ベクトル（diff）から、シリンダーの「長さ（height）」と「中間点（mid）」を算出します。
	 * 2. JavaFXのCylinderは初期状態で「垂直（Y軸方向）」を向いているため、これを接続方向へ回転させる必要があります。
	 * 3. 外積（crossProduct）を用いて回転の支柱となる「回転軸」を求め、内積（dotProduct）を用いて
	 * 垂直軸からの「回転角度」を導き出します。
	 * 4. これにより、256個の微小なシリンダーが隙間なく連結され、巨大な彗星の軌道を描き出します。
	 */
	private void createOrbitSegment(Point3D p1, Point3D p2, PhongMaterial mat) {

		// 1. 線分の長さと中心座標の特定

		// 点1から点2へのベクトル
		Point3D diff = p2.subtract(p1);
		// ベクトルの長さ（シリンダーの高さとして使用）
		double height = diff.magnitude();
		// シリンダーの配置基準となる中間点
		Point3D mid = p1.midpoint(p2);

		// 2. 回転軸の算出（外積演算）
		// 標準の垂直軸 (Rotate.Y_AXIS) と接続方向ベクトル (diff) の両方に直交するベクトルを求め、
		// それを回転の回転軸 (axis) とします。
		Point3D axis = diff.crossProduct(Rotate.Y_AXIS);

		// 3. 回転角度の算出（内積演算）
		// 正規化したベクトル同士の内積から、垂直軸と接続方向がなす角（ラジアン）を求めます。
		double angle = Math.acos(diff.normalize().dotProduct(Rotate.Y_AXIS));

		// 4. 3Dオブジェクトの生成
		// 太さ 0.25 の極細シリンダー。これにより「線」としての視覚効果を得つつ、
		// 3D空間内での実体を持たせてジャギーを抑制します。
		Cylinder segment = new Cylinder(0.25, height);
		segment.setMaterial(mat);

		// 5. 座標配置
		// 中間点 (mid) に配置することで、両端がちょうど p1 と p2 に一致します。
		segment.setTranslateX(mid.getX());
		segment.setTranslateY(mid.getY());
		segment.setTranslateZ(mid.getZ());

		// 6. 姿勢制御（ベクトル回転の適用）
		// 算出した回転軸を中心に、度数法に変換した角度分だけ回転させます。
		// JavaFXの座標系特性に合わせ、逆方向の回転角を適用しています。
		Rotate rot = new Rotate(-Math.toDegrees(angle), 0, 0, 0, axis);
		segment.getTransforms().add(rot);

		// 彗星専用の軌道グループに追加
		this.orbitGroup.getChildren().add(segment);

	} // createOrbitSegment

	/**
	 * update: 毎フレームの更新処理。
	 */
	@Override
	public void update(double rotX, double rotY, double camDist, double sizeFactor, boolean showLabels) {

		// 1. 物理法則に従った軌道座標の算出
		computePosition();

		// 2. 動的演出（指向・伸長・太陽回避）の適用
		if (node != null) {
			updateTailOrientation();
			updateTailVisuals();
		}

		// 3. ラベル（ビルボード）更新
		if(textLabel != null){
			this.applyBillboard(rotX, rotY, camDist, sizeFactor, showLabels);
		}

	} // end update

	/**
	 * updateTailVisuals: 太陽との距離に連動した動的な演出（尾の伸長・透明度）と、近日点での太陽回避制御を行います。
	 * * 【演出のメカニズム】
	 * 1. 太陽からの距離が閾値(1200)を下回ると、太陽風の影響を受けて「尾」が可視化されます。
	 * 2. 距離が近いほど「強度(intensity)」が高まり、尾は長く、色は濃く変化します。
	 * 3. 近日点通過時に太陽（SunBody）とモデルが重ならないよう、計算上の軌道から放射状に
	 * 外側へ押し出す「視覚的な回避補正」を適用します。
	 */
	private void updateTailVisuals() {

		// 太陽（原点 0,0,0）からの現在距離を算出（三平方の定理の3D応用）
		double distanceToSun = Math.sqrt(x * x + y * y + z * z);

		// // 太陽の物理半径（SunBodyの設定と同期）
		double sunRadius = 25;
		// 彗星が活動を開始する距離（閾値）
		double threshold = 1200;

		// 1. 閾値内（太陽接近時）の動的演出
		if (distanceToSun < threshold) {

			// 接近度合いを 0.0（遠方）～ 1.0（近日点）の係数として算出
			double intensity = (threshold - distanceToSun) / threshold;
			tail.setVisible(true);

			// 【演出 1: 尾の伸長】
			// 近づくほど尾のスケールを拡大（最大 1.5倍）。宇宙空間でのスピード感を強調します。
			double currentScale = intensity * 1.5;
			tail.setScaleY(currentScale);

			// 【演出 2: 接合部補正】
			// シリンダーのスケールが変わると中心位置もズレるため、
			// 核（Head）の背面に常に尾が接続されるよう、Y軸の移動量を再計算します。
			double tailOffset = (baseTailLength * currentScale) / 2 + (radius * 0.75);
			tail.setTranslateY(tailOffset);

			// 【演出 3: 太陽回避補正（押し出し）】
			// 彗星が太陽の中を突き抜けないよう、視覚的に太陽の外側へ押し出す処理です。
			double safetyMargin = 5.0;
			double maxPushOut = sunRadius + radius + safetyMargin; 
			double currentPushOut = maxPushOut * intensity; 

			if (distanceToSun > 0) {
				// 現在のベクトル方向を維持したまま、距離を拡張する比率を算出
				double ratio = (distanceToSun + currentPushOut) / distanceToSun;
				node.setTranslateX(x * ratio);
				node.setTranslateY(y * ratio);
				node.setTranslateZ(z * ratio);
			}

			// 【演出 4: 透明度（アルファ値）の動的変更】
			// 接近するほど尾が濃く（不透明に）なるよう、色情報のアルファ成分を操作します。
			// 0.05（淡い）～ 0.35（はっきり）の範囲でグラデーションさせます。
			double newAlpha = 0.05 + (intensity * 0.3);
			tailMaterial.setDiffuseColor(diffuseColor.deriveColor(0, 1, 1, newAlpha));

		// 2. 閾値外（遠方）の状態
		} else {

			// 尾を非表示にし、ノードの位置は数学的な純粋軌道（x, y, z）に戻します。
			tail.setVisible(false);
			node.setTranslateX(x);
			node.setTranslateY(y);
			node.setTranslateZ(z);

		} // end if

	} // end updateTailVisuals

	/**
	 * updateTailOrientation: 彗星の尾が常に「太陽の反対側」を向くように、天体モデル全体の姿勢を制御します。
	 * * 【天文学的・数学的アプローチ】
	 * 1. 彗星の尾は移動方向ではなく、太陽風の影響で常に太陽から遠ざかる方向に伸びます。
	 * 2. 現在の座標（x, y, z）自体が太陽（原点）からのベクトルとなるため、これを正規化して方向（dir）を特定します。
	 * 3. 標準の垂直ノードをその方向へ向けるため、外積（crossProduct）で回転の支柱を、内積（dotProduct）で回転角を算出します。
	 * 4. ゼロベクトルによる計算エラーを回避するための微小値判定を含み、常に安定した描画を保証します。
	 */
	private void updateTailOrientation() {

		// 1. 太陽から天体へ向かう方向ベクトル（dir）を算出
		// 現在の三次元座標をそのままベクトルとして扱い、単位ベクトルに正規化します。
		Point3D dir = new Point3D(x, y, z).normalize();

		// 2. 回転軸の算出（外積演算）
		// JavaFXのシリンダー（尾）の基本軸であるY軸と、向けたい方向（dir）の双方に直交する軸を求めます。
		Point3D axis = Rotate.Y_AXIS.crossProduct(dir);

		// 3. 回転角度の算出（内積演算）
		// 二つのベクトルのなす角をアークコサインで求め、JavaFXの回転プロパティ用に度数法（Degrees）に変換します。
		double angle = Math.toDegrees(Math.acos(Rotate.Y_AXIS.dotProduct(dir)));

		// 4. 姿勢の更新（ゼロ除算・不安定状態の回避）
		// 回転軸が極めて小さい（方向がY軸と完全に一致、またはゼロの場合）際の予期せぬ挙動を防ぎます。
		if (axis.magnitude() > 0.000001) {
			// 天体モデル全体の回転軸と角度をセットし、尾が太陽の反対を向くように瞬時に同期させます。
			node.setRotationAxis(axis);
			node.setRotate(angle);
		}

	} // end updateTailOrientation

	/**
	 * computePosition: 彗星の極端な楕円軌道および、空間的な傾斜・回転を数学的に算出します。
	 * 毎フレーム呼び出され、天体の基本となる物理座標（x, y, z）を確定させます。
	 * * 【数学的アルゴリズムの3ステップ】
	 * 1. 基礎楕円の算出: 長半径(a)と短半径(b)を用い、太陽を焦点(offset)とした平面軌道を定義。
	 * 2. 軌道傾斜(Pitch)の適用: 平面軌道を X 軸を中心に回転させ、宇宙空間での「高さ」を生成。
	 * 3. 公転面の回転(Rotation): 宇宙の垂直軸(Y軸)を中心に全体を回転させ、公転面の向きを確定。
	 */
	@Override
	protected void computePosition() {

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

		// 2. 平面上の基礎楕円座標 (XZ平面)
		// 太陽を焦点に据えるため、長軸方向に offset 分だけずらして算出します
		double flatX = Math.cos(rad) * a + offset; 
		double flatZ = Math.sin(rad) * b;

		// 3. 軌道傾斜角 (Pitch) の適用
		// 彗星特有の「黄道面から大きく外れた軌道」を表現するための座標変換です
		double pRad = Math.toRadians(pitch);
		double rRad = Math.toRadians(rotation);

		// Z 座標成分を、ピッチ角に応じて Y（高さ）と Z（奥行き）に射影します
		// これにより、平面だった軌道が立体的な「傾き」を持ちます
		double pitchedY = - flatZ * Math.sin(pRad); 
		double pitchedZ =   flatZ * Math.cos(pRad);
		double pitchedX =   flatX; 

		// 4. 公転面全体の回転 (Rotation / Yaw)
		// 銀河面、あるいは太陽系全体において、この彗星の軌道がどの「方角」を向いているかを決定します
		// 回転行列（Y軸周りの回転公式）を用い、最終的な宇宙空間座標を確定させます
		this.x = pitchedX * Math.cos(rRad) - pitchedZ * Math.sin(rRad);
		this.z = pitchedX * Math.sin(rRad) + pitchedZ * Math.cos(rRad);
		this.y = pitchedY;

	} // end computePosition

} // end class CometBody
