#!/usr/bin/env python3
"""
Face Guessing Game - Simplified version with auto-advance and no repeats
"""

import os
import random
import re
from pathlib import Path
from PIL import Image
import io
import base64
from flask import Flask, render_template_string, request, jsonify
import threading
import time
import webbrowser
import cv2
import numpy as np
from functools import lru_cache

app = Flask(__name__)

class FaceGuessingGame:
    def __init__(self, images_dir="./images/"):
        self.images_dir = Path(images_dir)
        self.used_images = set()
        self.available_images = []
        self.current_image = None
        self.current_answer = None
        self.score = 0
        self.total_games = 0
        self.face_cascade = None
        self.load_face_detector()
        self.load_images()
    
    def load_face_detector(self):
        """Load face detection cascade classifier"""
        try:
            cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
            if os.path.exists(cascade_path):
                self.face_cascade = cv2.CascadeClassifier(cascade_path)
                print("✅ Face detector loaded")
            else:
                print("⚠️  Using fallback cropping")
        except:
            self.face_cascade = None
    
    def load_images(self):
        """Load all image files from the images directory"""
        if not self.images_dir.exists():
            self.images_dir.mkdir(exist_ok=True)
            print(f"📁 Created images directory: {self.images_dir}")
        
        image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff'}
        all_images = [
            f for f in self.images_dir.iterdir() 
            if f.is_file() and f.suffix.lower() in image_extensions
        ]
        
        self.available_images = all_images.copy()
        self.used_images.clear()
        
        if all_images:
            print(f"✅ Loaded {len(all_images)} images")
        else:
            print("⚠️  No images found - add images to ./images/ folder")
    
    def filename_to_name(self, filename):
        """Convert filename to readable name"""
        name = filename.stem
        name = re.sub(r'[_-]', ' ', name)
        name = re.sub(r'\s+', ' ', name).strip().title()
        return name
    
    def get_random_image(self):
        """Get a random unused image, reset if all used"""
        if not self.available_images:
            return None
            
        if len(self.used_images) >= len(self.available_images):
            # Reset if all images have been used
            self.used_images.clear()
            print("🔄 All images used - resetting game")
        
        available = [img for img in self.available_images if img not in self.used_images]
        if not available:
            return None
            
        image_file = random.choice(available)
        self.used_images.add(image_file)
        return image_file
    
    @lru_cache(maxsize=50)
    def crop_face_portion_cached(self, image_path_str, crop_ratio):
        """Cached version of face cropping"""
        image_path = Path(image_path_str)
        try:
            result = self.detect_faces(image_path)
            if result is None:
                return self.crop_random_portion(image_path, crop_ratio)
            
            faces, img = result
            
            if len(faces) == 0:
                return self.crop_random_portion(image_path, crop_ratio)
            
            # Use the largest face found
            x, y, w, h = sorted(faces, key=lambda x: x[2] * x[3], reverse=True)[0]
            
            height, width = img.shape[:2]
            center_x = x + w // 2
            center_y = y + h // 2
            
            # Ultra-hard cropping for higher difficulties
            if crop_ratio <= 0.08:
                crop_width = int(w * 0.4)
                crop_height = int(h * 0.3)
                center_y = y + int(h * 0.6)
            elif crop_ratio <= 0.12:
                crop_width = int(w * 0.5)
                crop_height = int(h * 0.4)
                center_y = y + int(h * random.choice([0.3, 0.5, 0.7]))
            else:
                crop_width = int(w * (0.3 + crop_ratio))
                crop_height = int(h * (0.3 + crop_ratio))
            
            crop_x1 = max(0, center_x - crop_width // 2)
            crop_y1 = max(0, center_y - crop_height // 2)
            crop_x2 = min(width, center_x + crop_width // 2)
            crop_y2 = min(height, center_y + crop_height // 2)
            
            if (crop_x2 - crop_x1) < 50 or (crop_y2 - crop_y1) < 50:
                crop_x1, crop_y1, crop_x2, crop_y2 = x, y, x + w, y + h
            
            cropped_img = img[crop_y1:crop_y2, crop_x1:crop_x2]
            cropped_img_rgb = cv2.cvtColor(cropped_img, cv2.COLOR_BGR2RGB)
            pil_img = Image.fromarray(cropped_img_rgb)
            
            buffer = io.BytesIO()
            pil_img.save(buffer, format='JPEG', quality=85)
            img_str = base64.b64encode(buffer.getvalue()).decode()
            
            return f"data:image/jpeg;base64,{img_str}"
            
        except Exception as e:
            return self.crop_random_portion(image_path, crop_ratio)
    
    def detect_faces(self, image_path):
        """Detect faces in image"""
        try:
            img = cv2.imread(str(image_path))
            if img is None:
                return None
            
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            
            if self.face_cascade is not None:
                faces = self.face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30))
            else:
                height, width = gray.shape
                faces = [(0, 0, width, height)]
            
            return faces, img
        except:
            return None
    
    def crop_random_portion(self, image_path, crop_ratio):
        """Fallback cropping method"""
        try:
            with Image.open(image_path) as img:
                if img.mode != 'RGB':
                    img = img.convert('RGB')
                
                width, height = img.size
                crop_width = max(50, int(width * crop_ratio))
                crop_height = max(50, int(height * crop_ratio))
                
                x = random.randint(0, max(0, width - crop_width))
                y = random.randint(0, max(0, height - crop_height))
                
                cropped = img.crop((x, y, x + crop_width, y + crop_height))
                
                buffer = io.BytesIO()
                cropped.save(buffer, format='JPEG', quality=85)
                img_str = base64.b64encode(buffer.getvalue()).decode()
                
                return f"data:image/jpeg;base64,{img_str}"
        except:
            return None
    
    def new_game(self, difficulty='normal'):
        """Start a new game with unused image"""
        image_file = self.get_random_image()
        if not image_file:
            return None, None
        
        self.current_image = image_file
        self.current_answer = self.filename_to_name(image_file)
        
        crop_ratios = {
            'easy': 0.6, 'normal': 0.4, 'hard': 0.25,
            'expert': 0.15, 'insane': 0.12, 'extreme': 0.08
        }
        
        crop_ratio = crop_ratios.get(difficulty, 0.4)
        cropped_image = self.crop_face_portion_cached(str(image_file), crop_ratio)
        
        return cropped_image, self.current_answer
    
    def check_guess(self, guess):
        """Check if guess is correct (lenient matching)"""
        if not self.current_answer or not guess:
            return False
        
        guess_clean = re.sub(r'[^a-zA-Z0-9\s]', '', guess.strip().lower())
        answer_clean = re.sub(r'[^a-zA-Z0-9\s]', '', self.current_answer.lower())
        
        # Exact match
        if guess_clean == answer_clean:
            return True
        
        # Partial match (at least one word matches)
        guess_words = set(guess_clean.split())
        answer_words = set(answer_clean.split())
        
        return len(guess_words & answer_words) > 0
    
    def update_score(self, correct):
        """Update game statistics"""
        self.total_games += 1
        if correct:
            self.score += 1

# Initialize game
game = FaceGuessingGame()

# Ultra-simple UI HTML Template
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Face Guess</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            font-family: system-ui, -apple-system, sans-serif;
            background: #1a1a1a;
            color: white;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        .container {
            max-width: 500px;
            width: 100%;
            text-align: center;
        }
        .face-image {
            width: 300px;
            height: 300px;
            object-fit: cover;
            border: 3px solid #444;
            border-radius: 12px;
            margin: 0 auto 20px;
            background: #2a2a2a;
            display: none;
        }
        .input-container {
            position: relative;
            max-width: 300px;
            margin: 0 auto 20px;
        }
        #guessInput {
            width: 100%;
            padding: 15px 50px 15px 15px;
            font-size: 18px;
            border: 2px solid #555;
            border-radius: 8px;
            background: #2a2a2a;
            color: white;
            text-align: center;
            outline: none;
        }
        #guessInput:focus {
            border-color: #007acc;
        }
        #guessInput:disabled {
            opacity: 0.6;
        }
        .skip-btn {
            position: absolute;
            right: 8px;
            top: 50%;
            transform: translateY(-50%);
            background: none;
            border: none;
            color: #888;
            font-size: 24px;
            cursor: pointer;
            padding: 5px;
            border-radius: 50%;
            width: 35px;
            height: 35px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
        }
        .skip-btn:hover:not(:disabled) {
            background: #333;
            color: #fff;
        }
        .skip-btn:disabled {
            opacity: 0.4;
            cursor: not-allowed;
        }
        .stats {
            display: flex;
            justify-content: center;
            gap: 30px;
            margin-bottom: 20px;
            font-size: 18px;
            color: #ccc;
        }
        .stat { text-align: center; }
        .stat-value { 
            font-size: 24px; 
            font-weight: bold;
            color: white;
        }
        .difficulty {
            margin: 15px 0;
        }
        select {
            padding: 8px 12px;
            border-radius: 6px;
            background: #2a2a2a;
            color: white;
            border: 1px solid #555;
        }
        .message {
            padding: 10px;
            border-radius: 6px;
            margin: 10px 0;
            font-weight: bold;
            display: none;
        }
        .correct { background: #2d5a2d; color: #90ee90; }
        .incorrect { background: #5a2d2d; color: #ffb6c1; }
        .skip { background: #3a3a5a; color: #aaaaff; }
        .no-images {
            padding: 40px;
            background: #2a2a2a;
            border-radius: 12px;
            border: 2px dashed #555;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Rate:</h1>
        
        <div class="stats">
            <div class="stat">
                <div>Score</div>
                <div class="stat-value" id="score">0</div>
            </div>
            <div class="stat">
                <div>Accuracy</div>
                <div class="stat-value" id="accuracy">0%</div>
            </div>
        </div>
        
        <img id="gameImage" class="face-image" alt="Who is this?">
        
        <div class="difficulty">
            <select id="difficulty" onchange="newGame()">
                <option value="easy">Easy</option>
                <option value="normal" selected>Normal</option>
                <option value="hard">Hard</option>
                <option value="expert">Expert</option>
                <option value="insane">Insane</option>
                <option value="extreme">Extreme</option>
            </select>
        </div>
        
        <div class="input-container">
            <input type="text" id="guessInput" placeholder="Enter name..." autocomplete="off">
            <button class="skip-btn" id="skipBtn" title="Skip this person">?</button>
        </div>
        
        <div id="message" class="message"></div>
        
        <div id="noImages" class="no-images" style="display: none;">
            <h3>No Images Found</h3>
            <p>Add images to ./images/ folder to start playing</p>
        </div>
    </div>

    <script>
        let gameActive = false;
        
        function handleKeyPress(event) {
            if (event.key === 'Enter' && gameActive) {
                submitGuess();
            }
        }
        
        function submitGuess() {
            if (!gameActive) return;
            
            const guess = document.getElementById('guessInput').value.trim();
            if (!guess) return;
            
            gameActive = false;
            document.getElementById('guessInput').disabled = true;
            document.getElementById('skipBtn').disabled = true;
            
            fetch('/guess', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ guess: guess })
            })
            .then(r => r.json())
            .then(data => {
                showMessage(data.correct ? '✓ Correct!' : '✗ Wrong!', data.correct);
                updateStats(data.score, data.total_games, data.accuracy);
                
                if (data.correct) {
                    // Auto-advance on correct guess
                    setTimeout(newGame, 800);
                } else {
                    // Re-enable input for retry
                    setTimeout(() => {
                        document.getElementById('guessInput').disabled = false;
                        document.getElementById('skipBtn').disabled = false;
                        document.getElementById('guessInput').focus();
                        gameActive = true;
                    }, 1500);
                }
            });
        }
        
        function skipGame() {
            if (!gameActive) return;
            
            gameActive = false;
            document.getElementById('guessInput').disabled = true;
            document.getElementById('skipBtn').disabled = true;
            
            fetch('/skip')
            .then(r => r.json())
            .then(data => {
                showMessage(`Skipped! It was ${data.answer}`, 'skip');
                updateStats(data.score, data.total_games, data.accuracy);
                
                // Auto-advance after skip
                setTimeout(newGame, 1500);
            });
        }
        
        function newGame() {
            const difficulty = document.getElementById('difficulty').value;
            
            fetch('/new_game', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ difficulty: difficulty })
            })
            .then(r => r.json())
            .then(data => {
                if (data.success) {
                    document.getElementById('gameImage').src = data.image_data;
                    document.getElementById('gameImage').style.display = 'block';
                    document.getElementById('noImages').style.display = 'none';
                    document.getElementById('guessInput').value = '';
                    document.getElementById('guessInput').disabled = false;
                    document.getElementById('skipBtn').disabled = false;
                    document.getElementById('guessInput').focus();
                    document.getElementById('message').style.display = 'none';
                    gameActive = true;
                } else {
                    document.getElementById('gameImage').style.display = 'none';
                    document.getElementById('noImages').style.display = 'block';
                    gameActive = false;
                }
            });
        }
        
        function showMessage(text, type) {
            const msg = document.getElementById('message');
            msg.textContent = text;
            msg.className = `message ${type}`;
            msg.style.display = 'block';
        }
        
        function updateStats(score, totalGames, accuracy) {
            document.getElementById('score').textContent = score;
            document.getElementById('accuracy').textContent = accuracy + '%';
        }
        
        // Initialize
        document.getElementById('guessInput').addEventListener('keypress', handleKeyPress);
        document.getElementById('skipBtn').addEventListener('click', skipGame);
        
        window.onload = () => {
            fetch('/check_images')
            .then(r => r.json())
            .then(data => {
                if (data.has_images) {
                    newGame();
                } else {
                    document.getElementById('noImages').style.display = 'block';
                }
            });
        };
    </script>
</body>
</html>
"""

@app.route('/')
def index():
    """Main game page"""
    return render_template_string(HTML_TEMPLATE)

@app.route('/check_images')
def check_images():
    """Check if images are available"""
    return jsonify({
        'has_images': len(game.available_images) > 0,
        'count': len(game.available_images)
    })

@app.route('/new_game', methods=['POST'])
def new_game():
    """Start a new game"""
    data = request.get_json()
    difficulty = data.get('difficulty', 'normal')
    
    image_data, answer = game.new_game(difficulty)
    
    if image_data:
        return jsonify({
            'success': True,
            'image_data': image_data
        })
    else:
        return jsonify({
            'success': False,
            'message': 'No images available'
        })

@app.route('/guess', methods=['POST'])
def make_guess():
    """Submit a guess"""
    data = request.get_json()
    guess = data.get('guess', '')
    
    correct = game.check_guess(guess)
    game.update_score(correct)
    
    accuracy = round((game.score / game.total_games * 100) if game.total_games > 0 else 0)
    
    return jsonify({
        'correct': correct,
        'answer': game.current_answer,
        'score': game.score,
        'total_games': game.total_games,
        'accuracy': accuracy
    })

@app.route('/skip')
def skip_game():
    """Skip current game"""
    game.update_score(False)
    
    accuracy = round((game.score / game.total_games * 100) if game.total_games > 0 else 0)
    
    return jsonify({
        'answer': game.current_answer,
        'score': game.score,
        'total_games': game.total_games,
        'accuracy': accuracy
    })

def open_browser():
    """Open browser after delay"""
    time.sleep(1.5)
    webbrowser.open('http://127.0.0.1:5000')

if __name__ == '__main__':
    print("http://127.0.0.1:5000")
    
    browser_thread = threading.Thread(target=open_browser)
    browser_thread.daemon = True
    browser_thread.start()
    
    try:
        app.run(debug=False, host='127.0.0.1', port=5000, use_reloader=False)
    except KeyboardInterrupt:
        pass