import numpy as np
import cv2
from PIL import Image, ImageDraw, ImageFilter, ImageChops, ImageEnhance
import imageio
import os
from pathlib import Path
import math

class DigitalReassemblyAnimator:
    def __init__(self):
        # Base neon colors with increased vibrancy
        self.neon_colors = [
            (0, 255, 255),    # Cyan
            (255, 0, 255),    # Magenta
            (255, 128, 0),    # Orange
            (0, 255, 128)     # Turquoise
        ]
        
        # Enhanced pulse colors for energy waves
        self.pulse_colors = [
            (128, 255, 255),  # Bright Cyan
            (255, 128, 255),  # Bright Magenta
            (255, 192, 128),  # Bright Orange
            (128, 255, 192)   # Bright Turquoise
        ]
        
        # Core energy colors (near-white variations)
        self.core_colors = [
            (192, 255, 255),  # White-Cyan
            (255, 192, 255),  # White-Magenta
            (255, 224, 192),  # White-Orange
            (192, 255, 224)   # White-Turquoise
        ]
        
        self.frames = []
        self.size = (1024, 1024)
        
        # Animation parameters
        self.frames_per_color = 30
        self.pulse_frequency = 15
        self.pulse_width = 40
        self.wave_speed = 3
        self.energy_buildup = 0.0
        
        # Digital Reassembly parameters
        self.reassembly_frames = 60  # Total frames for disassemble and reassemble
        self.disassemble_duration = self.reassembly_frames // 2
        self.reassemble_duration = self.reassembly_frames // 2
        self.max_displacement = 100  # Maximum pixels to move outward
        
        # Define custom edge points (provided by the user)
        self.edge_points = [
            (887, 967),
            (901, 997),
            (909, 999),
            (924, 967),
            (917, 967),
            (906, 989),
            (894, 967)
        ]
        
        # Center of the logo for calculating displacement direction
        self.center = (self.size[0] // 2, self.size[1] // 2)
        
        # Initialize displaced points
        self.displaced_points = self.calculate_displaced_points()
        
    def calculate_displaced_points(self):
        displaced = []
        for x, y in self.edge_points:
            # Calculate direction from center to point
            dx = x - self.center[0]
            dy = y - self.center[1]
            distance = math.hypot(dx, dy)
            if distance == 0:
                angle = 0
            else:
                angle = math.atan2(dy, dx)
            # Normalize direction
            nx = math.cos(angle)
            ny = math.sin(angle)
            displaced.append((nx, ny))
        return displaced
            
    def create_glow(self, img, amount=2, color=(255, 255, 255), intensity=1.0):
        img_pil = Image.fromarray(img)
        if len(color) == 3:
            color = (*color, int(255 * intensity))
        glow = img_pil.copy()
        glow = glow.filter(ImageFilter.GaussianBlur(amount))
        glow = ImageEnhance.Brightness(glow).enhance(1.5 * intensity)
        colored_glow = Image.new('RGBA', glow.size, color)
        glow = ImageChops.multiply(glow, colored_glow)
        return np.array(glow)
    
    def create_pulse_wave(self, points, progress, color, intensity=1.0):
        wave = Image.new('RGBA', self.size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(wave)
        
        n_points = len(points)
        wave_center = int(progress * n_points) % n_points
        
        for i in range(n_points):
            dist = min((i - wave_center) % n_points, (wave_center - i) % n_points)
            if dist < self.pulse_width:
                pulse_intensity = intensity * (1 - (dist / self.pulse_width) ** 2)
                x, y = points[i]
                alpha = int(200 * pulse_intensity)
                draw.ellipse([x-3, y-3, x+3, y+3], 
                           fill=(*color, alpha))
        
        return wave
    
    def generate_digital_reassembly_animation(self):
        # Total frames considering color cycles and reassembly
        total_frames = (self.frames_per_color * len(self.neon_colors)) + self.reassembly_frames
        
        for frame in range(total_frames):
            # Color calculations
            color_idx = (frame // self.frames_per_color) % len(self.neon_colors)
            next_color_idx = (color_idx + 1) % len(self.neon_colors)
            blend_factor = (frame % self.frames_per_color) / self.frames_per_color
            
            # Blend colors for smooth transitions
            base_color = tuple(int(c1 * (1 - blend_factor) + c2 * blend_factor)
                             for c1, c2 in zip(self.neon_colors[color_idx], 
                                             self.neon_colors[next_color_idx]))
            
            pulse_color = tuple(int(c1 * (1 - blend_factor) + c2 * blend_factor)
                              for c1, c2 in zip(self.pulse_colors[color_idx],
                                              self.pulse_colors[next_color_idx]))
            
            core_color = tuple(int(c1 * (1 - blend_factor) + c2 * blend_factor)
                             for c1, c2 in zip(self.core_colors[color_idx],
                                             self.core_colors[next_color_idx]))
            
            # Create base neon layer
            base_layer = Image.new('RGBA', self.size, (0, 0, 0, 0))
            draw = ImageDraw.Draw(base_layer)
            
            # Determine if we are in the reassembly phase
            if frame >= (self.frames_per_color * len(self.neon_colors)):
                re_frame = frame - (self.frames_per_color * len(self.neon_colors))
                if re_frame < self.reassembly_frames:
                    # Half the frames for disassembly, half for reassembly
                    if re_frame < self.disassemble_duration:
                        progress = re_frame / self.disassemble_duration
                        current_points = self.get_displaced_points(progress)
                    else:
                        progress = (re_frame - self.disassemble_duration) / self.reassemble_duration
                        current_points = self.get_displaced_points(1 - progress)
                else:
                    current_points = self.edge_points
            else:
                current_points = self.edge_points
            
            # Energy buildup calculation
            energy_phase = (frame % self.pulse_frequency) / self.pulse_frequency
            energy_intensity = 1.0 + 0.5 * math.sin(energy_phase * 2 * math.pi)
            
            # Draw edges
            for x, y in current_points:
                draw.ellipse([x-2, y-2, x+2, y+2], 
                           fill=(*base_color, int(180 * energy_intensity)))
            
            # Create pulse waves
            wave_progress = (frame * self.wave_speed) / len(current_points)
            pulse_wave = self.create_pulse_wave(current_points, wave_progress, pulse_color, energy_intensity)
            
            # Convert layers to numpy for processing
            base_array = np.array(base_layer)
            wave_array = np.array(pulse_wave)
            
            # Create multiple glow layers with different intensities
            glow1 = self.create_glow(base_array, 3, core_color, energy_intensity * 1.2)
            glow2 = self.create_glow(base_array, 6, pulse_color, energy_intensity * 0.8)
            glow3 = self.create_glow(wave_array, 4, core_color, energy_intensity)
            
            # Combine all effects
            final_image = Image.new('RGBA', self.size, (0, 0, 0, 0))
            final_image = Image.alpha_composite(final_image, Image.fromarray(glow2))
            final_image = Image.alpha_composite(final_image, Image.fromarray(glow1))
            final_image = Image.alpha_composite(final_image, base_layer)
            final_image = Image.alpha_composite(final_image, pulse_wave)
            final_image = Image.alpha_composite(final_image, Image.fromarray(glow3))
            
            self.frames.append(np.array(final_image))
    
    def get_displaced_points(self, progress):
        displaced = []
        for idx, (x, y) in enumerate(self.edge_points):
            dx, dy = self.displaced_points[idx]
            # Move outward based on progress
            new_x = x + dx * self.max_displacement * progress
            new_y = y + dy * self.max_displacement * progress
            displaced.append((int(new_x), int(new_y)))
        return displaced
    
    def save_gif(self, output_path):
        if not self.frames:
            raise ValueError("No frames generated yet. Run generate_digital_reassembly_animation first.")
            
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        
        imageio.mimsave(
            output_path,
            self.frames,
            duration=0.04,  # Duration per frame in seconds (0.04s = 40ms)
            loop=0
        )
        print(f"Animation saved to: {output_path}")

def main():
    try:
        animator = DigitalReassemblyAnimator()
        
        # Specify your desired output path here
        output_path = os.path.expanduser('~/auto/output/reassemble.gif')
        
        print("Generating Digital Reassembly Animation...")
        animator.generate_digital_reassembly_animation()
        print("Saving Animation...")
        animator.save_gif(output_path)
        print("Animation completed successfully!")
        
    except Exception as e:
        print(f"Error: {str(e)}")

if __name__ == "__main__":
    main() the 1024x1024 is that exact size for a reason, any adjustment will mess everything up. The logo is in the bottom right corner of such a lathe transparent image is beayse when I lay it over another image it is right where the logo Shoukd be... Any questions?