<?php

/*
 * Abstract class for Translations groups objects
 *
 * @since 0.2
 */
abstract class Lingotek_Group {
	static public $creating_translation; // used to avoid uploading a translation when using automatinc upload

	/*
	 * constructor
	 *
	 * @since 0.2
	 */
	public function __construct($term, &$pllm) {
		$this->pllm = &$pllm;
		$this->load($term);
	}

	/*
	 * assigns this object properties from the underlying term
	 *
	 * @since 0.2
	 *
	 * @param object $term term translation object
	 */
	protected function load($term) {
		$this->term_id = (int) $term->term_id;
		$this->tt_id = (int) $term->term_taxonomy_id;
		$this->document_id = $term->slug;
		$this->taxonomy = $term->taxonomy;
		$this->desc_array = unserialize($term->description);

		foreach (array('type', 'source', 'status', 'translations') as $prop)
			$this->$prop = &$this->desc_array['lingotek'][$prop];
	}

	/*
	 * updates the translation term in DB
	 *
	 * @since 0.2
	 */
	public function save() {
		wp_update_term((int) $this->term_id, $this->taxonomy, array('slug' => $this->document_id, 'name' => $this->document_id, 'description' => serialize($this->desc_array)));
	}

	/*
	 * provides a safe way to update the translations statuses when receiving "simultaneous" TMS callbacks
	 *
	 * @since 0.2
	 *
	 * @param string $locale
	 * @param string $status
	 * @param array $arr translations to add
	 */
	protected function safe_translation_status_update($locale, $status, $arr = array()) {
		global $wpdb;
		$wpdb->query("LOCK TABLES $wpdb->term_taxonomy WRITE");
		$d = $wpdb->get_var($wpdb->prepare("SELECT description FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $this->tt_id));
		$d = unserialize($d);
		$this->translations[$locale] = $d['lingotek']['translations'][$locale] = $status;
		$d = array_merge($d, $arr); // optionally add a new translation
		$d = serialize($d);
		$wpdb->query($wpdb->prepare("UPDATE $wpdb->term_taxonomy SET description = %s WHERE term_taxonomy_id = %d", $d, $this->tt_id));
		$wpdb->query("UNLOCK TABLES");
	}

	/*
	 * creates a new term translation object in DB
	 *
	 * @since 0.2
	 *
	 * @param int $object_id the id of the object to translate
	 * @param string $document_id Lingotek document id
	 * @param array $desc data to store in the Lingotek array
	 * @param string $taxonomy either 'post_translations' or 'term_translations'
	 */
	protected static function _create($object_id, $document_id, $desc, $taxonomy) {
		$terms = wp_get_object_terms($object_id, $taxonomy);
		$term = array_pop($terms);

		if (empty($term)) {
			wp_insert_term($document_id, $taxonomy, array('description' => serialize($desc)));
		}

		// the translation already exists but was not managed by Lingotek
		else {
			if (is_array($old_desc = maybe_unserialize($term->description)))
				$desc = array_merge($old_desc, $desc);
			wp_update_term((int) $term->term_id, $taxonomy, array('slug' => $document_id, 'name' => $document_id, 'description' => serialize($desc)));
		}

		wp_set_object_terms($object_id, $document_id, $taxonomy);
	}

	/*
	 * disassociates translations from the Lingotek TMS
	 *
	 * @since 0.2
	 *
	 * @param bool $delete whether to delete the Lingotek document or not
	 */
	public function disassociate() {
		$client = new Lingotek_API();
		$prefs = Lingotek_Model::get_prefs();

		if ($prefs['delete_document_from_tms']) {
			$client->delete_document($this->document_id, $this->source);
			unset($this->desc_array['lingotek']);
			$this->save();
		}
		else {
			unset($this->desc_array['lingotek']);
			$this->save();
		}

	}

	/*
	 * uploads a modified source
	 *
	 * @since 0.2
	 *
	 * @param string $title
	 * @param object $content can be a post object, a term object
	 */
	public function patch($title, $content, $external_url = '', $filters = array()) {
		$client = new Lingotek_API();

		$params = array(
			'title' => $title,
			'content' => $this->get_content($content),
			'external_url' => $external_url,
		);
		$params = array_merge($params, $filters);

		$res = $client->patch_document($this->document_id, $params, $this->source);

		if ($res) {
			$this->status = 'importing';
			$this->translations = array_fill_keys(array_keys($this->translations), 'pending');
			$this->save();
		}
	}

	/*
	 * checks the status of source document
	 *
	 * @since 0.2
	 */
 	public function source_status() {
		$client = new Lingotek_API();

		if ('importing' == $this->status && $client->get_document_status($this->document_id)){
			$this->status = 'current';
			$this->save();
		}
	}

	/*
	 * sets source status to ready
	 *
	 * @since 0.2
	 */
	public function source_ready() {
		$this->status = 'current';
		$this->save();
	}

	/*
	 * requests a translation to Lingotek TMS
	 *
	 * @since 0.2
	 *
	 * @param string $locale
	 */
	public function request_translation($locale) {
		$workflow = $this->get_workflow_object($this->get_source_language(), $locale, $this->type, $this->source);
		if ($workflow->has_custom_request_procedure()) {
			$workflow->do_custom_request();
		} else {
			$client = new Lingotek_API();
			$language = $this->pllm->get_language($locale);
			$workflow = Lingotek_Model::get_profile_option('workflow_id', $this->type, $this->get_source_language(), $language, $this->source);
			$args = $workflow ? array('workflow_id' => $workflow) : array();

			if (!$this->is_disabled_target($language) && empty($this->translations[$language->locale])) {
				// don't change translations to pending if the api call failed
				if ($client->request_translation($this->document_id, $language->locale, $args, $this->source)) {
					$this->status = 'current';
					$this->translations[$language->locale] = 'pending';
				}

				$this->save();
			}
		}
	}

	/*
	 * requests translations to Lingotek TMS
	 *
	 * @since 0.2
	 *
	 * @param object $source_language language of the source
	 */
	protected function _request_translations($source_language) {
		
		$type_id;
		$client = new Lingotek_API();

		foreach ($this->pllm->get_languages_list() as $lang) {
			$workflow = $this->get_workflow_object($source_language, $lang->locale, $this->type, $this->source);
			if ($workflow->has_custom_request_procedure()) {
				$workflow->do_custom_request();
			} else {
				if ($source_language->slug != $lang->slug && !$this->is_disabled_target($source_language, $lang) && empty($this->translations[$lang->locale])) {
					$workflow = Lingotek_Model::get_profile_option('workflow_id', $this->type, $source_language, $lang, $this->source);
					$args = $workflow ? array('workflow_id' => $workflow) : array();

					if ($this->type == 'string') {
						$type_id = $this->name;
					}
					else {
						$type_id = $this->source;
					}
					// don't change translations to pending if the api call failed
					if ($client->request_translation($this->document_id, $lang->locale, $args, $type_id)) {

						/**
						 * This is a fix that reloads the object before editing & saving it. The problem 
						 * was that the callbacks were coming back before this method finished so the 
						 * $this->translations array was out of sync with what was in the database. We fix this
						 * by reading the DB only when we need to -> make our edit -> save the edit. This keeps us from holding on to
						 * old data and overwritting the new data.
						 */
						if ('post_translations' === $this->taxonomy) {
							$this->load( PLL()->model->post->get_object_term((int) $this->source, 'post_translations') );
						} else if ('term_translations' === $this->taxonomy) {
							$this->load( PLL()->model->term->get_object_term((int) $this->source, 'term_translations') );
						}
						$this->status = 'current';
						if (!isset($this->translations[$lang->locale]) || isset($this->translations[$lang->locale]) && $this->translations[$lang->locale] != 'current') {
							$this->translations[$lang->locale] = 'pending';
						}
						$this->save();
					}
				}
			}
		}
	}

	/**
	* Publicly exposes the safe_translation_status_update method that allows us to safely update
	* translation statuses. This method is used when a request translation call is made to bridge and that 
	* translation was requested successfully. 
	*/
	public function update_translation_status($locale, $status)
	{
		$this->safe_translation_status_update($locale, $status);
	}

	/*
	 * checks the translations status of a document
	 *
	 * @since 0.1
	 */
	public function translations_status() {
		// $client = new Lingotek_API();
		// $translations = $client->get_translations_status($this->document_id, $this->source); // key are Lingotek locales
		// foreach($this->translations as $locale => $status) {
		// 	$lingotek_locale = $this->pllm->get_language($locale)->lingotek_locale;
		// 	if ('current' != $status && isset($translations[$lingotek_locale]) && 100 == $translations[$lingotek_locale])
		// 		$this->translations[$locale] = 'ready';
		// }
		// $this->save();

		$this->translation_status_hard_refresh();
	}

	public function translation_status_hard_refresh() {
		$client = new Lingotek_API();
		$translations = $client->get_translations_status($this->document_id, $this->source); // key are Lingotek locales
		$lingotek_locale_to_pll_locale = array();
		foreach (PLL()->model->get_languages_list() as $pll_language) {
			$lingotek_locale_to_pll_locale[$pll_language->lingotek_locale] = $pll_language->locale;
		}
		foreach ($translations as $lingotek_locale => $percent)
		{
			if (!isset($lingotek_locale_to_pll_locale[$lingotek_locale])) { continue; }
			$wp_locale = $lingotek_locale_to_pll_locale[$lingotek_locale];
			if ($translations[$lingotek_locale] < 100) {
				$this->translations[$wp_locale] = 'pending';
			} else if (!isset($this->translations[$wp_locale]) || $this->translations[$wp_locale] !== 'current'){
				$this->translations[$wp_locale] = 'ready';
			}
		}

		$this->save();
	}

	/*
	 * sets translation status to ready
	 *
	 * @since 0.1
	 * @uses Lingotek_Group::safe_translation_status_update() as the status can be automatically set by the TMS callback
	 */
	public function translation_ready($locale) {
		$this->safe_translation_status_update($locale, 'ready');
	}

	/*
	 * attempts to create all translations from an object
	 *
	 * @since 0.2
	 */
	public function create_translations() {
		if (isset($this->translations)) {
			foreach ($this->translations as $locale => $status)
				if ('pending' == $status || 'ready' == $status)
					$this->create_translation($locale);
		}
	}

	/*
	 * sets document status to edited
	 *
	 * @since 0.1
	 */
	public function source_edited() {
		$this->status = 'edited';
		// $this->translations = array_fill_keys(array_keys($this->translations), 'not-current');
		$this->save();
	}

	/*
	 * returns true if at least one of the translations has the requested status
	 *
	 * @since 0.2
	 *
	 * @param string $status
	 * @return bool
	 */
	public function has_translation_status($status) {
		return isset($this->translations) && array_intersect(array_keys($this->translations, $status), $this->pllm->get_languages_list(array('fields' => 'locale')));
	}

	/*
	 * checks if target should be automatically downloaded
	 *
	 * @since 0.2
	 *
	 * @param string $locale
	 * @return bool
	 */
	public function is_automatic_download($locale) {
		return 'automatic' == Lingotek_Model::get_profile_option('download', $this->type, $this->get_source_language(), $this->pllm->get_language($locale), $this->source);
	}

	public function is_automatic_upload() {
		$workflow = $this->get_workflow_object($this->get_source_language(), false, $this->type, $this->source);
		$can_auto_upload = $workflow->auto_upload_allowed();
		if ($can_auto_upload) {
			/**
			 * Check each of the translations and if one of them doesn't allow automatic upload then we don't auto upload the doc.
			 */
			foreach ($this->translations as $locale => $progress) {
				$workflow = $this->get_workflow_object($this->get_source_language(), $locale, $this->type, $this->source);
				$can_auto_upload = $can_auto_upload && $workflow->auto_upload_allowed();
			}
		}
		
		return $can_auto_upload;
	}

	/*
	 * checks if translation is disabled for a target language
	 *
	 * @since 0.2
	 *
	 * @param string $type post type or taxonomy
	 * @param object $language
	 */
	public function is_disabled_target($language, $target = null) {
		$profile = Lingotek_Model::get_profile($this->type, $language, $this->source);
		if ($target) {
			return isset($profile['targets'][$target->slug]) && ('disabled' == $profile['targets'][$target->slug] || 'copy' == $profile['targets'][$target->slug]);
		}
		else {
			return isset($profile['targets'][$language->slug]) && ('disabled' == $profile['targets'][$language->slug] || 'copy' == $profile['targets'][$language->slug]);
		}
	}

	/**
	 * Goes through the source document and all locales and calls the pre_upload_to_lingotek() on the Workflow object unless
	 * a locale has been disabled.
	 *
	 * @param string $item_id
	 * @param string $type
	 * @param object $source_language
	 * @return void
	 */
	public function pre_upload_to_lingotek($item_id, $type, $source_language, $item_type) {
		$workflow = $this->get_workflow_object($source_language, false, $type, $item_id);
		$workflow->pre_upload_to_lingotek($item_id, $item_type);
		foreach ($this->pllm->get_languages_list() as $lang) {
			if ($this->_is_disabled_target($lang, $type, $item_id)) {
				continue;
			}
			$workflow = $this->get_workflow_object($source_language, $lang->locale, $type, $item_id);
			$workflow->pre_upload_to_lingotek($item_id, $item_type);
		}
	}

	/**
	 * Goes through the source document and all locales and calls the save_post_hook() on the Workflow object unless
	 * a locale has been disabled.
	 *
	 * @param string $item_id
	 * @param string $type
	 * @param object $source_language
	 * @return void
	 */
	public function pre_save_post($item_id, $type, $source_language) {
		$workflow = $this->get_workflow_object($source_language, false, $type, $item_id);
		$workflow->save_post_hook();
		foreach ($this->pllm->get_languages_list() as $lang) {
			if ($this->_is_disabled_target($lang, $type, $item_id)) {
				continue;
			}
			$workflow = $this->get_workflow_object($source_language, $lang->locale, $type, $item_id);
			$workflow->save_post_hook();
		}
	}

	/**
	 * Goes through the source document and all locales and calls the save_term_hook() on the Workflow object unless
	 * a locale has been disabled.
	 *
	 * @param string $item_id
	 * @param string $type
	 * @param object $source_language
	 * @return void
	 */
	public function pre_save_terms($item_id, $type, $source_language) {
		$workflow = $this->get_workflow_object($source_language, false, $type, $item_id);
		$workflow->save_term_hook();
		foreach ($this->pllm->get_languages_list() as $lang) {
			if ($this->_is_disabled_target($lang, $type, $item_id)) {
				continue;
			}
			$workflow = $this->get_workflow_object($source_language, $lang->locale, $type, $item_id);
			$workflow->save_term_hook();
		}
	}

	public function get_custom_in_progress_icon($language) {
		$workflow = $this->get_workflow_object($this->get_source_language(), $language->locale, $this->type, $this->source);
		return $workflow->get_custom_in_progress_icon();
	}

	/**
	 * Checks the source language and all of its target language's workflows to determine whether a bulk translation request is allowed. 
	 * If one or more of the workflows return true on has_custom_request_procedure() then the bulk translation request will be aborted.
	 *
	 * @param object $source_language
	 * @param string $type
	 * @param string $item_id
	 * @return boolean
	 */
	private function can_bulk_request_translations($source_language, $type, $item_id) {
		$workflow = $this->get_workflow_object($source_language, false, $type, $item_id);
		if ($workflow->has_custom_request_procedure()) { return false; }

		foreach ($this->pllm->get_languages_list() as $lang) {
			if ($this->_is_disabled_target($lang, $type, $item_id)) {
				continue;
			}
			$workflow = $this->get_workflow_object($source_language, $lang->locale, $type, $item_id);
			if ($workflow->has_custom_request_procedure()) { return false; }
		}

		return true;
	}

	/**
	 * Instantiates and returns a workflow object. If only the source language is passed in then it will return the workflow object
	 * for the source locale; however, if a locale is passed in with the source language then a workflow object will be returned 
	 * for the locale.
	 *
	 * @param string $source_language
	 * @param boolean | string $locale
	 * @param string $type
	 * @param string $item_id
	 * @return void
	 */
	private function get_workflow_object( $source_language, $locale = false, $type,  $item_id ) {
		$target_language = ($locale) ? $this->pllm->get_language($locale) : false;
		$source_language = (!$source_language) ? PLL()->model->post->get_language( $this->source ) : $source_language;
		$workflow_id;
		if ($type === 'post') {
			$post = ($item_id) ? get_post($item_id) : get_post( $this->source );
			$workflow_id = Lingotek_Model::get_profile_option( 'workflow_id', $post->post_type, $source_language, $target_language , $this->source );
		} else {
			$workflow_id = Lingotek_Model::get_profile_option( 'workflow_id', $type, $source_language, $target_language );
		}
		$workflow = Lingotek_Workflow_Factory::get_workflow_instance( $workflow_id ); 
		return $workflow;
	}

	/**
	 * Checks if a target language has been disabled. Is different than the other is_disabled_target method by 
	 * allowing the caller to supply all of the arguments used.
	 *
	 * @param object $target_language
	 * @param string $type
	 * @param string $item_id
	 * @return void
	 */
	private function _is_disabled_target($target_language, $type, $item_id) {
		$profile = Lingotek_Model::get_profile($type, $target_language, $item_id);
		return isset($profile['targets'][$target_language->slug]) && ('disabled' == $profile['targets'][$target_language->slug] || 'copy' == $profile['targets'][$target_language->slug]);
	}
}
