#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FreeCAD 3D Model Renderer - PARÇA FİLTRELEME EKLENDİ
"""

import sys, os, time, json, argparse
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass, asdict
from pathlib import Path

# FreeCAD modülleri yükleniyor
if os.name == 'nt':
    FREECAD_PATH = r"D:\__sistem__\ProgramFiles\FreeCAD\bin"
else:
    FREECAD_PATH = "/usr/lib/freecad-python3/lib"

if FREECAD_PATH not in sys.path:
    sys.path.append(FREECAD_PATH)

import FreeCAD
from FreeCAD import Base

try:
    import FreeCADGui
    FreeCADGui.showMainWindow()
    import ImportGui
    print("[INFO] FreeCADGui loaded successfully", flush=True)
except Exception as e:
    print(f"[ERROR] FreeCADGui yüklenemedi: {e}", flush=True)
    sys.exit(1)

try:
    from PIL import Image, ImageEnhance, ImageFilter, ImageDraw
    import numpy as np
    USE_PIL = True
except ImportError:
    import imageio.v2 as imageio
    USE_PIL = False


@dataclass
class RenderConfig:
    width: int = 1920
    height: int = 1080
    background: str = "White"
    view_type: str = "axonometric"
    render_quality: str = "high"
    anti_aliasing: bool = True


@dataclass
class AnimationConfig:
    enabled: bool = True
    num_frames: int = 36
    angle_step: int = 10
    duration: float = 0.08
    pause_duration: float = 1.5
    loop: int = 0
    fps: int = 12
    yaw_enabled: bool = True
    pitch_enabled: bool = True
    roll_enabled: bool = False
    yaw_range: Tuple[int, int] = (0, 360)
    pitch_range: Tuple[int, int] = (0, 360)
    roll_range: Tuple[int, int] = (0, 360)


@dataclass
class OutputConfig:
    output_dir: str = "."
    base_name: str = "render"
    save_png: bool = True
    save_gif: bool = True
    save_frames: bool = False
    gif_optimize: bool = True
    gif_quality: int = 85


@dataclass
class CameraConfig:
    zoom_level: float = 1.0
    camera_position: Optional[Tuple[float, float, float]] = None
    look_at: Optional[Tuple[float, float, float]] = None


@dataclass
class PostProcessingConfig:
    edge_detection: bool = False
    sharpen: bool = True
    sharpen_intensity: float = 1.5
    smooth: bool = False
    smooth_radius: int = 2
    contrast: float = 1.2
    brightness: float = 1.0
    saturation: float = 1.1
    ambient_occlusion: bool = False
    ao_intensity: float = 0.3
    shadow: bool = False
    shadow_offset_x: int = 10
    shadow_offset_y: int = 10
    shadow_blur: int = 5
    shadow_opacity: float = 0.5
    vignette: bool = False
    vignette_intensity: float = 0.5
    wireframe: bool = False
    wireframe_density: int = 10
    depth_of_field: bool = False
    dof_focus_distance: float = 50
    dof_aperture: float = 2.8
    bloom: bool = False
    bloom_intensity: float = 0.5
    bloom_threshold: float = 0.8
    motion_blur: bool = False
    motion_blur_intensity: float = 0.5
    grid_overlay: bool = False
    grid_size: int = 10
    ruler_enabled: bool = False
    fog: bool = False
    fog_density: float = 0.02
    fog_color: str = "#CCCCCC"


@dataclass
class LightingConfig:
    enabled: bool = True
    intensity: float = 1.0
    color: Tuple[int, int, int] = (255, 255, 200)


@dataclass
class MaterialConfig:
    metallic: float = 0.5
    roughness: float = 0.5
    reflection_intensity: float = 0.3
    normal_map_intensity: float = 1.0


@dataclass
class BackgroundConfig:
    gradient: bool = False
    gradient_color_1: str = "#F0F0FF"
    gradient_color_2: str = "#C8C8DC"


class FreeCADRenderer:
    """FreeCAD render motoru - PARÇA FİLTRELEME DESTEKLİ"""

    def __init__(self, input_file: str, render_cfg: RenderConfig,
                 anim_cfg: AnimationConfig, output_cfg: OutputConfig,
                 camera_cfg: CameraConfig,
                 post_cfg: Optional[PostProcessingConfig] = None,
                 lighting_cfg: Optional[LightingConfig] = None,
                 material_cfg: Optional[MaterialConfig] = None,
                 background_cfg: Optional[BackgroundConfig] = None,
                 target_object: Optional[str] = None):  # 🎯 YENİ PARAMETRE
        self.input_file = input_file
        self.render_cfg = render_cfg
        self.anim_cfg = anim_cfg
        self.output_cfg = output_cfg
        self.camera_cfg = camera_cfg
        self.post_cfg = post_cfg or PostProcessingConfig()
        self.lighting_cfg = lighting_cfg or LightingConfig()
        self.material_cfg = material_cfg or MaterialConfig()
        self.background_cfg = background_cfg or BackgroundConfig()
        self.target_object = target_object  # 🎯 Sadece bu parçayı render et

        self.doc = None
        self.view = None
        self.frames: List[str] = []

    def debug(self, msg: str, level: str = "INFO"):
        print(f"[{level}] {msg}", flush=True)

    def setup_document(self):
        """Doküman ve model kurulumu"""
        if not os.path.exists(self.input_file):
            raise FileNotFoundError(f"Girdi dosyası bulunamadı: {self.input_file}")

        self.doc = FreeCAD.newDocument("AutoRender")
        self.debug("Yeni belge oluşturuldu")

        ImportGui.insert(self.input_file, self.doc.Name)
        self.debug(f"Model içe aktarıldı: {self.input_file}")

    def setup_view(self):
        """Görünüm ayarları - 🎯 PARÇA FİLTRELEME EKLENDİ"""
        FreeCADGui.showMainWindow()
        FreeCADGui.ActiveDocument = FreeCADGui.getDocument(self.doc.Name)
        self.view = FreeCADGui.ActiveDocument.ActiveView

        # 🎯 PARÇA FİLTRELEME: Sadece hedef parçayı göster
        if self.target_object:
            self.debug(f"🎯 Sadece '{self.target_object}' parçası gösterilecek...")
            
            target_found = False
            for obj in self.doc.Objects:
                if hasattr(obj, 'ViewObject'):
                    # Hedef parça mı kontrol et (Name, Label veya Label2)
                    is_target = (
                        obj.Name == self.target_object or 
                        (hasattr(obj, 'Label') and obj.Label == self.target_object) or
                        (hasattr(obj, 'Label2') and obj.Label2 == self.target_object)
                    )
                    
                    # Görünürlük ayarla
                    obj.ViewObject.Visibility = is_target
                    
                    if is_target:
                        target_found = True
                        self.debug(f"  ✅ Gösteriliyor: {obj.Label} (Internal: {obj.Name})")
                    else:
                        self.debug(f"  ❌ Gizleniyor: {obj.Label} (Internal: {obj.Name})")
            
            if not target_found:
                raise ValueError(f"❌ HATA: '{self.target_object}' parçası bulunamadı!")
            
            self.debug(f"✅ Parça filtreleme tamamlandı")

        # View type ayarla
        if self.render_cfg.view_type == "axonometric":
            self.view.viewAxonometric()
        elif self.render_cfg.view_type == "perspective":
            self.view.viewPerspective()
        elif self.render_cfg.view_type == "orthographic":
            self.view.viewOrthographic()

        # Kamera pozisyonu
        if self.camera_cfg.camera_position:
            self.view.setCameraOrientation(
                FreeCAD.Rotation(*self.camera_cfg.camera_position)
            )

        self.view.fitAll()

        # Zoom ayarı
        if self.camera_cfg.zoom_level != 1.0:
            cam = self.view.getCameraNode()
            if cam:
                try:
                    if hasattr(cam, 'heightAngle'):
                        current_height = cam.heightAngle.getValue()
                        new_height = current_height / self.camera_cfg.zoom_level
                        cam.heightAngle.setValue(new_height)
                    elif hasattr(cam, 'height'):
                        current_height = cam.height.getValue()
                        new_height = current_height / self.camera_cfg.zoom_level
                        cam.height.setValue(new_height)
                except Exception as e:
                    self.debug(f"Zoom uygulanamadı: {e}, fallback kullanılıyor", "WARNING")
                    zoom_diff = self.camera_cfg.zoom_level - 1.0
                    zoom_steps = int(abs(zoom_diff) * 10)
                    for _ in range(zoom_steps):
                        if zoom_diff > 0:
                            self.view.zoomIn()
                        else:
                            self.view.zoomOut()

        FreeCADGui.updateGui()
        self.debug(f"Görünüm ayarlandı (zoom: {self.camera_cfg.zoom_level}x)")

    def render_static_image(self) -> str:
        if not self.output_cfg.save_png:
            return None

        output_file = os.path.join(
            self.output_cfg.output_dir,
            f"{self.output_cfg.base_name}.png"
        )

        self.view.saveImage(
            output_file,
            self.render_cfg.width,
            self.render_cfg.height,
            self.render_cfg.background
        )

        self.debug(f"Statik görüntü kaydedildi: {output_file}")
        return output_file

    def create_rotation_frames(self, axis: str, angle_range: Tuple[int, int]) -> List[str]:
        frames = []
        start_angle, end_angle = angle_range
        num_frames = int((end_angle - start_angle) / self.anim_cfg.angle_step)

        self.debug(f"{axis.upper()} ekseni dönüş frameleri oluşturuluyor ({num_frames} frame)...")

        for i in range(num_frames):
            angle = start_angle + (i * self.anim_cfg.angle_step)

            if axis == "yaw":
                rotation = FreeCAD.Rotation(Base.Vector(0, 1, 0), angle)
            elif axis == "pitch":
                rotation = FreeCAD.Rotation(Base.Vector(1, 0, 0), angle)
            elif axis == "roll":
                rotation = FreeCAD.Rotation(Base.Vector(0, 0, 1), angle)
            else:
                continue

            self.view.setCameraOrientation(rotation)
            self.view.fitAll()

            # Zoom'u her frame'de yeniden uygula
            if self.camera_cfg.zoom_level != 1.0:
                cam = self.view.getCameraNode()
                if cam:
                    try:
                        if hasattr(cam, 'heightAngle'):
                            current_height = cam.heightAngle.getValue()
                            new_height = current_height / self.camera_cfg.zoom_level
                            cam.heightAngle.setValue(new_height)
                        elif hasattr(cam, 'height'):
                            current_height = cam.height.getValue()
                            new_height = current_height / self.camera_cfg.zoom_level
                            cam.height.setValue(new_height)
                    except:
                        pass

            FreeCADGui.updateGui()
            time.sleep(0.05)

            frame_file = os.path.join(
                self.output_cfg.output_dir,
                f"frame_{axis}_{i:04d}.png"
            )

            self.view.saveImage(
                frame_file,
                self.render_cfg.width,
                self.render_cfg.height,
                self.render_cfg.background
            )

            frames.append(frame_file)

            if (i + 1) % 10 == 0:
                self.debug(f"  Frame {i+1}/{num_frames} ({axis.upper()})")

        return frames

    def create_pause_frames(self, count: int) -> List[str]:
        frames = []
        for i in range(count):
            frame_file = os.path.join(
                self.output_cfg.output_dir,
                f"frame_pause_{i:04d}.png"
            )
            self.view.saveImage(
                frame_file,
                self.render_cfg.width,
                self.render_cfg.height,
                self.render_cfg.background
            )
            frames.append(frame_file)
        return frames

    def create_animation(self) -> Optional[str]:
        if not self.anim_cfg.enabled or not self.output_cfg.save_gif:
            return None

        self.debug("Animasyon frameleri oluşturuluyor...")
        self.frames = []

        # YAW
        if self.anim_cfg.yaw_enabled:
            yaw_frames = self.create_rotation_frames("yaw", self.anim_cfg.yaw_range)
            self.frames.extend(yaw_frames)

            pause_count = int(self.anim_cfg.pause_duration * self.anim_cfg.fps)
            pause_frames = self.create_pause_frames(pause_count)
            self.frames.extend(pause_frames)

        # PITCH
        if self.anim_cfg.pitch_enabled:
            pitch_frames = self.create_rotation_frames("pitch", self.anim_cfg.pitch_range)
            self.frames.extend(pitch_frames)

            if self.anim_cfg.roll_enabled:
                pause_count = int(self.anim_cfg.pause_duration * self.anim_cfg.fps)
                pause_frames = self.create_pause_frames(pause_count)
                self.frames.extend(pause_frames)

        # ROLL
        if self.anim_cfg.roll_enabled:
            roll_frames = self.create_rotation_frames("roll", self.anim_cfg.roll_range)
            self.frames.extend(roll_frames)

        # GIF oluştur
        gif_path = os.path.join(
            self.output_cfg.output_dir,
            f"{self.output_cfg.base_name}.gif"
        )

        self.save_gif(gif_path)

        if not self.output_cfg.save_frames:
            self.cleanup_frames()

        return gif_path

    def save_gif(self, output_path: str):
        self.debug(f"GIF oluşturuluyor ({len(self.frames)} frame)...")

        if USE_PIL:
            self._save_gif_pil(output_path)
        else:
            self._save_gif_imageio(output_path)

    def _save_gif_pil(self, output_path: str):
        images = []

        self.debug("Frame'ler yükleniyor ve post-processing uygulanıyor...")

        for i, f in enumerate(self.frames):
            if not os.path.exists(f):
                continue

            img = Image.open(f)
            img = self.apply_post_processing(img)
            images.append(img)

            if (i + 1) % 10 == 0:
                self.debug(f"  Post-processing: {i+1}/{len(self.frames)} frame işlendi")

        if not images:
            raise ValueError("Hiç frame oluşturulamadı!")

        self.debug(f"Toplam {len(images)} frame işlendi, GIF oluşturuluyor...")

        images[0].save(
            output_path,
            save_all=True,
            append_images=images[1:],
            duration=int(self.anim_cfg.duration * 1000),
            loop=self.anim_cfg.loop,
            optimize=self.output_cfg.gif_optimize,
            quality=self.output_cfg.gif_quality
        )

        self.debug(f"GIF oluşturuldu (PIL): {output_path}")

    def _save_gif_imageio(self, output_path: str):
        images = [imageio.imread(f) for f in self.frames if os.path.exists(f)]

        if not images:
            raise ValueError("Hiç frame oluşturulamadı!")

        imageio.mimsave(
            output_path,
            images,
            duration=self.anim_cfg.duration,
            loop=self.anim_cfg.loop,
            subrectangles=True
        )

        self.debug(f"GIF oluşturuldu (imageio): {output_path}")

    def apply_post_processing(self, img: Image.Image) -> Image.Image:
        if not USE_PIL:
            return img

        cfg = self.post_cfg

        if cfg.sharpen:
            enhancer = ImageEnhance.Sharpness(img)
            img = enhancer.enhance(cfg.sharpen_intensity)

        if cfg.smooth:
            img = img.filter(ImageFilter.GaussianBlur(radius=cfg.smooth_radius))

        if cfg.contrast != 1.0:
            enhancer = ImageEnhance.Contrast(img)
            img = enhancer.enhance(cfg.contrast)

        if cfg.brightness != 1.0:
            enhancer = ImageEnhance.Brightness(img)
            img = enhancer.enhance(cfg.brightness)

        if cfg.saturation != 1.0:
            enhancer = ImageEnhance.Color(img)
            img = enhancer.enhance(cfg.saturation)

        if cfg.edge_detection:
            edges = img.filter(ImageFilter.FIND_EDGES)
            img = Image.blend(img, edges, 0.5)

        if cfg.vignette:
            img = self._apply_vignette(img, cfg.vignette_intensity)

        if cfg.shadow:
            img = self._apply_shadow(img, cfg.shadow_offset_x, cfg.shadow_offset_y,
                                     cfg.shadow_blur, cfg.shadow_opacity)

        if cfg.grid_overlay:
            img = self._apply_grid(img, cfg.grid_size, cfg.ruler_enabled)

        if cfg.bloom:
            img = self._apply_bloom(img, cfg.bloom_intensity, cfg.bloom_threshold)

        if cfg.fog:
            img = self._apply_fog(img, cfg.fog_density, cfg.fog_color)

        return img

    def _apply_vignette(self, img: Image.Image, intensity: float) -> Image.Image:
        width, height = img.size
        vignette = Image.new('L', (width, height), 0)
        draw = ImageDraw.Draw(vignette)

        center_x, center_y = width // 2, height // 2
        max_radius = min(width, height) // 2

        for i in range(max_radius, 0, -1):
            alpha = int(255 * (1 - (i / max_radius) * intensity))
            bbox = [center_x - i, center_y - i, center_x + i, center_y + i]
            draw.ellipse(bbox, fill=alpha)

        img = img.convert('RGBA')
        vignette = vignette.convert('RGBA')
        return Image.composite(img, Image.new('RGBA', img.size, (0, 0, 0, 255)), vignette)

    def _apply_shadow(self, img: Image.Image, offset_x: int, offset_y: int,
                      blur: int, opacity: float) -> Image.Image:
        shadow = Image.new('RGBA', img.size, (0, 0, 0, 0))
        shadow.paste(img, (offset_x, offset_y))
        shadow = shadow.filter(ImageFilter.GaussianBlur(blur))

        alpha = shadow.split()[3]
        alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
        shadow.putalpha(alpha)

        result = Image.alpha_composite(shadow, img.convert('RGBA'))
        return result

    def _apply_grid(self, img: Image.Image, grid_size: int, ruler: bool) -> Image.Image:
        img = img.convert('RGBA')
        overlay = Image.new('RGBA', img.size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(overlay)

        width, height = img.size
        grid_color = (200, 200, 200, 128)

        for x in range(0, width, grid_size):
            draw.line([(x, 0), (x, height)], fill=grid_color, width=1)

        for y in range(0, height, grid_size):
            draw.line([(0, y), (width, y)], fill=grid_color, width=1)

        if ruler:
            ruler_color = (255, 100, 100, 180)
            center_x, center_y = width // 2, height // 2
            draw.line([(0, center_y), (width, center_y)], fill=ruler_color, width=2)
            draw.line([(center_x, 0), (center_x, height)], fill=ruler_color, width=2)

        return Image.alpha_composite(img, overlay)

    def _apply_bloom(self, img: Image.Image, intensity: float, threshold: float) -> Image.Image:
        enhancer = ImageEnhance.Brightness(img)
        bright = enhancer.enhance(2.0)

        bright_np = np.array(bright)
        threshold_val = int(threshold * 255)
        mask = np.all(bright_np > threshold_val, axis=-1) if bright_np.ndim == 3 else bright_np > threshold_val

        bloom = bright.filter(ImageFilter.GaussianBlur(radius=15))
        result = Image.blend(img, bloom, intensity * 0.5)
        return result

    def _apply_fog(self, img: Image.Image, density: float, color_hex: str) -> Image.Image:
        color_hex = color_hex.lstrip('#')
        fog_color = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))

        fog_layer = Image.new('RGB', img.size, fog_color)
        result = Image.blend(img.convert('RGB'), fog_layer, density)
        return result

    def cleanup_frames(self):
        self.debug("Ara frameler temizleniyor...")
        for frame in self.frames:
            if os.path.exists(frame):
                try:
                    os.remove(frame)
                except Exception as e:
                    self.debug(f"Frame silinemedi {frame}: {e}", "WARNING")

    def render(self) -> Dict[str, str]:
        results = {}

        try:
            self.setup_document()
            self.setup_view()

            png_path = self.render_static_image()
            if png_path:
                results['png'] = png_path

            gif_path = self.create_animation()
            if gif_path:
                results['gif'] = gif_path

            self.debug("İşlem başarıyla tamamlandı!", "SUCCESS")

        except Exception as e:
            self.debug(f"HATA: {e}", "ERROR")
            raise

        return results


def load_config_from_json(json_path: str) -> Dict:
    with open(json_path, 'r', encoding='utf-8') as f:
        return json.load(f)


def save_config_template(output_path: str):
    config = {
        "input_file": "model.stp",
        "target_object": None,  # 🎯 Belirli bir parça için buraya isim yazın
        "render": asdict(RenderConfig()),
        "animation": asdict(AnimationConfig()),
        "output": asdict(OutputConfig()),
        "camera": asdict(CameraConfig())
    }

    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=2, ensure_ascii=False)

    print(f"Konfigürasyon şablonu oluşturuldu: {output_path}")


def main():
    parser = argparse.ArgumentParser(
        description="FreeCAD 3D Model Renderer - Parça Filtreleme Destekli"
    )

    parser.add_argument('input_file', nargs='?', help='STEP dosya yolu (.stp)')
    parser.add_argument('-c', '--config', help='JSON konfigürasyon dosyası')
    parser.add_argument('--target-object', help='🎯 Sadece bu parçayı render et')
    parser.add_argument('-o', '--output', help='Çıktı dizini')
    parser.add_argument('--width', type=int, help='Görüntü genişliği')
    parser.add_argument('--height', type=int, help='Görüntü yüksekliği')
    parser.add_argument('--frames', type=int, help='Animasyon frame sayısı')
    parser.add_argument('--fps', type=int, help='Animasyon FPS')
    parser.add_argument('--no-gif', action='store_true', help='GIF oluşturma')
    parser.add_argument('--no-png', action='store_true', help='PNG kaydetme')
    parser.add_argument('--save-frames', action='store_true', help='Ara frameleri sakla')
    parser.add_argument('--create-config', help='Örnek config.json oluştur')

    args = parser.parse_args()

    if args.create_config:
        save_config_template(args.create_config)
        return

    # Config yükle
    target_object = None
    if args.config:
        config_data = load_config_from_json(args.config)
        input_file = config_data.get('input_file', args.input_file)
        target_object = config_data.get('target_object')  # 🎯 JSON'dan parça ismi
        render_cfg = RenderConfig(**config_data.get('render', {}))
        anim_cfg = AnimationConfig(**config_data.get('animation', {}))
        output_cfg = OutputConfig(**config_data.get('output', {}))
        camera_cfg = CameraConfig(**config_data.get('camera', {}))
        post_cfg = PostProcessingConfig(**config_data.get('post_processing', {}))
        lighting_cfg = LightingConfig(**config_data.get('lighting', {}))
        material_cfg = MaterialConfig(**config_data.get('material', {}))
        background_cfg = BackgroundConfig(**config_data.get('background', {}))
    else:
        input_file = args.input_file
        render_cfg = RenderConfig()
        anim_cfg = AnimationConfig()
        output_cfg = OutputConfig()
        camera_cfg = CameraConfig()
        post_cfg = PostProcessingConfig()
        lighting_cfg = LightingConfig()
        material_cfg = MaterialConfig()
        background_cfg = BackgroundConfig()

    # CLI override
    if args.target_object:
        target_object = args.target_object  # 🎯 CLI'dan parça ismi

    if args.output:
        output_cfg.output_dir = args.output
    if args.width:
        render_cfg.width = args.width
    if args.height:
        render_cfg.height = args.height
    if args.frames:
        anim_cfg.num_frames = args.frames
    if args.fps:
        anim_cfg.fps = args.fps
    if args.no_gif:
        output_cfg.save_gif = False
    if args.no_png:
        output_cfg.save_png = False
    if args.save_frames:
        output_cfg.save_frames = True

    os.makedirs(output_cfg.output_dir, exist_ok=True)

    # 🎯 Parça ismi bilgisi
    if target_object:
        print(f"\n🎯 TEK PARÇA RENDER MODU")
        print(f"   Hedef Parça: {target_object}")
        print(f"=" * 60)

    # Render et
    renderer = FreeCADRenderer(
        input_file,
        render_cfg,
        anim_cfg,
        output_cfg,
        camera_cfg,
        post_cfg,
        lighting_cfg,
        material_cfg,
        background_cfg,
        target_object  # 🎯 Parça filtresi
    )

    results = renderer.render()

    # Sonuçları yazdır
    print("\n" + "="*60)
    print("RENDER TAMAMLANDI")
    print("="*60)
    for key, path in results.items():
        print(f"{key.upper()}: {path}")
    print("="*60)


if __name__ == "__main__":
    main()