<?

// Используются только потомки класса!!!!
class Graph {

	// здесь модель не RGB а BGR!

	const BLACK = 0x000000;
	const WHITE = 0xFFFFFF;
	const SILVER = 0xE6E6E6;
	const GREEN = 0x0eb00e;
	const LIGHT_BLUE = 0xfafafa;

	const FORM_BG = FORM_BG; //0xeaeaea;// цвет формы

	public $active_bg = 0xfafafa;
	public $hidden_bg = self::FORM_BG;

	const SELECT_ACTIVE = 0xdadada;
	const SELECT_HIDDEN = self::FORM_BG;

	const AXIS_ACTIVE = 0xaeaeae;
	const AXIS_HIDDEN = 0x8c8c8c;

	const LABEL_COLOR_ACTIVE = self::BLACK;
	const LABEL_COLOR_HIDDEN = 0x8c8c8c;

	const LABEL_BG_ACTIVE = 0xfafafa;
	const LABEL_BG_HIDDEN = self::FORM_BG;

	public $dotted_color = 0x999999;

	public $name = ''; // имя UI объекта на котором рисуем
	
	public $label_font_size = 8;

	public $col_color = array(self::GREEN, self::LIGHT_BLUE); //основной цвет и цвет когда выделен
	public $col_border_color = 0xfafafa;

	public $zoom_max_width = 1; // масшатибрование графика по вертикали. Чтобы можно было минимальные значения просмотреть нормально. от 0,00001 до 1

	// значение столбца
	public $value_factor = 1;

	public $value_font_size = 7;
	public $value_font_color = self::BLACK;
	public $value_font_bg = 0xfafafa;
	// пофиг, и так нормально выходит. А обрезать у гигабайта все что после запятой слишком жестко. 
	// public $value_max_symbols = 5; //максимальное кол-во символов без учета единицы измерения и запятой в float. Если не вмещаемся, обрезаем после запятой.

	public $col_min_height = 1;// 3; // если столбец слишком маленький, рисуем минимальной высотой
								// так как в размер входит рамка то, меньше 3 пикселей canvas вообще не рисует прямоугольники.
	public $col_max_value = 5; // максимальное значение столбца
	public $col_max_height = 100; // максимальная высота столбца (без надписи сверху)
	
	public $col_width = 35; // ширина столбца

	public $cur_col = 0; //

	public $factor_font_size = 1.5;//1.8; //преобразование размера шрифта в пиксели
	private $select = false; // если выделение сделано то будет написано array('x1', 'x2')
	public $need_redraw_all = false; // эту пометку ставит график когда в результате обновления отдельных столбцов может образоваться грязь

	public $overall_w; // ширина графика в целом
	// public $data_area_width = 500; // ширина поля где размещаются столбцы

	public $keys = array();
	public $key_points = array();

	// Подписи у осей
	public $x_measure = 'size'; // 'speed' 
	public $y_measure = 'size';
	public $x_measure_view = true;
	public $y_measure_view = true;

	public $type; // bars или singlebar (все строки расположены в одном баре друг за другом)
	
	public $value_label = true;

	// отступы права и сверху для данных. В основном это нужно чтобы данные не залезали на стрелки осей.
	public $area_data_margin_right = 10;
	public $area_data_margin_top = 14;

	// начало координат
	public $yZero = 70; 
	public $xZero = 45; 

	public $is_enabled = true; // если false, просто делаем график темнее, типа он заблокирован. А пересчеты все идут. 
							// и надпись на весь график "не используется"

	public $select_sumvalues = 0; // сумма всех выделенных строк
	public $select_col_nomer = 0; // максимальный выделенный столбец

	public $axis; // объект оси
	public $select_bar; // объект селект бар
	public $cols = array(); // массив с объектами col
	public $col_colors = array();
	public $legend = false; // объект Gr_legend если нужен

	// адаптация размер графика и его объектов под размер панели оболочки
	protected $gr_size = false; // Объект Gr_size
	protected $adapt_size = false; // вкл адаптации
	protected $objs_below_graph = false; // имена объектов под графиком

	//значения отдельных строк можно отображать во внешних объекта
	// установка значений происходит так: ui_name->text = $col_value;
	// array(col_name=>ui_name)
	public $external_col_displays = array();

	protected $fmName = 'Fm';

	function __construct($name, $keys, $select_bar_name='', $options='') {
		GLOBAL $log;

		$this->name = $name;

		if($options) {
			foreach($options as $n=>$v) {
				//$log->line('настройка ' . $n);
				$this->$n = $v;
			}
		}

		$graph_ui = c($this->fmName.'->'.$name);

		// убираем рамку
		$graph_ui->bevelOuter = bvNone;

		$this->bitmap = $btm = new TBitmap();
		//$btm->setSizes($graph_ui->w, $graph_ui->h);
		$this->cv = new TControlCanvas($btm);

		$GLOBALS['All_canvas'][] = $this;

		// создаем плоскость
		// на ней ничего не рисуем, только загружаем в нее готовый bitmap
		$this->real_cv = new TControlCanvas;
		$this->real_cv->control = c($this->fmName.'->'.$name, 1);

		$this->keys = $keys;

		// пересчитываем размеры графика
		$this->calc_graph_size(false);

		// оси координат, подписи под названиями столбцов
		$class = "Gr_{$this->type}_axis";
		$this->axis = new $class($this);
		

		if($select_bar_name)
			$this->select_bar = new $select_bar_name($select_bar_name, $this);

		if(isset($options['legend_fields'])) {
			$this->legend = new Gr_legend($this, $options['legend_fields']);
		}

		if($this->adapt_size) {
			$this->gr_size = new Gr_size($this, $this->objs_below_graph);
		}
	}
	
	// адаптация по размеру wrap панели
	function adapt_size() {
		$this->gr_size->adapt_size();
	}

	// обнуляем строки
	function clear_cols() {
		foreach($this->keys as $name) {
			$this->set_col($name, 0);
		}

		$this->redraw_all();
	}

	function set_max_value($col_max_value, $is_redraw=true) {
		if(!($col_max_value and is_numeric($col_max_value)))
			throw new Error_def("Передан неправильный параметр col_max_value"); 

		$this->col_max_value = $col_max_value;
		$this->_calc_value_factor();

		if($is_redraw)
			$this->redraw_all();
	}


	// высчитываем элементы графика (те что рисуем) в зависимости от размера панели графика
	function calc_graph_size($is_redraw=true) {
		GLOBAL $log;

		$graph_ui = get_ui($this->fmName.'->'.$this->name);
		$graph_height  = $graph_ui->h;

		// устанавливаем размер bitmap
		$this->bitmap->setSizes($graph_ui->w, $graph_ui->h);

		$this->col_max_height = 
			$graph_height  
			// - $this->value_font_size * $this->factor_font_size 
			- $this->label_font_size * $this->factor_font_size;

		// если будет 0 то кое где будет деление на 0 что вызовет warning ошибку
		if($this->col_max_height <= 0)
			$this->col_max_height = 1;

		$this->yZero = $graph_height 
						- $this->label_font_size * $this->factor_font_size;

		//print "col:" .  $this->col_max_height ;
		$this->_calc_value_factor();

		$this->overall_w = $graph_ui->w;
		$this->overall_h = $graph_ui->h;

		$max_area_width = $this->overall_w - $this->xZero - $this->area_data_margin_right;
		$this->col_width = floor($max_area_width / sizeof($this->keys));

		$this->area_data = array();
		$this->area_data['w'] = $this->col_width * sizeof($this->keys); // ширина с учетом округления 
		$this->area_data['h'] = $this->overall_h - ($this->overall_h - $this->yZero) - $this->area_data_margin_top;

		//$log->line("area_data['h'] " . $this->area_data['h']);
		
		$this->factor_px_collNomer = $this->area_data['w'] / (sizeof($this->keys));

		if($is_redraw)
			$this->redraw_all();
	}


	// устанавливает размер строки
	public function set_col($key, $value, $is_redraw=true) {
		GLOBAL $log, $Measure_convert;

		//$log->line(time() . " " . $this->name . " set_col: $value");

		$key = (string)$key;

		if(!isset($this->cols[$key])) {
			// не делаем вылет исключения, потому что ошибка не критическая
			$log->line("[Ошибка] в графике $this->name отсутствует столбец с ключом '$key'");
			return false;
		}

		$col = $this->cols[$key];

		//$log->line('set_col: ' .  $sizeKb);

		// обновляем максимальное значение столбца
		$zoom_value = $value * $this->zoom_max_width;
		if($zoom_value > $this->col_max_value) {
			$this->set_max_value($zoom_value, false);
			$this->need_redraw_all = true;
			//$log->line('обновляем максимальное значение');
		}

		// обновляет только если значение изменилось
		if($col->get_value() != $value) {
			//$log->line("set_col(): ключ $key, было " .$col->get_value(). ", стало $value");
			//$this->get_max_value();
			$col->set_value($value, $is_redraw);
			$this->need_redraw_all = true;

			$value_mb = $Measure_convert->pretty_size($value, 'MB');
			// если указан внешний дисплей для строки, отправляем туда значенийе
			if(isset($this->external_col_displays[$key])) {
				$ui_name = $this->external_col_displays[$key];
				c($this->fmName.'->'.$ui_name)->text = $value_mb;
			}

			// если у графика есть легенда, отправляем данные туда
			if($this->legend) {
				$this->legend->set($key, $value_mb);
			}
		}

		return true;
	}
	

	// ищет максимальное значение у столбцов
	public function get_max_value() {
		$values = array();
		foreach($this->cols as $name=>$col) {
			$values[(string)$name] = $col->get_value();
		}
		arsort($values);
		$max_value = reset($values);

		return $max_value;
	}

	// пиксели в значение
	function xAxis_x_to_value($x) {
		GLOBAL $log;

		$approximate_key = $x / $this->factor_px_collNomer;
		$nomer_key = floor($approximate_key);
		$residue = $approximate_key - $nomer_key;

		if($nomer_key >= sizeof($this->keys) and !isset($this->keys[$nomer_key])) {
			// если указан слишком большой x, выдаем последнее значение
			$res = end($this->keys);
		}
		else {
			$integer = $this->keys[$nomer_key];

			// следующее число
			if(isset($this->keys[$nomer_key + 1]))
				$nomer_next_key  = $nomer_key + 1;
			else
				$nomer_next_key = $nomer_key;

			// остаток
			$next_integer = $this->keys[$nomer_next_key];
			$result_residue = $residue * ($next_integer - $integer);

			$res = $integer + $result_residue;
		}
		
		$res = round_nobug($res, 5);

		return $res;
	}

	// значение в пиксели
	function xAxis_value_to_x($v) {
		GLOBAL $log;
		// $mas = array_fill_keys($this->keys, 0);

		list($floor, $ceil, $near) = near_keys($this->cols, $v);
		//$log->line("xAxis_value_to_x()  f:$floor, c:$ceil, n:$near");
		
		$k = array_search($floor, $this->keys, false); //$key = $this->keys[$near];

		//print_r($this->keys);
		//var_dump($k);

		//$log->line("k: $k, near:$near");

		$residue = 0;
		$x = $k * $this->factor_px_collNomer;

		if($ceil and $ceil != $floor) {
			$ostatok = $v - $floor;

			if(isset($this->keys[$k+1]))
				$k_next = $k + 1;
			else
				$k_next = $k;

			$x_next = $k_next * $this->factor_px_collNomer;
			$factor_x_value = ($x_next - $x) / ($ceil - $floor);

			$residue = $ostatok * $factor_x_value;

			//$log->line("x:$x; x_next:$x_next;  residue: $residue;   ");
		}

		$res = $x + $residue;

		return $res;
	}

	// возвращаем имена ближайших столбцов - floor, ceil, near
	function near_cols($mb) {
		return near_keys($this->cols, $mb);
	}

	function select($x1, $x2, $is_redraw=true) {
		GLOBAL $log;

		$new_select_coord = false;
		if(!$this->select or ($this->select['x1'] != $x1 or $this->select['x2'] != $x2))
			$new_select_coord = true;

		$sum_select = 0;
		$x1 = round_nobug($x1);
		$x2 = round_nobug($x2);
		
		$this->select = array('x1'=>$x1, 'x2'=>$x2);

		// окрашиваем все выбранные столбцы
		// определение маскимального столбца
		$max_col_nomer = 0;
		$nomer = floor( $x2 / $this->factor_px_collNomer);
		$nomer --; // чтобы выделилась строка которая полностью вошла в x2 а не только частично
		//$log->line($nomer);

		if(isset($this->keys[$nomer])) {
			$max_col_nomer = $nomer;
			$col_name = $this->keys[$nomer];
			//$log->line('col_name ' . $col_name);
		}

		$this->select_col_nomer = $max_col_nomer;

		//$log->line("$this->name graph->select()");

		// выделяем и снимаем выделение
		foreach ($this->keys as $nomer => $col_name) {
			if($nomer <= $max_col_nomer) {
				$is_select = 1;

				$col_value = $this->cols[(string)$col_name]->get_value();
				$sum_select += $col_value;

				//if($col_value)
				//	$log->line("добавляем $col_value");
			}
			else
				$is_select = 0;

			$this->cols[(string)$col_name]->set_is_select($is_select, false);
		}

		// перерисовываем если выделение изменилось
		if($new_select_coord) {
			$this->need_redraw_all = true;

			if($is_redraw)
				$this->redraw_all();
		}

		$this->select_sumvalues = $sum_select;

		return $sum_select;
	}

	// просто обновляем канвайс графика без перерисовки
	public function canvas_refresh() {
		GLOBAL $log;
		// показываем то что нарисовали
		$this->real_cv->drawBitmap($this->bitmap); // это способ если используем панель

		//$log->line('canvas_refresh');

		// это способ с объетом TMImage
		// тормозной способ. При перемещении ползунка графика, мерцает.
		//c($this->name)->picture->assign($this->bitmap); // или
		//c($this->name)->loadFromBitmap($this->bitmap);
	}

	// сохраняем текущий bitmap в png
	// поскольку bitmap в bmp только сохраняется, приходиться переконвертировать
	// width пока выбрать нельзя
	// а вот height можно. но он будет немного меньше. на 60 пикселей примерно
	// при выборе произвольного размера, мы тупо измения панель графика а потом возвращаем ее на место
	public function saveToPng($fname, $width='', $height='') {
		GLOBAL $log;

		if($height) {
			if(!$graph_wrap = get_ui($this->fmName.'->'.$this->name . '_wrap'))
				if(!$graph_wrap = get_ui($this->fmName.'->'.$this->name))
					throw new Error_def("Не удалось выбрать панель $name");

			$orig_height =$graph_wrap->h;
			$graph_wrap->h = $height;
			$this->adapt_size();
		}

		//$log->line('точка 0: ' . $this->yZero);

		$this->redraw_all(); // для надежности
		$this->bitmap->saveToFile('temp.bmp');

		//imagecreatefromxbm('temp.bmp');
		$image = Draw::imageCreateFromBMP('temp.bmp');
		imagepng($image, $fname);
		imagedestroy($image);

		if($height) {
			$graph_wrap->h = $orig_height;
			$this->adapt_size();
		}
	}

	public function redraw_all() {
		GLOBAL $log;

		//$log->line(time() . ' ' . $this->name . '::redraw_all()');

		$this->cv->brush->color = $this->active_bg;
		if(!$this->is_enabled) // цвет если заблокирован
			$this->cv->brush->color = $this->hidden_bg;

		$this->cv->clear();

		// выделение
		$this->_draw_select();

		// строки
		foreach($this->cols as $col) {
			$col->redraw();
		}

		// оси координат
		$this->axis->draw();

		// отключили потому что кроме визуального шума ничего не дает.
		// надпись "график не используется"
		// if(!$this->is_enabled)
		// 	$this->_label_graph_no_enabled();

		//объем
		

		// отправляем на канвайз
		$this->canvas_refresh();

		$this->need_redraw_all = false;
	}

	function set_enabled($status) {
		GLOBAL $log;

		//$log->line($this->name . '->set_enabled ' .  $status);
		$this->is_enabled = $status;

		// скрываем сообщение что график надо пересчитать
		if(!$status) {
			$name = str_replace('graph_', '', $this->name); // убейте меня за эту порнуху )
			$this->view_msg_recalc($name, false);
		}

		if($this->select_bar)
			c($this->fmName.'->'.$this->select_bar->name)->visible = $status;

		$this->redraw_all();
	}

	private function _draw_select() {

		if($this->is_enabled) {
			$color = self::SELECT_ACTIVE;
		}
		else {
			$color = self::SELECT_HIDDEN;
		}

		// если не проверять x2, то за счет бордюра будет виден даже нулевой прямоугольник
		if($this->select && $this->select['x2']>0) {
			//$log->line($this->select['x2']);
			$y1 = 0;
			$y2 = $this->yZero;
			$x1 = $this->xZero + $this->select['x1'];
			$x2 = $this->xZero + $this->select['x2'];
			$this->cv->brush->color = $color;
			$this->cv->pen->color = $color;
			$this->cv->rectangle($x1, $y1, $x2, $y2);
		}
	}

	private function _label_graph_no_enabled() {
		GLOBAL $log;

		if($this->is_enabled) return true;

		$cv = $this->cv;

		$cv->brush->color = self::SELECT_HIDDEN;
		$cv->pen->color = self::SELECT_HIDDEN;
		$cv->font->color = self::BLACK;
		$cv->font->style = 'fsBold';
		$cv->font->size = 12;

		$y = $this->yZero / 2 - 15;
		$x = $this->area_data['w'] / 2 - 95; // 95 примерная длина половины текста
		
		$cv->textOut($x, $y, GRAPH_NO_USED);
	}


	// проверка актуальности информации в строках.
	// если выделили какой то другой участок в связанном графике
	// выводим табличку что график устарел (показывается только на этапе анализа)
	// суко, очень не красиво, зато работает
	function check_data_relevance($linked_graph_name) {
		GLOBAL $log, $INFO, $OP;

		if(!isset($INFO['step'])) return false;

		$visible = false;
		$recalc_sumvalues = $INFO["{$this->name}_relevance_col_nomer"]; // например graph_speed_relevance_col_nomer

		if($INFO['step'] == 'analize_blocks') {
			if($recalc_sumvalues != $this->select_col_nomer) {
				$visible = true;
				//$log->line("$param_name  != $this->select_sumvalues");
			}
		}

		// выводим или прячем сообщение у связанного графика
		$this->view_msg_recalc($linked_graph_name, $visible);
	}

	// вторая пиздец функция
	function view_msg_recalc($graph_name, $visible) {
		GLOBAL $Graphs, $Ui_thread, $log, $INFO;
		
		if(!$recalc_ui = get_ui($this->fmName.'->'."graph_{$graph_name}_recalc"))
			return false;

		$is_enabled = $Graphs[$graph_name];

		if($recalc_ui->visible != $visible) {
			if($is_enabled)
				$recalc_ui->visible = $visible;
			else 
				$recalc_ui->visible = false;
			
			$Graphs[$graph_name]->canvas_refresh();
		}
	
		
		# Кнопка пересчета
		// показываем/скрываем
		// это if это жесткий костыль
		if($INFO['step'] == 'analize_blocks') {
			if($visible) {
				// если кнопку запуска выделяем, то кнопку перехода на следующий этап автоматом снимаем выделение
				visible_group('run_analize_blocks', true, $graph_name);
				visible_group('next_analize_blocks', false);
			}
			else {
				visible_group('run_analize_blocks', false, $graph_name);
			}

			//$log->line("visible_group($graph_name ): visible $visible");
		}

	}

	// == Не используем ===

	// не используем,
	public function add_to_col($name, $value) {
		$name = (string)$name;

		if(!isset($this->cols[$name])) {
			// не делаем вылет исключения, потому что ошибка не критическая
			$log->line("[Ошибка] в графике $this->name отсутствует столбец с ключом '$name'");
			return false;
		}

		$col = $this->cols[$name];
		$col->add_value($value);

		$this->need_redraw_all = true;
		//$col->redraw();
	}
	
	// не используем
	public function set_cur_col($name, $value) {

		// деактивируем старый столбец
		if($this->cur_col AND $this->cur_col != $name) {
			$col = $this->cols[(string)$this->cur_col];
			$col->set_uncur();
			//$col->redraw();
		}

		// активируем новый
		$col = $this->cols[(string)$name];
		$col->set_cur($value);
		//$col->redraw();

		$this->cur_col = $name;
	}

}

?>