<?php

use WPML\FP\Obj;
use WPML\LIB\WP\User;

abstract class WPML_Translation_Job extends WPML_Translation_Job_Helper {
	protected $basic_data;
	protected $element_id = - 1;
	protected $status     = - 1;
	protected $job_id;
	protected $batch_id;

	/** @var  WPML_TM_Blog_Translators $blog_translators */
	protected $blog_translators;

	/**
	 * @param  int                      $job_id
	 * @param int|null                 $batch_id
	 * @param WPML_TM_Blog_Translators $blog_translators
	 */
	function __construct( $job_id, $batch_id = null, &$blog_translators = null ) {
		$this->job_id           = $job_id;
		$batch_id               = $batch_id ? $batch_id : $this->get_batch_id();
		$this->batch_id         = $batch_id ? $batch_id : TranslationProxy_Batch::update_translation_batch();
		$this->blog_translators = $blog_translators ? $blog_translators : wpml_tm_load_blog_translators();
	}

	abstract public function cancel();

	abstract public function get_original_element_id();

	abstract public function to_array();

	/**
	 * @return string
	 */
	abstract function get_title();

	public function get_status() {
		if ( $this->status == - 1 ) {
			$this->status = $this->load_status();
		}

		return $this->status;
	}

	public function get_status_value() {
		$this->maybe_load_basic_data();

		return $this->basic_data->status;
	}

	public function get_review_status() {
		$this->maybe_load_basic_data();

		return $this->basic_data->review_status;
	}

	public function get_id() {
		return $this->job_id;
	}

	public function get_resultant_element_id( $force = false ) {
		if ( $this->element_id == - 1 || $force === true ) {
			$this->element_id = $this->load_resultant_element_id();
		}

		return $this->element_id;
	}

	/**
	 * Checks whether the input user is allowed to edit this job.
	 *
	 * If he is an administrator, he is allowed to edit any job.
	 * If he is an editor, he is allowed to edit any job as long as he's able to translate such language pair.
	 * Otherwise, we check two conditions:
	 *  - job needs to be assigned to the user or to no one
	 *  - user needs to be able to translate the language pair
	 *
	 * @param WP_User $user
	 *
	 * @return bool
	 */
	public function user_can_translate( $user ) {
		if ( User::isAdministrator( $user ) ) {
			return apply_filters( 'wpml_user_can_translate', true, $user );
		}

		$translator_has_job_language_pairs = $this->blog_translators->is_translator(
			$user->ID,
			$this->filter_is_translator_args( [
				'lang_from' => $this->get_source_language_code(),
				'lang_to'   => $this->get_language_code(),
			] )
		);

		if ( $translator_has_job_language_pairs && User::isEditor( $user ) ) {
			return apply_filters( 'wpml_user_can_translate', true, $user );
		}

		$translator_id          = $this->get_translator_id();
		$user_can_take_this_job = 0 === $translator_id
		                          || $this->is_current_user_allowed_to_translate( $user, $translator_id );

		return apply_filters( 'wpml_user_can_translate', $user_can_take_this_job && $translator_has_job_language_pairs, $user );
	}

	/**
	 * @param array $args
	 *
	 * @return array
	 */
	protected function filter_is_translator_args( array $args ) {
		return $args;
	}

	/**
	 * @param WP_User $user
	 * @param int     $translator_id
	 *
	 * @return bool
	 */
	private function is_current_user_allowed_to_translate( WP_User $user, $translator_id ) {
		$allowed_translators   = apply_filters( 'wpml_tm_allowed_translators_for_job', array(), $this );
		$allowed_translators[] = $translator_id;

		return in_array( (int) $user->ID, $allowed_translators, true );
	}

	public function get_batch_id() {
		if ( ! isset( $this->batch_id ) ) {
			$this->load_batch_id();
		}

		return $this->batch_id;
	}

	/**
	 * @param bool|false $as_name if true will return the language's display name if applicable
	 *
	 * @return bool|string
	 */
	public function get_language_code( $as_name = false ) {
		$this->maybe_load_basic_data();
		$code = isset( $this->basic_data->language_code ) ? $this->basic_data->language_code : false;

		return $code && $as_name ? $this->lang_code_to_name( $code ) : $code;
	}

	/**
	 * @param bool|false $as_name if true will return the language's display name if applicable
	 *
	 * @return bool|string
	 */
	function get_source_language_code( $as_name = false ) {
		$this->maybe_load_basic_data();
		$code = isset( $this->basic_data->source_language_code ) ? $this->basic_data->source_language_code : false;

		return $code && $as_name ? $this->lang_code_to_name( $code ) : $code;
	}

	/**
	 * @return string|false
	 */
	public function get_translator_name() {
		$this->maybe_load_basic_data();
		if ( Obj::prop( 'translation_service', $this->basic_data ) == TranslationProxy::get_current_service_id() ) {
			$this->basic_data->translator_name = TranslationProxy_Translator::get_translator_name( Obj::prop('translator_id', $this->basic_data) );
		} else {
			$this->basic_data->translator_name = false;
		}

		return $this->basic_data->translator_name;
	}

	/**
	 * Returns the id of the assigned translator or 0 if no translator is assigned to the job
	 *
	 * @return int
	 */
	public function get_translator_id() {
		$this->maybe_load_basic_data();

		$this->basic_data->translator_id = ! empty( $this->basic_data->translator_id )
			? $this->basic_data->translator_id : 0;

		return (int) $this->basic_data->translator_id;
	}

	public function get_basic_data() {
		$this->maybe_load_basic_data();

		return $this->basic_data;
	}

	/**
	 * @param  int    $translator_id
	 * @param string $service
	 *
	 * @return bool true on success false on failure
	 */
	public function assign_to( $translator_id, $service = 'local' ) {
		$this->maybe_load_basic_data();
		$prev_translator_id = $this->get_translator_id();
		$prev_service       = $this->get_translation_service();
		if ( $translator_id == $prev_translator_id && $service = $this->get_translation_service() ) {

			return true;
		}
		$this->basic_data->translator_id       = $translator_id;
		$this->basic_data->translation_service = $service;

		if ( $this->save_updated_assignment() === false ) {
			$this->basic_data->translator_id       = $prev_translator_id;
			$this->basic_data->translation_service = $prev_service;

			return false;
		}

		$job_id = $this->get_id();
		if ( $this->get_tm_setting( array( 'notification', 'resigned' ) ) == ICL_TM_NOTIFICATION_IMMEDIATELY
			 && ! empty( $prev_translator_id )
			 && $prev_translator_id != $translator_id
			 && $job_id ) {
			do_action( 'wpml_tm_remove_job_notification', $prev_translator_id, $this );
		}

		if ( $this->get_tm_setting( array( 'notification', 'new-job' ) ) == ICL_TM_NOTIFICATION_IMMEDIATELY ) {
			if ( empty( $translator_id ) ) {
				do_action( 'wpml_tm_new_job_notification', $this );
			} else {
				do_action( 'wpml_tm_assign_job_notification', $this, $translator_id );
			}
		}

		return true;
	}

	/**
	 * Returns either the translation service id for the job or 'local' for local jobs
	 *
	 * @return int|string
	 */
	public function get_translation_service() {
		$this->maybe_load_basic_data();
		$this->basic_data->translation_service = ! empty( $this->basic_data->translation_service )
			? $this->basic_data->translation_service : 'local';

		return $this->basic_data->translation_service;
	}

	abstract protected function save_updated_assignment();

	abstract protected function load_resultant_element_id();

	abstract protected function load_status();

	abstract protected function load_job_data( $id );

	abstract function get_type();

	protected function basic_data_to_array( $job_data ) {
		$this->maybe_load_basic_data();
		$data_array = (array) $job_data;
		if ( isset( $data_array['post_title'] ) ) {
			$data_array['post_title'] = esc_html( $data_array['post_title'] );
		}
		$data_array['translator_name']      = $this->get_translator_name();
		$data_array['batch_id']             = Obj::prop('batch_id', $job_data);
		$data_array['source_language_code'] = Obj::prop('source_language_code', $this->basic_data);
		$data_array['language_code']        = Obj::prop('language_code', $this->basic_data);
		$data_array['type']                 = $this->get_type();
		$data_array['lang_text']            = $this->generate_lang_text();

		return $data_array;
	}

	protected function maybe_load_basic_data() {
		if ( ! $this->basic_data ) {
			$this->basic_data = $this->load_job_data( $this->job_id );
			$this->basic_data = $this->basic_data ? $this->basic_data : new stdClass();
		}
	}

	private function get_inactive_translation_service( $translation_service_id ) {
		$cache_key   = $translation_service_id;
		$cache_group = 'get_inactive_translation_service';
		$cache_found = false;

		$service = wp_cache_get( $cache_key, $cache_group, false, $cache_found );

		if ( ! $cache_found ) {
			try {
				$service = TranslationProxy_Service::get_service( $translation_service_id );
			} catch ( WPMLTranslationProxyApiException $ex ) {
				$service = false;
			}
			if ( ! $service ) {
				$service       = new stdClass();
				$service->name = __( '(inactive and unknown service)', 'wpml-translation-management' );
			}
			wp_cache_set( $cache_key, $service, $cache_group );
		}

		return $service;
	}

	/**
	 * Retrieves the batch ID associated to the job ID
	 */
	abstract protected function load_batch_id();

	/**
	 * @return string
	 */
	protected function generate_lang_text() {
		$this->maybe_load_basic_data();

		return $this->lang_code_to_name( (string) $this->get_source_language_code() )
			   . html_entity_decode( ' &raquo; ' )
			   . $this->lang_code_to_name( (string) $this->get_language_code() );
	}

	/**
	 * @param string $code
	 *
	 * @return string
	 */
	private function lang_code_to_name( $code ) {
		global $sitepress;

		$lang_details = $sitepress->get_language_details( $code );

		return isset( $lang_details['display_name'] ) ? $lang_details['display_name'] : $code;
	}

	/**
	 * @param string $name
	 *
	 * @return mixed
	 */
	public function get_basic_data_property( $name ) {
		$this->maybe_load_basic_data();

		return Obj::prop( $name, $this->basic_data );
	}

	/**
	 * @param string $name
	 * @param mixed $value
	 */
	public function set_basic_data_property( $name, $value ) {
		$this->basic_data = Obj::assoc( $name, $value, $this->basic_data );
	}
}
