<?php
namespace Imagify\Optimization\Process;

use Imagify\Optimization\File;
use WP_Error;

/**
 * Optimization class for the attachments in the WP library.
 * This class constructor accepts:
 * - A post ID (int).
 * - A \WP_Post object.
 * - A \Imagify\Media\MediaInterface object.
 * - A \Imagify\Media\DataInterface object.
 *
 * @since  1.9
 * @author Grégory Viguier
 */
class WP extends AbstractProcess {

	/** ----------------------------------------------------------------------------------------- */
	/** MISSING THUMBNAILS ====================================================================== */
	/** ----------------------------------------------------------------------------------------- */

	/**
	 * Get the sizes for this media that have not get through optimization.
	 * No sizes are returned if the file is not optimized, has no backup, or is not an image.
	 * The 'full' size os never returned.
	 *
	 * @since  1.9
	 * @access public
	 * @author Grégory Viguier
	 *
	 * @return array|WP_Error {
	 *     A WP_Error object on failure.
	 *     An array of data for the thumbnail sizes on success.
	 *     Size names are used as array keys.
	 *
	 *     @type int    $width  The image width.
	 *     @type int    $height The image height.
	 *     @type bool   $crop   True to crop, false to resize.
	 *     @type string $name   The size name.
	 *     @type string $file   The name the thumbnail "should" have.
	 * }
	 */
	public function get_missing_sizes() {
		// The media must have been optimized once and have a backup.
		if ( ! $this->is_valid() ) {
			return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
		}

		$media = $this->get_media();

		if ( ! $media ) {
			return new WP_Error( 'no_media', __( 'No media found.', 'imagify' ) );
		}

		if ( ! $media->is_supported() ) {
			return new WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
		}

		$data = $this->get_data();

		if ( ! $data->is_optimized() ) {
			return new WP_Error( 'media_not_optimized', __( 'This media is not optimized yet.', 'imagify' ) );
		}

		if ( ! $media->has_backup() ) {
			return new WP_Error( 'no_backup', __( 'This file has no backup file.', 'imagify' ) );
		}

		if ( ! $media->is_image() ) {
			return new WP_Error( 'media_not_an_image', __( 'This media is not an image.', 'imagify' ) );
		}

		// Compare registered sizes and optimized sizes.
		$context_sizes   = $media->get_context_instance()->get_thumbnail_sizes();
		$optimized_sizes = $data->get_optimization_data();
		$missing_sizes   = array_diff_key( $context_sizes, $optimized_sizes['sizes'] );

		if ( ! $missing_sizes ) {
			// We have everything we need.
			return [];
		}

		$media_sizes = $media->get_media_files();
		$full_size   = $media_sizes['full'];

		if ( ! $full_size['path'] || ! $full_size['width'] || ! $full_size['height'] ) {
			return [];
		}

		$file_name = $this->filesystem->path_info( $full_size['path'] );
		$file_name = $file_name['file_base'] . '-{%suffix%}.' . $file_name['extension'];

		// Test if the missing sizes are needed.
		foreach ( $missing_sizes as $size_name => $size_data ) {
			if ( $full_size['width'] === $size_data['width'] && $full_size['height'] === $size_data['height'] ) {
				// Same dimensions as the full size.
				unset( $missing_sizes[ $size_name ] );
				continue;
			}

			if ( ! empty( $media_sizes[ $size_name ]['disabled'] ) ) {
				// This size must not be optimized.
				unset( $missing_sizes[ $size_name ] );
				continue;
			}

			$resize_result = image_resize_dimensions( $full_size['width'], $full_size['height'], $size_data['width'], $size_data['height'], $size_data['crop'] );

			if ( ! $resize_result ) {
				// This thumbnail is not needed, it is smaller than this size.
				unset( $missing_sizes[ $size_name ] );
				continue;
			}

			// Provide what should be the file name.
			list( , , , , $new_width, $new_height ) = $resize_result;
			$missing_sizes[ $size_name ]['file']    = str_replace( '{%suffix%}', "{$new_width}x{$new_height}", $file_name );
		}

		return $missing_sizes;
	}

	/**
	 * Optimize missing thumbnail sizes.
	 *
	 * @since  1.9
	 * @access public
	 * @author Grégory Viguier
	 *
	 * @return bool|WP_Error True if successfully launched. A \WP_Error instance on failure.
	 */
	public function optimize_missing_thumbnails() {
		if ( ! $this->is_valid() ) {
			return new WP_Error( 'invalid_media', __( 'This media is not valid.', 'imagify' ) );
		}

		$media = $this->get_media();

		if ( ! $media ) {
			return new WP_Error( 'no_media', __( 'No media found.', 'imagify' ) );
		}

		if ( ! $media->is_supported() ) {
			return new WP_Error( 'media_not_supported', __( 'This media is not supported.', 'imagify' ) );
		}

		$missing_sizes = $this->get_missing_sizes();

		if ( ! $missing_sizes ) {
			return new WP_Error( 'no_sizes', __( 'No thumbnails seem to be missing.', 'imagify' ) );
		}

		if ( is_wp_error( $missing_sizes ) ) {
			return $missing_sizes;
		}

		if ( $this->is_locked() ) {
			return new WP_Error( 'media_locked', __( 'This media is already being processed.', 'imagify' ) );
		}

		$this->lock();

		// Create the missing thumbnails.
		$sizes = $this->create_missing_thumbnails( $missing_sizes );

		if ( ! $sizes ) {
			$this->unlock();
			return new WP_Error( 'thumbnail_creation_failed', __( 'The thumbnails failed to be created.', 'imagify' ) );
		}

		$optimization_level = $this->get_data()->get_optimization_level();

		if ( false === $optimization_level ) {
			$this->unlock();
			return new \WP_Error( 'optimization_level_not_set', __( 'The optimization level of this media seems to have disappear from the database. You should restore this media and then launch a new optimization.', 'imagify' ) );
		}

		$args = [
			'hook_suffix' => 'optimize_missing_thumbnails',
			'locked'      => true,
		];

		// Optimize.
		return $this->optimize_sizes( array_keys( $sizes ), $optimization_level, $args );
	}

	/**
	 * Create all missing thumbnails if they don't exist and update the attachment metadata.
	 *
	 * @since  1.9
	 * @access protected
	 * @see    $this->get_missing_sizes()
	 * @author Grégory Viguier
	 *
	 * @param  array $missing_sizes array {
	 *     An array of data for the thumbnail sizes on success.
	 *     Size names are used as array keys.
	 *
	 *     @type int    $width  The image width.
	 *     @type int    $height The image height.
	 *     @type bool   $crop   True to crop, false to resize.
	 *     @type string $name   The size name.
	 *     @type string $file   The name the thumbnail "should" have.
	 * }
	 * @return array {
	 *     An array of thumbnail data (those without errors):
	 *
	 *     @type string $file      File name.
	 *     @type int    $width     The image width.
	 *     @type int    $height    The image height.
	 *     @type string $mime-type The mime type.
	 * }
	 */
	protected function create_missing_thumbnails( $missing_sizes ) {
		if ( ! $missing_sizes ) {
			return [];
		}

		$media = $this->get_media();

		if ( ! $media ) {
			return [];
		}

		$media_id          = $media->get_id();
		$metadata          = wp_get_attachment_metadata( $media_id );
		$metadata['sizes'] = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : [];

		$destination_dir = $this->filesystem->dir_path( $media->get_raw_fullsize_path() );
		$backup_file     = new File( $media->get_backup_path() );
		$without_errors  = [];
		$has_new_data    = false;

		// Create the missing thumbnails.
		foreach ( $missing_sizes as $size_name => $thumbnail_data ) {
			// The path to the destination file.
			$thumbnail_data['path'] = $destination_dir . $thumbnail_data['file'];

			if ( ! $this->filesystem->exists( $thumbnail_data['path'] ) ) {
				$result = $backup_file->create_thumbnail( $thumbnail_data );

				if ( is_array( $result ) ) {
					// New file.
					$metadata['sizes'][ $size_name ] = $result;
					$has_new_data                    = true;
				}
			} else {
				$result = true;
			}

			if ( ! empty( $metadata['sizes'][ $size_name ] ) && ! is_wp_error( $result ) ) { // phpstan-ignore-line
				// Not an error.
				$without_errors[ $size_name ] = $metadata['sizes'][ $size_name ];
			}
		}

		// Save the new data into the attachment metadata.
		if ( $has_new_data ) {
			/**
			 * Here we don't use wp_update_attachment_metadata() to prevent triggering unwanted hooks.
			 */
			update_post_meta( $media_id, '_wp_attachment_metadata', $metadata );
		}

		return $without_errors;
	}
}
