<?php

###############################################################################
#
# Автор (Author): Brigadir (forum.mydune.ru)
# Дата (Date): 30-12-2019
# Последнее обновление (Latest update): 02-04-2023
#
###############################################################################

require_once 'lib/ext_tv_channel.php';
require_once 'lib/ext_epg_program.php';

///////////////////////////////////////////////////////////////////////////////

class EpgEngine
{
	///////////////////////////////////////////////////////////////////////////

	const	epg_script = "#!/bin/sh\n\nJSON=\"\$FS_PREFIX/tmp/ext_epg/\$(echo \$PATH_INFO | sed 's/^.//;s/\\//-/').json\"\n\nWAIT=0\n\nwhile [[ ! -f \$JSON ]]\ndo\n\tif [[ \$WAIT -gt 120 ]]\n\tthen\n\t\tbreak\n\tfi\n\n\tWAIT=$(( \$WAIT + 1 ))\n\tusleep 100000\ndone\n\necho -e \"Content-Type: application/octet-stream\\n\"\necho \$(< \$JSON)";

	///////////////////////////////////////////////////////////////////////////

	protected	$raw_cache;
	protected	$parsers;
	protected	$tv_info;
	protected	$epg_ids;
	protected	$groups;
	protected	$channels;
	protected	$fav_channel_ids;
	protected	$tv_info_faulty;
	protected	$force_program_gen;
	protected	$epg_disk_cache_path;
	protected	$channels_epg_ids;
	protected	$resave_days;
	protected	$global_params;

	public	$time_zone_offset;
	public	$tv_fav_channels_group_name;
	public	$tv_fav_channels_group_id;
	public	$tv_all_channels_group_id;

	///////////////////////////////////////////////////////////////////////////

	protected function download_epg_event($ext_epg_id, $event_id, $timeshift_hours)
	{
		$event = null;
		$epg_info = $this->parse_epg_id($ext_epg_id);
		$global_timeshift_hours = isset($this->global_params[$epg_info->parser_name]['timeshift_hours'])? $this->global_params[$epg_info->parser_name]['timeshift_hours'] : 0;
		$timeshift_hours = empty($timeshift_hours)? $global_timeshift_hours : ($timeshift_hours + $global_timeshift_hours);
		$parser = isset($this->parsers[$epg_info->parser_name])? $this->parsers[$epg_info->parser_name] : null;

		if (isset($parser))
		{
			if (!empty($epg_info->channel_id) && method_exists($parser, 'get_epg_event_by_id'))
				$event = $parser->get_epg_event_by_id($epg_info->channel_id . (empty($epg_info->region_id)? '' : '@' . $epg_info->region_id), $event_id, $timeshift_hours);
		}

		if (is_array($event))
			return $event;

		return false;
	}

	protected function download_day_epg($ext_epg_id, $day_start_ts, $timeshift_hours)
	{
		$epg = null;
		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);
		$epg_info = $this->parse_epg_id($ext_epg_id);
		$parser = isset($this->parsers[$epg_info->parser_name])? $this->parsers[$epg_info->parser_name] : null;
		$global_timeshift_hours = isset($this->global_params[$epg_info->parser_name]['timeshift_hours'])? $this->global_params[$epg_info->parser_name]['timeshift_hours'] : 0;
		$timeshift_hours = empty($timeshift_hours)? $global_timeshift_hours : ($timeshift_hours + $global_timeshift_hours);
		$time = time();
		$attempts = 0;

		while((time() - $time < 30) && ($attempts < 2))
		{
			$attempts++;

			if (isset($parser))
			{
				if (!empty($epg_info->channel_id) && method_exists($parser, 'get_day_epg_by_id'))
					$epg = $parser->get_day_epg_by_id($epg_info->channel_id . (empty($epg_info->region_id)? '' : '@' . $epg_info->region_id), $day_start_ts, $timeshift_hours);
				else if (!empty($epg_info->channel_url) && method_exists($parser, 'get_day_epg_by_url'))
					$epg = $parser->get_day_epg_by_url($epg_info->channel_url, $day_start_ts, $timeshift_hours);
			}
			else
				break;

			if (is_array($epg))
			{
				$this->raw_cache[$epg_id][$day_start_ts] = $epg;

				return
					ksort($this->raw_cache[$epg_id]);
			}

			usleep(500000);
		}

		$this->raw_cache[$epg_id][$day_start_ts] = array();
		return false;
	}

	protected function load_day_epg($ext_epg_id, $day_start_ts, $timeshift_hours)
	{
		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);
		$epg_info = $this->parse_epg_id($ext_epg_id);
		$global_timeshift_hours = isset($this->global_params[$epg_info->parser_name]['timeshift_hours'])? $this->global_params[$epg_info->parser_name]['timeshift_hours'] : 0;
		$timeshift_hours = empty($timeshift_hours)? $global_timeshift_hours : ($timeshift_hours + $global_timeshift_hours);

		if (!empty($this->raw_cache[$epg_id][$day_start_ts]))
			return $this->raw_cache[$epg_id][$day_start_ts];

		if (!empty($this->epg_disk_cache_path))
			foreach (glob("{$this->epg_disk_cache_path}/{$epg_id}_{$day_start_ts}_*") as $file)
			{
				$timeshift_secs = empty($timeshift_hours)? 0 : $timeshift_hours * 3600;
				$epg = unserialize(file_get_contents($file));

				if (!empty($timeshift_secs))
					foreach($epg as $key => $value)
					{
						$value[ExtEpgProgram::start_tm] += $timeshift_secs;
						$value[ExtEpgProgram::end_tm] += $timeshift_secs;
						$this->raw_cache[$epg_id][$day_start_ts][$key + $timeshift_secs] = $value;
					}
				else
					$this->raw_cache[$epg_id][$day_start_ts] = $epg;

				hd_print('Tv program loaded from disk cache "' . basename($file) . '"');

				return
					ksort($this->raw_cache[$epg_id]);
			}

		return false;
	}

	protected function save_day_epg($ext_epg_id, $day_start_ts, $timeshift_hours)
	{
		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);
		$epg_info = $this->parse_epg_id($ext_epg_id);
		$global_timeshift_hours = isset($this->global_params[$epg_info->parser_name]['timeshift_hours'])? $this->global_params[$epg_info->parser_name]['timeshift_hours'] : 0;
		$timeshift_hours = empty($timeshift_hours)? $global_timeshift_hours : ($timeshift_hours + $global_timeshift_hours);

		if (!empty($this->epg_disk_cache_path) && !empty($this->raw_cache[$epg_id][$day_start_ts]))
		{
			$file = "{$this->epg_disk_cache_path}/{$epg_id}_{$day_start_ts}_" . ($day_start_ts + 86400 * ($this->parsers[$epg_info->parser_name]->get_past_days() + 2));
			$timeshift_secs = empty($timeshift_hours)? 0 : $timeshift_hours * 3600;

			if (!empty($timeshift_secs))
				foreach($this->raw_cache[$epg_id][$day_start_ts] as $key => $value)
				{
					$value[ExtEpgProgram::start_tm] -= $timeshift_secs;
					$value[ExtEpgProgram::end_tm] -= $timeshift_secs;
					$epg[$key - $timeshift_secs] = $value;
				}
			else
				$epg = $this->raw_cache[$epg_id][$day_start_ts];

			if (!empty($epg))
				if (file_put_contents($file, serialize($epg)) > 0)
					hd_print('Tv program saved to disk cache "' . basename($file) . '"');

			return true;
		}

		return false;
	}

	///////////////////////////////////////////////////////////////////////////

	public function get_groups()
	{
		$fav = array();

		if ($this->tv_info[PluginTvInfo::favorites_supported])
		{
			$fav[] = array
			(
				PluginTvGroup::id => $this->tv_fav_channels_group_id,
				PluginTvGroup::caption => $this->tv_fav_channels_group_name,
				PluginTvGroup::icon_url => $this->tv_info[PluginTvInfo::favorites_icon_url],
			);
		}

		return
			 array_merge($fav, empty($this->tv_info[PluginTvInfo::groups])? array() : $this->tv_info[PluginTvInfo::groups]);
	}

	public function get_group($group_id)
	{
		if ($group_id == $this->tv_fav_channels_group_id)
			return array
			(
				PluginTvGroup::id => $this->tv_fav_channels_group_id,
				PluginTvGroup::caption => $this->tv_fav_channels_group_name,
				PluginTvGroup::icon_url => $this->tv_info[PluginTvInfo::favorites_icon_url],
			);

		if (isset($this->groups[$group_id]))
			return $this->groups[$group_id];

		hd_print('Error! Failed value $group_id (' . __FILE__ . ' line ' . __LINE__ . ')');
		return null;
	}

	public function get_channels()
	{
		return $this->tv_info[PluginTvInfo::channels];
	}

	public function get_channel($channel_id)
	{
		if (isset($this->channels[$channel_id]))
			return $this->channels[$channel_id];

		hd_print('Warning! Failed value $channel_id (' . __FILE__ . ' line ' . __LINE__ . ')');

		return null;
	}

	public function get_tv_fav_channels_group_id()
	{
		return $this->tv_fav_channels_group_id;
	}

	public function get_tv_fav_channels_group_name()
	{
		return $this->tv_fav_channels_group_name;
	}

	public function get_parser_info($channel_id)
	{
		if (empty($channel_id))
			return null;

		$ext_epg_id = $this->channels[$channel_id][ExtTvChannel::ext_epg_id];
		$epg_info = $this->parse_epg_id($ext_epg_id);

		if (!isset($this->parsers[$epg_info->parser_name]) || ($epg_info->parser_name == 'empty'))
			return null;

		$epg_info->thumbnails_support = $this->parsers[$epg_info->parser_name]->thumbnails_support();

		return $epg_info;
	}

	public function get_epg_data($channel_id, $day_start_ts, $program_start_ts)
	{
		if (empty($channel_id) || empty($day_start_ts) || empty($program_start_ts))
			return null;

		$ext_epg_id = $this->channels[$channel_id][ExtTvChannel::ext_epg_id];
		$epg_info = $this->parse_epg_id($ext_epg_id);

		if (!isset($this->parsers[$epg_info->parser_name]) || ($epg_info->parser_name == 'empty'))
			return null;

		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);

		return
			isset($this->raw_cache[$epg_id][$day_start_ts][$program_start_ts])? $this->raw_cache[$epg_id][$day_start_ts][$program_start_ts] : null;
	}

    public function get_tv_favorites()
    {
    	return
    		is_array($this->fav_channel_ids)? $this->fav_channel_ids : array();
	}

    public function set_tv_favorites($fav_channels_ids)
    {
    	$this->fav_channel_ids = array();

    	if (is_array($fav_channels_ids))
			foreach ($fav_channels_ids as $channel_id)
			{
				if (!isset($this->channels[$channel_id]))
					continue;

				$this->fav_channel_ids[] = $channel_id;
			}
	}

	public function tv_info_set_faulty($faulty = false)
	{
		$this->tv_info_faulty = $faulty;

		if ($faulty)
			$this->tv_info = null;
	}

	///////////////////////////////////////////////////////////////////////////

	public function __construct($params = null)
	{
		$this->global_params = $params;
		$this->parsers = array('empty' => '');

		// Инициализация нулевой таймзоны
		if (date_default_timezone_get() <> 'UTC')
			date_default_timezone_set('UTC');

		foreach(glob(dirname(__FILE__) . '/parsers/*.php') as $php_src_file)
			require_once($php_src_file);

		foreach(get_declared_classes() as $class)
			if (is_subclass_of($class, 'DefaultEpgParser'))
			{
				$parser = new $class($this);
				$this->parsers[$parser->get_parser_id()] = $parser;
			}
	}

	///////////////////////////////////////////////////////////////////////////

	public function init(&$tv_info, $tv_fav_channels_group_id, $tv_all_channels_group_id, $epg_enable, $ext_epg_enable, $force_program_gen = false)
	{
		if (empty($tv_info[PluginTvInfo::show_group_channels_only]))
			throw new Exception(get_class($this).': FALSE value of param PluginTvInfo::show_group_channels_only not supported!');

		$this->tv_info_faulty = true;
		$this->epg_ids = array();
		$this->groups = array();
		$this->channels = array();
		$this->resave_days = array();
		$this->force_program_gen = $force_program_gen;
		$this->tv_fav_channels_group_name = get_system_language_string_value('dune_tv_favorites');
		$this->tv_fav_channels_group_id = $tv_fav_channels_group_id;
		$this->tv_all_channels_group_id = $tv_all_channels_group_id;
		$this->time_zone_offset = isset($tv_info[PluginTvInfo::epg_day_shift_sec])? $tv_info[PluginTvInfo::epg_day_shift_sec] : 0;

		// Инициализация нулевой таймзоны
		if (date_default_timezone_get() <> 'UTC')
			date_default_timezone_set('UTC');

		// Инициализация дискового кеша
		if (empty($this->epg_disk_cache_path))
		{
			while(1)
			{
				$base_dir = get_paved_path(str_replace(DuneSystem::$properties['plugin_name'], get_class($this), DuneSystem::$properties['data_dir_path']));

				if (get_platform_kind() <> 'android')
					if (preg_match('/flashdata\/plugins_data/', $base_dir))
						break;

				if (disk_free_space($base_dir) < 30000000)
					break;

				clearstatcache();
				$this->epg_disk_cache_path = get_paved_path("$base_dir/cache");
				hd_print("EPG disk cache initialized in \"{$this->epg_disk_cache_path}\"");

				if (!file_exists($app_hash_path = get_paved_path("$base_dir/apps") . '/' . md5(DuneSystem::$properties['plugin_name'])))
				{
					$ini = '';
					$info['name'] = DuneSystem::$properties['plugin_name'];
					$info['path'] = DuneSystem::$properties['install_dir_path'];
					$plugin_info = get_plugin_info('entry_point');

					foreach($plugin_info['entry_point'] as $plugin_entry_point)
						if (!preg_match('/setup/i', $plugin_entry_point['parent_media_url']) && (($plugin_entry_point['parent_media_url'] == 'root://tv') || !isset($info['icon_url'])))
						{
							if (isset($plugin_entry_point['caption']))
								$info['caption'] = $plugin_entry_point['caption'];

							if (isset($plugin_entry_point['icon_url']))
								$info['icon_url'] = preg_replace('/plugin_file:\/+/', $info['path'] . '/', $plugin_entry_point['icon_url']);
							else if (isset($info['icon_url']))
								unset($info['icon_url']);

							if (isset($plugin_entry_point['small_icon_url']))
								$info['small_icon_url'] = preg_replace('/plugin_file:\/+/', $info['path'] . '/', $plugin_entry_point['small_icon_url']);
							else if (isset($info['small_icon_url']))
								unset($info['small_icon_url']);

							if (isset($plugin_entry_point['badge_icon_url']))
								$info['badge_icon_url'] = preg_replace('/plugin_file:\/+/', $info['path'] . '/', $plugin_entry_point['badge_icon_url']);
							else if (isset($info['badge_icon_url']))
								unset($info['badge_icon_url']);

							if (isset($plugin_entry_point['override_default_badge']))
								$info['override_default_badge'] = $plugin_entry_point['override_default_badge'];
							else if (isset($info['override_default_badge']))
								unset($info['override_default_badge']);

							if (isset($plugin_entry_point['icon_fit_scale_factor']))
								$info['icon_fit_scale_factor'] = $plugin_entry_point['icon_fit_scale_factor'];
							else if (isset($info['icon_fit_scale_factor']))
								unset($info['icon_fit_scale_factor']);

							if ($plugin_entry_point['parent_media_url'] == 'root://tv')
								break;
						}

					if (!isset($info['caption']))
						$info['caption'] = $plugin_info['app_caption'];

					foreach($info as $k => $v)
						$ini .= "$k = $v\n";

					file_put_contents($app_hash_path, rtrim($ini, "\n"));
				}

				break;
			}

			// Очистка дискового кеша
			if (!empty($this->epg_disk_cache_path))
			{
				$ts = time();

				foreach (glob("{$this->epg_disk_cache_path}/*") as $file)
				{
					$fname_chunks = explode('_', basename($file));

					if (isset($fname_chunks[2]) && ($ts > $fname_chunks[2]))
					{
						unlink($file);
						hd_print("Removed from EPG disk cache (outdated) \"$file\"");
					}
				}
			}
		}

		if (!empty($this->epg_disk_cache_path))
			if (!is_dir($this->epg_disk_cache_path))
				$this->epg_disk_cache_path = get_paved_path($this->epg_disk_cache_path);

		// Инициализация массива групп
		if (!empty($tv_info[PluginTvInfo::groups]))
			foreach($tv_info[PluginTvInfo::groups] as $group)
				$this->groups[$group[PluginTvGroup::id]] = $group;

		// Инициализация массива тв-каналов
		foreach($tv_info[PluginTvInfo::channels] as &$channel)
		{
			if (!isset($channel[ExtTvChannel::id]))
				throw new Exception(get_class($this).': Not set channel ID!');

			$channel_id = $channel[ExtTvChannel::id];
			$epg_id = empty($channel[ExtTvChannel::ext_epg_id])? 'empty' : $channel[ExtTvChannel::ext_epg_id];
			$h = ($tv_info[PluginTvInfo::epg_font_size] == PLUGIN_FONT_SMALL)? 25 : 40;
			$epg_icon_width = max(min(isset($channel[ExtTvChannel::epg_icon_width])? $channel[ExtTvChannel::epg_icon_width] : 40, $h), 0);
			$epg_icon_height = max(min(isset($channel[ExtTvChannel::epg_icon_height])? $channel[ExtTvChannel::epg_icon_height] : 40, $h), 0);
			$epg = $this->parse_epg_id($epg_id);

			if (isset($this->parsers[$epg->parser_name]))
			{
				$epg_id_md5_hash = md5($epg_id);
				$channel[ExtTvChannel::past_epg_days] = min(round((isset($channel[ExtTvChannel::archive_past_sec])? $channel[ExtTvChannel::archive_past_sec] : PHP_INT_MAX) / 86400), ($epg_id == 'empty')? 0 : $this->parsers[$epg->parser_name]->get_past_days());
				$channel[ExtTvChannel::future_epg_days] = ($epg_id == 'empty')? 0 : $this->parsers[$epg->parser_name]->get_future_days();
				$channel[ExtTvChannel::epg_icon_width] = $epg_icon_width;
				$channel[ExtTvChannel::epg_icon_height] = $epg_icon_height;
				$channel[ExtTvChannel::epg_icon_x_offset] = (($epg_icon_width - $h) / 2) + floor($h / 6);
				$channel[ExtTvChannel::epg_icon_y_offset] = (($epg_icon_height - $h) / 2) + floor($h / 8);
				$channel[ExtTvChannel::ext_epg_enabled] = ($epg_id == 'empty')? false : ($ext_epg_enable && defined('PluginTvInfo::ext_epg_enabled') && $this->parsers[$epg->parser_name]->thumbnails_support());

				while(1)
				{
					if (isset($this->epg_ids[$epg_id_md5_hash]))
					{
						$epg_id_md5_hash = md5($epg_id_md5_hash);
						continue;
					}

					$this->epg_ids[$epg_id_md5_hash] = array(ExtTvChannel::id => $channel_id, ExtTvChannel::ext_epg_id => $epg_id);
					$channel[ExtTvChannel::ext_epg_id] = $epg_id_md5_hash;
					$this->channels[$channel_id] = $channel;
					break;
				}
			}
			else
				throw new Exception(get_class($this).': EPG service "' . $epg->parser_name . '" not registered!');
		}

		// Список избранных каналов
		$this->fav_channel_ids = array();

		if (is_array($tv_info[PluginTvInfo::favorite_channel_ids]))
			foreach ($tv_info[PluginTvInfo::favorite_channel_ids] as $channel_id)
			{
				if (!isset($this->channels[$channel_id]))
					continue;

				$this->fav_channel_ids[] = $channel_id;
			}

		$tv_info[PluginTvInfo::epg_mode] = $epg_enable? PLUGIN_EPG_USE_DAY_REQUEST : PLUGIN_EPG_DISABLED;

		// Инициализация расширенного телегида
		if ($ext_epg_enable && defined('PluginTvInfo::ext_epg_enabled'))
		{
			if (array_walk($this->epg_ids, create_function('$k,$v,$out','{$out.="$v={$k[ExtTvChannel::id]}\n";}'), &$channel_ids_str) && file_put_contents('/tmp/www/channel_ids.txt', $channel_ids_str) !== false)
			{
				$debug_mode_on = preg_match('/android/', get_platform_kind()) && preg_match('/\/dune_plugins\//', DuneSystem::$properties['install_dir_path']);
				$epg_script_path = $debug_mode_on? '/tmp/www/cgi-bin/epg' : DuneSystem::$properties['install_dir_path'] . '/www/cgi-bin/epg';

				if ($debug_mode_on || !file_exists($epg_script_path) || (filesize($epg_script_path) < 10))
					if (!file_put_contents($epg_script_path, self::epg_script))
						hd_print("Error in '" . get_class($this) . "': can't save $epg_script_path (Line " . __LINE__ . ")");
					else if (system("chmod -R +x \"$epg_script_path\"") === false)
						hd_print("Error in '" . get_class($this) . "': can't chmod $epg_script_path (Line " . __LINE__ . ")");

				$tv_info[PluginTvInfo::ext_epg_enabled] = file_exists('/tmp/www/channel_ids.txt') && file_exists($epg_script_path);
				$tv_info[PluginTvInfo::ext_epg_base_url] = $tv_info[PluginTvInfo::ext_epg_enabled]? ($debug_mode_on? str_replace('/plugins/' . DuneSystem::$properties['plugin_name'], '', DuneSystem::$properties['plugin_cgi_url']) : DuneSystem::$properties['plugin_cgi_url']) : '';
				$tv_info[PluginTvInfo::ext_epg_channel_ids_url] = $tv_info[PluginTvInfo::ext_epg_enabled]? str_replace('/plugins/' . DuneSystem::$properties['plugin_name'], '', DuneSystem::$properties['plugin_www_url']) . 'channel_ids.txt' : '';
			}
			else if (empty($channel_ids_str))
				hd_print("Error in '" . get_class($this) . "': empty of 'channel_ids_str' (Line " . __LINE__ . ")");
			else
				hd_print("Error in '" . get_class($this) . "': can't save /tmp/www/channel_ids.txt (Line " . __LINE__ . ")");
		}

		$this->clear_ram_cache();
		$this->tv_info = $tv_info;
		$this->tv_info_faulty = true;

		hd_print(get_class($this) . ' initialized successfully!');
	}

	public function get_tv_info()
	{
		if (!empty($this->tv_info) && !$this->tv_info_faulty)
		{
			$this->tv_info_faulty = true;
			return $this->tv_info;
		}

		return null;
	}

	public function update_epg_cache()
	{
		if (is_array($this->resave_days))
			foreach($this->resave_days as $channel_id => $days)
				foreach($days as $day_start_ts => $last_event_ts)
					$this->save_day_epg($channel_id, $day_start_ts, empty($this->channels[$channel_id][ExtTvChannel::timeshift_hours])? 0 : $this->channels[$channel_id][ExtTvChannel::timeshift_hours]);

		$this->resave_days = array();
	}

	public function get_disk_cache_info()
	{
		clearstatcache();
		$dir = get_class($this);
		$result =
			array
			(
				array('tag' => 'ramdisk', 'base_dir' => "/tmp/plugins/$dir", 'cache_path' => "/cache"),
				array('tag' => 'flashdata', 'base_dir' => "/flashdata/plugins_data/$dir", 'cache_path' => "/cache"),
				array('tag' => 'persistfs', 'base_dir' => "/persistfs/plugins_data/$dir", 'cache_path' => "/cache"),
			);

		foreach($result as $k => &$v)
		{
			$base_dir = $v['base_dir'] . $v['cache_path'];

			if (file_exists($base_dir))
			{
				$v['files_count'] = 0;
				$v['used_bytes'] = 0;

				foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($base_dir, FilesystemIterator::SKIP_DOTS)) as $file)
				{
					$v['files_count']++;
				    $v['used_bytes'] += $file->getSize();
				}

				$v['apps'] = array();
				$base_dir = $v['base_dir'] . '/apps';

				if (file_exists($base_dir))
					foreach (glob("$base_dir/*") as $file)
					{
					    $ini = parse_ini_file($file, false, INI_SCANNER_RAW);
						$v['apps'][] = array_merge(array('tag' => basename($file)), $ini);
					}
			}
			else
				unset($result[$k]);
		}

		return $result;
	}

	public function get_time_zone_offset()
	{
		return $this->time_zone_offset;
	}

	public function get_day_start_ts($timestamp = null, $timezone_offset_sec = null)
	{
		return
			strtotime(date('d-M-Y', (isset($timestamp)? $timestamp : time()) - (isset($timezone_offset_sec)? $timezone_offset_sec : $this->time_zone_offset)));
	}

	public function get_parsers_names()
	{
		foreach($this->parsers as $id => $object)
			if ($id <> 'empty')
				$result[$id] = $object->get_parser_name();

		return
			isset($result)? $result : array();
	}

	public function parse_epg_id($epg_id_str)
	{
		$arr = explode('@', is_numeric("0x$epg_id_str")? $this->epg_ids[$epg_id_str][ExtTvChannel::ext_epg_id] : $epg_id_str);//$epg_id_str);

		return
			(object) (empty($epg_id_str)? array('parser_name' => '', 'channel_id' => '', 'region_id' => '', 'channel_url' => '') : array_combine(array('parser_name', 'channel_id', 'region_id', 'channel_url'), is_array($arr)? (isset($arr[2])? (isset($arr[1])? array($arr[0], $arr[1], $arr[2], '') : array($arr[0], '', $arr[2], '')) : (isset($arr[1])? (preg_match('/http.*:\/\//i', $arr[1])? array($arr[0], '', '', $arr[1]) : array($arr[0], $arr[1], '', '')) : array($arr[0], '', '', ''))) : array('', '', '', '')));
	}

	public function get_parsers_list()
	{
		return array_keys($this->parsers);
	}

	public function get_reminders_list(&$plugin_cookies)
	{
		$ts = time();

		if (!isset($plugin_cookies->tv_reminders) || ($plugin_cookies->tv_reminders == ''))
			$plugin_cookies->tv_reminders = serialize(array());

		$change = false;
		$reminders = unserialize($plugin_cookies->tv_reminders);

		foreach ($reminders as $channel_id => $broadcasts)
		{
			$epg_info = $this->parse_epg_id($this->channels[$channel_id][ExtTvChannel::ext_epg_id]);

			if (!isset($this->parsers[$epg_info->parser_name]) || ($epg_info->parser_name == 'empty'))
			{
				$change = true;
				unset($reminders[$channel_id]);
				continue;
			}

			foreach ($broadcasts as $start_ts => $day_ts)
				if ($ts - $start_ts > $this->channels[$channel_id][ExtTvChannel::archive_past_sec] - 60)
				{
					$change = true;
					unset($reminders[$channel_id][$start_ts]);

					if (empty($reminders[$channel_id]))
						unset($reminders[$channel_id]);
				}
		}

		if ($change)
			$this->set_reminders_list($reminders, $plugin_cookies);

		return $reminders;
	}

	public function set_reminders_list($reminders, &$plugin_cookies)
	{
		$plugin_cookies->tv_reminders = serialize(is_array($reminders)? $reminders : array());
	}

	public function clear_ram_cache()
	{
		foreach($this->parsers as $name => $parser)
			if (is_object($parser))
				$parser->clear_cache();

		$this->raw_cache = array();
		$this->tv_info = null;
	}

	public function clear_cache()
	{
		$this->clear_ram_cache();
		$app_hash = md5(DuneSystem::$properties['plugin_name']);

		foreach($this->get_disk_cache_info() as $cache_info)
		{
			$apps_empty = true;
			$app_found = false;

			foreach($cache_info['apps'] as $app)
				if ($app['tag'] == $app_hash)
					$app_found = unlink($cache_info['base_dir'] . "/apps/$app_hash");
				else
					$apps_empty = false;

			if ($app_found || $apps_empty)
			{
				shell_exec("rm -rf " . $cache_info['base_dir'] . $cache_info['cache_path'] . '/*');
				rmdir($cache_info['base_dir'] . $cache_info['cache_path']);
				shell_exec("rm -rf " . $cache_info['base_dir'] . '/apps/*');
				rmdir($cache_info['base_dir'] . '/apps');
				rmdir($cache_info['base_dir']);
			}
		}
	}

	public function get_program_info($channel_id, $program_ts, $download = true)
	{

		$epg = array();
		$day_ts = $this->get_day_start_ts($program_ts);
		$ext_epg_id = $this->channels[$channel_id][ExtTvChannel::ext_epg_id];
		$timeshift_hours = empty($this->channels[$channel_id][ExtTvChannel::timeshift_hours])? 0 : $this->channels[$channel_id][ExtTvChannel::timeshift_hours];
		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);
		$epg_info = $this->parse_epg_id($ext_epg_id);

		if (isset($this->parsers[$epg_info->parser_name]) && ($epg_info->parser_name <> 'empty'))
			while(1)
			{
				$downloaded = false;

				if (empty($this->raw_cache[$epg_id][$day_ts]))
					if (!$this->load_day_epg($ext_epg_id, $day_ts, $timeshift_hours))
						if (!$download || (!$downloaded = $this->download_day_epg($ext_epg_id, $day_ts, $timeshift_hours)))
							return null;

				if (!empty($this->raw_cache[$epg_id][$day_ts]))
				{
					$found_ts = 0;
					$channel_day_epg = $this->raw_cache[$epg_id][$day_ts];

					if (!empty($channel_day_epg))
					{
						foreach($channel_day_epg as $item)
							if (($program_ts >= $item[ExtEpgProgram::start_tm]) && ($program_ts < $item[ExtEpgProgram::end_tm]))
							{
								$found_ts = $item[ExtEpgProgram::start_tm];
								break;
							}

						if (!empty($found_ts))
						{
							if (!empty($this->epg_disk_cache_path))
							{
								$begin_item = reset($this->raw_cache[$epg_id][$day_ts]);
								$end_item = end($this->raw_cache[$epg_id][$day_ts]);

								if ($end_item[ExtEpgProgram::end_tm] - $begin_item[ExtEpgProgram::start_tm] >= 75600)
									if ($downloaded && !$this->save_day_epg($ext_epg_id, $day_ts, $timeshift_hours))
									{
										hd_print('Error! Can\'t save tv program to disk cache. Subsequent disk cache operations will be canceled!');
										$this->epg_disk_cache_path = '';
									}
							}

							if (!preg_match('/Телепередача\s\d+$/iu', $item['title']))
								return $item;

							return null;
						}
						else
						{
							$item = reset($channel_day_epg);

							if ($item[ExtEpgProgram::start_tm] > $program_ts)
								$day_ts -= 86400;
							else
								return null;
						}
					}
					else
						return null;
				}
				else
					return null;
			}

		return null;
	}

	public function get_program_event($channel_id, $event_ts)
	{
		$day_ts = $this->get_day_start_ts($event_ts);
		$ext_epg_id = $this->channels[$channel_id][ExtTvChannel::ext_epg_id];
		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);
		$timeshift_hours = empty($this->channels[$channel_id][ExtTvChannel::timeshift_hours])? 0 : $this->channels[$channel_id][ExtTvChannel::timeshift_hours];

		foreach($this->raw_cache[$epg_id] as $day_ts => &$day_epg)
			foreach($day_epg as $ts => &$event_data)
				if ($ts == $event_ts)
					if (!is_null($event_data[ExtEpgProgram::id]))
					{
						if (is_array($event = $this->download_epg_event($ext_epg_id, $event_data[ExtEpgProgram::id], $timeshift_hours)))
						{
							$event_data = $event;
							$this->resave_days[$ext_epg_id][$day_ts] = $ts;
							return $event;
						}
						else
							return $event_data;
					}
					else
						return $event_data;
	}

	public function get_day_slice($channel_id, &$day_start_ts)
	{
		static	$last_result;

		$day_start_ts = max(0, $day_start_ts);

		if (empty($day_start_ts))
		{
			$day_start_ts = null;
			return false;
		}

		// reveal $today_start_ts /////////////////////////////////////////////

		$live_program_start_ts = 0;
		$live_ts = strtotime(date('d-M-Y H:i'));
		$timeshift_hours = empty($this->channels[$channel_id][ExtTvChannel::timeshift_hours])? 0 : $this->channels[$channel_id][ExtTvChannel::timeshift_hours];
		$ext_epg_id = $this->channels[$channel_id][ExtTvChannel::ext_epg_id];
		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);
		$epg_info = $this->parse_epg_id($ext_epg_id);

		if (!isset($last_result["$epg_id.$live_ts"]))
		{
			$days = array();
			$today_start_ts = null;

			while(1)
			{
				if (!empty($today_start_ts) && !in_array($today_start_ts, $days))
				{
					array_push($days, $today_start_ts);

					if (!$this->load_day_epg($ext_epg_id, $today_start_ts, $timeshift_hours))
					{
						$day_start_ts = isset($this->raw_cache[$epg_id][$today_start_ts])? null : $today_start_ts;
						return false;
					}
				}

				if (isset($this->raw_cache[$epg_id], $this->parsers[$epg_info->parser_name]) && ($epg_info->parser_name <> 'empty'))
				{
					$program_start_ts = 0;
					$channel_epg = &$this->raw_cache[$epg_id];

					foreach (array_keys($channel_epg) as $day_ts)
					{
						reset($channel_epg[$day_ts]);

						while (list($key) = each($channel_epg[$day_ts]))
						{
							if ($key >= $live_ts)
							{
								$live_program_start_ts = $program_start_ts;

								if (empty($program_start_ts))
									break 2;
								else
									break 3;
							}

							$program_start_ts = $key;
						}
					}

					if (empty($live_program_start_ts))
					{
						$today = $this->get_day_start_ts($live_ts);

						if (empty($program_start_ts))
							$today_start_ts = isset($channel_epg[$today])? (isset($channel_epg[$today - 86400])? (isset($channel_epg[$today + 86400])? null : $today + 86400) : $today - 86400) : $today;

						if (in_array($today_start_ts, $days))
						{
							$today_start_ts = $today;
							$live_program_start_ts = $live_ts;
							break;
						}

						if (!empty($today_start_ts))
							continue;

						$live_program_start_ts = $live_ts;
					}
				}
				else
				{
					$today_start_ts = (isset($this->raw_cache[$epg_id]) || !isset($this->parsers[$epg_info->parser_name]) || ($epg_info->parser_name == 'empty'))? null : $this->get_day_start_ts($live_ts);// - $this->time_zone_offset);

					if (!empty($today_start_ts))
						continue;
				}

				break;
			}

			if (empty($live_program_start_ts))
			{
				$day_start_ts = $today_start_ts;
				return false;
			}

			$today_start_ts = $this->get_day_start_ts($live_program_start_ts);
			$last_result["$epg_id.$live_ts"] = $today_start_ts;
		}
		else
			$today_start_ts = $last_result["$epg_id.$live_ts"];

		///////////////////////////////////////////////////////////////////////

		$ext_epg_id = $this->channels[$channel_id][ExtTvChannel::ext_epg_id];
		$epg_info = $this->parse_epg_id($ext_epg_id);
		$past_days = $this->parsers[$epg_info->parser_name]->get_past_days();
		$future_days = $this->parsers[$epg_info->parser_name]->get_future_days();
		$find_day_start_ts = $this->get_day_start_ts($day_start_ts);
		$day_length_sec = 86400;

		if (($today_start_ts - $find_day_start_ts > 86400 * $past_days) ||
			($find_day_start_ts - $today_start_ts - $day_length_sec >= 86400 * $future_days))
		{
			$day_start_ts = null;
			return false;
		}

		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);
		$days = array();
		$day_ts = $find_day_start_ts;
		$begin_day_ts = -1;
		$end_day_ts = -1;
		$begin_item_ts = $find_day_start_ts + $this->time_zone_offset;
		$end_item_ts = $find_day_start_ts + $day_length_sec + $this->time_zone_offset;

		while(1)
		{
			if (!isset($this->raw_cache[$epg_id][$day_ts]) && !in_array($day_ts, $days))
			{
				array_push($days, $day_ts);

				if (!$this->load_day_epg($ext_epg_id, $day_ts, $timeshift_hours))
					if (($today_start_ts - $day_ts <= 86400 * $past_days) && ($day_ts - $today_start_ts <= 86400 * $future_days))
					{
						$day_start_ts = $day_ts;
						return false;
					}
			}

			$day = isset($this->raw_cache[$epg_id][$day_ts])? $this->raw_cache[$epg_id][$day_ts] : array();

			if ($begin_day_ts < 0)
			{
				if (!($item = reset($day)) || ($item[ExtEpgProgram::start_tm] > $begin_item_ts))
				{
					if ($day_ts < $find_day_start_ts)
					{
						$begin_day_ts = $day_ts;
						$day_ts = $find_day_start_ts;
						continue;
					}

					$day_ts -= 86400;
					continue;
				}

				$begin_day_ts = $day_ts;
				$day_ts = $find_day_start_ts;
				continue;
			}

			if (!($item = end($day)) || ($item[ExtEpgProgram::start_tm] < $end_item_ts))
			{
				if ($day_ts > $find_day_start_ts)
				{
					$end_day_ts = $day_ts;
					break;
				}

				$day_ts += 86400;
				continue;
			}

			$end_day_ts = $day_ts;
			break;
		}

		$epg_uts_start = 0;

		foreach ($this->raw_cache[$epg_id] as $day_ts => $day_epg)
		{
			if (!empty($begin_day_ts) && ($day_ts < $begin_day_ts))
				continue;

			if (!empty($end_day_ts) && ($day_ts > $end_day_ts))
				break;

			foreach ($day_epg as $key => $epg)
			{
				if ($key < $begin_item_ts)
					continue;

				$rounded_start_ts = strtotime(date('d-M-Y H:i', $key));

				if (preg_match('/перерыв.+вещани/iu', $epg[ExtEpgProgram::title], $matches))
					continue;

				if (isset($start_ts))
					$epg_arr[$start_ts][PluginTvEpgProgram::end_tm_sec] = $rounded_start_ts;

				if ($key >= $end_item_ts)
					break;

				$start_ts = $key;
				$epg_arr[$start_ts] =
					array
					(
						PluginTvEpgProgram::start_tm_sec => $rounded_start_ts,
						PluginTvEpgProgram::end_tm_sec => ($epg[ExtEpgProgram::end_tm] - $start_ts >= 60)? strtotime(date('d-M-Y H:i', $epg[ExtEpgProgram::end_tm])) : null,
						'day_ts' => $day_ts,
					);
			}
		}

		if (empty($epg_arr))
		{
			$day_start_ts = null;
			return false;
		}

		return array_values($epg_arr);
	}

	public function get_day_epg($channel_id, $day_start_ts, &$plugin_cookies, $ext_icon_url = '')
	{
		$ext_epg_id = $this->channels[$channel_id][ExtTvChannel::ext_epg_id];
		$epg_id = md5($this->epg_ids[$ext_epg_id][ExtTvChannel::ext_epg_id]);
		$timeshift_hours = empty($this->channels[$channel_id][ExtTvChannel::timeshift_hours])? 0 : $this->channels[$channel_id][ExtTvChannel::timeshift_hours];
		$download_days = array();

		while(1)
		{
			$day_ts = $day_start_ts;

			if ($day_epg = $this->get_day_slice($channel_id, $day_ts))
				break;

			if (empty($day_ts))
				break;

			if (!isset($this->raw_cache[$epg_id][$day_ts]))
			{
				if ($this->download_day_epg($ext_epg_id, $day_ts, $timeshift_hours))
				{
					array_push($download_days, $day_ts);
					continue;
				}
			}

			break;
		}

		$epg_info = $this->parse_epg_id($ext_epg_id);
		$reminders = $this->get_reminders_list($plugin_cookies);
		$json_fname = get_paved_path('/tmp/ext_epg/') . "/$channel_id-" . strftime('%Y-%m-%d', $day_start_ts) . '.json';

		if (!empty($day_epg))
		{
			foreach($day_epg as $item)
			{
				$start_tm_sec = $item[PluginTvEpgProgram::start_tm_sec];
				$epg = $this->raw_cache[$epg_id][$item['day_ts']][$item[PluginTvEpgProgram::start_tm_sec]];
				$epg_program_desc = '';
				$epg_program_name = trim($epg[ExtEpgProgram::title]);

				if (defined('PluginTvInfo::ext_epg_enabled') && !empty($this->tv_info[PluginTvInfo::ext_epg_enabled]) && $this->parsers[$epg_info->parser_name]->thumbnails_support() && !file_exists($json_fname))
				{
					$json[$start_tm_sec] =
						array
						(
							ExtEpgProgram::start_tm => $start_tm_sec,
							ExtEpgProgram::title => preg_replace('/(х\/ф|т\/с)\s+/iu', '', $epg[ExtEpgProgram::title]),
							ExtEpgProgram::sub_title => isset($epg[ExtEpgProgram::sub_title])? $epg[ExtEpgProgram::sub_title] : '',
							ExtEpgProgram::desc => preg_replace('/(!|\?)\.+\s*$/Uu', '$1', $epg[ExtEpgProgram::desc]),
							ExtEpgProgram::main_icon => (isset($epg[ExtEpgProgram::main_icon]) && !empty($epg[ExtEpgProgram::main_icon]))? $epg[ExtEpgProgram::main_icon] : (!empty($ext_icon_url)? $ext_icon_url : ''),
							ExtEpgProgram::main_category => isset($epg[ExtEpgProgram::main_category])? $epg[ExtEpgProgram::main_category] : '',
						);

					if (!empty($epg[ExtEpgProgram::country]))
						$json[$start_tm_sec][ExtEpgProgram::country] = $epg[ExtEpgProgram::country];
					if (!empty($epg[ExtEpgProgram::year]))
						$json[$start_tm_sec][ExtEpgProgram::year] = $epg[ExtEpgProgram::year];
					if (!empty($epg[ExtEpgProgram::presenter]))
						$json[$start_tm_sec][ExtEpgProgram::presenter] = $epg[ExtEpgProgram::presenter];
					if (!empty($epg[ExtEpgProgram::director]))
						$json[$start_tm_sec][ExtEpgProgram::director] = $epg[ExtEpgProgram::director];
					if (!empty($epg[ExtEpgProgram::actor]))
						$json[$start_tm_sec][ExtEpgProgram::actor] = $epg[ExtEpgProgram::actor];
					if (!empty($epg[ExtEpgProgram::writer]))
						$json[$start_tm_sec][ExtEpgProgram::writer] = $epg[ExtEpgProgram::writer];
					if (!empty($epg[ExtEpgProgram::composer]))
						$json[$start_tm_sec][ExtEpgProgram::composer] = $epg[ExtEpgProgram::composer];
					if (!empty($epg[ExtEpgProgram::operator]))
						$json[$start_tm_sec][ExtEpgProgram::operator] = $epg[ExtEpgProgram::operator];
					if (isset($epg[ExtEpgProgram::icon_urls]))
						$json[$start_tm_sec][ExtEpgProgram::icon_urls] = $epg[ExtEpgProgram::icon_urls];
					if (!empty($epg[ExtEpgProgram::imdb_rating]))
						$json[$start_tm_sec][ExtEpgProgram::imdb_rating] = $epg[ExtEpgProgram::imdb_rating];
				}
				else
				{
					if (!empty($epg[ExtEpgProgram::sub_title]) && !preg_match("/{$epg[ExtEpgProgram::sub_title]}/iu", $epg_program_name))
						$epg_program_name .= (preg_match('/[,.:;!?]$/', $epg_program_name)? ' ' : '. ') . $epg[ExtEpgProgram::sub_title];

					if (!empty($epg[ExtEpgProgram::main_category]))
						$epg_program_desc .= preg_match('/^(х\/ф|т\/с)\s+/iu', $epg[ExtEpgProgram::title])? preg_replace('/\](.*)$/uU', ']', $epg[ExtEpgProgram::main_category]) . ' ' : $epg[ExtEpgProgram::main_category] . ', ';

					$epg_program_desc .= rtrim(str_replace(array(' ,', "\n"), array('', ' '), (empty($epg[ExtEpgProgram::country])? '' : $epg[ExtEpgProgram::country] . ', ') . (empty($epg[ExtEpgProgram::year])? '' : $epg[ExtEpgProgram::year] . ', ')), ', ') . ', ';// . (empty($epg[ExtEpgProgram::genre])? '' : (preg_match('/(х\/ф|т\/с)\s+/iu', $epg[ExtEpgProgram::title])? '' : $epg[ExtEpgProgram::genre] . ', ')));

					if (!empty($epg[ExtEpgProgram::director]))
					{
						if (!empty($epg[ExtEpgProgram::actor]))
						{
							$epg_program_desc .= 'реж. ' . implode(', ', $epg[ExtEpgProgram::director]) . '; актеры: ' . implode(', ', $epg[ExtEpgProgram::actor]) . '; ' .
								(empty($epg[ExtEpgProgram::producer])? '' : '; прод. ' . implode(', ', $epg[ExtEpgProgram::producer]) . '; ');// .
						}
						else
							$epg_program_desc .= 'авт. ' . implode(', ', $epg[ExtEpgProgram::director]);
					}
					else
					{
						$epg_program_desc .= (empty($epg[ExtEpgProgram::actor])? '' : 'актеры: ' . implode(', ', $epg[ExtEpgProgram::actor]) . '; ');

						if (!empty($epg[ExtEpgProgram::presenter]))
							$epg_program_desc .= 'вед. ' . implode(', ', $epg[ExtEpgProgram::presenter]);
					}

					$epg_program_desc = (empty($epg[ExtEpgProgram::desc])? rtrim($epg_program_desc, ' .,;') : rtrim($epg_program_desc, ' !?.,:;')) . '. ';

					if (strlen($epg_program_desc) <= 2)
						$epg_program_desc = '';

					if (!empty($epg[ExtEpgProgram::desc]))
						$epg_program_desc .= str_replace("\n", ' ', $epg[ExtEpgProgram::desc]) . ((strrpos($epg_program_desc, '.') === strlen($epg_program_desc) - 2)? '' : $epg_program_desc . '...');
				}

				$epg_arr[$start_tm_sec] =
					array
					(
						PluginTvEpgProgram::start_tm_sec => $start_tm_sec,
						PluginTvEpgProgram::end_tm_sec => $item[PluginTvEpgProgram::end_tm_sec],
						PluginTvEpgProgram::name => $epg_program_name,
						PluginTvEpgProgram::description => preg_replace('/(!|\?)\.+\s*$/Uu', '$1', $epg_program_desc),
					);

				if (isset($reminders[$channel_id][$start_tm_sec]))
					$epg_arr[$start_tm_sec][PluginTvEpgProgram::icon_url] = PLUGIN_IMG_PATH . '/bell.png';
			}
		}

		if (!empty($this->epg_disk_cache_path))
			while($day_ts = array_pop($download_days))
				if (!empty($this->raw_cache[$epg_id][$day_ts]))
				{
					$begin_item = reset($this->raw_cache[$epg_id][$day_ts]);
					$end_item = end($this->raw_cache[$epg_id][$day_ts]);
					if ($end_item[ExtEpgProgram::end_tm] - $begin_item[ExtEpgProgram::start_tm] > 43200)
						if (!$this->save_day_epg($ext_epg_id, $day_ts, $timeshift_hours))
						{
							hd_print('Error! Can\'t save tv program to disk cache. Subsequent disk cache operations will be canceled!');
							$this->epg_disk_cache_path = '';
							break;
						}
				}

		while(1)
		{
			if (empty($epg_arr) && $this->force_program_gen && $this->channels[$channel_id][ExtTvChannel::have_archive] && ($day_start_ts <= $this->get_day_start_ts()))
			{
				$n = 0;
				for ($start_tm_sec = $day_start_ts + $this->time_zone_offset; $start_tm_sec < $day_start_ts + $this->time_zone_offset + 86400; $start_tm_sec += 3600)
				{
					$epg_program_name = 'Телепередача ' . ++$n;
					$this->raw_cache[$epg_id][$day_start_ts][$start_tm_sec] =
						array
						(
							ExtEpgProgram::id => '',
							ExtEpgProgram::start_tm => $start_tm_sec,
							ExtEpgProgram::end_tm => $start_tm_sec + 3600,
							ExtEpgProgram::title => $epg_program_name,
							ExtEpgProgram::desc => '',
							ExtEpgProgram::main_icon => '',
							ExtEpgProgram::main_category => '',
						);

					$epg_arr[$start_tm_sec] =
						array
						(
							PluginTvEpgProgram::start_tm_sec => $start_tm_sec,
							PluginTvEpgProgram::end_tm_sec => $start_tm_sec + 3600,
							PluginTvEpgProgram::name => $epg_program_name,
							PluginTvEpgProgram::description => '',
						);

					if (isset($reminders[$channel_id][$start_tm_sec]))
						$epg_arr[$start_tm_sec][PluginTvEpgProgram::icon_url] = PLUGIN_IMG_PATH . '/bell.png';

					if (defined('PluginTvInfo::ext_epg_enabled') && !empty($this->tv_info[PluginTvInfo::ext_epg_enabled]) && ($epg_info->parser_name <> 'empty') && $this->parsers[$epg_info->parser_name]->thumbnails_support() && !file_exists($json_fname))
						$json[$start_tm_sec] =
							array
							(
								ExtEpgProgram::start_tm => $start_tm_sec,
								ExtEpgProgram::title => $epg_program_name,
								ExtEpgProgram::sub_title => '',
								ExtEpgProgram::desc => '',
								ExtEpgProgram::main_icon => !empty($ext_icon_url)? $ext_icon_url : '',
								ExtEpgProgram::main_category => '',
							);
				}

				ksort($this->raw_cache[$epg_id]);
			}

			if (!empty($epg_arr))
			{
				$begin_item = reset($epg_arr);
				$end_item = end($epg_arr);

				if ($end_item[PluginTvEpgProgram::end_tm_sec] - $begin_item[PluginTvEpgProgram::start_tm_sec] < 43200)
				{
					$epg_arr = null;
					continue;
				}
			}

			break;
		}

		if (isset($json))
			file_put_contents($json_fname, json_encode_unicode($json));

		return
			isset($epg_arr)? array_values($epg_arr) : array();
	}

}

///////////////////////////////////////////////////////////////////////////////
?>
