<?php

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * MT engine implementation for eTranslation.
 */
class ETM_eTranslation_Machine_Translator extends ETM_Machine_Translator {

	/**
	 * The interval in microseconds (100ms) used for database queries.
	 *
	 * @var int
	 */
	private $db_query_interval = 100 * 1000;

	/**
	 * The instance of the eTranslation_Service class.
	 *
	 * @var eTranslation_Service
	 */
	private $etranslation_service;

	/**
	 * The instance of the eTranslation_Query class.
	 *
	 * @var eTranslation_Query
	 */
	public $etranslation_query;

	/**
	 * The opening tag for XML segments.
	 *
	 * @var string
	 */
	private $segment_opening = '<segment>';

	/**
	 * The closing tag for XML segments.
	 *
	 * @var string
	 */
	private $segment_closing = '</segment>';

	/**
	 * The delimiter used to separate XML segments.
	 *
	 * @var string
	 */
	private $delimiter;

	/**
	 * ETranslation_Helper constructor.
	 *
	 * @param array $settings The settings array.
	 */
	public function __construct( $settings ) {
		parent::__construct( $settings );

		$this->etranslation_service = new eTranslation_Service( $this->get_app_name(), $this->get_password(), $settings );
		$this->etranslation_query   = new eTranslation_Query();
		$this->delimiter            = $this->segment_closing . $this->segment_opening;
	}

	/**
	 * Check the document for translation.
	 *
	 * @param int   $id              The document ID.
	 * @param float $start_timestamp The starting timestamp.
	 *
	 * @return string The translated document.
	 */
	private function check_document( $id, $start_timestamp ): string {
		$last_checked = microtime( true );
		$timeout      = $this->settings['etm_advanced_settings']['etranslation_wait_timeout'] ?? DEFAULT_ETRANSLATION_TIMEOUT;
		$editor_mode  = isset( $_GET['etm-edit-translation'] );

		while ( ( $last_checked - $start_timestamp ) < $timeout || $timeout <= 0 || $editor_mode ) {
			$translation = $this->etranslation_query->search_saved_translation( $id );
			if ( $translation != null ) {
				return $translation;
			}
			usleep( $this->db_query_interval );
			$last_checked = microtime( true );
		}

		$this->on_translation_timeout( $id, $timeout );
		return '';
	}

	/**
	 * Handle translation timeout.
	 *
	 * @param int   $id      The translation ID.
	 * @param float $timeout The timeout in seconds.
	 * @return void
	 */
	private function on_translation_timeout( $id, $timeout ) {
		error_log( "Could not find translation in DB [ID=$id, timeout=" . $timeout . 's]. Consider increasing eTranslation timeout in Advanced settings!' );
		$this->etranslation_query->update_translation_status( $id, 'TIMEOUT' );
	}

	/**
	 * Translate the document using eTranslation service.
	 *
	 * @param string $source_language_code The source language code.
	 * @param string $target_language_code The target language code.
	 * @param array  $strings_array        The array of strings to translate.
	 * @param array  $original_strings     The array of original strings.
	 * @param float  $start_timestamp      The starting timestamp.
	 * @param bool   $is_gettext           Flag indicating if gettext format is used.
	 *
	 * @return array The translated strings array.
	 */
	public function translate_document( $source_language_code, $target_language_code, $strings_array, $original_strings, $start_timestamp, $is_gettext ): array {
		$id_prefix = $is_gettext ? 'g-' : 'r-';
		$id        = $id_prefix . uniqid();

		$source_language = $this->machine_translation_codes[ $source_language_code ];
		$target_language = $this->machine_translation_codes[ $target_language_code ];
		$domain          = $this->settings['translation-languages-domain-parameter'][ $target_language_code ];
		if ( ! $domain || '-' === $domain ) {
			$domain = 'GEN';
		}

		$xml_prefix              = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?><root>' . $this->segment_opening;
		$xml_suffix              = $this->segment_closing . '</root>';
		$content                 = $xml_prefix . implode( $this->delimiter, $strings_array ) . $xml_suffix;
		$original_string_content = implode( $this->translation_delimiter, $original_strings );

		$temp_entry = $this->etranslation_query->insert_translation_entry( $id, strtolower( $source_language_code ), strtolower( $target_language_code ), $original_string_content );
		if ( $temp_entry ) {
			$response   = $this->etranslation_service->send_translate_document_request( $source_language, $target_language, $content, $domain, $id );
			$request_id = isset( $response['body'] ) && is_numeric( $response['body'] ) ? (int) $response['body'] : null;
			if ( $response['response'] != 200 || $request_id < 0 ) {
				return array();
			}

			$response = $this->check_document( $id, $start_timestamp );
			if ( empty( $response ) ) {
				return array();
			}
			$result            = $this->xml_document_to_string_array( $response );
			$original_count    = count( $strings_array );
			$translation_count = count( $result );
			if ( $translation_count != $original_count && ! ( $translation_count == $original_count + 1 && end( $result ) == '' ) ) {
				error_log( 'Original string list size differs from translation list size (' . count( $strings_array ) . ' != ' . count( $result ) . ") [ID=$id]. Please check that eTranslation serivce is working correctly or contact plugin's technical support." );
			}
			return $result;
		} else {
			return array();
		}
	}

	/**
	 * Convert XML document to an array of strings.
	 *
	 * @param string $response The XML response from eTranslation service.
	 *
	 * @return array The array of translated strings.
	 */
	public function xml_document_to_string_array( $response ) {
		// extract strings.
		$segment_tag_length = strlen( $this->segment_opening );
		$first_segment_pos  = strpos( $response, $this->segment_opening );
		$data_start_pos     = $first_segment_pos + $segment_tag_length;
		$data_end_pos       = strrpos( $response, $this->segment_closing );
		$segment_string     = substr( $response, $data_start_pos, $data_end_pos - $data_start_pos );

		$translations = explode( $this->delimiter, $segment_string );
		return $translations;
	}

	/**
	 * Returns an array with the API provided translations of the $new_strings array.
	 *
	 * @param array   $new_strings                   array with the strings that need translation. The keys are the node number in the DOM so we need to preserve them.
	 * @param string  $original_strings              untransformed version of $new_strings, matching 'original' column values in database. Needed to manually replace translations after eTranslation timeout.
	 * @param string  $target_language_code          language code of the language that we will be translating to. Not equal to the google language code.
	 * @param string  $source_language_code          language code of the language that we will be translating from. Not equal to the google language code.
	 * @param boolean $is_gettext                    whether given strings are of type gettext.
	 * @return array                                 array with the translation strings and the preserved keys or an empty array if something went wrong
	 */
	public function translate_array( $new_strings, $original_strings, $target_language_code, $source_language_code = null, $is_gettext = false ) {
		if ( $source_language_code == null ) {
			$source_language_code = $this->settings['default-language'];
		}
		if ( empty( $new_strings ) || ! $this->verify_request_parameters( $target_language_code, $source_language_code ) ) {
			return array();
		}

		$start_time = microtime( true );

		$source_language = $this->machine_translation_codes[ $source_language_code ];
		$target_language = $this->machine_translation_codes[ $target_language_code ];

		$translated_strings = array();

		$length_limit       = 20e+6 / 4;
		$new_strings_chunks = array_chunk( $new_strings, $length_limit, true );
		/* if there are more than 20MB we make multiple requests */
		foreach ( $new_strings_chunks as $new_strings_chunk ) {
			$i        = 0;
			$response = $this->translate_document( $source_language_code, $target_language_code, $new_strings_chunk, $original_strings, $start_time, $is_gettext );

			// this is run only if "Log machine translation queries." is set to Yes.
			$this->machine_translator_logger->log(
				array(
					'strings'     => serialize( $new_strings_chunk ),
					'response'    => serialize( $response ),
					'lang_source' => $source_language,
					'lang_target' => $target_language,
				)
			);

			/* analyze the response */
			if ( is_array( $response ) && ! empty( $response ) ) {

				$this->machine_translator_logger->count_towards_quota( $new_strings_chunk );

				if ( count( $response ) > 0 && count( $response ) < count( $new_strings_chunk ) ) {
					error_log( "[$source_language_code => $target_language_code] Translation list is incomplete. Please check that eTranslation serivce is working correctly or contact plugin's technical support. Using original string for last " . ( count( $new_strings_chunk ) - count( $response ) ) . ' strings.' );
				}
				foreach ( $new_strings_chunk as $key => $old_string ) {
					if ( isset( $response[ $i ] ) ) {
						$translated_strings[ $key ] = $response[ $i ];
					} else {
						$translated_strings[ $key ] = $old_string;
					}
					$i++;
				}
			}

			if ( $this->machine_translator_logger->quota_exceeded() ) {
				break;
			}
		}

		// will have the same indexes as $new_string or it will be an empty array if something went wrong.
		return $translated_strings;
	}

	/**
	 * Send a test request to verify if the eTranslation functionality is working.
	 *
	 * @return mixed The response from the eTranslation service.
	 */
	public function test_request() {
		return $this->etranslation_service->send_domain_request();
	}

	/**
	 * Get the eTranslation application name from the settings.
	 *
	 * @return string The eTranslation application name.
	 */
	public function get_app_name() {
		return isset( $this->settings['etm_machine_translation_settings'], $this->settings['etm_machine_translation_settings']['etranslation-app-name'] ) ? $this->settings['etm_machine_translation_settings']['etranslation-app-name'] : '';
	}

	/**
	 * Get the decrypted eTranslation password from the settings.
	 *
	 * @return string The decrypted eTranslation password.
	 */
	public function get_password() {
		return isset( $this->settings['etm_machine_translation_settings'], $this->settings['etm_machine_translation_settings']['etranslation-pwd'] ) ? ETM_eTranslation_Utils::decrypt_password( $this->settings['etm_machine_translation_settings']['etranslation-pwd'] ) : '';
	}

	/**
	 * Get the eTranslation API key (app name and password) as an array.
	 *
	 * @return array The eTranslation API key (app name and password) as an array.
	 */
	public function get_api_key() {
		return array( $this->get_app_name(), $this->get_password() );
	}

	/**
	 * Get the list of supported languages from the eTranslation service.
	 *
	 * @return array The array of supported languages.
	 */
	public function get_supported_languages() {
		$response = $this->etranslation_service->get_available_domain_language_pairs();
		if ( $response['response'] == 200 ) {
			$domains        = $response['body'];
			$language_pairs = $domains->GEN->languagePairs;
			$from_languages = array_map( array( $this, 'extract_source_language' ), $language_pairs );
			return array_unique( $from_languages );
		}
		return array();
	}

	/**
	 * Extract the source language from the language pair string.
	 *
	 * @param string $lang_pair_str The language pair string.
	 * @return string The extracted source language.
	 */
	private function extract_source_language( $lang_pair_str ) {
		return strtolower( explode( '-', $lang_pair_str )[0] );
	}

	/**
	 * Get the domain list from the eTranslation service.
	 *
	 * @return mixed The response from the eTranslation service.
	 */
	public function get_domain_list() {
		return $this->etranslation_service->get_available_domain_language_pairs();
	}

	/**
	 * Get engine-specific language codes using ETM_Languages class.
	 *
	 * @param array $languages The array of language codes.
	 * @return array The engine-specific language codes.
	 */
	public function get_engine_specific_language_codes( $languages ) {
		return $this->etm_languages->get_iso_codes( $languages );
	}

	/**
	 * Check formality for supported languages.
	 *
	 * @return array The array of formality-supported languages.
	 */
	public function check_formality() {
		$formality_supported_languages = array();

		return $formality_supported_languages;
	}

	/**
	 * Check the validity of the eTranslation API key.
	 *
	 * @return array The result of the API key validity check.
	 */
	public function check_api_key_validity() {
		$machine_translator = $this;
		$translation_engine = $this->settings['etm_machine_translation_settings']['translation-engine'];
		$api_key            = $machine_translator->get_api_key();

		$is_error       = false;
		$return_message = '';

		if ( 'etranslation' === $translation_engine && isset( $this->settings['etm_machine_translation_settings']['machine-translation'] ) &&
				$this->settings['etm_machine_translation_settings']['machine-translation'] === 'yes' ) {

			if ( isset( $this->correct_api_key ) && $this->correct_api_key != null ) {
				return $this->correct_api_key;
			}

			if ( ! $this->credentials_set() ) {
				$is_error       = true;
				$return_message = __( 'Please enter your eTranslation credentials.', 'etranslation-multilingual' );
			} else {
				$response = $machine_translator->test_request();
				$code     = $response['response'];
				if ( 200 !== $code ) {
					$is_error           = true;
					$translate_response = etm_etranslation_response_codes( $code );
					$return_message     = $translate_response['message'];

					error_log( 'Error on eTranslation request: ' . print_r( $response, true ) . ' Please recheck your credentials or try again later!' );
				}
			}
			$this->correct_api_key = array(
				'message' => $return_message,
				'error'   => $is_error,
			);
		}

		return array(
			'message' => $return_message,
			'error'   => $is_error,
		);
	}
}
