<?


// Работа с потоком снаружи
// По хорошему надо избавиться от прямого вызова one_thread. Но потом.

/* 

переделать вызовы $Ui_thread->имя переменной
*/


class Ui_thread { 

	public $one_thread = false; // экземпляр One_thread
	public $is_run = false; // запущен ли процесс.  Пауза это одно из состояний 'run' процесса.
	public $pause = false; // когда поток стоит на паузе. В status по прежнему будет светится run 
	public $store_after_stop = array(); //хранилище данных после остановки процесса
	private $pause_sumtime = 0; // время проведенное на паузе. Сбрасывается при запуске каждого нового этапа
	private $pause_start_time = 0; //время начала паузы
	

	public $wait_gui = false; // устанавливаеся в true когда поток запросил у программы вычисления и ждет пока их вернет. Это нужно чтобы код определяющий что поток завис не подумал что поток завис.

	public $interval_check_handstop = 2; 
	 //данные, передающиеся с защитой между потоком и приложением
	 // set и get protect работают и без этого массива.
	// что он дает:
	// 	- при запуске потока инициализирует эти переменные чтобы не устанавливать их руками
	// 	- после завершения потока, данные переносятся в кеш чтобы get мог возвращать последние данные установленные в потоке
	public $protect_thread_data = array('log_lines', 'INFO', 'recount_free_space', 'wait_disk_stop', 'setting_recovery_save', 'demo_overlimit', 'status', 'error_msg', 'cur_fname', 'clear_disk_prog_restart');

	function __construct() {
		$this->one_thread = new One_thread(); // тупо чтобы можно было данные устанавливать даже если поток не запущен.
												// это пока не переведем на защищенные методы
		// $this->one_thread->set_protect('status', '');
		// $this->one_thread->start();
	}

	/*
	// пока не перевели на защищенные методы get и set
	function __get($vname) {
		return $this->one_thread->$vname;
	}
	function __set($vname, $value) { 
		$this->one_thread->$vname = $value;
	}*/

	// время проведенное на паузе
	function get_pause_sumtime() {
		// если сейчас пауза, то надо посчитать
		if($this->pause) {
			// высчитываем время проведенное на текущей паузе
			$add = time() - $this->pause_start_time;

			return $this->pause_sumtime + $add;
		} else {
			return $this->pause_sumtime;
		}
	}

	function set_protect($name, $value) {
		$this->store_after_stop[$name] = $value;

		// если поток не запущен, возвращаем ''
		if(!$this->is_run)
			return '';

		// устанавливаем данные в потоке
		return $this->one_thread->set_protect($name, $value);
	}

	function get_protect($name) {
		// если поток не запущен
		if(!$this->is_run) {
			// возвращаем данные если они были сохранены при остановке процесса
			if(isset($this->store_after_stop[$name]))
				return $this->store_after_stop[$name];

			return '';
		}
			
		// запрашиваем данные из потока
		return $this->one_thread->get_protect($name);
	}

	// проверяем запущен ли процесс
	function check_run() {
		GLOBAL $log;

		// Недостаточно проверить что status существует. После завершения потока, status остается существовать, но пустой.
		// нельзя делать прямой вызов через one_thread-> потому что вылезает исключение.
		$status = $this->get_protect('status');
		if($status == 'run')
			return true;
		else
			return false;
	}
	
	// выдает окно с сообщением если процесс запущен
	function check_run_dialog() {
		if($this->check_run()) {
			messageDlg("Сначала остановите обработку текущего этапа.\nНа данный момент, это можно сделать только закрыв программу :)", mtWarning, MB_OK);
			return true;
		}
		return false;
	}


	// вызывается из Bt_steps
	function stop($step_name) {
		GLOBAL $INFO, $OP, $setting, $Paths, $log;
		
		logFile("Bt_steps::stop()");

		$status = $this->one_thread->get_protect('status', false);
		if(!$status) {
			// если status не определен то поток не запущен
			return true;
		}

		// если остановка ручная, то нужно изменить status у потока
		// проверяем, вдруг там не run а error
		
		if($this->check_run())
			$this->one_thread->set_protect('status', 'hand_stop');

		// сохраняем данные из потока на всякий случай, вдруг их не успел взять ui_timers
		foreach($this->protect_thread_data as $name) {
			$this->store_after_stop[$name] = $this->one_thread->get_protect($name);
		}

		$this->is_run = false;

		// if($this->pause) {
		// 	$this->pause_add_sumtime();
		// 	$this->pause = false;
		// }

		$this->one_thread->stop();
		$this->clear_memory($INFO['one_thread_pid']); // прибераем за потоком, потому что остаются каки 
		$to_info = array();
		$to_info['one_thread_pid'] = 0; // для красоты, потока же больше нет

		// отмечаем время остановки
		$OP[$step_name.'_stop_time'] = time();

		//$log->line("$step_name устаналиваем время остановки");

		$to_info[$step_name.'_full_time'] = $INFO[$step_name.'_full_time'] + (time() - $INFO['start_time_thread']);
		$to_info['start_time_thread'] = 0;

		$to_info['stop_time_thread'] = time(); // это нужно для сообщения что обработка еще не завершена

		$to_info['one_thread_working'] = false; // по этой метке определяем, был ли завершен поток через ui или программа вылетела. Это нужно для автозапуска процессов после зависания всей программы

		$INFO = array_merge($INFO, $to_info);

		$setting->save();

		// если файл для восстановления существует, прописываем в него full_time, поскольку там он старый
		$setting->edit_recovery_info_if_exist($to_info);

		// выключаем кнопку паузы
		c('bt_pause')->enabled = false;

		// на всякий случай
		c('bt_run')->enabled = true;
		c('bt_skip_files')->enabled = true;
		c('bt_step_reset')->enabled = true;

		if($status == 'complete')
			run_method_exists("Ui_$step_name", 'after_run_complete');
		elseif($status == 'error')
			run_method_exists("Ui_$step_name", 'after_run_error');

		return true; // на всякий случай, потому что метод Ui_thread::run() должен это делать
	}
	
	// вызывается из Bt_steps
	// работает в двух вариантах: запускает поток или восстанавливает зависший
	function run($step_name) {
		GLOBAL $GOP, $OP, $INFO, $GOP, $log, $Paths, $setting;

		// проверка что поток не запущен
		if($this->check_run_dialog())
			return false;
		
		// текущий обрабатываемый диск нужен чтобы при перезагрузке программа сразу его выбрала
		$GOP['processing_disc_unid'] = $Paths->disc_unid;

		$GLOBALS['error_type'] = -1;

		// перемещаем настройки из формы в OP
		$setting->save(); // сохраняем настройки. Это действительно нужно потому что потоки не могут работать с элементами формы

		// синхронизируем OP в обычном setting и recovery
		//$setting->edit_recovery_info_if_exist(null, $OP);

		// load сам разберется setting или recovery_setting загружать
		$setting->load(); // и тут же их загружаем. Ведь от того что человек изменил поля, они не переместили автоматом в OP
		
		// запуск чекалки этапа. Не надо его не запускать при восстановлении потока. Внутри чекера будет такая возможность если надо
		if(!run_method_exists("Ui_$step_name", 'before_run'))
			return false;


		$INFO['step'] = $step_name; // в некоторых случаях в info[step] может находиться значение отличное от $this->step, что плохо.

		if(empty($INFO['recovery_fname'])) {
			// обнуляем статистику до указанного этапа включительно
			// поскольку работает с $OP, местоположение имеет значение
			Stat::clean_up_step($step_name);
			Stat::clear_step($step_name);

			// это надо чтобы в случае повреждения recovery_info, поток мог взять данные из blocks. А не приходилось начинать все с начала.
			// это надо делать когда recovery_info поврежден, поскольку recovery_fname к тому моменту давно измениться в INFO
			// if($step_name==CREATE_BLOCKS OR $step_name==READ_BLOCKS) {
			// 	$INFO['recovery_fname'] = 1;
			// 	$setting->save();
			// 	$INFO['recovery_fname'] = 0; // специально чтобы поток не попытался восстановиться, ведь ему надо запускаться с начала.
			// }
		}

		//выключаем кнопки чтобы не спамили. После того как процесс вернет метку, что он точно запустился, тогда и включим кнопки обратно
		c('bt_run')->enabled = false;
		c('bt_skip_files')->enabled = false;
		c('bt_step_reset')->enabled = false;

		$INFO['one_thread_can_stop'] = false; // в потоке мы изменим его на true, и тогда gui активирует кнопки

		$this->one_thread->Stop();
		$this->one_thread = $thread = new One_thread(); // для каждого запуска надо создавать заново, иначе что-то работает не так.

		// определяем защищенные переменные для обмена данными между потоком и приложением
		// чтобы их не надо было в самом потоке определять
		foreach($this->protect_thread_data as $name) {
			$thread->set_protect($name, '');
		}

		$OP['interval_check_handstop'] = $this->interval_check_handstop;
		$INFO['thread_send_time_zavis'] = time() + 5; // укажем здесь чтобы до первого получения INFO из потока, не было подозрений на зависание.

		$INFO['one_thread_working'] = true; //надо размещать перед созданием потока, чтобы туда значение передалось
			
		$setting->save_recovery_info(); // чтобы one_thread_working сохранился, мало ли что в потоке случится

		// устанавливаем защенными те переменные которые потом будем возвращать
		$thread->set_protect('paths_vars', $Paths->export_vars());
		$thread->set_protect('is_demo', $GLOBALS['Is_demo']);
		$thread->set_protect('operation', $step_name);
		$thread->set_protect('OP', $OP);
		$thread->set_protect('GOP', $GOP);
		$thread->set_protect('INFO', $INFO);

		//$thread->set_protect('status', 'need_run');
		$thread->Start();

		$this->is_run = true;
		$setting->save(); // чтобы one_thread_working сохранить

		// включаем кнопку паузы
		c('bt_pause')->enabled = true;

		// сбрасываем время проведенное на паузе
		$this->pause_sumtime = 0;


		// сообщене об очистке. Именно здесь, потому что очистка может запускаться из нескольких мест. Не везде же прописывать это сообщение
		// нельзя его показывать, потому что человек может ждать пока оно закроется. А оно не закроется
		// if(!empty($INFO['create_blocks.mode_onli_cleaning'])) {
		// 	// именно через print, чтобы сообщние не блокировало выполнение
		// 	print "ВНИМАНИЕ. Идет очистка папки \$BAD_BLOCKS.NOT_DEL\n";
		// }

		return true;
	}


	function pause() {
		//print "Ui_thread: пауза";
		//обязательно проверяем что поток работает
		// потому что если сделать Suspend подряд 2 раза, поток больше не оживет
		//if($get_protect('status') == 'run') {
		if(!$this->pause) {
			$this->one_thread->Suspend();
			$this->pause = true;

			$this->pause_start_time = time();
		}
	}

	function resume() {

		//if($this->get_protect('status') == 'pause') {
		if($this->pause) {
			$this->one_thread->Start();
			$this->pause = false;

			// прибавляем время на паузе
			$this->pause_add_sumtime();
		}
	}

	// прибавляем время проведенное на паузе
	// вызывается когда пауза окончена
	function pause_add_sumtime() {
		$add = time() - $this->pause_start_time;
		$this->pause_sumtime += $add;
	}

	// очистка памяти, делается вне потока чтобы если поток завис, все равно за ним убрали.
	// process_id берется из getmypid();
	function clear_memory($process_id) {
		GLOBAL $log;

		$time = microtime(true);

		WinApi::$Base = array();
		//if(0 !== ($handle = User32::FindWindowA(null, 'Калькулятор'))) { // Ищем процесс по заголовку
		 //   $Pid = User32::NewFFIStruct('LPDWORD');
		 //   User32::GetWindowThreadProcessId($handle, $Pid); // получаем PID процесса

		$OpenPHandle = Kernel32::OpenProcess(0x1F0FFF, false, $process_id); // открываем процесс для работы
		if($OpenPHandle != 0) {
		    if(PsApi::EmptyWorkingSet($OpenPHandle)) { // чистим память
		        logFile('Очистка оперативной памяти. Удачно!');
		    } else {
		         logFile('Очистка оперативной памяти. Не удачно!');
		    }

		    Kernel32::CloseHandle($OpenPHandle); // закрытие handle а не процесса!
		} else {
		     logFile('Очистка оперативной памяти. Ошибка');
		}

		logFile("Затраченно время на очистку:" . (microtime(true) - $time));
	}

}



?>