<?php
// Copyright 2014-2016 RealFaviconGenerator

define( 'RFG_PACKAGE_URL', 'package_url' );
define( 'RFG_COMPRESSION', 'compression' );
define( 'RFG_HTML_CODE', 'html_code' );
define( 'RFG_FILES_URLS', 'files_url' );
define( 'RFG_FILES_IN_ROOT', 'files_in_root' );
define( 'RFG_FILES_PATH', 'files_path' );
define( 'RFG_PREVIEW_PICTURE_URL', 'preview_picture_url' );
define( 'RFG_CUSTOM_PARAMETER', 'custom_parameter' );
define( 'RFG_VERSION', 'version' );
define( 'RFG_NON_INTERACTIVE_API_REQUEST', 'non_interactive_api_request' );

define( 'RFG_FAVICON_PRODUCTION_PACKAGE_PATH', 'favicon_production_package_path' );
define( 'RFG_FAVICON_COMPRESSED_PACKAGE_PATH', 'favicon_compressed_package_path' );
define( 'RFG_FAVICON_UNCOMPRESSED_PACKAGE_PATH', 'favicon_uncompressed_package_path' );
define( 'RFG_PREVIEW_PATH', 'preview_path' );

class Favicon_By_RealFaviconGenerator_Api_Response {

	private $params = array();

	public function __construct( $json ) {
		if ( $json == null ) {
			throw new InvalidArgumentException( 'No response from RealFaviconGenerator' );
		}

		$response = json_decode( $json, true );

		if ( $response == null ) {
			throw new InvalidArgumentException( 'JSON could not be parsed' );
		}

		$response = $this->getParam( $response, 'favicon_generation_result' );
		$result   = $this->getParam( $response, 'result' );
		$status   = $this->getParam( $result, 'status' );

		if ( $status != 'success' ) {
			$msg = $this->getParam( $result, 'error_message', false );
			$msg = $msg != null ? $msg : 'An error occured';
			throw new InvalidArgumentException( $msg );
		}

		$favicon                         = $this->getParam( $response, 'favicon' );
		$this->params[ RFG_PACKAGE_URL ] = $this->getParam( $favicon, 'package_url' );
		$this->params[ RFG_COMPRESSION ] = $this->getParam( $favicon, 'compression' ) == 'true';
		$this->params[ RFG_HTML_CODE ]   = $this->getParam( $favicon, 'html_code' );
		$this->params[ RFG_FILES_URLS ]  = $this->getParam( $favicon, 'files_urls' );

		$filesLoc                          = $this->getParam( $response, 'files_location' );
		$this->params[ RFG_FILES_IN_ROOT ] = $this->getParam( $filesLoc, 'type' ) == 'root';
		$this->params[ RFG_FILES_PATH ]    = $this->params[ RFG_FILES_IN_ROOT ] ? '/' : $this->getParam( $filesLoc, 'path' );

		$this->params[ RFG_PREVIEW_PICTURE_URL ] = $this->getParam( $response, 'preview_picture_url', false );

		$this->params[ RFG_CUSTOM_PARAMETER ] = $this->getParam( $response, 'custom_parameter', false );
		$this->params[ RFG_VERSION ]          = $this->getParam( $response, 'version', false );

		$this->params[ RFG_NON_INTERACTIVE_API_REQUEST ] = $this->getParam( $response, 'non_interactive_request', false );
	}

	/**
	 * For example: <code>"http://realfavicongenerator.net/files/1234f5d2s34f3ds2/package.zip"</code>
	 */
	public function getPackageUrl() {
		return $this->params[ RFG_PACKAGE_URL ];
	}

	/**
	 * For example: <code>array( "http://realfavicongenerator.net/files/1234f5d2s34f3ds2/apple-touch-icon.png", ... )</code>
	 */
	public function getFilesURLs() {
		return $this->params[ RFG_FILES_URLS ];
	}

	/**
	 * For example: <code>"&lt;link ..."</code>
	 */
	public function getHtmlCode() {
		return $this->params[ RFG_HTML_CODE ];
	}

	/**
	 * <code>true</code> if the user chose to compress the pictures, <code>false</code> otherwise.
	 */
	public function isCompressed() {
		return $this->params[ RFG_COMPRESSION ];
	}

	/**
	 * <code>true</code> if the favicon files are to be stored in the root directory of the target web site, <code>false</code> otherwise.
	 */
	public function isFilesInRoot() {
		return $this->params[ RFG_FILES_IN_ROOT ];
	}

	/**
	 * Indicate where the favicon files should be stored in the target web site. For example: <code>"/"</code>, <code>"/path/to/icons"</code>.
	 */
	public function getFilesLocation() {
		return $this->params[ RFG_FILES_PATH ];
	}

	/**
	 * For example: <code>"http://realfavicongenerator.net/files/1234f5d2s34f3ds2/preview.png"</code>
	 */
	public function getPreviewUrl() {
		return $this->params[ RFG_PREVIEW_PICTURE_URL ];
	}

	/**
	 * Return the customer parameter, as it was transmitted during the invocation of the API.
	 */
	public function getCustomParameter() {
		return $this->params[ RFG_CUSTOM_PARAMETER ];
	}

	/**
	 * Return version of RealFaviconGenerator used to generate the favicon. Save this value to later check for updates.
	 */
	public function getVersion() {
		return $this->params[ RFG_VERSION ];
	}

	/**
	 * Return the non-interactive API request that matches the current interactive request.
	 */
	public function getNonInteractiveAPIRequest() {
		return $this->params[ RFG_NON_INTERACTIVE_API_REQUEST ];
	}

	private function getParam( $params, $paramName, $throwIfNotFound = true ) {
		if ( isset( $params[ $paramName ] ) ) {
			return $params[ $paramName ];
		} elseif ( $throwIfNotFound ) {
			throw new InvalidArgumentException( 'Cannot find parameter ' . $paramName );
		}
	}

	/**
	 * Download and extract the files referenced by the response sent back by RealFaviconGenerator.
	 *
	 * Warning: as this method does HTTP accesses, calling it can take a few seconds. Better invoke it
	 * in an Ajax call, to not slow down the user experience.
	 */
	public function downloadAndUnpack( $outputDirectory = null ) {
		global $wp_filesystem;
		if ( $outputDirectory == null ) {
			$outputDirectory = get_temp_dir();
		}

		$outputDirectory = $this->append_directory_separator( $outputDirectory );

		if ( $this->getPackageUrl() != null ) {
			$extractedPath = $outputDirectory . 'favicon_package';
			if ( ! file_exists( $extractedPath ) ) {
				if ( mkdir( $extractedPath, 0755, true ) !== true ) {
					throw new InvalidArgumentException(
						sprintf( __( 'Cannot create directory %s to store the favicon package content', FBRFG_PLUGIN_SLUG ), $extractedPath )
					);
				}
			}

			try {
				$this->downloadZipFile( $outputDirectory, $extractedPath, $this->getPackageUrl() );
			} catch ( Exception $e ) {
				// Zip file download failed, try getting files directly
				$this->downloadFilesDirectly( $extractedPath, array() );
			}
		}

		if ( $this->getPreviewUrl() != null ) {
			$previewPath = $outputDirectory . 'favicon_preview.png';
			$this->downloadFile( $previewPath, $this->getPreviewUrl() );
			$this->params[ RFG_PREVIEW_PATH ] = $previewPath;
		}
	}

	public function downloadFilesDirectly( $outputDirectory, $filesURLs ) {
		foreach ( $this->getFilesURLs() as $fileURL ) {
			$parts    = parse_url( $fileURL );
			$filePath = $outputDirectory . DIRECTORY_SEPARATOR .
				basename( $parts['path'] );
			$this->downloadFile( $filePath, $fileURL );
		}

		$this->params[ RFG_FAVICON_PRODUCTION_PACKAGE_PATH ] = $outputDirectory;
		if ( $this->isCompressed() ) {
			$this->params[ RFG_FAVICON_COMPRESSED_PACKAGE_PATH ]   = $extractedPath;
			$this->params[ RFG_FAVICON_UNCOMPRESSED_PACKAGE_PATH ] = null;
		} else {
			$this->params[ RFG_FAVICON_COMPRESSED_PACKAGE_PATH ]   = null;
			$this->params[ RFG_FAVICON_UNCOMPRESSED_PACKAGE_PATH ] = $extractedPath;
		}
	}

	public function downloadZipFile( $packageDirectory, $extractedPath, $packageUrl ) {
		$packagePath = $packageDirectory . 'favicon_package.zip';
		$this->downloadFile( $packagePath, $packageUrl );

		$ret    = WP_Filesystem();
		$result = unzip_file( $packagePath, $extractedPath );
		if ( $result !== true ) {
			$explanation = ( is_wp_error( $result ) )
				? $result->get_error_message()
				: __( 'Unknown reason', Favicon_By_RealFaviconGenerator_Common::PLUGIN_SLUG );
			if ( get_class( $wp_filesystem ) != 'WP_Filesystem_Direct' ) {
				$explanation .= ' ' . __(
					'Apparently WordPress has no direct access to the file system (it uses another mean such as FTP). ' .
					'This may be the root cause of this issue.',
					Favicon_By_RealFaviconGenerator_Common::PLUGIN_SLUG
				);
			}
			throw new InvalidArgumentException(
				sprintf(
					__( 'Error while unziping the favicon package %1$s to directory %2$s', Favicon_By_RealFaviconGenerator_Common::PLUGIN_SLUG ),
					$packagePath,
					$extractedPath
				) . ': ' . $explanation
			);
		}

		if ( $this->isCompressed() ) {
			// As of today, when the user chooses the compress the picture,
			// the package is provided in two flavors, in two distinct directories.
			// Later, only the compressed version will be provided. Thus, the following code
			// handles both scenarios.
			if ( is_dir( $extractedPath . DIRECTORY_SEPARATOR . 'compressed' ) ) {
				$this->params[ RFG_FAVICON_COMPRESSED_PACKAGE_PATH ] = $extractedPath .
					DIRECTORY_SEPARATOR . 'compressed';
			} else {
				$this->params[ RFG_FAVICON_COMPRESSED_PACKAGE_PATH ] = $extractedPath;
			}

			if ( is_dir( $extractedPath . DIRECTORY_SEPARATOR . 'uncompressed' ) ) {
				$this->params[ RFG_FAVICON_UNCOMPRESSED_PACKAGE_PATH ] = $extractedPath .
					DIRECTORY_SEPARATOR . 'uncompressed';
			} else {
				$this->params[ RFG_FAVICON_UNCOMPRESSED_PACKAGE_PATH ] = $extractedPath;
			}

			$this->params[ RFG_FAVICON_PRODUCTION_PACKAGE_PATH ] = $this->params[ RFG_FAVICON_COMPRESSED_PACKAGE_PATH ];
		} else {
			$this->params[ RFG_FAVICON_COMPRESSED_PACKAGE_PATH ]   = null;
			$this->params[ RFG_FAVICON_UNCOMPRESSED_PACKAGE_PATH ] = $extractedPath;
			$this->params[ RFG_FAVICON_PRODUCTION_PACKAGE_PATH ]   = $this->params[ RFG_FAVICON_UNCOMPRESSED_PACKAGE_PATH ];
		}
	}

	/**
	 * Directory where the compressed files are stored. Method <code>downloadAndUnpack</code> must be called first in order to populate this field.
	 * Can be <code>NULL</code>.
	 */
	public function getCompressedPackagePath() {
		return $this->params[ RFG_FAVICON_COMPRESSED_PACKAGE_PATH ];
	}

	/**
	 * Directory where the uncompressed files are stored. Method <code>downloadAndUnpack</code> must be called first in order to populate this field.
	 * Can be <code>NULL</code>.
	 */
	public function getUncompressedPackagePath() {
		return $this->params[ RFG_FAVICON_UNCOMPRESSED_PACKAGE_PATH ];
	}

	/**
	 * Directory where the production favicon files are stored.
	 * These are the files to deployed to the targeted web site. When the user asked for compression,
	 * this is the path to the compressed folder. Else, this is the path to the regular files folder.
	 * Method <code>downloadAndUnpack</code> must be called first in order to populate this field.
	 */
	public function getProductionPackagePath() {
		return $this->params[ RFG_FAVICON_PRODUCTION_PACKAGE_PATH ];
	}

	/**
	 * Path to the preview picture.
	 */
	public function getPreviewPath() {
		return $this->params[ RFG_PREVIEW_PATH ];
	}

	private function downloadFile( $localPath, $url ) {
		$resp = wp_remote_get(
			$url,
			array(
				'filename' => $localPath,
				'stream'   => true,
			)
		);
		if ( ( $resp == null ) || ( $resp == false ) || ( is_wp_error( $resp ) ) || ( $resp['response'] == null ) ||
			 ( $resp['response']['code'] == null ) || ( $resp['response']['code'] != 200 ) ) {
			$explanation = is_wp_error( $resp ) ? ( ': ' . $resp->get_error_message() ) : '';
			throw new InvalidArgumentException(
				sprintf( __( 'Cannot download file at %s', Favicon_By_RealFaviconGenerator_Common::PLUGIN_SLUG ), $url ) . $explanation
			);
		}
		if ( ( ! file_exists( $localPath ) ) || ( filesize( $localPath ) <= 0 ) ) {
			throw new InvalidArgumentException( __( 'Cannot store downloaded file locally', Favicon_By_RealFaviconGenerator_Common::PLUGIN_SLUG ) );
		}
	}

	public function append_directory_separator( $filename ) {
		return $filename .
			( ( $this->ends_with( $filename, DIRECTORY_SEPARATOR ) )
				? ''
				: DIRECTORY_SEPARATOR );
	}

	// See http://stackoverflow.com/questions/834303/startswith-and-endswith-functions-in-php#answer-10473026
	public function ends_with( $haystack, $needle ) {
		return $needle === '' ||
			( ( $temp = strlen( $haystack ) - strlen( $needle ) ) >= 0
				&& strpos( $haystack, $needle, $temp ) !== false );
	}

}
