<?php 
namespace Next3Offload\Modules;
defined( 'ABSPATH' ) || exit;

class Optimizer{
    private static $instance;

    public $default_max_width_sizes = array(
		array(
			'label' => '2560px',
			'value' => 2560,
			'selected' => 0,
		),
		array(
			'label' => '2048px',
			'value' => 2048,
			'selected' => 0,
		),
		array(
			'label' => '1920px',
			'value' => 1920,
			'selected' => 0,
		),
		array(
			'label' => 'Disabled',
			'value' => 0,
			'selected' => 0,
		),
	);

    const BATCH_LIMIT = 200;

	const PNGS_SIZE_LIMIT = 1048576;

	public $type = 'image';

	public $non_optimized = 'next3_optimizer_total_unoptimized_images';

	public $batch_skipped = 'next3_optimizer_is_optimized';

	public $process_map = array(
		'filter'   => 'next3_optimizer_image_optimization_timeout',
		'attempts' => 'next3_optimizer_optimization_attempts',
		'failed'   => 'next3_optimizer_optimization_failed',
	);

    public $options_map = array(
		'completed' => 'next3_optimizer_image_optimization_completed',
		'status'    => 'next3_optimizer_image_optimization_status',
		'stopped'   => 'next3_optimizer_image_optimization_stopped',
	);
	
	public $compression_level_map = array(
		// IMAGETYPE_GIF.
		1 => array(
			'1'    => '-O1', // Low.
			'2' => '-O2', // Medium.
			'3'   => '-O3', // High.
		),
		// IMAGETYPE_JPEG.
		2 => array(
			'1'    => '-m85', // Low.
			'2' => '-m60', // Medium.
			'3'   => '-m20', // High.
		),
		// IMAGETYPE_PNG.
		3 => array(
			'1'    => '-o1',
			'2' => '-o2',
			'3'   => '-o3',
		),
	);


    public function init() {
        // Get the resize_images option
        $settings_options = next3_options();
        $optimizer_resize_images = ($settings_options['optimization']['optimizer_resize_images']) ?? 2560;

		$resize_images = apply_filters( 'next3_set_max_image_width', intval( $optimizer_resize_images ) );
		// Resize newly uploaded images
		if ( 2560 !== $resize_images ) {
			add_filter( 'big_image_size_threshold', array( $this, 'resize' ) );
		}

		// delete backup file
		add_action( 'delete_attachment', array( $this, 'delete_backups' ) );

        $compression_enable = ($settings_options['optimization']['compression']) ?? 'no';
        if( $compression_enable != 'yes'){
            return;
        }

		$webp_enable = ($settings_options['optimization']['webp_enable']) ?? 'no';
		if( $webp_enable == 'yes'){
            return;
        }
        $compression_level = ($settings_options['optimization']['compression_level']) ?? '0';

        // Optimize newly uploaded images.
		if ( '0' !== $compression_level ) {
			add_action( 'wp_generate_attachment_metadata', array( $this, 'optimize_new_image' ), 10, 2 );
		} else {
			//add_action( 'wp_generate_attachment_metadata', array( $this, 'maybe_update_total_unoptimized_images' ) );
		}
		
        add_action( 'edit_attachment', array( $this, 'custom_attachment_compression_level' ) );
		add_filter( 'attachment_fields_to_edit', array( $this, 'custom_attachment_compression_level_field' ), null, 2 );

    }

    public function resize( $image_data ) {
		// Getting the option value from the db and applying additional filters, if any.
		$settings_options = next3_options();
        $optimizer_resize_images = ($settings_options['optimization']['optimizer_resize_images']) ?? 2560;

		// Disable resize, if it's set so in the DB and no filters are found.
		if ( 0 === intval ( $optimizer_resize_images ) ) {
			return false;
		}

		// Adding a min value.
		$optimizer_resize_images = intval( $optimizer_resize_images ) < 1200 ? 1200 : intval( $optimizer_resize_images );

		return intval( $optimizer_resize_images );
	}

    public function delete_backups( $id ) {
		
		$main_image = next3_get_attached_file( $id, true);
		$metadata   = next3_wp_get_attachment_metadata( $id, true);
		$basename   = basename( $main_image );
        $backup_file = preg_replace( '~.(png|jpg|jpeg|gif)$~', '.bak.$1', $main_image );
        if ( !file_exists( $backup_file ) ) {
            return;
        }
		@unlink( preg_replace( '~.(png|jpg|jpeg|gif)$~', '.bak.$1', $main_image ) );

		if ( ! empty( $metadata['sizes'] ) ) {
			// Loop through all image sizes and optimize them as well.
			foreach ( $metadata['sizes'] as $size ) {
				@unlink( preg_replace( '~.(png|jpg|jpeg|gif)$~', '.bak.$1', str_replace( $basename, $size['file'], $main_image ) ) );
			}
		}
		
	}

    public function optimize_new_image( $data, $attachment_id ) {
		
		// check settings
		$settings_options = next3_options();
		$copy_file = ($settings_options['storage']['copy_file']) ?? 'no';
		$remove_local = isset($settings_options['storage']['remove_local']) ? true : false;
		$webp_enable = ($settings_options['optimization']['webp_enable']) ?? 'no';

		// convert to compress
		$this->optimize( $attachment_id, $data );

		// compress webp
		next3_core()->webp_ins->optimize( $attachment_id, $data);

		$status_data = next3_service_status();
		$offload_status = ($status_data['offload']) ?? false;
		if( !$offload_status ){
			return $data;
		}
		// offload settings
		if( next3_get_post_meta($attachment_id, '_next3_attached_url') === false){
			if( $copy_file == 'yes' ){
				next3_core()->action_ins->wpmedia_to_aws('', $attachment_id, $remove_local);
			}
		}

		return $data;
	}

    public function optimize( $id, $metadata, $main_image = '' ) {

		// Get path to main image.
		if( empty($main_image) ){
			$main_image = next3_get_attached_file( $id, true);
		}
		$checkwebp = $main_image;

        $settings_options = next3_options();
        $compression_level = ($settings_options['optimization']['compression_level']) ?? 0;
        $overwrite_custom = ($settings_options['optimization']['overwrite_custom']) ?? 'no';
        $webp_enable = ($settings_options['optimization']['webp_enable']) ?? 'no';
		
		if(true === next3_check_post_meta($id, 'next3_optimizer_is_optimized') && 'yes' !== $overwrite_custom){
			return true;
		}
		// Bail if the override is disabled and the image has a custom compression level.
        if (
			'yes' !== $overwrite_custom &&
			! empty( next3_get_post_meta( $id, 'next3_optimizer_compression_level') )
		) {
			return false;
		}

		// overwrite image
		if('yes' == $overwrite_custom || !is_readable($main_image) ){
			$old_main_image = preg_replace( '~.(png|jpg|jpeg|gif)$~', '.bak.$1', $main_image );
			if ( file_exists( $old_main_image ) ) {
				copy( $old_main_image, $main_image );
			}
			// delete webp
			next3_core()->webp_ins->delete_webp_copy( $id );
			delete_post_meta($id, 'next3_optimizer_is_converted_to_webp');
		}
		
		// Get the basename.
		$basename = basename( $main_image );
		// Get the command placeholder. It will be used by main image and to optimize the different image sizes.
		$status = $this->execute_optimization_command( $main_image );
		
		// Optimization failed.
		if ( true === boolval( $status ) ) {
			next3_update_post_meta( $id, 'next3_optimizer_optimization_failed', 1 );
			return false;
		}
		
		// Check if there are any sizes.
		if ( ! empty( $metadata['sizes'] ) ) {
			foreach ( $metadata['sizes'] as $size ) {
				$checkwebp = str_replace( $basename, $size['file'], $main_image );

				// overwrite image
				if('yes' == $overwrite_custom || !is_readable($checkwebp) ){
					$old_main_image = preg_replace( '~.(png|jpg|jpeg|gif)$~', '.bak.$1', $checkwebp );
					if ( file_exists( $old_main_image ) ) {
						copy( $old_main_image, $checkwebp );
					}
				}

				$status = $this->execute_optimization_command( $checkwebp );
			}

			next3_update_post_meta( $id, 'next3_optimizer_original_filesize', $metadata['filesize'] ) ;
		}

		// Replace the filesize in the metadata.
		$metadata['filesize'] = filesize( $main_image );

		// Everything ran smoothly.
		if ( true !== boolval( $status ) ) {
			next3_update_post_meta( $id, 'next3_optimizer_is_optimized', 1 );

			// Update the attachment metadata.
			wp_update_attachment_metadata( $id, $metadata );
		}

		return true;
	}

    private function execute_optimization_command( $filepath, $compression_level = null ) {
		
		// Bail if the file doens't exists.
		if ( ! file_exists( $filepath ) ) {
			return true;
		}
		
		//enable read/write permission
		chmod($filepath, 0777);
        
        $settings_options = next3_options();
        $compression_level_option = ($settings_options['optimization']['compression_level']) ?? 0;
        $backup_orginal = ($settings_options['optimization']['backup_orginal']) ?? 'no';
        $webp_enable = ($settings_options['optimization']['webp_enable']) ?? 'no';

        $compression_level = is_null( $compression_level ) ? intval( $compression_level_option ) : $compression_level;

		// Bail if compression level is set to None.
		if ( 0 == $compression_level ) {
			return true;
		}
        
		$backup_filepath = preg_replace( '~.(png|jpg|jpeg|gif)$~', '.bak.$1', $filepath );

		if (
			$backup_orginal == 'yes' &&
			! file_exists( $backup_filepath )
		) {
			copy( $filepath, $backup_filepath );
		}
        
		$status = $this->optimize_image(
			$filepath,
			$compression_level
		);
        
		return $status;
	}


    public function optimize_image( $filepath, $level ) {
		// Get image type.
		$type = exif_imagetype( $filepath );

		//$output_filepath = preg_replace( '~\.bak.(png|jpg|jpeg|gif)$~', '.$1', $filepath );
		$output_filepath = $filepath;
        
		$ext = strtolower(pathinfo($filepath, PATHINFO_EXTENSION));

		$quality = 100;
		$quality = ($level == 1) ? 25 : $quality;
		$quality = ($level == 2) ? 60 : $quality;
		$quality = ($level == 3) ? 85 : $quality;

		if (in_array($ext, ["png"])){
			$quality = ($level == 1) ? 9 : $quality;
			$quality = ($level == 2) ? 6 : $quality;
			$quality = ($level == 3) ? 3 : $quality;
		}
		$quality = apply_filters( 'next3_compress_quality', $quality );
		
		if (!in_array($ext, [ "jpg", "jpeg", "png", "webp"])){
			return self::compressImage($filepath, $output_filepath, $quality);  
		}

		switch ( $type ) {
			case IMAGETYPE_GIF:
				$placeholder = 'gifsicle %s --careful %s -o %s 2>&1';
				break;

			case IMAGETYPE_JPEG:
				//$placeholder = 'jpegoptim %s --stdout %s > %s';
				$placeholder = 'jpegoptim %1$s %3$s 2>&1';
				break;

			case IMAGETYPE_PNG:
				if ( filesize( $filepath ) > self::PNGS_SIZE_LIMIT ) {
					return true;
				}
				//$placeholder = 'pngquant %s %s --output %s';
				$placeholder = 'optipng %s %s -out=%s 2>&1';
				break;

			default:
				return true;
		}
        
		// Optimize the image.
		if( function_exists('exec')){
			exec(
				sprintf(
					$placeholder, // The command.
					$this->compression_level_map[ $type ][ $level ], // The compression level.
					$filepath, // Image path.
					$output_filepath // New Image path.
				),
				$output,
				$status
			);
		}
       
		if( !function_exists('exec') || true === boolval( $status )){
			$status = self::compressImage($filepath, $output_filepath, $quality); 
		}
		return $status;
	}

	public static function compressImage($image, $output_filepath, $outputQuality = 100)
    {
        $extf = strtolower(pathinfo($image, PATHINFO_EXTENSION));
		$valid = ["bmp", "jpg", "jpeg", "png", "webp"];
      
		if (!in_array($extf, $valid) ) { 
			return true; 
		}
		if (in_array($extf, ['jpg', 'jpeg']) ) { $extf = "jpeg"; }
		
		$fnf = "imagecreatefrom$extf";
      	$fnt = "image$extf";

		$isAlpha = false;
		$info = getimagesize($image);

		if( function_exists($fnf) && function_exists($fnt)){
			
			// new code
			if ($info['mime'] == 'image/jpeg'){
				$img = imagecreatefromjpeg($image);
			} else if ($isAlpha = $info['mime'] == 'image/gif') {
				$img = imagecreatefromgif($image);
			} else if ($isAlpha = $info['mime'] == 'image/png') {
				$img = imagecreatefrompng($image);
			} else {
				$img = @$fnf($image);
			}

			if( $img ){
				if ($isAlpha) {
					imagepalettetotruecolor($img);
					imagealphablending($img, true);
					imagesavealpha($img, true);
				} else{
					imagepalettetotruecolor($img);
				}
				
				if ($outputQuality !== 100) {
					$compressedImage = $fnt($img, $output_filepath, $outputQuality);
				} else { 
					$compressedImage = $fnt($img, $output_filepath);
				}

				imagedestroy($img);

				if ($compressedImage) {
					return "0";
				}else{
					return true;
				}
			}
		}
		return true;
    }
    public function maybe_update_total_unoptimized_images( $data ) {
		if ( next3_check_options( $this->options_map['status'] ) ) {
			return $data;
		}
		next3_update_option(
			$this->non_optimized,
			next3_get_option( $this->non_optimized, 0 ) + 1
		);
		return $data;
	}

    public function custom_attachment_compression_level( $attachment_id ) {
		
		// check settings
		$settings_options = next3_options();
		$copy_file = ($settings_options['storage']['copy_file']) ?? 'no';
		$remove_local = isset($settings_options['storage']['remove_local']) ? true : false;
		$webp_enable = ($settings_options['optimization']['webp_enable']) ?? 'no';

        if ( ! isset( $_REQUEST['next3_compression_level'] ) ) {
			return $attachment_id;
		} 
        // Get attachment's filepath.
		$filepath = next3_get_attached_file( $attachment_id, true);

        if ( ! file_exists( $filepath ) ) {
			return $attachment_id;
		}
		next3_update_post_meta( $attachment_id, 'next3_optimizer_compression_level', $_REQUEST['next3_compression_level'] ); // phpcs:ignore
        
		$backup_filepath = preg_replace( '~.(png|jpg|jpeg|gif)$~', '.bak.$1', $filepath );

		if ( file_exists( $backup_filepath ) ) {
			copy( $backup_filepath, $filepath );
		}

		if ( 0 !== intval( $_REQUEST['next3_compression_level'] ) ) {
			$this->execute_optimization_command( $filepath, intval( $_REQUEST['next3_compression_level'] ) );
		}

		$status_data = next3_service_status();
		$offload_status = ($status_data['offload']) ?? false;
		if( !$offload_status ){
			return $attachment_id;
		}

		// offload settings
		if( next3_get_post_meta($attachment_id, '_next3_attached_url') === false){
            if( $copy_file == 'yes'){
				next3_core()->action_ins->wpmedia_to_aws('', $attachment_id, $remove_local);
			}
		}

		return $attachment_id;
	}
    public function custom_attachment_compression_level_field( $form_fields, $post ) {
		$field_value = next3_get_post_meta( $post->ID, 'next3_optimizer_compression_level');
		if ( ! is_numeric( $field_value ) ) {
			$settings_options = next3_options();
            $field_value = ($settings_options['optimization']['compression_level']) ?? 0;
		}
		$html = '<select name="next3_compression_level">';
		$options = apply_filters('next3/selected/compression/level', next3_allowed_compression_level());
		foreach ( $options as $key => $value ) {
			$html .= '<option' . selected( $field_value, $key, false ) . ' value="' . $key . '">' . $value . '</option>';
		}
		$html .= '</select>';
		$form_fields['compression_level'] = array(
			'value' => $field_value ? intval( $field_value ) : '',
			'label' => __( 'Compression Level', 'next3-offload' ),
			'input' => 'html',
			'html'  => $html,
		);
		return $form_fields;
	}

    public function get_human_readable_size( $filepath ) {
		$size = filesize( $filepath );
		$units = array( 'B', 'kB', 'MB' );
		$step = 1024;
		$i = 0;

		while ( ( $size / $step ) > 0.9 ) {
			$size = $size / $step;
			$i++;
		}
		return round( $size, 2 ) . $units[ $i ];
	}

    public static function check_for_unoptimized_images( $type ) {

		$meta = array(
			'image' => array(
				'next3_optimizer_is_optimized',
				'next3_optimizer_optimization_failed',
			),
			'webp'  => array(
				'next3_optimizer_is_converted_to_webp',
				'next3_optimizer_webp_conversion_failed',
			),
		);

		$images = get_posts(
			array(
				'post_type'      => 'attachment',
				'post_mime_type' => 'image',
				'posts_per_page' => -1,
				'fields'         => 'ids',
				'meta_query'     => array(
					array(
						'key'     => $meta[ $type ][0],
						'compare' => 'NOT EXISTS',
					),
					array(
						'key'     => $meta[ $type ][1],
						'compare' => 'NOT EXISTS',
					),
				),
			)
		);

		return count( $images );
	}

	public function restore_originals() {
		$basedir = self::get_uploads_dir();
		$result = true;
		if( function_exists('exec')){
			exec( "find $basedir -regextype posix-extended -type f -regex '.*bak.(png|jpg|jpeg|gif)$' -exec rename '.bak' '' {} \;", $output, $result );
		}
		if ( true !== boolval( $result ) ) {
			$this->reset_images_filesize_meta();
			$this->reset_image_optimization_status();
		}

		return $result;
	}
	public function reset_images_filesize_meta() {
		$images = get_posts(
			array(
				'post_type'      => 'attachment',
				'post_mime_type' => 'image',
				'posts_per_page' => -1,
				'fields'         => 'ids',
				'meta_query'     => array(
					array(
						'key'     => 'next3_optimizer_original_filesize',
						'compare' => 'EXISTS',
					),
				),
			)
		);

		if ( empty( $images ) ) {
			return;
		}

		foreach( $images as $image_id ) {
			$metadata = next3_wp_get_attachment_metadata( $image_id, true);
			$metadata['filesize'] = next3_get_post_meta( $image_id, 'next3_optimizer_original_filesize');
			wp_update_attachment_metadata( $image_id, $metadata );
		}
	}

	public function reset_image_optimization_status() {
		global $wpdb;
		$wpdb->query(
			"
				DELETE FROM $wpdb->postmeta
				WHERE `meta_key` = '" . $this->batch_skipped . "'
				OR `meta_key` = '" . $this->process_map['failed'] . "'
				OR `meta_key` = 'next3_optimizer_original_filesize'
			"
		);
	}

	public static function get_uploads_dir() {
		$upload_dir = wp_upload_dir();
		$base_dir = $upload_dir['basedir'];
		if ( defined( 'UPLOADS' ) ) {
			$base_dir = ABSPATH . UPLOADS;
		}
		return $base_dir;
	}
    public static function instance(){
		if (!self::$instance){
            self::$instance = new self();
        }
        return self::$instance;
	}
}