//+------------------------------------------------------------------+
//|                                               PitchforkPanel.mq4 |
//|                                                          ronoc74 |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "ronoc74"
#property link      ""
#property indicator_chart_window

extern bool      triangleToSchiff=true;
extern bool      setColors=true;
extern color     stdUpColor=Blue;
extern color     stdDownColor=Red;
extern color     schiffUpColor=Green;
extern color     schiffDownColor=Purple;

#import "user32.dll"
int GetFocus();
#import "PitchforkPanel.dll"
int GetDllVersion();
void AddWindow(int hwnd);
void Reset(int hwnd, string title);
void AddFork(int hwnd, string name, int time, int col, int isUp);
void UpdateFork(string name, int time, int col, int isUp);
void SelectNewestFork();
void ResetLines();
void AddLine(int hwnd, string name);
void InitForkOptions();
void _DeleteObject(string name);
void SortForks();
int GetCurrentWindow();
int GetCommandId(int i);
string GetObjectName(int i);
int GetCommandParam(int i);
void ClearCommands();
int IsSnapChecked();
void CloseDialog(int hwnd);

#define DLL_VERSION 110
#define CMD_SCHIFF 0
#define CMD_UNSCHIFF 1
#define CMD_WL 2
#define CMD_DELETE_WL 3
#define CMD_COPYSCHIFF 4
#define CMD_COPYANDREWS 5
#define CMD_PARALLEL 6
#define BUFFER_SIZE 100

bool enabled = true;
string prevForks[BUFFER_SIZE];
datetime prevDatetimes[BUFFER_SIZE][3];
double prevPrices[BUFFER_SIZE][3];
double prevBarError[BUFFER_SIZE];
int prevColors[BUFFER_SIZE];
int forksArraySize = BUFFER_SIZE;
int forksCount = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
   if(GetDllVersion() != DLL_VERSION)
   {
      Print("Indicator version does not match DLL version. Install indicator and DLL from the same source.");
      enabled = false;
   }
   MathSrand(TimeLocal());
   for(int i = 0; i < ObjectsTotal(); i++)
   {
      string name = ObjectName(i);
      int type = ObjectType(name);
      if(type == OBJ_PITCHFORK || type == OBJ_TRIANGLE)
      {
         RegisterName(name,
            ObjectGet(name, OBJPROP_TIME1),
            ObjectGet(name, OBJPROP_TIME2),
            ObjectGet(name, OBJPROP_TIME3),
            ObjectGet(name, OBJPROP_PRICE1),
            ObjectGet(name, OBJPROP_PRICE2),
            ObjectGet(name, OBJPROP_PRICE3),
            ObjectGet(name, OBJPROP_COLOR),
            0.0
         );
      }
   }
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
  {
   CloseDialog(WindowHandle(Symbol(),0));
   
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
   if(!enabled)
   {
      return (0);
   }
   int hwnd = WindowHandle(Symbol(), 0);
   AddWindow(hwnd);
   for(int i = 0; i < ObjectsTotal(); i++)
   {
      string name = ObjectName(i);
      int type = ObjectType(name);
      if(type == OBJ_PITCHFORK || (triangleToSchiff && type == OBJ_TRIANGLE))
      {
         datetime timeA = ObjectGet(name, OBJPROP_TIME1); // Pivot A datetime
         double priceA = ObjectGet(name, OBJPROP_PRICE1);  // Pivot A price
         datetime timeB = ObjectGet(name, OBJPROP_TIME2); // Pivot B datetime
         double priceB = ObjectGet(name, OBJPROP_PRICE2);  // Pivot B price
         datetime timeC = ObjectGet(name, OBJPROP_TIME3); // Pivot C datetime
         double priceC = ObjectGet(name, OBJPROP_PRICE3);  // Pivot C price
         int barA = FindBar(timeA); // Pivot A bar number
         int barB = FindBar(timeB); // Pivot B bar number
         int barC = FindBar(timeC); // Pivot C bar number
         color lineColor = ObjectGet(name, OBJPROP_COLOR);
         int objNum = FindFork(name);
         if(objNum < 0) // New object
         {
            color upColor = stdUpColor;
            color downColor = stdDownColor;
            double barError = 0.0;
            if(type == OBJ_TRIANGLE)
            {
               ObjectDelete(name);
               for(;;)
               {
                  name = "Andrews Pitchfork " + MathRand() + "_schiff";
                  if(ObjectFind(name) < 0)
                     break;
               }
               SnapToPivots(barA, priceA, barB, priceB, barC, priceC);
               int schiffBarA = (barA + barB) / 2;
               double dblSchiffBarA = (barA + barB) / 2.0;
               barError = dblSchiffBarA - schiffBarA;
               double schiffPriceA = (priceA + priceB) / 2.0; // Modified Schiff pivot A
               timeA = Time[schiffBarA]; // Can't average the datetimes in case there is a gap
               if((barA + barB) % 2 > 0)
               {
                  double slope = (priceC - priceA) / (barA - barC);
                  schiffPriceA += slope / 2.0;
               }
               priceA = schiffPriceA;
               ObjectCreate(name, OBJ_PITCHFORK, 0, timeA, priceA, timeB, priceB, timeC, priceC);
               ObjectSet(name, OBJPROP_COLOR, lineColor);
               ObjectSetText(name, "schiff");
               WindowRedraw();
               // Do not create a Schiff of the Schiff!
               upColor = schiffUpColor;
               downColor = schiffDownColor;
            }
            else
            {
               SnapToPivots(barA, priceA, barB, priceB, barC, priceC);
            }
            RegisterName(name, timeA, timeB, timeC, priceA, priceB, priceC, lineColor, barError);
            int isUp = 0;
            if(priceA <= (priceB + priceC) / 2.0)
               isUp = 1;
            if(setColors)
            {
               if(isUp > 0)
               {
                  ObjectSet(name, OBJPROP_COLOR, upColor);
               }
               else
               {
                  ObjectSet(name, OBJPROP_COLOR, downColor);
               }
            }
            if(GetCurrentWindow() == hwnd)
            {
                AddFork(hwnd, name, timeA, lineColor, isUp);
                SortForks();
                SelectNewestFork();
            }
         }
         else // Existing fork; check if anything has changed
         {
            bool setPoints = timeA != prevDatetimes[objNum][0]
               || timeB != prevDatetimes[objNum][1]
               || timeC != prevDatetimes[objNum][2]
               || priceA != prevPrices[objNum][0]
               || priceB != prevPrices[objNum][1]
               || priceC != prevPrices[objNum][2];
            lineColor = ObjectGet(name, OBJPROP_COLOR);
            if(setPoints)
            {
               if(SnapToPivots(barA, priceA, barB, priceB, barC, priceC) == true)
               {
                  if(StringFind(ObjectDescription(name), "schiff") < 0)
                  {
                     ObjectMove(name, 0, timeA, priceA);
                     ObjectMove(name, 1, timeB, priceB);
                  }
                  ObjectMove(name, 2, timeC, priceC);
               }
            }
            if(setPoints || lineColor != prevColors[objNum]) // User edited something
            {
               ReprocessFork(i, name);
               prevDatetimes[objNum][0] = timeA;
               prevDatetimes[objNum][1] = timeB;
               prevDatetimes[objNum][2] = timeC;
               prevPrices[objNum][0] = priceA;
               prevPrices[objNum][1] = priceB;
               prevPrices[objNum][2] = priceC;
               prevColors[objNum] = lineColor;
               isUp = 0;
               if(priceA <= (priceB + priceC) / 2.0)
                  isUp = 1;
               UpdateFork(name, timeA, lineColor, isUp);
               WindowRedraw();
            }
         }
      }
   }
   for(i = 0; i < forksCount; i++)
   {
      if(ObjectFind(prevForks[i]) < 0) // A fork has been deleted
      {
         for(int j = -200; j < 0; j += 25)
         {
            // Delete warning lines
            ObjectDelete(prevForks[i] + j);
         }
         for(j = 25; j < 300; j += 25)
         {
            ObjectDelete(prevForks[i] + "+" + j);
         }
         WindowRedraw();
         _DeleteObject(prevForks[i]);
         for(j = i + 1; j < forksCount; j++)
         {
            prevForks[j - 1] = prevForks[j];
            prevDatetimes[j - 1][0] = prevDatetimes[j][0];
            prevDatetimes[j - 1][1] = prevDatetimes[j][1];
            prevDatetimes[j - 1][2] = prevDatetimes[j][2];
            prevPrices[j - 1][0] = prevPrices[j][0];
            prevPrices[j - 1][1] = prevPrices[j][1];
            prevPrices[j - 1][2] = prevPrices[j][2];
            prevBarError[j - 1] = prevBarError[j];
            prevColors[j - 1] = prevColors[j];
         }
         forksCount--;
         i--;
      }
   }
   if(hwnd == GetFocus() && GetCurrentWindow() != hwnd)
   {
         Reset(hwnd, GenerateTitle());      
         for(i = 0; i < ObjectsTotal(); i++)
         {
            name = ObjectName(i);
            type = ObjectType(name);
            if(type == OBJ_PITCHFORK)
            {
               priceA = ObjectGet(name, OBJPROP_PRICE1);  // Pivot A price
               priceB = ObjectGet(name, OBJPROP_PRICE2);  // Pivot B price
               priceC = ObjectGet(name, OBJPROP_PRICE3);  // Pivot C price
               isUp = 0;
               if(priceA <= (priceB + priceC) / 2.0)
                  isUp = 1;
               AddFork(hwnd, name, ObjectGet(name, OBJPROP_TIME1), ObjectGet(name, OBJPROP_COLOR), isUp);
            }
         }
         SortForks();
   }
   if(GetCurrentWindow() == hwnd)
   {
      for(int cmd = 0; ;cmd++)
      {
         if(GetCommandId(cmd) < 0)
            break;
         int id = GetCommandId(cmd);
         string objName = GetObjectName(cmd);
         if(id == CMD_SCHIFF)
         {
            SchiffFork(objName, false);
         }
         else if(id == CMD_UNSCHIFF)
         {
            UnSchiffFork(objName, false);
         }
         else if(id == CMD_COPYSCHIFF)
         {
            SchiffFork(objName, true);
         }
         else if(id == CMD_COPYANDREWS)
         {
            UnSchiffFork(objName, true);
         }
         else if(id == CMD_WL)
         {
            DrawLine(objName, GetCommandParam(cmd));
         }
         else if(id == CMD_DELETE_WL)
         {
            ObjectDelete(GetLineName(objName, GetCommandParam(cmd)));
         }
         else if(id == CMD_PARALLEL)
         {
            for(i = -10; i > -25; i--)
            {
               name = GetLineName(objName, i);
               if(ObjectFind(name) < 0)
               {
                  DrawLine(objName, i);
                  break;
               }
            }
         }
      }
      ClearCommands();
      ResetLines();
      for(i = 0; i < ObjectsTotal(); i++)
      {
         name = ObjectName(i);
         if(ObjectType(name) == OBJ_TREND)
            AddLine(hwnd, name);
      }
      InitForkOptions();
   }
   
//----
   return(0);
  }
  
void RegisterName(string name, datetime timeA, datetime timeB, datetime timeC,
   double priceA, double priceB, double priceC, color lineColor, double barError)
{
   if(forksCount >= forksArraySize)
   {
      forksArraySize += BUFFER_SIZE;
      ArrayResize(prevForks, forksArraySize);
      ArrayResize(prevDatetimes, forksArraySize);
      ArrayResize(prevPrices, forksArraySize);
      ArrayResize(prevBarError, forksArraySize);
      ArrayResize(prevColors, forksArraySize);
   }
   prevForks[forksCount] = name;
   prevDatetimes[forksCount][0] = timeA;
   prevDatetimes[forksCount][1] = timeB;
   prevDatetimes[forksCount][2] = timeC;
   prevPrices[forksCount][0] = priceA;
   prevPrices[forksCount][1] = priceB;
   prevPrices[forksCount][2] = priceC;
   prevBarError[forksCount] = barError;
   prevColors[forksCount] = lineColor;
   forksCount++;
}

int FindFork(string name)
{
   for(int i = 0; i < forksCount; i++)
      if(name == prevForks[i])
         return (i);
   return (-1);
}

string GenerateTitle()
{
   int i = Period();
   if((i % 43200) == 0 && i / 43200 == 1)
      return (Symbol() + ",Monthly");
   if((i % 10080) == 0)
      return (Symbol() + ",W" + i / 10080);
   if((i % 1440) == 0)
      return (Symbol() + ",D" + i / 1440);
   if((i % 60) == 0)
      return (Symbol() + ",H" + i / 60);
   return (Symbol() + ",M" + i);
}

bool SnapToPivots(int barA,
         double& priceA,
         datetime barB,
         double& priceB,
         datetime barC,
         double& priceC)
{
   if(IsSnapChecked() == 0)
      return (false);
   if(priceA >= (High[barA] + Low[barA]) / 2.0)
      priceA = High[barA];
   else priceA = Low[barA];
   if(priceB >= (High[barB] + Low[barB]) / 2.0)
      priceB = High[barB];
   else priceB = Low[barB];
   if(priceC >= (High[barC] + Low[barC]) / 2.0)
      priceC = High[barC];
   else priceC = Low[barC];
   Print(priceA, " ", priceB, " ", priceC);
   return (true);
}

void SchiffFork(string name, bool copy)
{
   datetime timeA = ObjectGet(name, OBJPROP_TIME1); // Pivot A datetime
   double priceA = ObjectGet(name, OBJPROP_PRICE1);  // Pivot A price
   datetime timeB = ObjectGet(name, OBJPROP_TIME2); // Pivot B datetime
   double priceB = ObjectGet(name, OBJPROP_PRICE2);  // Pivot B price
   datetime timeC = ObjectGet(name, OBJPROP_TIME3); // Pivot C datetime
   double priceC = ObjectGet(name, OBJPROP_PRICE3);  // Pivot C price
   int barA = FindBar(timeA); // Pivot A bar number
   int barB = FindBar(timeB); // Pivot B bar number
   int barC = FindBar(timeC); // Pivot C bar number
   double schiffPriceA = (priceA + priceB) / 2.0; // Modified Schiff pivot A
   int schiffBarA = (barA + barB) / 2;
   int objNum = FindFork(name);
   if(objNum >= 0)
   {
      double dblSchiffBarA = (barA + prevBarError[objNum] + barB) / 2.0;
      prevBarError[objNum] = dblSchiffBarA - schiffBarA;
   }
   if((barA + barB) % 2 > 0)
   {
      double slope = (priceC - priceA) / (barA - barC);
      schiffPriceA += slope / 2.0;
   }
   datetime schiffTimeA = Time[schiffBarA]; // Can't average the datetimes in case there is a gap
   if(copy)
   {
      ObjectCreate(name + "_schiff", OBJ_PITCHFORK, 0, schiffTimeA, schiffPriceA, timeB, priceB, timeC, priceC);
   }
   else
   {
      ObjectMove(name, 0, schiffTimeA, schiffPriceA);
      string descr = ObjectDescription(name);
      if(StringFind(descr, "schiff") < 0)
      {
         ObjectSetText(name, descr + "schiff");
      }
   }
}

void UnSchiffFork(string name, bool copy)
{
   datetime timeA = ObjectGet(name, OBJPROP_TIME1); // Pivot A datetime
   double priceA = ObjectGet(name, OBJPROP_PRICE1);  // Pivot A price
   datetime timeB = ObjectGet(name, OBJPROP_TIME2); // Pivot B datetime
   double priceB = ObjectGet(name, OBJPROP_PRICE2);  // Pivot B price
   datetime timeC = ObjectGet(name, OBJPROP_TIME3); // Pivot C datetime
   double priceC = ObjectGet(name, OBJPROP_PRICE3);  // Pivot C price
   int barA = FindBar(timeA); // Pivot A bar number
   int barB = FindBar(timeB); // Pivot B bar number
   int barC = FindBar(timeC); // Pivot C bar number
   double andrewsPriceA = priceA - (priceB - priceA); // Andrews pivot A
   int andrewsBarA = barA + (barA - barB);
   int objNum = FindFork(name);
   if(objNum >= 0)
   {
      double a = barA + prevBarError[objNum];
      a = a + (a - barB);
      andrewsBarA = a + 0.000001;
      if(barA != barC)
      {
         double midbar = (barB + barC) / 2.0;
         double slope = ((priceB + priceC) / 2.0 - priceA) / (barA - midbar);
         double corrPriceA = priceA - slope * prevBarError[objNum];
         andrewsPriceA = corrPriceA - (priceB - corrPriceA);
      }
      prevBarError[objNum] = a - andrewsBarA;
   }
   datetime andrewsTimeA = Time[andrewsBarA]; // Can't average the datetimes in case there is a gap
   if(copy)
   {
      string andrewsName;
      int i = StringFind(name, "_schiff");
      if(i > 0)
      {
         andrewsName = StringSubstr(name, 0, i);
      }
      else
      {
         andrewsName = name + "_andrews";
      }
      string coreName = andrewsName;
      for(int n = 1; n < 10; n++)
      {
         if(ObjectFind(andrewsName) < 0)
         {
            break;
         }
         andrewsName = coreName + n;
      }
      ObjectCreate(andrewsName, OBJ_PITCHFORK, 0, andrewsTimeA, andrewsPriceA, timeB, priceB, timeC, priceC);
   }
   else
   {
      ObjectMove(name, 0, andrewsTimeA, andrewsPriceA);
      string descr = ObjectDescription(name);
      i = StringFind(descr, "schiff");
      if(i >= 0)
      {
         ObjectSetText(name, StringSubstr(descr, 0, i) + StringSubstr(descr, i + 6, 0));
      }
   }
}

void ReprocessFork(int objNum, string forkName)
{
   int len = StringLen(forkName);
   for(int i = 0; i < ObjectsTotal(); i++)
   {
      if(i != objNum)
      {
         string name = ObjectName(i);
         if(ObjectType(name) == OBJ_TREND && StringFind(name, forkName) == 0)
         {
            int sign = StringGetChar(name, len);
            if(sign == '+' || sign == '-')
            {
               int position = StrToInteger(StringSubstr(name, len));
               if(position != 0)
               {
                  DrawLine(forkName, position);
               }
            }
         }
      }
   }
}

string GetLineName(string name, int position)
{
   if(position >= 0)
   {
      return (name + "+" + position);
   }
   else
   {
      return (name + position);
   }
}

//void DrawLine(string name, double slope, double height, int barB, double priceB, int barC, double priceC, int position, color lineColor)
void DrawLine(string name, int position)
{
   string warnName = GetLineName(name, position);
   datetime timeA = ObjectGet(name, OBJPROP_TIME1); // Pivot A datetime
   double priceA = ObjectGet(name, OBJPROP_PRICE1);  // Pivot A price
   datetime timeB = ObjectGet(name, OBJPROP_TIME2); // Pivot B datetime
   double priceB = ObjectGet(name, OBJPROP_PRICE2);  // Pivot B price
   datetime timeC = ObjectGet(name, OBJPROP_TIME3); // Pivot C datetime
   double priceC = ObjectGet(name, OBJPROP_PRICE3);  // Pivot C price
   int barA = FindBar(timeA); // Pivot A bar number
   int barB = FindBar(timeB); // Pivot B bar number
   int barC = FindBar(timeC); // Pivot C bar number
   color lineColor = ObjectGet(name, OBJPROP_COLOR);
   double midBar = (barB + barC) / 2.0;
   if(barA == midBar)
      return;
   double slope = ((priceB + priceC) / 2.0 - priceA) / (barA - midBar);
   double height = (priceB + slope * (barB - barC)) - priceC;
   if(priceC > priceB)
   {
      position = 100 - position;
   }
   double price1 = priceB - height + position * height / 100.0;
   int width = barB - barC;
   if(width < 30)
   {
      barC = barB - 30;
      if(barC < 0)
         barC = 0;
      timeC = Time[barC];
   }
   double price2 = price1 + slope * (barB - barC);
   int lineStyle = STYLE_DOT;
   if(position % 50 == 0 || position % 25 != 0)
   {
      lineStyle = STYLE_SOLID;
   }
   CreateWarningLine(warnName, timeB, price1, timeC, price2, lineColor, lineStyle);
}

void CreateWarningLine(string warnName, int timeA, double priceA, int timeB, double priceB, color lineColor, int lineStyle)
{
   if(ObjectCreate(warnName, OBJ_TREND, 0, timeA, priceA, timeB, priceB) == false)
   {
      ObjectMove(warnName, 0, timeA, priceA);
      ObjectMove(warnName, 1, timeB, priceB);
   }
   ObjectSet(warnName, OBJPROP_COLOR, lineColor);
   ObjectSet(warnName, OBJPROP_STYLE, lineStyle);
}

int FindBar(datetime dt)
{
   for(int i = 0; i < Bars - 1; i++)
      if(Time[i] <= dt) break;
   return (i);
}

//+------------------------------------------------------------------+