//+------------------------------------------------------------------+
//|                                                    MiniChart.mq4 |
//|                        Copyright 2014, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014"
#property link      "https://www.mql5.com/en/code/1050"
#property version   "1.40"
#property indicator_chart_window
#property strict

#include <IncGUI_BitPica.mqh>
#include <GetNextBar.mqh>

extern ENUM_TIMEFRAMES TimeFrame = PERIOD_H1;
extern string Pair = "";
extern bool   Testing = false;
extern int ScreenX0 = 10; //Left upper corner X
extern int ScreenY0 = 30; //Left upper corner Y
extern int Width = 300;
extern int Height = 200;
extern int MarginX = 5; //Internal X margin
extern int MarginY = 5; //Internal Y margin
extern int BarWidth = 4;
extern int BarSpace = 2;
extern color Background = C'35,35,35';
extern uchar Transparency = 255; //Transparency 0=Max  255=Min
extern int SubWindow = 0; //Which subwindow should we use
extern int UpdateInterval = 30; //Update interval [sec]

extern color ClrBarUp = clrGreen;
extern color ClrCandleUp = clrLime;
extern color ClrBarDn = clrFireBrick;
extern color ClrCandleDn = clrRed;

  
datetime LastDrawTime; //stores last draw time
string DrawPair = "";
double PriceMin, PriceMax;
int BarCount;
bool Initialized = false;
int LastBar = 0;

CBitPic Graph; //declare variable Graph


int OnInit(){
  Initialized = false;
  LastDrawTime = 0;
  isNewBarT0 = 0;
  BarCount = (int)MathRound((double)(Width - 2 * MarginX) / (double)(BarWidth + BarSpace)) + 1;
  
  DrawPair = StringTrimLeft(Pair);
  DrawPair = StringTrimRight(DrawPair);
  if (DrawPair == "")
    DrawPair = Symbol();
    
  string ScreenSuffix = DrawPair + (string)SubWindow + "_" + (string)ScreenX0 + "_" + (string)ScreenY0 + "_" + (string)TimeFrame;
  Graph.Init("MiniCandle" + ScreenSuffix, Width, Height); //width height
  Graph.SetSubWindow(SubWindow);
  Graph.SetBGColor(Background, Transparency);
  Graph.SetPos(ScreenX0, ScreenY0);   
  Graph.Clear();
  Graph.Show();
  Initialized = true;   
  return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason){
  Graph.Hide(); //remove from chart
}


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[]){
  PaintGraph();
  return(rates_total); 
}      

void GetData(int Shift, double &O, double &C, double &H, double &L){
  if (Testing){  
    int Shift2 = iBarShift(DrawPair, TimeFrame, iTime(DrawPair, Period(), LastBar));

    Shift = Shift + Shift2;
  }
  O = iOpen(DrawPair, TimeFrame, Shift);
  C = iClose(DrawPair, TimeFrame, Shift);
  H = iHigh(DrawPair, TimeFrame, Shift);
  L = iLow(DrawPair, TimeFrame, Shift);
}

int GetScreenY(double Price){
  if (CloseEnough(PriceMax, PriceMin)) //avoid division by 0
    return(0);
  int Y = MarginY + (int)MathRound((double)(Height - 2 * MarginY)*(Price - PriceMin)/(PriceMax - PriceMin));
  Y = Height - Y;
  return Y;
}

void PaintGraph(bool ForceRedraw = false){
  
  if (!ForceRedraw && TimeGMT() - LastDrawTime < UpdateInterval)
    return; 
  LastDrawTime = TimeGMT();
  Graph.Clear();
      
  int Limit;

  Limit = BarCount;  
         
  double O, C, H, L;
  int X, X1, X2, Y1, Y2;
  color Color;
  Graph.SetDrawWidth(1);
  
//**** Find Price Min & Max  
  PriceMin = 0;
  PriceMax = 0;
  for (int Shift = 0; Shift < Limit; Shift++){
    GetData(Shift, O, C, H, L);
    if (H > PriceMax)
      PriceMax = H;
    if (PriceMin == 0 || L < PriceMin)
      PriceMin = L;
  }
  
//**** PAINT    
  for (int Shift = 0; Shift < Limit; Shift++){
    GetData(Shift, O, C, H, L);
    X = Width - MarginX - Shift * (BarWidth + BarSpace); //right coordinate
    
//**** WICK    
    X1 = X - BarWidth / 2;
    Y1 = GetScreenY(L);
    Y2 = GetScreenY(H);
    if (C >= O)
      Color = ClrBarUp;
    else
      Color = ClrBarDn;
    Graph.SetDrawColor(Color);
    Graph.DrawLine(X1, Y1, X1, Y2);//wicks
    
//**** candle FRAME    
    X1 = X; 
    X2 = X - BarWidth;
    Y1 = GetScreenY(O);
    Y2 = GetScreenY(C);
    Graph.SetFilled(false);
    Graph.DrawRectangle(X1, Y1, X2, Y2);
    
//**** candle BODY    
    X1 = X1 - 1;
    X2 = X2 + 1;
    if (O > C){
      Y1 = Y1 + 1;
      Y2 = Y2 - 1;
    }
    else{
      Y1 = Y1 - 1;
      Y2 = Y2 + 1;
    }
    if (C >= O)
      Color = ClrCandleUp;
    else
      Color = ClrCandleDn;
    Graph.SetFillColor(Color);  
    Graph.SetFilled(true);
    Graph.DrawRectangle(X1, Y1, X2, Y2);        
  }
   
//**** TimeFrame
  
  Graph.SetDrawColor(clrWhite);
  Graph.SetFont(5);
  Graph.TypeText(15, 3, TF2Str(TimeFrame));
  
  Graph.Redraw();  

  return;
}



void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam){
  if (Testing && Initialized && id == CHARTEVENT_CHART_CHANGE){
    int PrevLastBar = LastBar;
    if (LastBar > 0){   
      LastBar = 0;
    }
    LastBar = GetNextBar(0);    
    if (Testing && PrevLastBar > 0 && LastBar >= 0 && PrevLastBar != LastBar){
      PaintGraph(true);
    }  
  }
}

bool CloseEnough(double num1, double num2)
{
   /*
   This function addresses the problem of the way in which mql4 compares doubles. It often messes up the 8th
   decimal point.
   For example, if A = 1.5 and B = 1.5, then these numbers are clearly equal. Unseen by the coder, mql4 may
   actually be giving B the value of 1.50000001, and so the variable are not equal, even though they are.
   This nice little quirk explains some of the problems I have endured in the past when comparing doubles. This
   is common to a lot of program languages, so watch out for it if you program elsewhere.
   Gary (garyfritz) offered this solution, so our thanks to him.
   */
   
   if (num1 == 0 && num2 == 0) return(true); //0==0
   if (MathAbs(num1 - num2) / (MathAbs(num1) + MathAbs(num2)) < 0.00000001) return(true);
   
   //Doubles are unequal
   return(false);

}//End bool CloseEnough(double num1, double num2)



datetime isNewBarT0 = 0;  
bool isNewBar(){
  datetime T = iTime(NULL, 0 , 0);
  if (isNewBarT0 < T){
    isNewBarT0 = T;
    return (true); 
  } 
  else{
    return (false);
  }
}

string TF2Str(int iPeriod) {
  if (iPeriod == 0)
    iPeriod = Period();
  switch(iPeriod) {    
    case PERIOD_M1: return("M1");
    case PERIOD_M5: return("M5");
    case PERIOD_M15: return("M15");
    case PERIOD_M30: return("M30");
    case PERIOD_H1: return("H1");
    case PERIOD_H4: return("H4");
    case PERIOD_D1: return("D1");
    case PERIOD_W1: return("W1");
    case PERIOD_MN1: return("MN1");
    default: return("M"+(string)iPeriod);
  }
  return("M?");
}
  