/* TA-LIB Copyright (c) 1999-2025, Mario Fortier
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * - Neither name of author nor the names of its contributors
 *   may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* Important:
 *  This file is automatically generated by the utility ta_codegen.
 *  Any modifications will be lost on next execution of ta_codegen.
 *
 *  Modifications should instead be done with the "C" source file
 *  in ta-lib\src\ta_func
 */

/* List of contributors:
 *
 *  Initial  Name/description
 *  -------------------------------------------------------------------
 *  MHL      Matthew Lindblom
 *  MF       Mario Fortier
 *
 * Change history:
 *
 *  MMDDYY BY     Description
 *  -------------------------------------------------------------------
 *  120802 MF     Template creation.
 *  032003 MHL    Implementation of T3
 *  040503 MF     Adapt for compatibility with published code
 *                for TradeStation and Metastock.
 *                See "Smoothing Techniques For More Accurate Signals"
 *                from Tim Tillson in Stock&Commodities V16:1 Page 33-37
 *  052603 MF     Adapt code to compile with .NET Managed C++
 *  070226 MF,CC  Allow period of 1: output is an exact copy of the
 *                input, consistent with TA_MA (issues #48, #59). The
 *                natural math is only near-identity at period=1: the
 *                coefficients sum to 1 in real arithmetic but not in
 *                floating point (~1e-14 drift), so the copy is explicit.
 */

// Import types from parent module
use super::*;

// Allow non-snake-case names to maintain TA-Lib API compatibility
#[allow(non_snake_case)]
#[allow(unused_variables)]
#[allow(dead_code)]
#[allow(unused_mut)]
#[allow(unused_assignments)]
impl Core {
    /// Lookback period for [`Core::t3`]: the number of leading input values consumed before the
    /// first output value can be produced.
    ///
    /// # Arguments
    ///
    /// * `optInTimePeriod` — EMA period for each of the six stages (default 5, range 1..=100000)
    /// * `optInVFactor` — Volume factor weighting the coefficients (0 = plain triple EMA, higher
    ///   = more DEMA-like sharpening) (default 0.7, range 0..=1)
    ///
    /// Returns `usize::MAX` when a parameter is out of range. Integer parameters accept `i32::MIN`
    /// to select their default value.
    #[inline]
    pub fn t3_lookback(&self, mut optInTimePeriod: i32, mut optInVFactor: f64) -> usize {
        if ((optInTimePeriod) as i32) == (i32::MIN) {
            optInTimePeriod = 5;
        } else if (((optInTimePeriod) as i32) < 1) || (((optInTimePeriod) as i32) > 100000) {
            return usize::MAX;
        }
        return (6 * (optInTimePeriod - 1) + self.unstable_period[FuncUnstId::T3 as usize]) as usize;
    }
    /// Tillson's T3: a low-lag moving average built from six chained EMAs, combined via
    /// volume-factor-weighted coefficients. Not the same as EMA3, despite both being called "triple
    /// EMA".
    ///
    /// # Formula
    ///
    /// ```text
    /// k = 2/(period+1); e1=EMA(x), e2=EMA(e1), ... e6=EMA(e5) (six chained EMAs).
    /// v = vFactor: c1 = -v^3; c2 = 3(v^2 - c1); c3 = -6v^2 - 3(v - c1); c4 = 1 + 3v - c1 + 3v^2.
    /// T3 = c1*e6 + c2*e5 + c3*e4 + c4*e3
    /// ```
    ///
    /// # Notes
    ///
    /// * A period of 1 performs no smoothing: the output is a copy of the input. Allowed since
    ///   0.6.5 (issues #48/#59).
    ///
    /// # Arguments
    ///
    /// * `startIdx` — Start index of the requested calculation range.
    /// * `endIdx` — End index of the requested calculation range (inclusive).
    /// * `inReal` — Source series to smooth.
    /// * `optInTimePeriod` — EMA period for each of the six stages (default 5, range 1..=100000)
    /// * `optInVFactor` — Volume factor weighting the coefficients (0 = plain triple EMA, higher
    ///   = more DEMA-like sharpening) (default 0.7, range 0..=1)
    /// * `outBegIdx` — Set to the input index of the first output value.
    /// * `outNBElement` — Set to the number of output values written.
    /// * `outReal` — T3 smoothed line.
    ///
    /// Integer parameters accept `i32::MIN` to select their default value.
    ///
    /// # Errors
    ///
    /// Returns [`RetCode::OutOfRangeStartIndex`] when `endIdx < startIdx`, and
    /// [`RetCode::BadParam`] when an optional parameter is outside its documented range.
    ///
    /// # Panics
    ///
    /// Input slices must cover `startIdx..=endIdx` and output slices must hold the number of values
    /// produced for that range: undersized slices panic or, for functions that forward to unchecked
    /// internals, cause undefined behavior. Sizing every output slice to the input length is always
    /// sufficient.
    ///
    /// # Examples
    ///
    /// ```
    /// use ta_lib::{Core, RetCode};
    ///
    /// let data: Vec<f64> = (0..252).map(|i| 100.0 + 10.0 * (0.1 * i as f64).sin()).collect();
    ///
    /// let core = Core::new();
    /// let mut out_beg = 0;
    /// let mut out_nb = 0;
    /// let mut out = vec![0.0; 252];
    ///
    /// let ret = core.t3(0, data.len() - 1, &data, 5, 0.7, &mut out_beg, &mut out_nb, &mut out);
    /// assert_eq!(ret, RetCode::Success);
    /// assert!(out_nb > 0);
    /// ```
    ///
    /// # See also
    ///
    /// [`Core::ema`] · [`Core::dema`] · [`Core::tema`] · [`Core::ma`]
    ///
    /// Further reading: [ta-lib.org/functions/t3](https://ta-lib.org/functions/t3/)
    #[doc(alias = "TillsonT3")]
    #[doc(alias = "TripleExponentialMovingAverage")]
    pub fn t3(
        &self,
        startIdx: usize,
        endIdx: usize,
        inReal: &[f64],
        mut optInTimePeriod: i32,
        mut optInVFactor: f64,
        outBegIdx: &mut usize,
        outNBElement: &mut usize,
        outReal: &mut [f64],
    ) -> RetCode {
        if endIdx < startIdx {
            return RetCode::OutOfRangeStartIndex;
        }
        if ((optInTimePeriod) as i32) == (i32::MIN) {
            optInTimePeriod = 5;
        } else if (((optInTimePeriod) as i32) < 1) || (((optInTimePeriod) as i32) > 100000) {
            return RetCode::BadParam;
        }
        let mut startIdx = startIdx;
        let mut outIdx: usize = 0_usize;
        let mut lookbackTotal: usize = 0_usize;
        let mut today: usize = 0_usize;
        let mut i: usize = 0_usize;
        let mut k: f64 = 0.0_f64;
        let mut one_minus_k: f64 = 0.0_f64;
        let mut e1: f64 = 0.0_f64;
        let mut e2: f64 = 0.0_f64;
        let mut e3: f64 = 0.0_f64;
        let mut e4: f64 = 0.0_f64;
        let mut e5: f64 = 0.0_f64;
        let mut e6: f64 = 0.0_f64;
        let mut c1: f64 = 0.0_f64;
        let mut c2: f64 = 0.0_f64;
        let mut c3: f64 = 0.0_f64;
        let mut c4: f64 = 0.0_f64;
        let mut tempReal: f64 = 0.0_f64;
        // For an explanation of this function, please read:
        //
        // Magazine articles written by Tim Tillson
        //
        // Essentially, a T3 of time serie 't' is:
        //   EMA1(x,Period) = EMA(x,Period)
        //   EMA2(x,Period) = EMA(EMA1(x,Period),Period)
        //   GD(x,Period,vFactor) = (EMA1(x,Period)*(1+vFactor)) - (EMA2(x,Period)*vFactor)
        //   T3 = GD (GD ( GD(t, Period, vFactor), Period, vFactor), Period, vFactor);
        //
        // T3 offers a moving average with less lags then the
        // traditional EMA.
        //
        // Do not confuse a T3 with EMA3. Both are called "Triple EMA"
        // in the litterature.
        lookbackTotal = (6 * (optInTimePeriod - 1) + self.unstable_period[FuncUnstId::T3 as usize]) as usize;
        if startIdx <= lookbackTotal {
            startIdx = lookbackTotal;
        }
        // Make sure there is still something to evaluate.
        if startIdx > endIdx {
            (*outNBElement) = 0;
            (*outBegIdx) = 0;
            return RetCode::Success;
        }
        // No smoothing at period of 1: the output is a copy of the input
        // (same convention as TA_MA for every MAType). Explicit because the
        // coefficients below sum to 1 only in real arithmetic; going through
        // the math would leave ~1e-14 floating-point drift on every value.
        if optInTimePeriod == 1 {
            (*outBegIdx) = startIdx;
            outIdx = 0;
            today = startIdx;
            while today <= endIdx {
                outReal[outIdx] = ((inReal[{ let _v = today; today += 1; _v }]) as f64);
                outIdx += 1;
            }
            (*outNBElement) = outIdx;
            return RetCode::Success;
        }
        (*outBegIdx) = startIdx;
        today = startIdx - lookbackTotal;
        k = 2.0 / (((optInTimePeriod) as f64) + 1.0);
        one_minus_k = 1.0 - k;
        // Initialize e1
        tempReal = inReal[{ let _v = today; today += 1; _v }];
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            tempReal += inReal[{ let _v = today; today += 1; _v }];
            i -= 1;
        }
        e1 = tempReal / ((optInTimePeriod) as f64);
        // Initialize e2
        tempReal = e1;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(inReal[{ let _v = today; today += 1; _v }], one_minus_k * e1);
            tempReal += e1;
            i -= 1;
        }
        e2 = tempReal / ((optInTimePeriod) as f64);
        // Initialize e3
        tempReal = e2;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(inReal[{ let _v = today; today += 1; _v }], one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            tempReal += e2;
            i -= 1;
        }
        e3 = tempReal / ((optInTimePeriod) as f64);
        // Initialize e4
        tempReal = e3;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(inReal[{ let _v = today; today += 1; _v }], one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            tempReal += e3;
            i -= 1;
        }
        e4 = tempReal / ((optInTimePeriod) as f64);
        // Initialize e5
        tempReal = e4;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(inReal[{ let _v = today; today += 1; _v }], one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            e4 = (k as f64).mul_add(e3, one_minus_k * e4);
            tempReal += e4;
            i -= 1;
        }
        e5 = tempReal / ((optInTimePeriod) as f64);
        // Initialize e6
        tempReal = e5;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(inReal[{ let _v = today; today += 1; _v }], one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            e4 = (k as f64).mul_add(e3, one_minus_k * e4);
            e5 = (k as f64).mul_add(e4, one_minus_k * e5);
            tempReal += e5;
            i -= 1;
        }
        e6 = tempReal / ((optInTimePeriod) as f64);
        // Skip the unstable period
        while today <= startIdx {
            // Do the calculation but do not write the output
            e1 = (k as f64).mul_add(inReal[{ let _v = today; today += 1; _v }], one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            e4 = (k as f64).mul_add(e3, one_minus_k * e4);
            e5 = (k as f64).mul_add(e4, one_minus_k * e5);
            e6 = (k as f64).mul_add(e5, one_minus_k * e6);
        }
        // Calculate the constants
        tempReal = optInVFactor * optInVFactor;
        c1 = 0_f64 - tempReal * optInVFactor;
        c2 = 3.0 * (tempReal - c1);
        c3 = (0_f64 - 6.0) * tempReal - 3.0 * (optInVFactor - c1);
        c4 = (3.0 as f64).mul_add(tempReal, (3.0 as f64).mul_add(optInVFactor, 1.0) - c1);
        // Write the first output
        outIdx = 0;
        outReal[outIdx] = (c4 as f64).mul_add(e3, (c3 as f64).mul_add(e4, (c1 as f64).mul_add(e6, c2 * e5)));
        outIdx += 1;
        // Calculate and output the remaining of the range.
        while today <= endIdx {
            e1 = (k as f64).mul_add(inReal[{ let _v = today; today += 1; _v }], one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            e4 = (k as f64).mul_add(e3, one_minus_k * e4);
            e5 = (k as f64).mul_add(e4, one_minus_k * e5);
            e6 = (k as f64).mul_add(e5, one_minus_k * e6);
            outReal[outIdx] = (c4 as f64).mul_add(e3, (c3 as f64).mul_add(e4, (c1 as f64).mul_add(e6, c2 * e5)));
            outIdx += 1;
        }
        // Indicates to the caller the number of output
        // successfully calculated.
        (*outNBElement) = outIdx;
        return RetCode::Success;
    }
    /// Unchecked variant of [`Core::t3`], used for internal cross-indicator calls.
    ///
    /// Skips parameter validation and uses unchecked indexing internally. Every argument must
    /// satisfy the constraints documented on [`Core::t3`]; an out-of-range parameter, an input
    /// slice not covering `startIdx..=endIdx`, or an undersized output slice may panic or cause
    /// undefined behavior. Prefer [`Core::t3`].
    #[inline]
    pub fn t3_unguarded(
        &self,
        mut startIdx: usize,
        endIdx: usize,
        inReal: &[f64],
        mut optInTimePeriod: i32,
        mut optInVFactor: f64,
        outBegIdx: &mut usize,
        outNBElement: &mut usize,
        outReal: &mut [f64],
    ) -> RetCode {
        let mut outIdx: usize = 0_usize;
        let mut lookbackTotal: usize = 0_usize;
        let mut today: usize = 0_usize;
        let mut i: usize = 0_usize;
        let mut k: f64 = 0.0_f64;
        let mut one_minus_k: f64 = 0.0_f64;
        let mut e1: f64 = 0.0_f64;
        let mut e2: f64 = 0.0_f64;
        let mut e3: f64 = 0.0_f64;
        let mut e4: f64 = 0.0_f64;
        let mut e5: f64 = 0.0_f64;
        let mut e6: f64 = 0.0_f64;
        let mut c1: f64 = 0.0_f64;
        let mut c2: f64 = 0.0_f64;
        let mut c3: f64 = 0.0_f64;
        let mut c4: f64 = 0.0_f64;
        let mut tempReal: f64 = 0.0_f64;
        unsafe {
        assert!(endIdx < inReal.len());
        let _assertLb = self.t3_lookback(optInTimePeriod, optInVFactor);
        let _assertStart = if startIdx > _assertLb { startIdx } else { _assertLb };
        assert!(_assertStart > endIdx || endIdx - _assertStart < outReal.len());
        lookbackTotal = (6 * (optInTimePeriod - 1) + self.unstable_period[FuncUnstId::T3 as usize]) as usize;
        if startIdx <= lookbackTotal {
            startIdx = lookbackTotal;
        }
        if startIdx > endIdx {
            (*outNBElement) = 0;
            (*outBegIdx) = 0;
            return RetCode::Success;
        }
        if optInTimePeriod == 1 {
            (*outBegIdx) = startIdx;
            outIdx = 0;
            today = startIdx;
            while today <= endIdx {
                *outReal.as_mut_ptr().add(outIdx) = ((*inReal.as_ptr().add({ let _v = today; today += 1; _v })) as f64);
                outIdx += 1;
            }
            (*outNBElement) = outIdx;
            return RetCode::Success;
        }
        (*outBegIdx) = startIdx;
        today = startIdx - lookbackTotal;
        k = 2.0 / (((optInTimePeriod) as f64) + 1.0);
        one_minus_k = 1.0 - k;
        tempReal = *inReal.as_ptr().add({ let _v = today; today += 1; _v });
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            tempReal += *inReal.as_ptr().add({ let _v = today; today += 1; _v });
            i -= 1;
        }
        e1 = tempReal / ((optInTimePeriod) as f64);
        tempReal = e1;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(*inReal.as_ptr().add({ let _v = today; today += 1; _v }), one_minus_k * e1);
            tempReal += e1;
            i -= 1;
        }
        e2 = tempReal / ((optInTimePeriod) as f64);
        tempReal = e2;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(*inReal.as_ptr().add({ let _v = today; today += 1; _v }), one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            tempReal += e2;
            i -= 1;
        }
        e3 = tempReal / ((optInTimePeriod) as f64);
        tempReal = e3;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(*inReal.as_ptr().add({ let _v = today; today += 1; _v }), one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            tempReal += e3;
            i -= 1;
        }
        e4 = tempReal / ((optInTimePeriod) as f64);
        tempReal = e4;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(*inReal.as_ptr().add({ let _v = today; today += 1; _v }), one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            e4 = (k as f64).mul_add(e3, one_minus_k * e4);
            tempReal += e4;
            i -= 1;
        }
        e5 = tempReal / ((optInTimePeriod) as f64);
        tempReal = e5;
        // for( i = (optInTimePeriod - 1) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod - 1) as usize;
        while i > 0 {
            e1 = (k as f64).mul_add(*inReal.as_ptr().add({ let _v = today; today += 1; _v }), one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            e4 = (k as f64).mul_add(e3, one_minus_k * e4);
            e5 = (k as f64).mul_add(e4, one_minus_k * e5);
            tempReal += e5;
            i -= 1;
        }
        e6 = tempReal / ((optInTimePeriod) as f64);
        while today <= startIdx {
            e1 = (k as f64).mul_add(*inReal.as_ptr().add({ let _v = today; today += 1; _v }), one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            e4 = (k as f64).mul_add(e3, one_minus_k * e4);
            e5 = (k as f64).mul_add(e4, one_minus_k * e5);
            e6 = (k as f64).mul_add(e5, one_minus_k * e6);
        }
        tempReal = optInVFactor * optInVFactor;
        c1 = 0_f64 - tempReal * optInVFactor;
        c2 = 3.0 * (tempReal - c1);
        c3 = (0_f64 - 6.0) * tempReal - 3.0 * (optInVFactor - c1);
        c4 = (3.0 as f64).mul_add(tempReal, (3.0 as f64).mul_add(optInVFactor, 1.0) - c1);
        outIdx = 0;
        *outReal.as_mut_ptr().add(outIdx) = (c4 as f64).mul_add(e3, (c3 as f64).mul_add(e4, (c1 as f64).mul_add(e6, c2 * e5)));
        outIdx += 1;
        while today <= endIdx {
            e1 = (k as f64).mul_add(*inReal.as_ptr().add({ let _v = today; today += 1; _v }), one_minus_k * e1);
            e2 = (k as f64).mul_add(e1, one_minus_k * e2);
            e3 = (k as f64).mul_add(e2, one_minus_k * e3);
            e4 = (k as f64).mul_add(e3, one_minus_k * e4);
            e5 = (k as f64).mul_add(e4, one_minus_k * e5);
            e6 = (k as f64).mul_add(e5, one_minus_k * e6);
            *outReal.as_mut_ptr().add(outIdx) = (c4 as f64).mul_add(e3, (c3 as f64).mul_add(e4, (c1 as f64).mul_add(e6, c2 * e5)));
            outIdx += 1;
        }
        (*outNBElement) = outIdx;
        return RetCode::Success;
        } // unsafe
    }
}
/***************/
/* End of File */
/***************/
