/*
Copyright 2020 FXcoder

This file is part of Chart.

Chart is free software: you can redistribute it and/or modify it under the terms of the GNU General
Public License as published by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Chart is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.

You should have received a copy of the GNU General Public License along with Chart. If not, see
http://www.gnu.org/licenses/.
*/

#property copyright "Chart 5.2. © FXcoder"
#property link      "https://fxcoder.blogspot.com"
#property strict

#property indicator_separate_window
#property indicator_buffers 15            // The first buffer is for value, others are for candle drawing

#ifndef __MQL4__
#property indicator_plots 2
#endif

//todo: синхронизация по текущему символу плохо работает, если на нём есть разрывы (например, межсессионные)

#include "Chart-include/bsl.mqh"
#include "Chart-include/class/message.mqh"

#include "Chart-include/plot/calcplot.mqh"
#include "Chart-include/plot/colorcandlesplot.mqh"


/* INPUT */

// Source
input string             ChartSym          = "";             // Symbol
input bool               Reverse           = false;          // Reverse
input ENUM_TIMEFRAMES    ChartPer          = PERIOD_CURRENT; // Timeframe

// Visual
input color              BullColor         = clrNONE;        // Bull Color (None = main chart colors)
input color              BearColor         = clrNONE;        // Bear Color (None = main chart colors)

// Service
input ENUM_APPLIED_PRICE ValueAppliedPrice = PRICE_CLOSE;    // Value Applied Price
input int                MaxBars           = 0;              // Maximum Bars
input int                Shift             = 0;              // Shift
input bool               LogScale          = false;          // Logarithmic Scale


/* GLOBALS */

CCalcPlot plot_value_;
CColorCandlesPlot plot_;

CBSymbol sym_(ChartSym);
CBSeries src_ser_(ChartSym, ChartPer);
CBSeries sync_ser_(_Symbol, ChartPer);

bool  is_initialized_ = false;

int   body_width_ = 2;
color bg_color_ = clrNONE;
color bull_color_ = BullColor;
color bear_color_ = BearColor;

string MSG_SYMBOL_ERR   = _mql.program_name() + ": Error in symbol";


/* EVENTS */

void OnInit()
{
	plot_value_.init(0, 0).label("Value");
	plot_value_.shift(Shift);

	plot_.init(plot_value_);
	plot_.set_two_color(BullColor, BearColor);
	plot_.shift(Shift);

	_indicator.short_name(_mql.program_name() + ":" + (Reverse ? "1/" : "") + sym_.name() + "," + _tf.to_string(ChartPer) + (LogScale ? ",log" : ""));

	is_initialized_ = sym_.exists();

	if (is_initialized_)
		_indicator.digits(8);

	if (!is_initialized_)
		_imsg.show(MSG_SYMBOL_ERR, clrRed);
}

int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[])
{
	if (!is_initialized_)
		return(0);

	_imsg.hide();

#ifndef __MQL4__
	ArraySetAsSeries(time, true);
#endif

	// Обновить внешний вид
	update_body_width();
	update_colors();

	/*
	Полная перерисовка должна происходить в случаях:
		1. появился новый бар у источника
		2. появился новый бар у текущего графика
		3. изменилось число доступных баров у источника
		4. изменилось число доступных баров у текущего графика
	
	3 и 4 заменяют 1 и 2, можно остановиться только на них. Однако получение числа баров - медленная операция, возможно
		придётся использовать только 1 и 2.
	
	Это не самый лучший алгоритм. Было бы лучше не только проверять наличие новых баров, но и пересчитывать только
		вновь появившиеся.
	*/

	// обновить всё при любом из условий:
	//   - таймфрейм или символ чужие
	//   - есть новые бары
	bool full_update = prev_calculated == 0;
	bool is_current_symbol = _sym.is_current(ChartSym);
	bool is_current_tf = _tf.is_current(ChartPer);
	bool is_current_series = is_current_tf && is_current_symbol;
	bool sync_by_chart_symbol = is_current_symbol || is_current_tf;
	
	if (!is_current_series)
	{
		// Проверить, изменилось ли число баров или время открытия последнего бара для текущего графика и источника.
		static CBState<int> chart_bars(0);
		static CBState<int> src_bars(0);
	
		bool is_chart_new_bars = chart_bars.changed(_series.bars_count());
		bool is_src_new_bars = src_bars.changed(src_ser_.bars_count());
	
		static CBState<datetime> chart_last_bar_time(0);
		static CBState<datetime> src_last_bar_time(0);
		
		bool is_chart_new_bar = chart_last_bar_time.changed(_series.time(0));
		bool is_src_new_bar = src_last_bar_time.changed(src_ser_.time(0));
	
		bool is_new_bars = is_chart_new_bars || is_src_new_bars || is_chart_new_bar || is_src_new_bar;
		
		full_update = full_update || is_new_bars;
	}
	

	// Определить количество баров для расчета
	int nbars = _chart.bars_count();
	int bars_to_calc = nbars - prev_calculated;
	if (prev_calculated > 0)
		bars_to_calc++;
	
	if (full_update)
		bars_to_calc = nbars;
	
	if ((MaxBars > 0) && (bars_to_calc > MaxBars))
		bars_to_calc = MaxBars;

	plot_.empty(full_update ? -1 : bars_to_calc);
	plot_value_.empty(full_update ? -1 : bars_to_calc);
	int calculated_bars = nbars - bars_to_calc;

	
	for (int i = 0; i < bars_to_calc; i++)
	{
		int pbar = nbars - 1 - i;

		// Синхронизация всегда происходит по символу и тф индикатора, даже если это приводит
		//   к разрывам или пропуску значимых баров.
		datetime dt = sync_ser_.time(i);
		int src_bar = src_ser_.bar_shift(dt, true);
		
		// Если бара для данного времени нет, то не отображаем ничего, но в значение пишем прошлый бар
		if (src_bar < 0)
		{
			if (pbar - 1 >= 0)
				plot_value_.buffer[pbar] = plot_value_.buffer[pbar - 1];
			
			continue;
		}
		
		double o, h, l, c, value;
		if (!get_price(src_bar, o, h, l, c, value))
			break;
		
		// Установка значений в буферы линий
		if (LogScale)
		{
			o = log(o);
			h = log(h);
			l = log(l);
			c = log(c);
			value = log(value);
		}

		plot_.set_candle(pbar, o, h, l, c);
		plot_value_.buffer[pbar] = value;
		calculated_bars++;
	}

	int draw_begin = MaxBars > 0 ? nbars - MaxBars : nbars - calculated_bars;
	plot_.draw_begin(draw_begin);
	plot_value_.draw_begin(draw_begin);

	if (full_update)
		update_digits();

	return(calculated_bars);
}

void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
{
	if (id == CHARTEVENT_CHART_CHANGE)
	{
		if (update_body_width())
			ChartRedraw();
			
		if (update_colors())
			ChartRedraw();
	}
}

void OnDeinit(const int reason)
{
	_imsg.hide();
}


/* WORKERS */

void update_digits()
{
	double max = -DBL_MAX;
	double min = DBL_MAX;

	for (int i = 0, count = plot_value_.size(); i < count; i++)
	{
		double value = plot_value_.buffer[count - 1 - i];
		
		if (value == EMPTY_VALUE)
			break;
			
		if (value > max)
			max = value;
			
		if (value < min)
			min = value;
	}

	if (max > min)
	{
		static const int max_digits = 8;
		static const int min_digits = 3;
		static int prev_digits = 8;

		int digits = int(ceil(log10(1.0 / (max - min)))) + 2;
		digits = _math.limit(digits, min_digits, max_digits);

		if (prev_digits != digits)
		{
			_indicator.digits(digits);
			prev_digits = digits;
		}
	}
}

bool update_colors()
{
	bool is_changing = false;

	// Цвета фона
	color new_bg_color = _chart.color_background();

	if (new_bg_color != bg_color_)
	{
		bg_color_ = new_bg_color;
		is_changing = true;
	}


	// Цвета свечи
	
	if (_color.is_none(BullColor))
	{
		color new_bull_color = _chart.color_chart_up();
		
		if (new_bull_color != bull_color_)
		{
			bull_color_ = new_bull_color;
			is_changing = true;
		}
	}

	if (_color.is_none(BearColor))
	{
		color new_bear_color = _chart.color_chart_down();
		
		if (new_bear_color != bear_color_)
		{
			bear_color_ = new_bear_color;
			is_changing = true;
		}
	}

	if (is_changing)
		plot_.set_two_color(bull_color_, bear_color_);

		
	return(is_changing);
}

bool update_body_width()
{
	plot_.set_two_color(bull_color_, bear_color_);
	return(true);
}

bool get_price(int bar, double &open, double &high, double &low, double &close, double &value)
{
	double o = src_ser_.open(bar);
	double h = src_ser_.high(bar);
	double l = src_ser_.low(bar);
	double c = src_ser_.close(bar);

	if ((o <= 0) || (h <= 0) || (l <= 0) || (c <= 0))
		return(false);

	open  = Reverse ? 1.0 / o : o;
	high  = Reverse ? 1.0 / l : h;
	low   = Reverse ? 1.0 / h : l;
	close = Reverse ? 1.0 / c : c;
	value = _conv.ohlc_to_price(open, high, low, close, ValueAppliedPrice);
	
	return(true);
}

/*
История

5.2:
	* исправлено: неверная работа в 5 (fix #59)

5.1:
	* исправлено: показывается только последний бар, если таймфрейм ниже текущего (fix #2)
	* исправлено: неверное определение числа знаков, если необходимо значение 8 (fix #3)
	* улучшено: полная перерисовка чужого таймфрема только на новом баре (было - каждый тик)

5.0:
	* поддержка MT5
	* не работал и был не нужен параметр BackColor, удалён, теперь цвет фона всегда берётся как цвет фона графика
	* удалён параметр BodyWidth, теперь ширина тела свечи всегда вычисляется автоматически
	* возможность установить цвета основного графика (включено по умолчанию)
	* автоматическое определение числа знаков, актуально для реверсов символов с большими значениями
	* убрано умножение на 100 при логарифмировании
*/
