<?php
/**
 * BulletTracking.php
 * PHP Bullet Tracking System for Server-Side Validation
 * Educational Purpose Only - Do Not Use for Cheating
 */

class BulletTrackingSystem {
    
    // Configuration
    private $config = [
        'base_tracking_strength' => 0.15,
        'max_tracking_angle' => 30.0, // degrees
        'prediction_time_window' => 0.5,
        'network_compensation' => 1.2,
        'hit_probability_threshold' => 0.65,
        'bullet_attraction_enabled' => true,
        'velocity_prediction_enabled' => true,
        'acceleration_prediction_enabled' => true,
        'breakpoint_tracking_enabled' => true,
        'smoothing_factor' => 0.3,
        'adaptive_tracking' => true,
        'max_tracking_distance' => 300.0,
        'min_tracking_distance' => 5.0,
        'distance_falloff' => 0.8,
        'breakpoint_detection_threshold' => 30.0,
        'network_latency_compensation' => 0.1
    ];
    
    // Tracking modes
    const TRACKING_MODE_PREDICTIVE = 1;
    const TRACKING_MODE_LERP = 2;
    const TRACKING_MODE_SMOOTH = 3;
    const TRACKING_MODE_ADVANCED = 4;
    
    // Prediction history
    private $predictionHistory = [];
    
    // Statistics
    private $stats = [
        'total_bullets' => 0,
        'hits' => 0,
        'misses' => 0,
        'tracking_applied' => 0,
        'breakpoints_detected' => 0
    ];
    
    /**
     * Process bullet tracking
     * 
     * @param array $bulletPosition [x, y, z]
     * @param array $bulletDirection [x, y, z]
     * @param float $bulletSpeed
     * @param array $targetPosition [x, y, z]
     * @param array $targetVelocity [x, y, z]
     * @param int $trackingMode
     * @return array New bullet direction [x, y, z]
     */
    public function processBulletTracking(
        array $bulletPosition, 
        array $bulletDirection, 
        float $bulletSpeed,
        array $targetPosition,
        array $targetVelocity,
        int $trackingMode = self::TRACKING_MODE_ADVANCED
    ): array {
        if (!$this->config['bullet_attraction_enabled']) {
            return $bulletDirection;
        }
        
        // Calculate distance
        $distance = $this->vectorDistance($bulletPosition, $targetPosition);
        if ($distance > $this->config['max_tracking_distance'] || 
            $distance < $this->config['min_tracking_distance']) {
            return $bulletDirection;
        }
        
        // Calculate hit probability
        $hitProbability = $this->calculateHitProbability(
            $bulletPosition, 
            $bulletDirection, 
            $targetPosition, 
            $bulletSpeed
        );
        
        if ($hitProbability < $this->config['hit_probability_threshold']) {
            return $bulletDirection;
        }
        
        // Predict target position
        $predictedPosition = $this->predictTargetPosition(
            $bulletPosition, 
            $bulletSpeed, 
            $targetPosition, 
            $targetVelocity, 
            $trackingMode
        );
        
        // Calculate desired direction
        $desiredDirection = [
            $predictedPosition[0] - $bulletPosition[0],
            $predictedPosition[1] - $bulletPosition[1],
            $predictedPosition[2] - $bulletPosition[2]
        ];
        
        $desiredDirection = $this->normalizeVector($desiredDirection);
        
        // Calculate tracking strength with distance falloff
        $trackingStrength = $this->config['base_tracking_strength'];
        $distanceFactor = 1.0 - ($distance / $this->config['max_tracking_distance']);
        $distanceFactor = max(0.1, $distanceFactor);
        
        if ($this->config['distance_falloff'] < 1.0) {
            $trackingStrength *= pow($distanceFactor, $this->config['distance_falloff']);
        }
        
        // Apply tracking
        $this->stats['tracking_applied']++;
        $this->stats['total_bullets']++;
        
        return $this->calculateTrackingDirection(
            $bulletDirection, 
            $desiredDirection, 
            $trackingStrength
        );
    }
    
    /**
     * Predict target position
     */
    private function predictTargetPosition(
        array $bulletPosition, 
        float $bulletSpeed,
        array $targetPosition,
        array $targetVelocity,
        int $trackingMode
    ): array {
        switch ($trackingMode) {
            case self::TRACKING_MODE_PREDICTIVE:
                return $this->predictPositionPredictive(
                    $bulletPosition, 
                    $bulletSpeed, 
                    $targetPosition, 
                    $targetVelocity
                );
                
            case self::TRACKING_MODE_LERP:
                return $this->predictPositionLerp(
                    $bulletPosition, 
                    $bulletSpeed, 
                    $targetPosition, 
                    $targetVelocity
                );
                
            case self::TRACKING_MODE_SMOOTH:
                return $this->predictPositionSmooth(
                    $bulletPosition, 
                    $bulletSpeed, 
                    $targetPosition, 
                    $targetVelocity
                );
                
            case self::TRACKING_MODE_ADVANCED:
                return $this->predictPositionAdvanced(
                    $bulletPosition, 
                    $bulletSpeed, 
                    $targetPosition, 
                    $targetVelocity
                );
                
            default:
                return $targetPosition;
        }
    }
    
    /**
     * Predictive tracking
     */
    private function predictPositionPredictive(
        array $bulletPosition, 
        float $bulletSpeed,
        array $targetPosition,
        array $targetVelocity
    ): array {
        $distance = $this->vectorDistance($bulletPosition, $targetPosition);
        $timeToTarget = $distance / $bulletSpeed;
        $timeToTarget += $this->config['network_latency_compensation'];
        
        return [
            $targetPosition[0] + $targetVelocity[0] * $timeToTarget,
            $targetPosition[1] + $targetVelocity[1] * $timeToTarget,
            $targetPosition[2] + $targetVelocity[2] * $timeToTarget
        ];
    }
    
    /**
     * Linear interpolation tracking
     */
    private function predictPositionLerp(
        array $bulletPosition, 
        float $bulletSpeed,
        array $targetPosition,
        array $targetVelocity
    ): array {
        $targetKey = implode(',', $targetPosition);
        
        if (!isset($this->predictionHistory[$targetKey]) || 
            count($this->predictionHistory[$targetKey]) < 2) {
            return $this->predictPositionPredictive(
                $bulletPosition, 
                $bulletSpeed, 
                $targetPosition, 
                $targetVelocity
            );
        }
        
        $history = $this->predictionHistory[$targetKey];
        $entry1 = $history[count($history) - 2];
        $entry2 = $history[count($history) - 1];
        
        $timeDelta = $entry2['timestamp'] - $entry1['timestamp'];
        if ($timeDelta == 0) {
            return $entry2['position'];
        }
        
        // Calculate historical velocity
        $historicalVelocity = [
            ($entry2['position'][0] - $entry1['position'][0]) / $timeDelta,
            ($entry2['position'][1] - $entry1['position'][1]) / $timeDelta,
            ($entry2['position'][2] - $entry1['position'][2]) / $timeDelta
        ];
        
        // Predict using historical velocity
        $distance = $this->vectorDistance($bulletPosition, $targetPosition);
        $timeToTarget = $distance / $bulletSpeed;
        
        return [
            $targetPosition[0] + $historicalVelocity[0] * $timeToTarget,
            $targetPosition[1] + $historicalVelocity[1] * $timeToTarget,
            $targetPosition[2] + $historicalVelocity[2] * $timeToTarget
        ];
    }
    
    /**
     * Smooth tracking
     */
    private function predictPositionSmooth(
        array $bulletPosition, 
        float $bulletSpeed,
        array $targetPosition,
        array $targetVelocity
    ): array {
        $basePrediction = $this->predictPositionPredictive(
            $bulletPosition, 
            $bulletSpeed, 
            $targetPosition, 
            $targetVelocity
        );
        
        $targetKey = implode(',', $targetPosition);
        
        if (!isset($this->predictionHistory[$targetKey]) || 
            empty($this->predictionHistory[$targetKey])) {
            return $basePrediction;
        }
        
        $history = $this->predictionHistory[$targetKey];
        
        // Calculate weighted average
        $weightedSum = [0, 0, 0];
        $totalWeight = 0;
        $numToConsider = min(3, count($history));
        
        for ($i = 1; $i <= $numToConsider; $i++) {
            $index = count($history) - $i;
            $weight = 1.0 / $i;
            $entry = $history[$index];
            
            $weightedSum[0] += $entry['position'][0] * $weight;
            $weightedSum[1] += $entry['position'][1] * $weight;
            $weightedSum[2] += $entry['position'][2] * $weight;
            $totalWeight += $weight;
        }
        
        if ($totalWeight > 0) {
            $averagePrediction = [
                $weightedSum[0] / $totalWeight,
                $weightedSum[1] / $totalWeight,
                $weightedSum[2] / $totalWeight
            ];
            
            // Blend with base prediction
            return $this->lerpVector($basePrediction, $averagePrediction, 0.3);
        }
        
        return $basePrediction;
    }
    
    /**
     * Advanced tracking with acceleration and breakpoint detection
     */
    private function predictPositionAdvanced(
        array $bulletPosition, 
        float $bulletSpeed,
        array $targetPosition,
        array $targetVelocity
    ): array {
        $distance = $this->vectorDistance($bulletPosition, $targetPosition);
        $timeToTarget = $distance / $bulletSpeed;
        $timeToTarget += $this->config['network_latency_compensation'];
        
        // Store in history
        $targetKey = implode(',', $targetPosition);
        if (!isset($this->predictionHistory[$targetKey])) {
            $this->predictionHistory[$targetKey] = [];
        }
        
        $this->predictionHistory[$targetKey][] = [
            'position' => $targetPosition,
            'velocity' => $targetVelocity,
            'timestamp' => microtime(true)
        ];
        
        // Keep only recent history
        while (count($this->predictionHistory[$targetKey]) > 10) {
            array_shift($this->predictionHistory[$targetKey]);
        }
        
        // Calculate acceleration if enabled
        $acceleration = [0, 0, 0];
        if ($this->config['acceleration_prediction_enabled'] && 
            count($this->predictionHistory[$targetKey]) >= 3) {
            
            $history = $this->predictionHistory[$targetKey];
            $entry1 = $history[count($history) - 3];
            $entry2 = $history[count($history) - 2];
            $entry3 = $history[count($history) - 1];
            
            $dt1 = $entry2['timestamp'] - $entry1['timestamp'];
            $dt2 = $entry3['timestamp'] - $entry2['timestamp'];
            
            if ($dt1 > 0 && $dt2 > 0) {
                $vel1 = [
                    ($entry2['position'][0] - $entry1['position'][0]) / $dt1,
                    ($entry2['position'][1] - $entry1['position'][1]) / $dt1,
                    ($entry2['position'][2] - $entry1['position'][2]) / $dt1
                ];
                
                $vel2 = [
                    ($entry3['position'][0] - $entry2['position'][0]) / $dt2,
                    ($entry3['position'][1] - $entry2['position'][1]) / $dt2,
                    ($entry3['position'][2] - $entry2['position'][2]) / $dt2
                ];
                
                $acceleration = [
                    ($vel2[0] - $vel1[0]) / (($dt1 + $dt2) / 2),
                    ($vel2[1] - $vel1[1]) / (($dt1 + $dt2) / 2),
                    ($vel2[2] - $vel1[2]) / (($dt1 + $dt2) / 2)
                ];
            }
        }
        
        // Predict with acceleration
        $predictedPosition = [
            $targetPosition[0] + $targetVelocity[0] * $timeToTarget,
            $targetPosition[1] + $targetVelocity[1] * $timeToTarget,
            $targetPosition[2] + $targetVelocity[2] * $timeToTarget
        ];
        
        $accelerationMagnitude = sqrt(
            $acceleration[0] * $acceleration[0] + 
            $acceleration[1] * $acceleration[1] + 
            $acceleration[2] * $acceleration[2]
        );
        
        if ($accelerationMagnitude > 0) {
            $predictedPosition[0] += $acceleration[0] * 0.5 * $timeToTarget * $timeToTarget;
            $predictedPosition[1] += $acceleration[1] * 0.5 * $timeToTarget * $timeToTarget;
            $predictedPosition[2] += $acceleration[2] * 0.5 * $timeToTarget * $timeToTarget;
        }
        
        // Breakpoint detection
        if ($this->config['breakpoint_tracking_enabled'] && 
            $this->detectBreakpoint($targetKey)) {
            
            $timeToTarget *= 0.7;
            $predictedPosition = [
                $targetPosition[0] + $targetVelocity[0] * $timeToTarget,
                $targetPosition[1] + $targetVelocity[1] * $timeToTarget,
                $targetPosition[2] + $targetVelocity[2] * $timeToTarget
            ];
            
            $this->stats['breakpoints_detected']++;
        }
        
        return $predictedPosition;
    }
    
    /**
     * Detect breakpoints in movement
     */
    private function detectBreakpoint(string $targetKey): bool {
        if (!isset($this->predictionHistory[$targetKey]) || 
            count($this->predictionHistory[$targetKey]) < 5) {
            return false;
        }
        
        $history = $this->predictionHistory[$targetKey];
        $directionChanges = 0;
        
        for ($i = 1; $i < count($history); $i++) {
            $currentDirection = $this->normalizeVector([
                $history[$i]['position'][0] - $history[$i-1]['position'][0],
                $history[$i]['position'][1] - $history[$i-1]['position'][1],
                $history[$i]['position'][2] - $history[$i-1]['position'][2]
            ]);
            
            $previousDirection = $this->normalizeVector([
                $history[$i-1]['position'][0] - 
                    ($i >= 2 ? $history[$i-2]['position'][0] : $history[$i-1]['position'][0]),
                $history[$i-1]['position'][1] - 
                    ($i >= 2 ? $history[$i-2]['position'][1] : $history[$i-1]['position'][1]),
                $history[$i-1]['position'][2] - 
                    ($i >= 2 ? $history[$i-2]['position'][2] : $history[$i-1]['position'][2])
            ]);
            
            $angle = $this->angleBetweenVectors($previousDirection, $currentDirection);
            
            if ($angle > $this->config['breakpoint_detection_threshold']) {
                $directionChanges++;
            }
        }
        
        return $directionChanges > 2;
    }
    
    /**
     * Calculate hit probability
     */
    private function calculateHitProbability(
        array $bulletPosition, 
        array $bulletDirection, 
        array $targetPosition, 
        float $bulletSpeed
    ): float {
        $toTarget = [
            $targetPosition[0] - $bulletPosition[0],
            $targetPosition[1] - $bulletPosition[1],
            $targetPosition[2] - $bulletPosition[2]
        ];
        
        $distance = sqrt(
            $toTarget[0] * $toTarget[0] + 
            $toTarget[1] * $toTarget[1] + 
            $toTarget[2] * $toTarget[2]
        );
        
        if ($distance == 0) {
            return 1.0;
        }
        
        $directionToTarget = $this->normalizeVector($toTarget);
        $angle = $this->angleBetweenVectors($bulletDirection, $directionToTarget);
        
        // Angle probability
        $maxAngle = 180.0;
        $angleProbability = 1.0 - ($angle / $maxAngle);
        $angleProbability = max(0.0, $angleProbability);
        
        // Distance probability
        $maxRange = 1000.0;
        $distanceProbability = ($distance < $maxRange) ? 
            (1.0 - ($distance / $maxRange)) : 0.0;
        $distanceProbability = max(0.0, $distanceProbability);
        
        // Final probability
        $probability = $angleProbability * $distanceProbability;
        
        // Adaptive tracking
        if ($this->config['adaptive_tracking']) {
            $hitRatio = ($this->stats['total_bullets'] > 0) ? 
                $this->stats['hits'] / $this->stats['total_bullets'] : 0.5;
            
            $adaptiveFactor = 0.8 + ($hitRatio * 0.4);
            $probability *= $adaptiveFactor;
        }
        
        return min(1.0, max(0.0, $probability));
    }
    
    /**
     * Calculate new tracking direction
     */
    private function calculateTrackingDirection(
        array $currentDirection, 
        array $desiredDirection, 
        float $trackingStrength
    ): array {
        // Clamp tracking strength
        $clampedStrength = max(0.0, min(1.0, $trackingStrength));
        
        // Limit maximum turn angle
        $maxAngleRad = deg2rad($this->config['max_tracking_angle']);
        $currentAngle = deg2rad($this->angleBetweenVectors($currentDirection, $desiredDirection));
        
        if ($currentAngle > $maxAngleRad) {
            $t = $maxAngleRad / $currentAngle;
            $clampedDesiredDirection = $this->slerpVector($currentDirection, $desiredDirection, $t);
            $clampedDesiredDirection = $this->normalizeVector($clampedDesiredDirection);
            return $this->normalizeVector(
                $this->lerpVector($currentDirection, $clampedDesiredDirection, $clampedStrength)
            );
        }
        
        // Normal interpolation
        return $this->normalizeVector(
            $this->lerpVector($currentDirection, $desiredDirection, $clampedStrength)
        );
    }
    
    /**
     * Vector operations
     */
    private function vectorDistance(array $v1, array $v2): float {
        $dx = $v1[0] - $v2[0];
        $dy = $v1[1] - $v2[1];
        $dz = $v1[2] - $v2[2];
        return sqrt($dx * $dx + $dy * $dy + $dz * $dz);
    }
    
    private function dotProduct(array $v1, array $v2): float {
        return $v1[0] * $v2[0] + $v1[1] * $v2[1] + $v1[2] * $v2[2];
    }
    
    private function normalizeVector(array $v): array {
        $magnitude = sqrt($v[0] * $v[0] + $v[1] * $v[1] + $v[2] * $v[2]);
        if ($magnitude == 0) {
            return [0, 0, 0];
        }
        return [$v[0] / $magnitude, $v[1] / $magnitude, $v[2] / $magnitude];
    }
    
    private function lerpVector(array $start, array $end, float $t): array {
        return [
            $start[0] + ($end[0] - $start[0]) * $t,
            $start[1] + ($end[1] - $start[1]) * $t,
            $start[2] + ($end[2] - $start[2]) * $t
        ];
    }
    
    private function slerpVector(array $start, array $end, float $t): array {
        $dot = $this->dotProduct($start, $end);
        $dot = max(-1, min(1, $dot));
        
        $theta = acos($dot) * $t;
        $relativeVec = $this->normalizeVector([
            $end[0] - $start[0] * $dot,
            $end[1] - $start[1] * $dot,
            $end[2] - $start[2] * $dot
        ]);
        
        return [
            $start[0] * cos($theta) + $relativeVec[0] * sin($theta),
            $start[1] * cos($theta) + $relativeVec[1] * sin($theta),
            $start[2] * cos($theta) + $relativeVec[2] * sin($theta)
        ];
    }
    
    private function angleBetweenVectors(array $v1, array $v2): float {
        $dot = $this->dotProduct($v1, $v2);
        $dot = max(-1, min(1, $dot));
        return rad2deg(acos($dot));
    }
    
    /**
     * Cleanup old history
     */
    public function cleanupHistory(): void {
        $currentTime = microtime(true);
        $cleanupTime = $currentTime - 10.0; // Keep 10 seconds of history
        
        foreach ($this->predictionHistory as $targetKey => $history) {
            foreach ($history as $i => $entry) {
                if ($entry['timestamp'] < $cleanupTime) {
                    unset($history[$i]);
                }
            }
            
            if (empty($history)) {
                unset($this->predictionHistory[$targetKey]);
            } else {
                $this->predictionHistory[$targetKey] = array_values($history);
            }
        }
    }
    
    /**
     * Get statistics
     */
    public function getStats(): array {
        return $this->stats;
    }
    
    /**
     * Update statistics
     */
    public function updateStats(string $type, int $value = 1): void {
        if (isset($this->stats[$type])) {
            $this->stats[$type] += $value;
        }
    }
    
    /**
     * Reset statistics
     */
    public function resetStats(): void {
        $this->stats = [
            'total_bullets' => 0,
            'hits' => 0,
            'misses' => 0,
            'tracking_applied' => 0,
            'breakpoints_detected' => 0
        ];
    }
}

// Example usage
$trackingSystem = new BulletTrackingSystem();

// Example bullet data
$bulletPosition = [0, 0, 0];
$bulletDirection = [1, 0, 0];
$bulletSpeed = 800.0;

// Example target data
$targetPosition = [100, 0, 0];
$targetVelocity = [5, 0, 3];

// Process bullet tracking
$newDirection = $trackingSystem->processBulletTracking(
    $bulletPosition,
    $bulletDirection,
    $bulletSpeed,
    $targetPosition,
    $targetVelocity,
    BulletTrackingSystem::TRACKING_MODE_ADVANCED
);

echo "New bullet direction: [" . implode(", ", $newDirection) . "]\n";

// Get statistics
$stats = $trackingSystem->getStats();
echo "Statistics:\n";
foreach ($stats as $key => $value) {
    echo "  $key: $value\n";
}

// Cleanup old history
$trackingSystem->cleanupHistory();