//+------------------------------------------------------------------+
//|                                                 ObjectPlacer.mqh |
//|                                Copyright 2026, Clemence Benjamin |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Clemence Benjamin"
#property link      "https://www.mql5.com"

#include "../Common/Structures.mqh"
#include "../Helpers/ObjectHelpers.mqh"
#include "SwingDetector.mqh"

//+------------------------------------------------------------------+
//| Class: CObjectPlacer                                             |
//| Purpose: Automatically places analytical objects on the chart    |
//+------------------------------------------------------------------+
class CObjectPlacer
  {
private:
   long              m_chartId;      // chart identifier
   string            m_prefix;       // object name prefix
   int               m_maxObjects;   // maximum objects per type
   bool              m_debug;        // debug mode flag
   int               m_maxTotal;     // maximum total objects

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
                     CObjectPlacer(long chartId, string prefix, int maxObjects,
                 int maxTotal, bool debug)
      :              m_chartId(chartId),
        m_prefix(prefix),
        m_maxObjects(maxObjects),
        m_maxTotal(maxTotal),
        m_debug(debug) {}

   //+------------------------------------------------------------------+
   //| Places all enabled object types                                  |
   //+------------------------------------------------------------------+
   void              PlaceAll(int lookback, int minBars,
                 bool placeTrendlines, bool placeSR, bool placeFibonacci,
                 bool placeChannels, bool placePitchforks)
     {
      if(ObjectsTotal(m_chartId, 0, -1) > m_maxTotal)
        {
         if(m_debug)
            Print("Too many objects, skipping placement");
         return;
        }
      if(placeTrendlines)
         PlaceTrendlines(lookback, minBars);
      if(placeSR)
         PlaceSupportResistance(lookback, minBars);
      if(placeFibonacci)
         PlaceFibonacci(lookback, minBars);
      if(placeChannels)
         PlaceChannels(lookback, minBars);
      if(placePitchforks)
         PlacePitchforks(lookback, minBars);
     }

   //+------------------------------------------------------------------+
   //| Cleans up old objects                                            |
   //+------------------------------------------------------------------+
   void              Cleanup()
     {
      int deleted = CleanupObjectsWithPrefix(m_chartId, m_prefix, m_maxTotal);
      if(m_debug && deleted > 0)
         PrintFormat("Cleaned up %d old objects", deleted);
     }

private:
   //+------------------------------------------------------------------+
   //| Places trendlines connecting swing points                        |
   //+------------------------------------------------------------------+
   void              PlaceTrendlines(int lookback, int minBars)
     {
      SSwingPoint swings[];
      CSwingDetector::FindSwings(swings, lookback, minBars);
      if(ArraySize(swings) < 2)
         return;
      int count = 0;
      for(int i = 0; i < ArraySize(swings) - 1 && count < m_maxObjects; i++)
        {
         if(swings[i].isHigh && swings[i + 1].isHigh)
           {
            string name = m_prefix + "TL_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(count);
            if(CreateTrendlineObject(m_chartId, name, swings[i].time, swings[i].price,
                                     swings[i + 1].time, swings[i + 1].price, clrRed))
               count++;
           }
         else
            if(!swings[i].isHigh && !swings[i + 1].isHigh)
              {
               string name = m_prefix + "TL_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(count);
               if(CreateTrendlineObject(m_chartId, name, swings[i].time, swings[i].price,
                                        swings[i + 1].time, swings[i + 1].price, clrBlue))
                  count++;
              }
        }
     }

   //+------------------------------------------------------------------+
   //| Places support/resistance horizontal lines                       |
   //+------------------------------------------------------------------+
   void              PlaceSupportResistance(int lookback, int minBars)
     {
      //--- Get swing points
      SSwingPoint swings[];
      CSwingDetector::FindSwings(swings, lookback, minBars);
      if(ArraySize(swings) < 1)
         return;
      int count = 0;
      for(int i = 0; i < ArraySize(swings) && count < m_maxObjects; i++)
        {
         if(swings[i].isHigh)
           {
            string name = m_prefix + "Res_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(count);
            if(CreateHorizontalLineObject(m_chartId, name, swings[i].price, clrRed))
               count++;
           }
         else
           {
            string name = m_prefix + "Sup_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(count);
            if(CreateHorizontalLineObject(m_chartId, name, swings[i].price, clrGreen))
               count++;
           }
        }
     }

   //+------------------------------------------------------------------+
   //| Places Fibonacci retracements between swing points               |
   //+------------------------------------------------------------------+
   void              PlaceFibonacci(int lookback, int minBars)
     {
      SSwingPoint swings[];
      CSwingDetector::FindSwings(swings, lookback, minBars);
      if(ArraySize(swings) < 2)
         return;
      int count = 0;
      for(int i = 0; i < ArraySize(swings) - 1 && count < m_maxObjects; i++)
        {
         if(swings[i].isHigh != swings[i + 1].isHigh)
           {
            string name = m_prefix + "Fibo_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(count);
            if(CreateFibonacciObject(m_chartId, name, swings[i].time, swings[i].price,
                                     swings[i + 1].time, swings[i + 1].price))
               count++;
           }
        }
     }

   //+------------------------------------------------------------------+
   //| Places channels using three swing points                         |
   //+------------------------------------------------------------------+
   void              PlaceChannels(int lookback, int minBars)
     {
      SSwingPoint swings[];
      CSwingDetector::FindSwings(swings, lookback, minBars);
      if(ArraySize(swings) < 3)
         return;
      int count = 0;
      for(int i = 0; i < ArraySize(swings) - 2 && count < m_maxObjects; i++)
        {
         if(swings[i].isHigh != swings[i + 1].isHigh && swings[i + 1].isHigh != swings[i + 2].isHigh)
           {
            string name = m_prefix + "Chan_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(count);
            if(CreateChannelObject(m_chartId, name,
                                   swings[i].time, swings[i].price,
                                   swings[i + 1].time, swings[i + 1].price,
                                   swings[i + 2].time, swings[i + 2].price))
               count++;
           }
        }
     }

   //+------------------------------------------------------------------+
   //| Places pitchforks using three swing points                       |
   //+------------------------------------------------------------------+
   void              PlacePitchforks(int lookback, int minBars)
     {
      SSwingPoint swings[];
      CSwingDetector::FindSwings(swings, lookback, minBars);
      if(ArraySize(swings) < 3)
         return;
      int count = 0;
      for(int i = 0; i < ArraySize(swings) - 2 && count < m_maxObjects; i++)
        {
         if(swings[i].isHigh != swings[i + 1].isHigh && swings[i + 1].isHigh != swings[i + 2].isHigh)
           {
            string name = m_prefix + "PF_" + IntegerToString(TimeCurrent()) + "_" + IntegerToString(count);
            if(CreatePitchforkObject(m_chartId, name,
                                     swings[i].time, swings[i].price,
                                     swings[i + 1].time, swings[i + 1].price,
                                     swings[i + 2].time, swings[i + 2].price))
               count++;
           }
        }
     }
  };
//+------------------------------------------------------------------+