/* 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
 *  -------------------------------------------------------------------
 *  MF       Mario Fortier
 *  BT       Barry Tsung
 *
 * Change history:
 *
 *  MMDDYY BY      Description
 *  -------------------------------------------------------------------
 *  112605 MF      Initial version.
 *  021806 MF,BT   Fix #1434450 reported by BT.
 */

// 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::cmo`]: the number of leading input values consumed before the
    /// first output value can be produced.
    ///
    /// # Arguments
    ///
    /// * `optInTimePeriod` — Bars over which gains/losses are smoothed (default 14, range
    ///   2..=100000)
    ///
    /// Returns `usize::MAX` when a parameter is out of range. Integer parameters accept `i32::MIN`
    /// to select their default value.
    #[inline]
    pub fn cmo_lookback(&self, mut optInTimePeriod: i32) -> usize {
        if ((optInTimePeriod) as i32) == (i32::MIN) {
            optInTimePeriod = 14;
        } else if (((optInTimePeriod) as i32) < 2) || (((optInTimePeriod) as i32) > 100000) {
            return usize::MAX;
        }
        let mut retValue: usize = 0_usize;
        retValue = (optInTimePeriod + self.unstable_period[FuncUnstId::Cmo as usize]) as usize;
        if self.compatibility == Compatibility::Metastock {
            retValue -= 1;
        }
        return retValue;
    }
    /// Chande Momentum Oscillator: bounded momentum measure from Wilder-smoothed average up-moves
    /// and down-moves. Identical to RSI except the numerator uses (gain-loss) instead of gain.
    /// Bounded in \[-100,+100]; positive = net upward momentum, negative = net downward.
    ///
    /// # Formula
    ///
    /// ```text
    /// d = P[t]-P[t-1]; over the initial period accumulate gain = sum of positive d, loss = sum of -d for negative d. Wilder-smooth each: prevGain = (prevGain*(period-1) + gain_today)/period (same for loss). CMO = 100 * (prevGain-prevLoss)/(prevGain+prevLoss); 0 when prevGain+prevLoss == 0.
    /// ```
    ///
    /// # Notes
    ///
    /// * Gains and losses are smoothed with Wilder's method (as in RSI) rather than the simple
    ///   period sums of Chande's original definition.
    /// * In Metastock-compatibility mode, an extra initial bar is emitted using a simple gain/loss
    ///   average.
    ///
    /// # Arguments
    ///
    /// * `startIdx` — Start index of the requested calculation range.
    /// * `endIdx` — End index of the requested calculation range (inclusive).
    /// * `inReal` — Source price/value series.
    /// * `optInTimePeriod` — Bars over which gains/losses are smoothed (default 14, range
    ///   2..=100000)
    /// * `outBegIdx` — Set to the input index of the first output value.
    /// * `outNBElement` — Set to the number of output values written.
    /// * `outReal` — CMO oscillator value.
    ///
    /// 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.cmo(0, data.len() - 1, &data, 14, &mut out_beg, &mut out_nb, &mut out);
    /// assert_eq!(ret, RetCode::Success);
    /// assert!(out_nb > 0);
    /// ```
    ///
    /// # See also
    ///
    /// [`Core::rsi`]
    ///
    /// # References
    ///
    /// * Tushar S. Chande, *The New Technical Trader*, John Wiley & Sons (ISBN 0471597805)
    ///
    /// Further reading: [ta-lib.org/functions/cmo](https://ta-lib.org/functions/cmo/)
    #[doc(alias = "ChandeMomentumOscillator")]
    pub fn cmo(
        &self,
        startIdx: usize,
        endIdx: usize,
        inReal: &[f64],
        mut optInTimePeriod: i32,
        outBegIdx: &mut usize,
        outNBElement: &mut usize,
        outReal: &mut [f64],
    ) -> RetCode {
        if endIdx < startIdx {
            return RetCode::OutOfRangeStartIndex;
        }
        if ((optInTimePeriod) as i32) == (i32::MIN) {
            optInTimePeriod = 14;
        } else if (((optInTimePeriod) as i32) < 2) || (((optInTimePeriod) as i32) > 100000) {
            return RetCode::BadParam;
        }
        let mut startIdx = startIdx;
        let mut outIdx: usize = 0_usize;
        let mut today: usize = 0_usize;
        let mut lookbackTotal: usize = 0_usize;
        let mut unstablePeriod: usize = 0_usize;
        let mut i: usize = 0_usize;
        let mut prevGain: f64 = 0.0_f64;
        let mut prevLoss: f64 = 0.0_f64;
        let mut prevValue: f64 = 0.0_f64;
        let mut savePrevValue: f64 = 0.0_f64;
        let mut tempValue1: f64 = 0.0_f64;
        let mut tempValue2: f64 = 0.0_f64;
        let mut tempValue3: f64 = 0.0_f64;
        let mut tempValue4: f64 = 0.0_f64;
        // CMO calculation is mostly identical to RSI.
        //
        // The only difference is in the last step of calculation:
        //
        //   RSI = gain / (gain+loss)
        //   CMO = (gain-loss) / (gain+loss)
        //
        // See the RSI function for potentially some more info
        // on this algo.
        (*outBegIdx) = 0;
        (*outNBElement) = 0;
        // Adjust startIdx to account for the lookback period.
        lookbackTotal = self.cmo_lookback(optInTimePeriod);
        if startIdx < lookbackTotal {
            startIdx = lookbackTotal;
        }
        // Make sure there is still something to evaluate.
        if startIdx > endIdx {
            return RetCode::Success;
        }
        outIdx = 0;
        // Index into the output.
        // Trap special case where the period is '1'.
        // In that case, just copy the input into the
        // output for the requested range (as-is !)
        if optInTimePeriod == 1 {
            (*outBegIdx) = startIdx;
            i = endIdx - startIdx + 1;
            (*outNBElement) = i;
            {
            let _n = (i * 1) as usize;
            let _di = (0) as usize;
            let _si = (startIdx) as usize;
            outReal[_di.._di + _n].copy_from_slice(&inReal[_si.._si + _n]);
        };
            return RetCode::Success;
        }
        // Accumulate Wilder's "Average Gain" and "Average Loss"
        // among the initial period.
        today = startIdx - lookbackTotal;
        prevValue = inReal[today];
        unstablePeriod = (self.unstable_period[FuncUnstId::Cmo as usize]) as usize;
        // If there is no unstable period,
        // calculate the 'additional' initial
        // price bar who is particuliar to
        // metastock.
        // If there is an unstable period,
        // no need to calculate since this
        // first value will be surely skip.
        if unstablePeriod == 0 && self.compatibility == Compatibility::Metastock {
            // Preserve prevValue because it may get
            // overwritten by the output.
            // (because output ptr could be the same as input ptr).
            savePrevValue = prevValue;
            // No unstable period, so must calculate first output
            // particular to Metastock.
            // (Metastock re-use the first price bar, so there
            //  is no loss/gain at first. Beats me why they
            //  are doing all this).
            prevGain = 0.0;
            prevLoss = 0.0;
            // for( i = (optInTimePeriod) as usize; i > 0; i -= 1 )
            i = (optInTimePeriod) as usize;
            while i > 0 {
                tempValue1 = inReal[{ let _v = today; today += 1; _v }];
                tempValue2 = tempValue1 - prevValue;
                prevValue = tempValue1;
                if tempValue2 < 0_f64 {
                    prevLoss -= tempValue2;
                } else {
                    prevGain += tempValue2;
                }
                i -= 1;
            }
            tempValue1 = prevLoss / ((optInTimePeriod) as f64);
            tempValue2 = prevGain / ((optInTimePeriod) as f64);
            tempValue3 = tempValue2 - tempValue1;
            tempValue4 = tempValue1 + tempValue2;
            // Write the output.
            if !((tempValue4).abs() < 1e-14) {
                outReal[outIdx] = 100_f64 * (tempValue3 / tempValue4);
                outIdx += 1;
            } else {
                outReal[outIdx] = 0.0;
                outIdx += 1;
            }
            // Are we done?
            if today > endIdx {
                (*outBegIdx) = startIdx;
                (*outNBElement) = outIdx;
                return RetCode::Success;
            }
            // Start over for the next price bar.
            today -= (optInTimePeriod) as usize;
            prevValue = savePrevValue;
        }
        // Remaining of the processing is identical
        // for both Classic calculation and Metastock.
        prevGain = 0.0;
        prevLoss = 0.0;
        today += 1;
        // for( i = (optInTimePeriod) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod) as usize;
        while i > 0 {
            tempValue1 = inReal[{ let _v = today; today += 1; _v }];
            tempValue2 = tempValue1 - prevValue;
            prevValue = tempValue1;
            if tempValue2 < 0_f64 {
                prevLoss -= tempValue2;
            } else {
                prevGain += tempValue2;
            }
            i -= 1;
        }
        // Subsequent prevLoss and prevGain are smoothed
        // using the previous values (Wilder's approach).
        //  1) Multiply the previous by 'period-1'.
        //  2) Add today value.
        //  3) Divide by 'period'.
        prevLoss /= ((optInTimePeriod) as f64);
        prevGain /= ((optInTimePeriod) as f64);
        // Often documentation present the RSI calculation as follow:
        //    RSI = 100 - (100 / 1 + (prevGain/prevLoss))
        //
        // The following is equivalent:
        //    RSI = 100 * (prevGain/(prevGain+prevLoss))
        //
        // The second equation is used here for speed optimization.
        if today > startIdx {
            tempValue1 = prevGain + prevLoss;
            if !((tempValue1).abs() < 1e-14) {
                outReal[outIdx] = 100.0 * ((prevGain - prevLoss) / tempValue1);
                outIdx += 1;
            } else {
                outReal[outIdx] = 0.0;
                outIdx += 1;
            }
        } else {
            // Skip the unstable period. Do the processing
            // but do not write it in the output.
            while today < startIdx {
                tempValue1 = inReal[today];
                tempValue2 = tempValue1 - prevValue;
                prevValue = tempValue1;
                prevLoss *= ((optInTimePeriod - 1) as f64);
                prevGain *= ((optInTimePeriod - 1) as f64);
                if tempValue2 < 0_f64 {
                    prevLoss -= tempValue2;
                } else {
                    prevGain += tempValue2;
                }
                prevLoss /= ((optInTimePeriod) as f64);
                prevGain /= ((optInTimePeriod) as f64);
                today += 1;
            }
        }
        // Unstable period skipped... now continue
        // processing if needed.
        while today <= endIdx {
            tempValue1 = inReal[{ let _v = today; today += 1; _v }];
            tempValue2 = tempValue1 - prevValue;
            prevValue = tempValue1;
            prevLoss *= ((optInTimePeriod - 1) as f64);
            prevGain *= ((optInTimePeriod - 1) as f64);
            if tempValue2 < 0_f64 {
                prevLoss -= tempValue2;
            } else {
                prevGain += tempValue2;
            }
            prevLoss /= ((optInTimePeriod) as f64);
            prevGain /= ((optInTimePeriod) as f64);
            tempValue1 = prevGain + prevLoss;
            if !((tempValue1).abs() < 1e-14) {
                outReal[outIdx] = 100.0 * ((prevGain - prevLoss) / tempValue1);
                outIdx += 1;
            } else {
                outReal[outIdx] = 0.0;
                outIdx += 1;
            }
        }
        (*outBegIdx) = startIdx;
        (*outNBElement) = outIdx;
        return RetCode::Success;
    }
    /// Unchecked variant of [`Core::cmo`], used for internal cross-indicator calls.
    ///
    /// Skips parameter validation and uses unchecked indexing internally. Every argument must
    /// satisfy the constraints documented on [`Core::cmo`]; 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::cmo`].
    #[inline]
    pub fn cmo_unguarded(
        &self,
        mut startIdx: usize,
        endIdx: usize,
        inReal: &[f64],
        mut optInTimePeriod: i32,
        outBegIdx: &mut usize,
        outNBElement: &mut usize,
        outReal: &mut [f64],
    ) -> RetCode {
        let mut outIdx: usize = 0_usize;
        let mut today: usize = 0_usize;
        let mut lookbackTotal: usize = 0_usize;
        let mut unstablePeriod: usize = 0_usize;
        let mut i: usize = 0_usize;
        let mut prevGain: f64 = 0.0_f64;
        let mut prevLoss: f64 = 0.0_f64;
        let mut prevValue: f64 = 0.0_f64;
        let mut savePrevValue: f64 = 0.0_f64;
        let mut tempValue1: f64 = 0.0_f64;
        let mut tempValue2: f64 = 0.0_f64;
        let mut tempValue3: f64 = 0.0_f64;
        let mut tempValue4: f64 = 0.0_f64;
        unsafe {
        assert!(endIdx < inReal.len());
        let _assertLb = self.cmo_lookback(optInTimePeriod);
        let _assertStart = if startIdx > _assertLb { startIdx } else { _assertLb };
        assert!(_assertStart > endIdx || endIdx - _assertStart < outReal.len());
        (*outBegIdx) = 0;
        (*outNBElement) = 0;
        lookbackTotal = self.cmo_lookback(optInTimePeriod);
        if startIdx < lookbackTotal {
            startIdx = lookbackTotal;
        }
        if startIdx > endIdx {
            return RetCode::Success;
        }
        outIdx = 0;
        if optInTimePeriod == 1 {
            (*outBegIdx) = startIdx;
            i = endIdx - startIdx + 1;
            (*outNBElement) = i;
            {
            let _n = (i * 1) as usize;
            let _di = (0) as usize;
            let _si = (startIdx) as usize;
            outReal[_di.._di + _n].copy_from_slice(&inReal[_si.._si + _n]);
        };
            return RetCode::Success;
        }
        today = startIdx - lookbackTotal;
        prevValue = *inReal.as_ptr().add(today);
        unstablePeriod = (self.unstable_period[FuncUnstId::Cmo as usize]) as usize;
        if unstablePeriod == 0 && self.compatibility == Compatibility::Metastock {
            savePrevValue = prevValue;
            prevGain = 0.0;
            prevLoss = 0.0;
            // for( i = (optInTimePeriod) as usize; i > 0; i -= 1 )
            i = (optInTimePeriod) as usize;
            while i > 0 {
                tempValue1 = *inReal.as_ptr().add({ let _v = today; today += 1; _v });
                tempValue2 = tempValue1 - prevValue;
                prevValue = tempValue1;
                if tempValue2 < 0_f64 {
                    prevLoss -= tempValue2;
                } else {
                    prevGain += tempValue2;
                }
                i -= 1;
            }
            tempValue1 = prevLoss / ((optInTimePeriod) as f64);
            tempValue2 = prevGain / ((optInTimePeriod) as f64);
            tempValue3 = tempValue2 - tempValue1;
            tempValue4 = tempValue1 + tempValue2;
            if !((tempValue4).abs() < 1e-14) {
                *outReal.as_mut_ptr().add(outIdx) = 100_f64 * (tempValue3 / tempValue4);
                outIdx += 1;
            } else {
                *outReal.as_mut_ptr().add(outIdx) = 0.0;
                outIdx += 1;
            }
            if today > endIdx {
                (*outBegIdx) = startIdx;
                (*outNBElement) = outIdx;
                return RetCode::Success;
            }
            today -= (optInTimePeriod) as usize;
            prevValue = savePrevValue;
        }
        prevGain = 0.0;
        prevLoss = 0.0;
        today += 1;
        // for( i = (optInTimePeriod) as usize; i > 0; i -= 1 )
        i = (optInTimePeriod) as usize;
        while i > 0 {
            tempValue1 = *inReal.as_ptr().add({ let _v = today; today += 1; _v });
            tempValue2 = tempValue1 - prevValue;
            prevValue = tempValue1;
            if tempValue2 < 0_f64 {
                prevLoss -= tempValue2;
            } else {
                prevGain += tempValue2;
            }
            i -= 1;
        }
        prevLoss /= ((optInTimePeriod) as f64);
        prevGain /= ((optInTimePeriod) as f64);
        if today > startIdx {
            tempValue1 = prevGain + prevLoss;
            if !((tempValue1).abs() < 1e-14) {
                *outReal.as_mut_ptr().add(outIdx) = 100.0 * ((prevGain - prevLoss) / tempValue1);
                outIdx += 1;
            } else {
                *outReal.as_mut_ptr().add(outIdx) = 0.0;
                outIdx += 1;
            }
        } else {
            while today < startIdx {
                tempValue1 = *inReal.as_ptr().add(today);
                tempValue2 = tempValue1 - prevValue;
                prevValue = tempValue1;
                prevLoss *= ((optInTimePeriod - 1) as f64);
                prevGain *= ((optInTimePeriod - 1) as f64);
                if tempValue2 < 0_f64 {
                    prevLoss -= tempValue2;
                } else {
                    prevGain += tempValue2;
                }
                prevLoss /= ((optInTimePeriod) as f64);
                prevGain /= ((optInTimePeriod) as f64);
                today += 1;
            }
        }
        while today <= endIdx {
            tempValue1 = *inReal.as_ptr().add({ let _v = today; today += 1; _v });
            tempValue2 = tempValue1 - prevValue;
            prevValue = tempValue1;
            prevLoss *= ((optInTimePeriod - 1) as f64);
            prevGain *= ((optInTimePeriod - 1) as f64);
            if tempValue2 < 0_f64 {
                prevLoss -= tempValue2;
            } else {
                prevGain += tempValue2;
            }
            prevLoss /= ((optInTimePeriod) as f64);
            prevGain /= ((optInTimePeriod) as f64);
            tempValue1 = prevGain + prevLoss;
            if !((tempValue1).abs() < 1e-14) {
                *outReal.as_mut_ptr().add(outIdx) = 100.0 * ((prevGain - prevLoss) / tempValue1);
                outIdx += 1;
            } else {
                *outReal.as_mut_ptr().add(outIdx) = 0.0;
                outIdx += 1;
            }
        }
        (*outBegIdx) = startIdx;
        (*outNBElement) = outIdx;
        return RetCode::Success;
        } // unsafe
    }
}
/***************/
/* End of File */
/***************/
