// XU_v48_RSIOMA_DLL.cpp
// ---------------------------------------------------------------
// This DLL implements an indicator identical to the MQL4 "XU v48‑RSIOMA".
// It is organized into the following procedures:
//   • Init              – Initialization: set properties, register parameters,
//                         create index buffers, set styles, and draw RSI zones.
//   • Done              – Deinitialization: free objects and clean up chart objects.
//   • OnParametersChange– Called when parameters change.
//   • Calculate(index)  – Calculates indicator values for one bar (or all bars if index = 0).
// The core calculation logic (EMA on Close, RSI on that EMA, second EMA on RSI,
// signal histograms, and trend detection via RSI crossing 50) is implemented internally.
// ---------------------------------------------------------------

#define NOMINMAX
#include <windows.h>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif

#include <vector>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstdio>
#include "IndicatorInterfaceUnit.h"  // Provided by Forex Tester SDK

using namespace std;

// ---------------------------------------------------------------------
// External (User-Defined) Parameters – (Identical to MQL4 externs)
// ---------------------------------------------------------------------
int   ext_win = 0;                      // Window parameter (internal use)
int   ext_RSIOMA = 10;                  // Period for EMA and RSI
int   ext_Ma_RSIOMA = 7;                // Period for second EMA on RSI
int   ext_BarsToCount = 1750;           // Bars to count (minimum 300 enforced)
bool  ext_showHeader = true;            // Display header text flag
bool  ext_Display = true;               // Display RSI zones flag
string ext_Timeframe = "0";             // Timeframe (if needed)
bool  ext_alertsOn = false;
bool  ext_alertsOnCurrent = false;
bool  ext_alertsmsg = true;
bool  ext_alertsSound = false;
bool  ext_alertsNotify = true;

int   ext_LR = 0, ext_UD = 0, ext_HDRsize = 45;
string ext_ID = "XU v48-RSIOMA";

// Colors for signals (using SDK color definitions)
COLORREF marisiomaXupSigColor = clAqua;      // Up-signal color
COLORREF marisiomaXdnSigColor = clDeepPink;    // Down-signal color

// ---------------------------------------------------------------------
// Global Calculation Variables & Index Buffers
// ---------------------------------------------------------------------
// We use 5 index buffers as follows:
//   Buffer 0: marsiomaXupSig (histogram; up-signal, Aqua)
//   Buffer 1: marsiomaXdnSig (histogram; down-signal, DeepPink)
//   Buffer 2: RSIBuffer     (line; DeepSkyBlue)
//   Buffer 3: marsioma      (line; Snow)
//   Buffer 4: trend         (invisible – internal trend state)
vector<double> RSIBuffer;
vector<double> marsioma;
vector<double> marsiomaXupSig;
vector<double> marsiomaXdnSig;
vector<double> trend;

// Temporary internal calculation buffers.
vector<double> MABuffer1;
vector<double> RSIBuffer1;
vector<double> marsioma1;

// Internal variables for calculations.
int   internalTimeFrame = 0;              // Numeric conversion of ext_Timeframe (if needed)
bool  internalDifferentTimeFrame = false; // True if selected timeframe differs from chart's
int   correction = 0;                     // ext_RSIOMA + ext_RSIOMA + ext_Ma_RSIOMA
double lastBarTime = -99999.0;            // Represents “no previous time”

// ---------------------------------------------------------------------
// Helper Calculation Functions (Accurate, internal implementations)
// ---------------------------------------------------------------------

// ✓ computeEMA: Computes the exponential moving average on a vector.
vector<double> computeEMA(const vector<double>& data, int period)
{
    int n = data.size();
    vector<double> ema(n, 0.0);
    if (n == 0)
        return ema;
    double multiplier = 2.0 / (period + 1);
    double sum = 0.0;
    int count = min(n, period);
    for (int i = 0; i < count; i++)
        sum += data[i];
    ema[0] = sum / count;
    for (int i = 1; i < n; i++)
        ema[i] = (data[i] - ema[i - 1]) * multiplier + ema[i - 1];
    return ema;
}

// ✓ reverseVector: Returns a reversed copy of a vector.
vector<double> reverseVector(const vector<double>& data)
{
    return vector<double>(data.rbegin(), data.rend());
}

// ✓ computeEMAForIndicator: Reverses input (oldest-first), computes EMA, then reverses back.
vector<double> computeEMAForIndicator(const vector<double>& data, int period)
{
    vector<double> rev = reverseVector(data);
    vector<double> emaRev = computeEMA(rev, period);
    return reverseVector(emaRev);
}

// ✓ computeRSI: Computes the Relative Strength Index over a vector.
vector<double> computeRSI(const vector<double>& data, int period)
{
    int n = data.size();
    const double EMPTY_VALUE = -99999.0;
    vector<double> rsi(n, EMPTY_VALUE);
    if (n <= period)
        return rsi;
    vector<double> diff(n, 0.0);
    for (int i = 1; i < n; i++)
        diff[i] = data[i] - data[i - 1];
    for (int i = period; i < n; i++)
    {
        double gain = 0.0, loss = 0.0;
        for (int j = i - period + 1; j <= i; j++)
        {
            double d = diff[j];
            if (d > 0)
                gain += d;
            else
                loss -= d;
        }
        double avgGain = gain / period;
        double avgLoss = loss / period;
        if (avgLoss == 0)
            rsi[i] = 100.0;
        else
        {
            double RS = avgGain / avgLoss;
            rsi[i] = 100.0 - (100.0 / (1.0 + RS));
        }
    }
    return rsi;
}

// ---------------------------------------------------------------------
// Indicator Core Functions (Production Ready)
// ---------------------------------------------------------------------

// --------------------
// Init procedure
// --------------------
// ✓ Initializes indicator properties, registers parameters, creates and styles index buffers,
// and draws RSI zones as specified by the Forex Tester SDK example.
extern "C" __declspec(dllexport) void __stdcall Init()
{
    // Configure the indicator.
    IndicatorShortName("XU v48-RSIOMA");
    SetOutputWindow(ow_SeparateWindow);
    // Set the scale of the indicator to 0–100.
    SetFixedMinMaxValues(0, 100);
    // Draw a level line at 50 (threshold for RSI crossing).
    AddLevel(50, psDot, 1, clGray);
    // Set the empty value for non-calculated bars.
    SetEmptyValue(-99999.0);

    // Register external parameters.
    AddSeparator("Common");
    RegOption("win", ot_Integer, &ext_win);
    RegOption("showHeader", ot_Boolean, &ext_showHeader);
    RegOption("Display", ot_Boolean, &ext_Display);
    RegOption("Timeframe", ot_String, &ext_Timeframe);
    RegOption("RSIOMA", ot_Integer, &ext_RSIOMA);
    RegOption("Ma_RSIOMA", ot_Integer, &ext_Ma_RSIOMA);
    RegOption("marsiomaXupSigColor", ot_Color, &marisiomaXupSigColor);
    RegOption("marsiomaXdnSigColor", ot_Color, &marisiomaXdnSigColor);
    RegOption("BarsToCount", ot_Integer, &ext_BarsToCount);
    RegOption("alertsOn", ot_Boolean, &ext_alertsOn);
    RegOption("alertsOnCurrent", ot_Boolean, &ext_alertsOnCurrent);
    RegOption("alertsmsg", ot_Boolean, &ext_alertsmsg);
    RegOption("alertsSound", ot_Boolean, &ext_alertsSound);
    RegOption("alertsNotify", ot_Boolean, &ext_alertsNotify);

    // Create 5 index buffers.
    IndicatorBuffers(5);
    int buff0 = CreateIndexBuffer(); // Buffer 0: marsiomaXupSig
    int buff1 = CreateIndexBuffer(); // Buffer 1: marsiomaXdnSig
    int buff2 = CreateIndexBuffer(); // Buffer 2: RSIBuffer
    int buff3 = CreateIndexBuffer(); // Buffer 3: marsioma
    int buff4 = CreateIndexBuffer(); // Buffer 4: trend

    SetIndexBuffer(0, buff0);
    SetIndexBuffer(1, buff1);
    SetIndexBuffer(2, buff2);
    SetIndexBuffer(3, buff3);
    SetIndexBuffer(4, buff4);

    // Set index styles.
    SetIndexStyle(0, psHistogram, 0, 2, clAqua);      // Buffer 0: Histogram, width 2, Aqua
    SetIndexStyle(1, psHistogram, 0, 2, clDeepPink);    // Buffer 1: Histogram, width 2, DeepPink
    SetIndexStyle(2, psLine, 0, 2, clDeepSkyBlue);      // Buffer 2: Line, width 2, DeepSkyBlue
    SetIndexStyle(3, psLine, 0, 2, clSnow);             // Buffer 3: Line, width 2, Snow
    SetIndexStyle(4, psNone, 0, 0, 0);                  // Buffer 4: Not drawn

    // If RSI zones are enabled, draw them.
    if (ext_Display)
    {
        // As in the sample, four RSI zones can be drawn.
        CreateXSECTION("xsection1", 71, 71, 100, 100, RGB(15,26,65));
        CreateXSECTION("xsection2", 51, 51, 69, 69, RGB(15,26,65));
        CreateXSECTION("xsection3", 31, 31, 49, 49, RGB(65,15,26));
        CreateXSECTION("xsection4", 0,  0,  29, 29, RGB(65,15,26));
    }

    // Set internal variables.
    internalTimeFrame = 0; // Implement conversion from ext_Timeframe if required.
    internalDifferentTimeFrame = false;
    correction = ext_RSIOMA + ext_RSIOMA + ext_Ma_RSIOMA;
    lastBarTime = -99999.0;
    
    IndicatorLog("Init: Completed initialization.");
}

// --------------------
// Done procedure
// --------------------
// ✓ Cleans up chart objects when the indicator is removed.
extern "C" __declspec(dllexport) void __stdcall Done()
{
    ChartCleaner();
    IndicatorLog("Done: Indicator deinitialized.");
}

// --------------------
// OnParametersChange procedure (optional)
// --------------------
extern "C" __declspec(dllexport) void __stdcall OnParametersChange()
{
    IndicatorLog("OnParametersChange: Parameters updated.");
    // Additional internal recalculation can be added here if needed.
}

// --------------------
// Calculate procedure
// --------------------
// ✓ Performs all internal calculations for the indicator.
extern "C" __declspec(dllexport) int __stdcall Calculate(int index)
{
    // Retrieve the total number of bars from the chart.
    int BarsCount = Bars();
    if (BarsCount <= 0)
        return 0;

    // New bar detection.
    double currentBarTime = Time(0);
    if (currentBarTime != lastBarTime)
    {
        lastBarTime = currentBarTime;
        IndicatorLog("Calculate: New bar detected.");
    }

    // Enforce a minimum BarsToCount (at least 300) and cap at BarsCount.
    int effectiveBarsToCount = min(BarsCount, max(ext_BarsToCount, 300));
    int limit = min(BarsCount, effectiveBarsToCount + correction);

    // Resize internal calculation buffers.
    MABuffer1.resize(limit);
    RSIBuffer1.resize(limit);
    marsioma1.resize(limit);
    RSIBuffer.resize(limit, -99999.0);
    marsioma.resize(limit, -99999.0);
    marsiomaXupSig.resize(limit, -99999.0);
    marsiomaXdnSig.resize(limit, -99999.0);
    trend.resize(limit + 1, 0.0); // Extra element for safe access

    // ----- Calculation Steps -----
    // Step 1: Compute MABuffer1 = EMA(Close, ext_RSIOMA)
    vector<double> prices(limit);
    for (int i = 0; i < limit; i++)
        prices[i] = Close(i);  // Actual closing prices from the chart.
    MABuffer1 = computeEMAForIndicator(prices, ext_RSIOMA);

    // Step 2: Compute RSIBuffer1 = RSI(MABuffer1, ext_RSIOMA)
    RSIBuffer1 = computeRSI(MABuffer1, ext_RSIOMA);

    // Step 3: Compute marsioma1 = EMA(RSIBuffer1, ext_Ma_RSIOMA)
    marsioma1 = computeEMAForIndicator(RSIBuffer1, ext_Ma_RSIOMA);

    // Step 4: Copy computed values into public buffers.
    for (int i = 0; i < limit; i++)
    {
        RSIBuffer[i] = RSIBuffer1[i];
        marsioma[i] = marsioma1[i];
    }

    // Step 5: Determine trend and generate signal histograms based on RSI crossing 50.
    trend[limit] = 0; // Base trend.
    for (int i = limit - 1; i >= 0; i--)
    {
        trend[i] = trend[i + 1];  // Inherit trend from next bar.
        marsiomaXupSig[i] = -99999.0;
        marsiomaXdnSig[i] = -99999.0;
        if (i < limit - 1)
        {
            if (RSIBuffer[i + 1] < 50.0 && RSIBuffer[i] > 50.0)
            {
                marsiomaXupSig[i] = 100.0;
                trend[i] = 1;  // BUY signal.
            }
            if (RSIBuffer[i + 1] > 50.0 && RSIBuffer[i] < 50.0)
            {
                marsiomaXdnSig[i] = 100.0;
                trend[i] = -1; // SELL signal.
            }
        }
    }

    // Update index buffers with the calculated values.
    SetIndexValues(0, marsiomaXupSig.data(), limit);
    SetIndexValues(1, marsiomaXdnSig.data(), limit);
    SetIndexValues(2, RSIBuffer.data(), limit);
    SetIndexValues(3, marsioma.data(), limit);
    SetIndexValues(4, trend.data(), limit);

    // Header Display: If enabled, display header text with the current RSI value.
    if (ext_showHeader)
    {
        COLORREF headerColor = clGray;
        if (trend[0] == 1)
            headerColor = clDeepSkyBlue;
        else if (trend[0] == -1)
            headerColor = clHotPink;
        char buf[32];
        sprintf(buf, "%.1f", RSIBuffer[0]);
        SetTextRight((ext_ID + "X001").c_str(), "RSI ", ext_LR + 3, ext_UD - 10, headerColor, 32);
        SetTextRight((ext_ID + "X002").c_str(), buf, ext_LR + 4, ext_UD + 29, headerColor, 32);
    }

    // Alerts Management: Check for trend changes and trigger alerts.
    manageAlerts(trend);

    return limit;
}
