<?php
defined( 'WPINC' ) OR exit;

// NOTE: Child thumbers are included at bottom of this file
include_once DG_PATH . 'inc/thumbers/class-abstract-thumber.php';

/**
 * Thumber wraps the functionality required to
 * generate thumbnails for arbitrary documents.
 *
 * @author drossiter
 */
class DG_Thumber extends DG_AbstractThumber {

	/**
	 * @var DG_Thumber The singleton instance.
	 */
	private static $instance;

	/**
	 * @return DG_Thumber The singleton instance.
	 */
	public static function getInstance() {
		return isset( self::$instance ) ? self::$instance : ( self::$instance = new DG_Thumber() );
	}

	/**
	 * Wraps generation of thumbnails for various attachment filetypes.
	 *
	 * @param int $ID Document ID
	 * @param int $pg Page number to get thumb from.
	 * @param bool $generate_if_missing Whether to attempt generating the thumbnail if missing.
	 * @param bool|null &$is_default Whether the returned URL points to a default icon.
	 *
	 * @return string URL to the thumbnail.
	 */
	public function getThumbnail( $ID, $pg = 1, $generate_if_missing = true, &$is_default = null ) {
		$options = self::getOptions();
		$dimensions = $options['width'] . 'x' . $options['height'];

		// if we haven't saved a thumb, generate one
		if ( ! DG_Thumb::thumbExists( $ID, $dimensions, false ) ) {
			// short-circuit generation if not required
			if ( ! $generate_if_missing ) {
				return null;
			}

			foreach ( self::getThumbers() as $thumber ) {
				if ( $thumber->supportsAttachment( $ID ) ) {
					if ( DG_Logger::logEnabled() ) {
						$toLog = sprintf( __( 'Attempting to generate thumbnail for attachment #%d with (%s)',
								'document-gallery' ), $ID, get_class( $thumber ) );
						DG_Logger::writeLog( DG_LogLevel::Detail, $toLog );
					}

					if ( $generated = self::thumbnailGenerationHarness( $thumber, $ID, $pg ) ) {
						break;
					}
				}
			}
		}

		$thumb = isset( $generated ) ? $generated : DG_Thumb::getThumb( $ID, $dimensions );
		$is_thumb = is_a( $thumb, 'DG_Thumb' );
		$is_default = ! $is_thumb || ! $thumb->isSuccess();
		if ( ! $is_thumb ) {
			$thumb = self::setThumbnailFailed( $ID );
		}

		return $thumb->isSuccess() ? $thumb->getUrl() : DG_DefaultThumber::getInstance()->getThumbnail( $ID, $pg );
	}

	/**
	 * @return string[] The extensions supported by this thumber.
	 */
	protected function getThumberExtensions() {
		throw new BadFunctionCallException( 'Not implemented.' );
	}

	/**
	 * @return int An integer from 0 to 100. Higher priorities will be attempted before lower priority thumbers.
	 */
	public function getPriority() {
		throw new BadFunctionCallException( 'Not implemented.' );
	}

	/**
	 * Returns the default mapping of thumber slug to whether it is active or not.
	 *
	 * @param $skeleton bool When true, values that require computation will be
	 *        skipped. Useful when only structure of options is needed.
	 *
	 * @return bool[] The default thumbnail generation methods.
	 */
	public static function getDefaultThumbers( $skeleton = false ) {
		$gs_active = $imagick_active = null;
		if ( ! $skeleton ) {
			$gs_active         = (bool) DG_GhostscriptThumber::getGhostscriptExecutable();
			$imagick_active    = DG_ImagickThumber::isImagickAvailable();
		}

		return array(
			'av'         => true,
			'gs'         => $gs_active,
			'imagick'    => $imagick_active,
			'thumber-co' => false
		);
	}

	/**
	 * Sets the thumbnail for the given attachment ID.
	 *
	 * @param int $ID Document ID.
	 * @param string $path System path to thumbnail.
	 * @param string $generator Descriptor for generation method -- usually method name.
	 *
	 * @return DG_Thumb|bool Thumb on success, false on failure.
	 */
	public static function setThumbnail( $ID, $path, $generator = 'unknown' ) {
		return self::thumbnailGenerationHarness( $generator, $ID, $path );
	}

	/**
	 * Sets the thumbnail for the given attachment ID to a failed state.
	 *
	 * @param int $ID Document ID.
	 * @return DG_Thumb The failed thumbnail.
	 */
	public static function setThumbnailFailed( $ID ) {
		$options = self::getOptions();
		$thumb = new DG_Thumb();
		$thumb->setPostId( $ID );
		$thumb->setTimestamp( time() );
		$thumb->setDimensions( $options['width'] . 'x' . $options['height'] );
		$thumb->save();

		return $thumb;
	}

	/*==========================================================================
	 * GENERAL THUMBNAIL HELPER FUNCTIONS
	 *=========================================================================*/

	/**
	 * @return mixed[]|null Thumber options from DB or null if options not initialized.
	 */
	public static function getOptions( $blog = null ) {
		$options = DocumentGallery::getOptions( $blog );

		return $options['thumber'];
	}

	/**
	 * @return WP_Post[] Each attachment that has been processed.
	 */
	public static function getThumbed() {
		$thumbs = DG_Thumb::getThumbs();
		if ( ! count( $thumbs ) ) return array();

		$args = array(
			'post_type'     => 'attachment',
			'post_status'   => 'inherit',
			'post_per_page' => - 1,
			'post__in'      => array_keys( $thumbs )
		);

		return get_posts( $args );
	}

	/**
	 * @filter dg_thumbers Allows developers to filter the Thumbers used
	 * for specific filetypes. Index is the regex to match file extensions
	 * supported and the value is anything that can be accepted by call_user_func().
	 * The function must take two parameters, 1st is the int ID of the attachment
	 * to get a thumbnail for, 2nd is the page to take a thumbnail of
	 * (may not be relevant for some filetypes).
	 *
	 * @return DG_AbstractThumber[]
	 */
	private static function getThumbers() {
		static $thumbers = null;

		if ( is_null( $thumbers ) ) {
			// allow users to filter thumbers used
			$thumbers = apply_filters( 'dg_thumbers', array() );
			$thumbers = array_filter( $thumbers, array( __CLASS__, 'isThumber' ) );
			usort( $thumbers, array( 'DG_AbstractThumber', 'cmpThumberByPriority' ) );

			// log which thumbers are being used
			if ( DG_Logger::logEnabled() ) {
				if ( count( $thumbers ) > 0 ) {
					$names = array_map( 'get_class', $thumbers );
					$entry = __( 'Thumbnail Generators: ', 'document-gallery' ) . implode( ', ', $names );
				} else {
					$entry = __( 'No thumbnail generators enabled.', 'document-gallery' );
				}

				DG_Logger::writeLog( DG_LogLevel::Detail, $entry );
			}
		}

		return $thumbers;
	}

	/**
	 * @param $maybe_thumber mixed Variable to be tested.
	 * @return bool Whether given variable is a thumber.
	 */
	private static function isThumber( $maybe_thumber ) {
		$ret = is_a( $maybe_thumber, 'DG_AbstractThumber' );
		if ( !$ret ) {
			DG_Logger::writeLog(
				DG_LogLevel::Error,
				'Attempted to add non-DG_AbstractThumber in thumbnail generation: ' . print_r( $maybe_thumber, true ) );
		}

		return $ret;
	}

	/**
	 * Template that handles generating a thumbnail.
	 *
	 * If image has already been generated through other means, $pg may be set to the system path where the
	 * thumbnail is located. In this case, $generator will not be invoked, but *will* be kept for historical purposes.
	 *
	 * @param DG_AbstractThumber|string $generator Takes ID and pg and returns path to temp file or false.
	 * @param int $ID ID for the attachment that we need a thumbnail for.
	 * @param int|string $pg Page number of the attachment to get a thumbnail for or the system path to the image to be used.
	 *
	 * @return DG_Thumb|bool The generated thumbnail or false on failure.
	 */
	private static function thumbnailGenerationHarness( $generator, $ID, $pg = 1 ) {
		// handle system page in $pg variable
		if ( is_string( $pg ) && ! is_numeric( $pg ) ) {
			$temp_path = $pg;
		} elseif ( is_a( $generator, 'DG_AbstractThumber' ) ) {
			// delegate thumbnail generation to $generator
			if ( false === ( $temp_path = $generator->getThumbnail( $ID, $pg ) ) ) {
				return false;
			}

			// NOTE: get string representation to be stored with thumb in DB
			$generator = get_class( $generator );
		} else {
			DG_Logger::writeLog(
				DG_LogLevel::Error,
				'Attempted to call thumbnailGenerationHarness with invalid generator: ' . print_r( $generator, true ) );
			return false;
		}

		// get some useful stuff
		$doc_path = get_attached_file( $ID );
		$dirname  = dirname( $doc_path );
		$basename = basename( $doc_path );
		if ( false === ( $len = strrpos( $basename, '.' ) ) ) {
			$len = strlen( $basename );
		}
		$extless = substr( $basename, 0, $len );
		$ext     = self::getExt( $temp_path );

		$thumb_name = self::getUniqueThumbName( $dirname, $extless, $ext );
		$thumb_path = "$dirname/$thumb_name";

		// scale generated image down
		$img = wp_get_image_editor( $temp_path );

		if ( is_wp_error( $img ) ) {
			DG_Logger::writeLog(
				DG_LogLevel::Error,
				__( 'Failed to get image editor: ', 'document-gallery' ) . $img->get_error_message() );

			return false;
		}

		$options = self::getOptions();
		$img->resize( $options['width'], $options['height'], false );
		$err = $img->save( $thumb_path );

		if ( is_wp_error( $err ) ) {
			DG_Logger::writeLog(
				DG_LogLevel::Error,
				__( 'Failed to save image: ', 'document-gallery' ) .
				$err->get_error_message() );

			return false;
		}

		// do some cleanup
		@unlink( $temp_path );

		// save new thumb
		DG_Logger::writeLog( DG_LogLevel::Detail, 'Creating thumb object.' );
		$upload = wp_upload_dir();
		$thumb  = new DG_Thumb();
		$thumb->setPostId( $ID );
		$thumb->setDimensions( $options['width'] . 'x' . $options['height'] );
		$thumb->setTimestamp( time() );
		$thumb->setRelativePath( substr( $thumb_path, strlen( $upload['basedir'] ) + 1 ) );
		$thumb->setGenerator( $generator );
		$thumb->save();

		return $thumb;
	}

	/**
	 * Constructs name for file's thumbnail, ensuring that it does not conflict
	 * with any existing file.
	 *
	 * @param string $dirname Directory where the document is located.
	 * @param string $extless Base name, less the extension.
	 * @param string $ext The extension of the image to be created.
	 *
	 * @return string          Name unique within the directory given, derived from the basename given.
	 */
	private static function getUniqueThumbName( $dirname, $extless, $ext = 'png' ) {
		return wp_unique_filename( $dirname, str_replace( '.', '-', $extless ) . '-thumb.' . $ext );
	}
}

// include all internal DG thumbers
foreach ( glob( DG_PATH . 'inc/thumbers/*.php' ) as $path ) {
	/** @noinspection PhpIncludeInspection */
	include_once $path;
}