// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © julzen2

//@version=5
indicator(title="Corona Trend Vigor v2.1", shorttitle="CTV v2.1", overlay=false,
     format=format.price, precision=2)

// --- Inputs ---
var TimeFrame = input.timeframe("0", "Time Frame") // "0" means current chart's timeframe
var PriceMode = input.int(4, "Price Mode (0-10)", minval=0, maxval=10)
var LineColor = input.color(color.blue, "Line Color")
var FuzzR = input.int(0, "Fuzz Red (0-255)", minval=0, maxval=255)
var FuzzG = input.int(0, "Fuzz Green (0-255)", minval=0, maxval=255)
var FuzzB = input.int(255, "Fuzz Blue (0-255)", minval=0, maxval=255)
var VisualMode = input.int(0, "Visual Mode (0-TV+Corona, 1-TV, 2-Corona)", minval=0, maxval=2)
var CoronaBars = input.int(1000, "Corona Bars (0 for all)", minval=0)

// --- Constants ---
float pi = math.atan(1) * 4

// --- Global Variables (using var for persistence across bars) ---
var float[] Q = array.new_float(60, 0.0)
var float[] I = array.new_float(60, 0.0)
var float[] Real = array.new_float(60, 0.0)
var float[] Imag = array.new_float(60, 0.0)
var float[] Ampl = array.new_float(60, 0.0)
var float[] DB = array.new_float(60, 0.0)
var float[] OldI = array.new_float(60, 0.0)
var float[] OlderI = array.new_float(60, 0.0)
var float[] OldQ = array.new_float(60, 0.0)
var float[] OlderQ = array.new_float(60, 0.0)
var float[] OldReal = array.new_float(60, 0.0)
var float[] OlderReal = array.new_float(60, 0.0)
var float[] OldImag = array.new_float(60, 0.0)
var float[] OlderImag = array.new_float(60, 0.0)
var float[] OldDB = array.new_float(60, 0.0)
var float[] Raster = array.new_float(51, 0.0)
var float[] OldRaster = array.new_float(51, 0.0)

var float FuzzWidth = 0.4 // Global constant

var int mcnt_bars = 0
var int per = 30 // MQL4 default

// --- Helper Functions ---

// Custom Heiken Ashi calculation
f_heiken_ashi(tf, price_type, bar_index_offset) =>
    // Determine the actual timeframe string for request.security
    string actual_tf = tf == "0" ? timeframe.period : tf
    
    // Get current bar's OHLC data for the specified timeframe using request.security
    [o, h, l, c] = request.security(syminfo.tickerid, actual_tf, [open, high, low, close], lookahead=barmerge.lookahead_on)

    // Using `var` to store HA values persistently, similar to MQL4 arrays
    var float ha_close = 0.0
    var float ha_open = 0.0
    var float ha_high = 0.0
    var float ha_low = 0.0

    // HA Close
    current_ha_close = (o + h + l + c) / 4.0
    
    // HA Open
    // For the very first bar, ha_open[1] and ha_close[1] will be na, so use nz()
    current_ha_open = bar_index == 0 ? (o + c) / 2.0 : (nz(ha_open[1]) + nz(ha_close[1])) / 2.0

    // HA High
    current_ha_high = math.max(h, current_ha_open, current_ha_close)

    // HA Low
    current_ha_low = math.min(l, current_ha_open, current_ha_close)
    
    // Update `var` HA values for next bar's calculation
    ha_close := current_ha_close
    ha_open := current_ha_open
    ha_high := current_ha_high
    ha_low := current_ha_low
    
    // Return the requested HA price type
    float ha_val = 0.0
    if price_type == 0 // haClose
        ha_val := current_ha_close
    else if price_type == 1 // haOpen
        ha_val := current_ha_open
    else if price_type == 2 // haHigh
        ha_val := current_ha_high
    else if price_type == 3 // haLow
        ha_val := current_ha_low
    
    ha_val

// Custom Median function (for series)
// Now accepts a 'series float' and creates a temporary array from its history.
f_median(input_series, count) =>
    // Return 'na' if there aren't enough bars for the 'count' period
    if bar_index < count - 1
        na
    else
        // Create a temporary array to hold the last 'count' values from the series
        temp_arr = array.new_float(count)
        
        // Populate the array with values from the series_input
        // input_series[0] is current, input_series[1] is 1 bar ago, etc.
        for i = 0 to count - 1
            array.set(temp_arr, i, input_series[i])

        array.sort(temp_arr)
        
        float median_val = 0.0
        int num = math.round(float(count - 1) / 2.0)
        
        if (count % 2) > 0 // Odd number of elements
            median_val := array.get(temp_arr, num)
        else // Even number of elements
            median_val := (array.get(temp_arr, num) + array.get(temp_arr, num + 1)) / 2.0
        
        median_val

// Function to get price based on PriceMode
f_get_price_series(mode, tf) =>
    // Determine the actual timeframe string for request.security
    string actual_tf = tf == "0" ? timeframe.period : tf

    // Request OHLC data from the specified timeframe unconditionally.
    [o, h, l, c] = request.security(syminfo.tickerid, actual_tf, [open, high, low, close], lookahead=barmerge.lookahead_on)

    // Calculate Heiken Ashi components unconditionally
    float ha_close_val = f_heiken_ashi(actual_tf, 0, 0)
    float ha_open_val  = f_heiken_ashi(actual_tf, 1, 0)
    float ha_high_val  = f_heiken_ashi(actual_tf, 2, 0)
    float ha_low_val   = f_heiken_ashi(actual_tf, 3, 0)

    // Use a switch statement to select the correct price series based on `mode`
    float price_series_val = switch mode
        0  => c              // Close
        1  => o              // Open
        2  => h              // High
        3  => l              // Low
        4  => (h + l) / 2    // Median Price (H+L)/2
        5  => (h + l + c) / 3 // Typical Price (H+L+C)/3
        6  => (h + l + c * 2) / 4 // Weighted Close (H+L+C*2)/4
        7  => ha_close_val   // Heiken Ashi Close
        8  => ha_open_val    // Heiken Ashi Open
        9  => ha_high_val    // Heiken Ashi High
        10 => ha_low_val     // Heiken Ashi Low
        => c // Default case (should not be reached due to input.int min/max)

    price_series_val

// --- Indicator Buffers (Pine Script equivalent: series variables) ---
var float TV_line = na // This will hold the main Trend Vigor line

// --- Main Calculation ---
// MQL4 `start()` function logic converted to Pine Script's bar-by-bar execution.

// Get price for the current timeframe and PriceMode
float current_price = f_get_price_series(PriceMode, TimeFrame)
float prev_price = f_get_price_series(PriceMode, TimeFrame)[1] // Price of previous bar on selected TF

// --- Calculate HP and SmoothHP ---
var float HP = 0.0 // MQL4: HP[y]
var float SmoothHP = 0.0 // MQL4: SmoothHP[y]
var float prev_HP = 0.0 // MQL4: HP[y-1]
var float prev_SmoothHP = 0.0 // MQL4: SmoothHP[y-1]
var float prev2_HP = 0.0
var float prev3_HP = 0.0
var float prev4_HP = 0.0
var float prev5_HP = 0.0

alpha1 = (1 - math.sin(2 * pi / 30)) / math.cos(2 * pi / 30)

// HP[y] = 0.5*(1 + alpha1)*(price[y] - price[y-1]) + alpha1*HP[y-1];
current_HP = 0.5 * (1 + alpha1) * (current_price - prev_price) + alpha1 * nz(prev_HP)

// SmoothHP[y] = (HP[y] + 2.0*HP[y-1] + 3.0*HP[y-2] + 3.0*HP[y-3] + 2.0*HP[y-4] + HP[y-5]) / 12.0;
current_SmoothHP = (current_HP + 2.0 * nz(prev_HP) + 3.0 * nz(prev2_HP) + 3.0 * nz(prev3_HP) + 2.0 * nz(prev4_HP) + nz(prev5_HP)) / 12.0

// MQL4 conditional: if(y < 6) SmoothHP[y] = price[y] - price[y-1]; if(y == 0) SmoothHP[y] = 0;
// Pine Script's `bar_index` handles initial bars:
if bar_index < 5 // Equivalent to y < 6 (as array indices are 0-based and [0] is current)
    current_SmoothHP := current_price - prev_price
if bar_index == 0
    current_SmoothHP := 0.0

// Update previous HP and SmoothHP for next bar's calculation
prev5_HP := prev4_HP
prev4_HP := prev3_HP
prev3_HP := prev2_HP
prev2_HP := prev_HP
prev_HP := current_HP
SmoothHP := current_SmoothHP // Assign to persistent SmoothHP variable

// --- Calculate Corona Trend Vigor ---
// MQL4's `y` is essentially `bar_index` in Pine Script.
float delta = -0.015 * (bar_index + 1) + 0.5
if delta < 0.1
    delta := 0.1

// Update OldI, OldQ, OldReal, OldImag, OldDB arrays for the current bar
for n = 11 to 59
    array.set(OlderI, n, array.get(OldI, n))
    array.set(OldI, n, array.get(I, n))
    array.set(OlderQ, n, array.get(OldQ, n))
    array.set(OldQ, n, array.get(Q, n))
    array.set(OlderReal, n, array.get(OldReal, n))
    array.set(OldReal, n, array.get(Real, n))
    array.set(OlderImag, n, array.get(OldImag, n))
    array.set(OldImag, n, array.get(Imag, n))
    array.set(OldDB, n, array.get(DB, n))

    float beta = math.cos(4 * pi / (n + 1))
    float gamma = 1.0 / math.cos(4 * pi * delta / (n + 1))
    float alpha = gamma - math.sqrt(gamma * gamma - 1)

    // Using `nz()` for previous bar's SmoothHP values as they are calculated on the current bar
    float prev_smoothHP_val = nz(SmoothHP[1])
    float prev2_smoothHP_val = nz(SmoothHP[2])

    array.set(Q, n, ((n + 1) / (4 * pi)) * (SmoothHP - prev_smoothHP_val))
    array.set(I, n, SmoothHP)

    float val_I = array.get(I, n)
    float val_OlderI = array.get(OlderI, n)
    float val_OldReal = array.get(OldReal, n)
    float val_OlderReal = array.get(OlderReal, n)
    
    float val_Q = array.get(Q, n)
    float val_OlderQ = array.get(OlderQ, n)
    float val_OldImag = array.get(OldImag, n)
    float val_OlderImag = array.get(OlderImag, n)

    array.set(Real, n, 0.5 * (1 - alpha) * (val_I - val_OlderI) + beta * (1 + alpha) * val_OldReal - alpha * val_OlderReal)
    array.set(Imag, n, 0.5 * (1 - alpha) * (val_Q - val_OlderQ) + beta * (1 + alpha) * val_OldImag - alpha * val_OlderImag)
    
    float real_val = array.get(Real, n)
    float imag_val = array.get(Imag, n)
    array.set(Ampl, n, real_val * real_val + imag_val * imag_val)


var float MaxAmpl = 0.0
// Find MaxAmpl
if bar_index > 11 // Need enough bars for initial calculations (similar to y > 11 in MQL4)
    MaxAmpl := array.get(Ampl, 11)
    for n = 11 to 59
        if array.get(Ampl, n) > MaxAmpl
            MaxAmpl := array.get(Ampl, n)

// Calculate DB values
if bar_index > 11
    for n = 11 to 59
        float dB_calc = 0.0
        if MaxAmpl != 0 and array.get(Ampl, n) / MaxAmpl > 0
            dB_calc := -10.0 * math.log10(0.01 / (1 - 0.99 * array.get(Ampl, n) / MaxAmpl))
        
        array.set(DB, n, 0.33 * dB_calc + 0.67 * array.get(OldDB, n))
        if array.get(DB, n) > 20
            array.set(DB, n, 20.0)

// Calculate Dominant Cycle (DC)
var float DC_val = 0.0 // MQL4: DC[y]
if bar_index > 11 // Needs enough bars for DB calculation
    float Num = 0.0
    float Denom = 0.0
    for n = 11 to 59
        if array.get(DB, n) <= 6
            Num := Num + (n + 1) * (20 - array.get(DB, n))
            Denom := Denom + (20 - array.get(DB, n))
    
    if Denom != 0
        DC_val := 0.5 * Num / Denom
    else if bar_index > 0
        DC_val := nz(DC_val[1]) // Equivalent to DC[y-1]

var float mDomCyc = 0.0 // MQL4: mDomCyc[y]
// We'll use series_DC directly as the input for f_median.
series_DC = DC_val // This remains a series float

// Ensure we have enough bars for the median calculation (current_bar_idx - i should be valid)
if bar_index >= 4 // For 5-period median, we need at least 4 previous bars (0-4)
    mDomCyc := f_median(series_DC, 5) // Call f_median with series_DC and count
else
    mDomCyc := nz(mDomCyc[1]) // Keep previous value or na if not enough bars

if mDomCyc < 6
    mDomCyc := 6.0

// Filter Bandpass component (IP)
var float IP_val = 0.0 // MQL4: IP[y]
var float prev_IP_val = 0.0
var float prev2_IP_val = 0.0
float delta1 = 0.1
float beta1 = math.cos(2 * pi / mDomCyc)
float gamma1 = 1.0 / math.cos(4 * pi * delta1 / mDomCyc)
float alpha2 = gamma1 - math.sqrt(gamma1 * gamma1 - 1)

// IP[y] = 0.5*(1 - alpha2)*(price[y] - price[y-2]) + beta1*(1 + alpha2)*IP[y-1] - alpha2*IP[y-2];
current_IP_calc = 0.5 * (1 - alpha2) * (current_price - nz(current_price[2])) + beta1 * (1 + alpha2) * nz(prev_IP_val) - alpha2 * nz(prev2_IP_val)

// Update previous IP values
prev2_IP_val := prev_IP_val
prev_IP_val := current_IP_calc
IP_val := current_IP_calc

// Quadrature component (Q1)
float Q1 = (mDomCyc / (2 * pi)) * (IP_val - nz(IP_val[1]))

// Pythagorean theorem to establish cycle amplitude (Ampl2)
float Ampl2 = math.sqrt(IP_val * IP_val + Q1 * Q1)

// Trend amplitude (Ratio)
var float Ratio_val = 0.0
int iDomCyc = math.round(mDomCyc)
float trend_val = current_price - nz(current_price[iDomCyc + 1]) // price[y-iDomCyc-1]

if trend_val != 0 and Ampl2 != 0
    Ratio_val := 0.33 * trend_val / Ampl2 + 0.67 * nz(Ratio_val[1])
if Ratio_val > 10
    Ratio_val := 10.0
if Ratio_val < -10
    Ratio_val := -10.0

var float mTV = 0.0 // MQL4: mTV[y]
mTV := 0.05 * (Ratio_val + 10)

// Clamp mTV
if mTV < 0.0
    mTV := 0.0
if mTV > 1.0
    mTV := 1.0

// Calculate Width for Raster
float Width = 0.01 // Default as per MQL4
if mTV >= 0.3 and mTV < 0.5
    Width := mTV - 0.3
else if mTV > 0.5 and mTV <= 0.7
    Width := -mTV + 0.7

// Calculate Raster values
for n = 1 to 50
    raster_n = 20.0 // Default value
    
    if n < math.round(50 * mTV)
        // MQL4: Raster[n] = 0.8*(MathPow(( 20*mTV - 0.4*n)/Width,0.85) + 0.2*OldRaster[n]);
        // Avoid division by zero if Width is 0
        pow_base = Width != 0 ? (20 * mTV - 0.4 * n) / Width : 0.0 // Handle Width=0
        if pow_base < 0
            pow_base := 0.0 // MathPow with negative base and non-integer exponent is complex. Clamp to 0 if negative.
        raster_n := 0.8 * (math.pow(pow_base, 0.85) + 0.2 * array.get(OldRaster, n))
    else if n > math.round(50 * mTV)
        // MQL4: Raster[n] = 0.8*(MathPow((-20*mTV + 0.4*n)/Width,0.85) + 0.2*OldRaster[n]);
        // Avoid division by zero if Width is 0
        pow_base = Width != 0 ? (-20 * mTV + 0.4 * n) / Width : 0.0 // Handle Width=0
        if pow_base < 0
            pow_base := 0.0 // Clamp to 0 if negative.
        raster_n := 0.8 * (math.pow(pow_base, 0.85) + 0.2 * array.get(OldRaster, n))
    else if n == math.round(50 * mTV)
        raster_n := 0.5 * array.get(OldRaster, n)

    if raster_n < 0
        raster_n := 0.0
    if raster_n > 20 or mTV < 0.3 or mTV > 0.7
        raster_n := 20.0
    
    array.set(Raster, n, raster_n)
    array.set(OldRaster, n, raster_n) // Update OldRaster for next bar


// --- Plotting Logic ---

// Main Trend Vigor line (TV)
TV_value = 20 * mTV - 10

// Determine the value to plot for TV_line, `na` if not visible
float plot_TV_line = if VisualMode == 0 or VisualMode == 1
    TV_value
else
    na

plot(plot_TV_line, title="Trend Vigor", color=LineColor, linewidth=2, style=plot.style_line, display=display.pane)

// Corona Visualization (Simplified Plotting)
// As previously discussed, exact replication of MQL4's OBJ_RECTANGLE for 50 items per bar
// in a separate pane is not directly supported by Pine Script.
// This version plots a single 'Corona Intensity' line with dynamic coloring.

// Calculate Corona Intensity values only if needed for plotting
float avg_raster = 0.0
if VisualMode == 0 or VisualMode == 2
    for n = 1 to 50
        avg_raster := avg_raster + array.get(Raster, n)
    avg_raster := avg_raster / 50.0

// Get individual RGB components of LineColor using color.r(), color.g(), color.b() functions
float line_r = color.r(LineColor)
float line_g = color.g(LineColor)
float line_b = color.b(LineColor)

int color1_comp = 0
int color2_comp = 0
int color3_comp = 0

// Replicate MQL4 color calculation logic
if avg_raster <= 10
    color1_comp := math.round(line_r + avg_raster * (FuzzR - line_r) / 10.0)
    color2_comp := math.round(line_g + avg_raster * (FuzzG - line_g) / 10.0)
    color3_comp := math.round(line_b + avg_raster * (FuzzB - line_b) / 10.0)
else // avg_raster > 10
    color1_comp := math.round(FuzzR * (2 - avg_raster / 10.0))
    color2_comp := math.round(FuzzG * (2 - avg_raster / 10.0))
    color3_comp := math.round(FuzzB * (2 - avg_raster / 10.0))

color corona_color = color.rgb(math.max(0, math.min(255, color1_comp)),
                         math.max(0, math.min(255, color2_comp)),
                         math.max(0, math.min(255, color3_comp)))

// Determine the value to plot for Corona Intensity, `na` if not visible
float plot_corona_intensity = if VisualMode == 0 or VisualMode == 2
    0 // Plotting at 0 for columns, as per original intent
else
    na

// Define the color for the Corona Intensity plot. This needs to be a series color.
// Use `na` if the plot is not visible.
color final_corona_color = if VisualMode == 0 or VisualMode == 2
    corona_color
else
    na

plot(plot_corona_intensity, title="Corona Intensity", color=final_corona_color, style=plot.style_columns, linewidth=1, display=display.pane)

// Plot indicator levels
hline(2, "Level +2", color=color.gray, linestyle=hline.style_dashed)
hline(-2, "Level -2", color=color.gray, linestyle=hline.style_dashed)

