<?php

// Prevent direct access to the file
if (!defined('ABSPATH')) {
    exit;
}

class AlbumCollageGenerator {

    private $db;
    private $background_color;
    private $text_color;
    private $western_font;
    private $japanese_font;
    private $blacklisted_artists;

    public function __construct() {
        global $wpdb;
        $this->db = $wpdb;

        add_action('rest_api_init', array($this, 'register_endpoint'));

        $this->background_color = $this->hex_to_rgb('#141617');
        $this->text_color = $this->hex_to_rgb('#ececec');
        $this->western_font = plugin_dir_path(__FILE__) . 'assets/co1251n.ttf';
        $this->japanese_font = plugin_dir_path(__FILE__) . 'assets/BIZUDPMincho-Regular.ttf';
        $this->blacklisted_artists = array('Drake', 'Mac Miller', 'Kendrick Lamar');
        $this->blacklisted_albums = array('OK Computer OKNOTOK 1997 2017');
    }

    public function register_endpoint() {
        register_rest_route('top-albums/v1', '/last-month', array(
            'methods' => 'GET',
            'callback' => array($this, 'get_or_generate_image'),
            'permission_callback' => '__return_true',
            'args' => array(
                'month' => array(
                    'required' => false,
                    'validate_callback' => array($this, 'is_valid_month'),
                ),
                'year' => array(
                    'required' => false,
                    'validate_callback' => array($this, 'is_valid_year'),
                ),
                'overwrite' => array(
                    'required' => false,
                    'default' => false,
                    'validate_callback' => array($this, 'is_valid_overwrite'),
                ),
            ),
        ));
    }

    public function is_valid_month($param, $request, $key) {
        $month = intval($param);
        return $month >= 1 && $month <= 12;
    }

    public function is_valid_year($param, $request, $key) {
        $year = intval($param);
        $current_year = intval(date('Y'));
        return $year >= 2000 && $year <= $current_year;
    }

    public function is_valid_overwrite($param, $request, $key) {
        // Accept boolean or string representations
        if (is_bool($param)) {
            return true;
        }
        if (is_string($param)) {
            $param = strtolower($param);
            return in_array($param, array('1', 'true', 'yes'), true);
        }
        if (is_numeric($param)) {
            return in_array($param, array(1, 0), true);
        }
        return false;
    }

    private function interpret_boolean($value) {
        if (is_bool($value)) {
            return $value;
        }
        if (is_string($value)) {
            $value = strtolower($value);
            return in_array($value, array('1', 'true', 'yes'), true);
        }
        if (is_numeric($value)) {
            return (bool)$value;
        }
        return false;
    }

    private function hex_to_rgb($hex) {
        $hex = str_replace('#', '', $hex);
        if(strlen($hex) == 3) {
            $r = hexdec(substr($hex,0,1).substr($hex,0,1));
            $g = hexdec(substr($hex,1,1).substr($hex,1,1));
            $b = hexdec(substr($hex,2,1).substr($hex,2,1));
        } else {
            $r = hexdec(substr($hex,0,2));
            $g = hexdec(substr($hex,2,2));
            $b = hexdec(substr($hex,4,2));
        }
        return array($r, $g, $b);
    }

    private function get_last_month_data() {
        $last_month = date('Y-m', strtotime('last day of previous month'));
        return explode('-', $last_month);
    }
    
    private function get_top_albums($year, $month) {
        // Prepare placeholders for blacklisted artists
        $artist_placeholders = array_fill(0, count($this->blacklisted_artists), '%s');
        $artist_placeholders_str = implode(', ', $artist_placeholders);
    
        // Prepare placeholders for blacklisted albums
        $album_placeholders = array_fill(0, count($this->blacklisted_albums), '%s');
        $album_placeholders_str = implode(', ', $album_placeholders);
    
        // Initialize query parts
        $artist_condition = '';
        $album_condition = '';
        $query_params = array($year, $month);
    
        // Handle blacklisted artists
        if (!empty($this->blacklisted_artists)) {
            $artist_condition = "AND author NOT IN ($artist_placeholders_str)";
            $query_params = array_merge($query_params, $this->blacklisted_artists);
        }
    
        // Handle blacklisted albums
        if (!empty($this->blacklisted_albums)) {
            $album_condition = "AND album_name NOT IN ($album_placeholders_str)";
            $query_params = array_merge($query_params, $this->blacklisted_albums);
        }
    
        // Prepare the SQL query with updated ORDER BY in ranked_covers
        $query = $this->db->prepare(
            "WITH ranked_covers AS (
                SELECT 
                    album_name,
                    author,
                    cover_url,
                    id, -- Ensure id is selected for ordering
                    ROW_NUMBER() OVER (PARTITION BY album_name, author ORDER BY id DESC) as rn
                FROM song_scrobbles
                WHERE cover_url != '' AND cover_url IS NOT NULL
            ),
            album_stats AS (
                SELECT 
                    album_name, 
                    author, 
                    COUNT(*) as play_count,
                    COUNT(DISTINCT song_name) as unique_song_count
                FROM song_scrobbles 
                WHERE YEAR(time) = %d AND MONTH(time) = %d 
                    AND album_name != '' AND album_name IS NOT NULL
                    $artist_condition
                    $album_condition
                    -- Exclude albums with '¥' symbol in album_name or author
                    AND album_name NOT LIKE '%%¥%%' 
                    AND author NOT LIKE '%%¥%%'
                    -- Exclude albums with Cyrillic characters in album_name or author
                    AND album_name NOT REGEXP '[А-яЁё]'
                    AND author NOT REGEXP '[А-яЁё]'
                GROUP BY album_name, author
                HAVING LENGTH(CONCAT(album_name, author)) <= 60 AND unique_song_count >= 1
            )
            SELECT 
                a.album_name,
                a.author,
                r.cover_url,
                a.play_count,
                a.unique_song_count,
                CASE 
                    WHEN a.unique_song_count = 1 THEN 1.0
                    WHEN a.unique_song_count = 5 THEN 1.25
                    WHEN a.unique_song_count = 12 THEN 1.6
                    ELSE 1.0 + (a.unique_song_count * 0.05)
                END as multiplier,
                (a.play_count * 
                    CASE 
                        WHEN a.unique_song_count = 1 THEN 1.0
                        WHEN a.unique_song_count = 5 THEN 1.25
                        WHEN a.unique_song_count = 12 THEN 1.6
                        ELSE 1.0 + (a.unique_song_count * 0.05)
                    END
                ) as rank_score
            FROM album_stats a
            LEFT JOIN ranked_covers r 
                ON a.album_name = r.album_name 
                AND a.author = r.author 
                AND r.rn = 1
            ORDER BY rank_score DESC 
            LIMIT 25",
            ...$query_params
        );
    
        return $this->db->get_results($query);
    }     

    public function get_or_generate_image($request) {
        $month = $request->get_param('month');
        $year = $request->get_param('year');
        $overwrite = $request->get_param('overwrite'); // Retrieve 'overwrite' parameter

        // Convert 'overwrite' to boolean
        $overwrite = $this->interpret_boolean($overwrite);

        if ($month && $year) {
            $month = intval($month);
            $year = intval($year);

            // Validate that the date is not in the future
            $provided_date = strtotime("{$year}-{$month}-01");
            $now = time();

            if ($provided_date > $now) {
                return new WP_Error('invalid_date', 'The provided month and year cannot be in the future.', array('status' => 400));
            }

            // Ensure the date is valid
            if (!checkdate($month, 1, $year)) {
                return new WP_Error('invalid_date', 'The provided month and year are invalid.', array('status' => 400));
            }

        } else {
            list($year, $month) = $this->get_last_month_data();
        }

        $cache_key = "top_albums_{$year}_{$month}";

        // Check if we have a cached image
        $cached_image_id = get_option($cache_key);

        if ($overwrite && $cached_image_id) { // If overwrite is true and cached image exists
            // Delete the old attachment
            $deleted = wp_delete_attachment($cached_image_id, true);
            if ($deleted) {
                // Remove the cached option
                delete_option($cache_key);
                $cached_image_id = false; // Reset to indicate no cached image
            } else {
                return new WP_Error('deletion_failed', 'Failed to delete the existing image.', array('status' => 500));
            }
        }

        if ($cached_image_id) {
            $attachment_path = get_attached_file($cached_image_id);
            if ($attachment_path && file_exists($attachment_path)) {
                // Serve the cached image directly as a response
                $image_data = file_get_contents($attachment_path);
                $mime_type = mime_content_type($attachment_path);

                // Send appropriate headers
                header('Content-Type: ' . $mime_type);
                header('Content-Length: ' . strlen($image_data));
                header('Content-Disposition: inline; filename="' . basename($attachment_path) . '"');

                // Prevent WordPress from sending additional content
                remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');

                echo $image_data;
                exit;
            }
        }

        // If no cached image, generate a new one
        $image_data = $this->generate_image($year, $month);

        // Save the generated image as an attachment
        $upload_dir = wp_upload_dir();
        $filename = "top_albums_{$year}_{$month}.jpg";
        $file_path = $upload_dir['path'] . '/' . $filename;
        file_put_contents($file_path, $image_data);

        $attachment = array(
            'post_mime_type' => 'image/jpeg',
            'post_title' => "Top Albums for {$year}-{$month}",
            'post_content' => '',
            'post_status' => 'inherit'
        );

        $attach_id = wp_insert_attachment($attachment, $file_path);
        require_once(ABSPATH . 'wp-admin/includes/image.php');
        $attach_data = wp_generate_attachment_metadata($attach_id, $file_path);
        wp_update_attachment_metadata($attach_id, $attach_data);

        // Save the attachment ID for future use
        update_option($cache_key, $attach_id);

        // Serve the newly generated image directly as a response
        $mime_type = mime_content_type($file_path);

        // Send appropriate headers
        header('Content-Type: ' . $mime_type);
        header('Content-Length: ' . strlen($image_data));
        header('Content-Disposition: inline; filename="' . basename($file_path) . '"');

        // Prevent WordPress from sending additional content
        remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');

        echo $image_data;
        exit;
    }

    private function generate_image($year, $month) {
        $albums = $this->get_top_albums($year, $month);
    
        $image_width = 2600;
        $image_height = 1680;
        $album_size = 300;
        $gap = 8;
        $left_margin = 100;
        $top_margin = 100;
    
        $image = imagecreatetruecolor($image_width, $image_height);
        $background_color = imagecolorallocate($image, $this->background_color[0], $this->background_color[1], $this->background_color[2]);
        imagefill($image, 0, 0, $background_color);
    
        $text_color = imagecolorallocate($image, $this->text_color[0], $this->text_color[1], $this->text_color[2]);
    
        // Add album covers (left side)
        for ($i = 0; $i < min(count($albums), 25); $i++) {
            $row = floor($i / 5);
            $col = $i % 5;
        
            $x = $left_margin + $col * ($album_size + $gap);
            $y = $top_margin + $row * ($album_size + $gap);
        
            // Detect the image type
            $image_type = @exif_imagetype($albums[$i]->cover_url);
            $album_cover = false;
        
            switch ($image_type) {
                case IMAGETYPE_JPEG:
                    $album_cover = @imagecreatefromjpeg($albums[$i]->cover_url);
                    break;
                case IMAGETYPE_PNG:
                    $album_cover = @imagecreatefrompng($albums[$i]->cover_url);
                    break;
                default:
                    // Unsupported image type
                    $album_cover = false;
                    break;
            }
        
            if ($album_cover !== false) {
                // If the image is PNG, preserve transparency
                if ($image_type === IMAGETYPE_PNG) {
                    imagealphablending($album_cover, true);
                    imagesavealpha($album_cover, true);
                }
            
                $this->apply_retro_effects($album_cover);
            
                // If the main image has transparency and the album cover is PNG, preserve it
                if ($image_type === IMAGETYPE_PNG) {
                    imagecopyresampled(
                        $image,
                        $album_cover,
                        $x,
                        $y,
                        0,
                        0,
                        $album_size,
                        $album_size,
                        imagesx($album_cover),
                        imagesy($album_cover)
                    );
                } else {
                    imagecopyresampled(
                        $image,
                        $album_cover,
                        $x,
                        $y,
                        0,
                        0,
                        $album_size,
                        $album_size,
                        imagesx($album_cover),
                        imagesy($album_cover)
                    );
                }
            
                imagedestroy($album_cover);
            } else {
                // Draw placeholder rectangle
                $placeholder_color = imagecolorallocate($image, 100, 100, 100);
                imagefilledrectangle($image, $x, $y, $x + $album_size, $y + $album_size, $placeholder_color);
            }
        }
    
        // Add album titles (right side)
        $text_x = $left_margin + 5 * ($album_size + $gap) + 20;
        $text_y = $top_margin + 18;
        $line_height = 35;
        $font_size = 20;
        $group_spacing = 50;
    
        for ($i = 0; $i < min(count($albums), 25); $i++) {
            // Normalize album and author names to replace weird characters
            $normalized_author = $this->normalize_text($albums[$i]->author);
            $normalized_album = $this->normalize_text($albums[$i]->album_name);
            $album_text = "{$normalized_author} - {$normalized_album}";
            
            $font = $this->is_japanese($album_text) ? $this->japanese_font : $this->western_font;
            
            $this->add_retro_text($image, $font_size, $text_x, $text_y, $text_color, $font, $album_text);
            
            if (($i + 1) % 5 == 0 && $i < 24) {
                $text_y += $line_height + $group_spacing;
            } else {
                $text_y += $line_height;
            }
        }
    
        $this->apply_overall_retro_effects($image);
    
        ob_start();
        imagejpeg($image);
        $image_data = ob_get_clean();
        imagedestroy($image);
    
        return $image_data;
    }

    /**
     * Normalize text by replacing accented or special characters with standard ones.
     *
     * @param string $text The input text to normalize.
     * @return string The normalized text.
     */
    private function normalize_text($text) {
        // Check if the intl extension is loaded
        if (class_exists('Transliterator')) {
            $transliterator = \Transliterator::create('NFD; [:Nonspacing Mark:] Remove; NFC;');
            if ($transliterator) {
                $text = $transliterator->transliterate($text);
            }
        } else {
            // Fallback to iconv if intl is not available
            $text = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text);
        }

        // Replace any remaining non-ASCII characters
        $text = preg_replace('/[^\x20-\x7E]/', '', $text);

        return $text;
    }

    private function apply_retro_effects(&$image) {
        // Add noise
        $this->add_noise($image, 20);
        
        // Reduce color depth
        $this->reduce_color_depth($image);
        
        // Add slight blur
        // imagefilter($image, IMG_FILTER_GAUSSIAN_BLUR);
    }

    private function add_noise(&$image, $amount) {
        $width = imagesx($image);
        $height = imagesy($image);
        
        for ($x = 0; $x < $width; $x++) {
            for ($y = 0; $y < $height; $y++) {
                if (rand(0, 100) < $amount) {
                    $rgb = imagecolorat($image, $x, $y);
                    $r = ($rgb >> 16) & 0xFF;
                    $g = ($rgb >> 8) & 0xFF;
                    $b = $rgb & 0xFF;
                    
                    $noise = rand(-20, 20);
                    $r = max(0, min(255, $r + $noise));
                    $g = max(0, min(255, $g + $noise));
                    $b = max(0, min(255, $b + $noise));
                    
                    $color = imagecolorallocate($image, $r, $g, $b);
                    imagesetpixel($image, $x, $y, $color);
                }
            }
        }
    }

    private function reduce_color_depth(&$image) {
        imagetruecolortopalette($image, false, 64);
    }

    private function add_retro_text(&$image, $font_size, $x, $y, $color, $font, $text) {
        // Add a slight shadow
        $shadow_color = imagecolorallocatealpha($image, 0, 0, 0, 75);
        imagettftext($image, $font_size, 0, $x + 1, $y + 1, $shadow_color, $font, $text);
        
        // Add main text with a slight glow
        $glow_color = imagecolorallocatealpha($image, 255, 255, 255, 75);
        imagettftext($image, $font_size, 0, $x - 1, $y - 1, $glow_color, $font, $text);
        imagettftext($image, $font_size, 0, $x, $y, $color, $font, $text);
    }

    private function apply_overall_retro_effects(&$image) {
        // Add scanlines
        $this->add_scanlines($image);
        
        // Add vignette effect
        $this->add_vignette($image);
        
        // Add slight color aberration
        $this->add_color_aberration($image);

        // Add subtle grain
        $this->add_grain($image);
    }

    private function add_scanlines(&$image) {
        $height = imagesy($image);
        $scanline_color = imagecolorallocatealpha($image, 0, 0, 0, 70);
        
        for ($y = 0; $y < $height; $y += 2) {
            imageline($image, 0, $y, imagesx($image), $y, $scanline_color);
        }
    }

    private function add_vignette(&$image) {
        $width = imagesx($image);
        $height = imagesy($image);
        $center_x = $width / 2;
        $center_y = $height / 2;
        $max_distance = sqrt($center_x * $center_x + $center_y * $center_y);
        
        for ($x = 0; $x < $width; $x++) {
            for ($y = 0; $y < $height; $y++) {
                $distance = sqrt(pow($x - $center_x, 2) + pow($y - $center_y, 2));
                $vignette = 1 - ($distance / $max_distance);
                $vignette = max(0, min(1, $vignette));
                
                // Make vignette more subtle
                $vignette = 1 - (1 - $vignette) * 0.25; // Adjust 0.25 to control intensity
                
                $rgb = imagecolorat($image, $x, $y);
                $r = ($rgb >> 16) & 0xFF;
                $g = ($rgb >> 8) & 0xFF;
                $b = $rgb & 0xFF;
                
                $r = (int)($r * $vignette);
                $g = (int)($g * $vignette);
                $b = (int)($b * $vignette);
                
                $color = imagecolorallocate($image, $r, $g, $b);
                imagesetpixel($image, $x, $y, $color);
            }
        }
    }

    private function add_color_aberration(&$image) {
        $width = imagesx($image);
        $height = imagesy($image);
        $aberration = 1; // Reduced from 2 to 1 for subtler effect
        
        $new_image = imagecreatetruecolor($width, $height);
        imagesavealpha($new_image, true);
        imagealphablending($new_image, false);
        $transparent = imagecolorallocatealpha($new_image, 0, 0, 0, 127);
        imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
        imagealphablending($new_image, true);
        
        for ($x = 0; $x < $width; $x++) {
            for ($y = 0; $y < $height; $y++) {
                $rgb = imagecolorat($image, $x, $y);
                $r = ($rgb >> 16) & 0xFF;
                $g = ($rgb >> 8) & 0xFF;
                $b = $rgb & 0xFF;
                
                // Shift red channel slightly to the left
                $red_x = max(0, $x - $aberration);
                $red_color = imagecolorallocate($new_image, $r, 0, 0);
                imagesetpixel($new_image, $red_x, $y, $red_color);
                
                // Keep green channel in place
                $green_color = imagecolorallocate($new_image, 0, $g, 0);
                imagesetpixel($new_image, $x, $y, $green_color);
                
                // Shift blue channel slightly to the right
                $blue_x = min($width - 1, $x + $aberration);
                $blue_color = imagecolorallocate($new_image, 0, 0, $b);
                imagesetpixel($new_image, $blue_x, $y, $blue_color);
            }
        }
        
        // Merge the aberrated image back onto the original with reduced opacity
        imagecopymerge($image, $new_image, 0, 0, 0, 0, $width, $height, 3); // Reduced from 50 to 30
        imagedestroy($new_image);
    }

    private function add_grain(&$image) {
        $width = imagesx($image);
        $height = imagesy($image);
        $grain_amount = 8; // Adjust this value to control grain intensity (1-10 recommended)

        for ($x = 0; $x < $width; $x++) {
            for ($y = 0; $y < $height; $y++) {
                if (rand(0, 100) < $grain_amount) {
                    $rgb = imagecolorat($image, $x, $y);
                    $r = ($rgb >> 16) & 0xFF;
                    $g = ($rgb >> 8) & 0xFF;
                    $b = $rgb & 0xFF;

                    $noise = rand(-10, 10);
                    $r = max(0, min(255, $r + $noise));
                    $g = max(0, min(255, $g + $noise));
                    $b = max(0, min(255, $b + $noise));

                    $color = imagecolorallocate($image, $r, $g, $b);
                    imagesetpixel($image, $x, $y, $color);
                }
            }
        }
    }

    private function is_japanese($text) {
        return preg_match('/[\x{4E00}-\x{9FBF}\x{3040}-\x{309F}\x{30A0}-\x{30FF}]/u', $text);
    }

}