/* 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
 *  CF       Christo Fogelberg
 *
 * Change history:
 *
 *  MMDDYY BY     Description
 *  -------------------------------------------------------------------
 *  010802 MF     Template creation.
 *  052603 MF     Adapt code to compile with .NET Managed C++
 *  122104 MF,CF  Fix#1089506 for out-of-bound access to ep_temp.
 */

// 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::sar`]: the number of leading input values consumed before the
    /// first output value can be produced.
    ///
    /// # Arguments
    ///
    /// * `optInAcceleration` — Step added to the acceleration factor on each new extreme point
    ///   (default 0.02, minimum 0)
    /// * `optInMaximum` — Ceiling on the acceleration factor (default 0.2, minimum 0)
    ///
    /// Returns `usize::MAX` when a parameter is out of range. Integer parameters accept `i32::MIN`
    /// to select their default value.
    #[inline]
    pub fn sar_lookback(&self, mut optInAcceleration: f64, mut optInMaximum: f64) -> usize {
        // SAR always sacrify one price bar to establish the
        // initial extreme price.
        return (1) as usize;
    }
    /// Wilder's Parabolic SAR (Stop And Reverse): a trailing stop/reverse level that accelerates
    /// toward price via an acceleration factor. Signals trend direction and trailing exit points.
    /// SAR below price = uptrend (long); SAR above price = downtrend (short). Price crossing SAR
    /// flips direction.
    ///
    /// # Formula
    ///
    /// ```text
    /// SAR_next = SAR + af * (EP - SAR)
    /// EP = extreme point (highest high in long / lowest low in short); af starts at Acceleration, += Acceleration each new EP, capped at Maximum.
    /// On penetration: reverse, SAR := prior EP, reset af = Acceleration. SAR clamped each bar so it does not penetrate the prior/current bar's range.
    /// ```
    ///
    /// # Arguments
    ///
    /// * `startIdx` — Start index of the requested calculation range.
    /// * `endIdx` — End index of the requested calculation range (inclusive).
    /// * `inHigh` — High prices per bar.
    /// * `inLow` — Low prices per bar.
    /// * `optInAcceleration` — Step added to the acceleration factor on each new extreme point
    ///   (default 0.02, minimum 0)
    /// * `optInMaximum` — Ceiling on the acceleration factor (default 0.2, minimum 0)
    /// * `outBegIdx` — Set to the input index of the first output value.
    /// * `outNBElement` — Set to the number of output values written.
    /// * `outReal` — Parabolic SAR stop/reverse level per bar.
    ///
    /// # 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 high: Vec<f64> = (0..252).map(|i| 101.0 + 10.0 * (0.1 * i as f64).sin()).collect();
    /// let low: Vec<f64> = (0..252).map(|i| 99.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.sar(
    ///     0, high.len() - 1, &high, &low, 0.02, 0.2,
    ///     &mut out_beg, &mut out_nb, &mut out,
    /// );
    /// assert_eq!(ret, RetCode::Success);
    /// assert!(out_nb > 0);
    /// ```
    ///
    /// # See also
    ///
    /// [`Core::sarext`] · [`Core::minus_dm`] · [`Core::plus_dm`]
    ///
    /// # References
    ///
    /// * J. Welles Wilder, *New Concepts in Technical Trading Systems*, Trend Research (ISBN
    ///   0894590278)
    ///
    /// Further reading: [ta-lib.org/functions/sar](https://ta-lib.org/functions/sar/)
    #[doc(alias = "ParabolicSAR")]
    #[doc(alias = "PSAR")]
    #[doc(alias = "StopandReverse")]
    pub fn sar(
        &self,
        startIdx: usize,
        endIdx: usize,
        inHigh: &[f64],
        inLow: &[f64],
        mut optInAcceleration: f64,
        mut optInMaximum: f64,
        outBegIdx: &mut usize,
        outNBElement: &mut usize,
        outReal: &mut [f64],
    ) -> RetCode {
        if endIdx < startIdx {
            return RetCode::OutOfRangeStartIndex;
        }
        let mut startIdx = startIdx;
        let mut retCode: RetCode = RetCode::Success;
        let mut isLong: usize = 0_usize;
        let mut todayIdx: usize = 0_usize;
        let mut outIdx: usize = 0_usize;
        let mut tempInt: usize = 0_usize;
        let mut newHigh: f64 = 0.0_f64;
        let mut newLow: f64 = 0.0_f64;
        let mut prevHigh: f64 = 0.0_f64;
        let mut prevLow: f64 = 0.0_f64;
        let mut af: f64 = 0.0_f64;
        let mut ep: f64 = 0.0_f64;
        let mut sar: f64 = 0.0_f64;
        let mut ep_temp: [f64; 1 as usize] = [0.0_f64; 1 as usize];
        // > 0 indicates long. == 0 indicates short
        // Implementation of the SAR has been a little bit open to interpretation
        // since Wilder (the original author) did not define a precise algorithm
        // on how to bootstrap the algorithm. Take any existing software application
        // and you will see slight variation on how the algorithm was adapted.
        //
        // What is the initial trade direction? Long or short?
        // ===================================================
        // The interpretation of what should be the initial SAR values is
        // open to interpretation, particularly since the caller to the function
        // does not specify the initial direction of the trade.
        //
        // In TA-Lib, the following logic is used:
        //  - Calculate +DM and -DM between the first and
        //    second bar. The highest directional indication will
        //    indicate the assumed direction of the trade for the second
        //    price bar.
        //  - In the case of a tie between +DM and -DM,
        //    the direction is LONG by default.
        //
        // What is the initial "extreme point" and thus SAR?
        // =================================================
        // The following shows how different people took different approach:
        //  - Metastock use the first price bar high/low depending of
        //    the direction. No SAR is calculated for the first price
        //    bar.
        //  - Tradestation use the closing price of the second bar. No
        //    SAR are calculated for the first price bar.
        //  - Wilder (the original author) use the SIP from the
        //    previous trade (cannot be implement here since the
        //    direction and length of the previous trade is unknonw).
        //  - The Magazine TASC seems to follow Wilder approach which
        //    is not practical here.
        //
        // TA-Lib "consume" the first price bar and use its high/low as the
        // initial SAR of the second price bar. I found that approach to be
        // the closest to Wilders idea of having the first entry day use
        // the previous extreme point, except that here the extreme point is
        // derived solely from the first price bar. I found the same approach
        // to be used by Metastock.
        // Identify the minimum number of price bar needed
        // to calculate at least one output.
        //
        // Move up the start index if there is not
        // enough initial data.
        if startIdx < 1 {
            startIdx = 1;
        }
        // Make sure there is still something to evaluate.
        if startIdx > endIdx {
            (*outBegIdx) = 0;
            (*outNBElement) = 0;
            return RetCode::Success;
        }
        // Make sure the acceleration and maximum are coherent.
        // If not, correct the acceleration.
        af = optInAcceleration;
        if af > optInMaximum {
            optInAcceleration = optInMaximum;
            af = optInAcceleration;
        }
        // Identify if the initial direction is long or short.
        // (ep is just used as a temp buffer here, the name
        //  of the parameter is not significant).
        let mut _dup_out: usize = 0_usize;
        retCode = self.minus_dm_unguarded(startIdx, startIdx, inHigh, inLow, 1, &mut tempInt, &mut _dup_out, &mut ep_temp);
        if ep_temp[0] > 0_f64 {
            isLong = 0;
        } else {
            isLong = 1;
        }
        if retCode != RetCode::Success {
            (*outBegIdx) = 0;
            (*outNBElement) = 0;
            return retCode;
        }
        (*outBegIdx) = startIdx;
        outIdx = 0;
        // Write the first SAR.
        todayIdx = startIdx;
        newHigh = inHigh[todayIdx - 1];
        newLow = inLow[todayIdx - 1];
        if isLong == 1 {
            ep = inHigh[todayIdx];
            sar = newLow;
        } else {
            ep = inLow[todayIdx];
            sar = newHigh;
        }
        // Cheat on the newLow and newHigh for the
        // first iteration.
        newLow = inLow[todayIdx];
        newHigh = inHigh[todayIdx];
        while todayIdx <= endIdx {
            prevLow = newLow;
            prevHigh = newHigh;
            newLow = inLow[todayIdx];
            newHigh = inHigh[todayIdx];
            todayIdx += 1;
            if isLong == 1 {
                // Switch to short if the low penetrates the SAR value.
                if newLow <= sar {
                    // Switch and Overide the SAR with the ep
                    isLong = 0;
                    sar = ep;
                    // Make sure the overide SAR is within
                    // yesterday's and today's range.
                    if sar < prevHigh {
                        sar = prevHigh;
                    }
                    if sar < newHigh {
                        sar = newHigh;
                    }
                    // Output the overide SAR
                    outReal[outIdx] = sar;
                    outIdx += 1;
                    // Adjust af and ep
                    af = optInAcceleration;
                    ep = newLow;
                    // Calculate the new SAR
                    sar = (af as f64).mul_add(ep - sar, sar);
                    // Make sure the new SAR is within
                    // yesterday's and today's range.
                    if sar < prevHigh {
                        sar = prevHigh;
                    }
                    if sar < newHigh {
                        sar = newHigh;
                    }
                } else {
                    // No switch
                    // Output the SAR (was calculated in the previous iteration)
                    outReal[outIdx] = sar;
                    outIdx += 1;
                    // Adjust af and ep.
                    if newHigh > ep {
                        ep = newHigh;
                        af += optInAcceleration;
                        if af > optInMaximum {
                            af = optInMaximum;
                        }
                    }
                    // Calculate the new SAR
                    sar = (af as f64).mul_add(ep - sar, sar);
                    // Make sure the new SAR is within
                    // yesterday's and today's range.
                    if sar > prevLow {
                        sar = prevLow;
                    }
                    if sar > newLow {
                        sar = newLow;
                    }
                }
            // Switch to long if the high penetrates the SAR value.
            } else if newHigh >= sar {
                // Switch and Overide the SAR with the ep
                isLong = 1;
                sar = ep;
                // Make sure the overide SAR is within
                // yesterday's and today's range.
                if sar > prevLow {
                    sar = prevLow;
                }
                if sar > newLow {
                    sar = newLow;
                }
                // Output the overide SAR
                outReal[outIdx] = sar;
                outIdx += 1;
                // Adjust af and ep
                af = optInAcceleration;
                ep = newHigh;
                // Calculate the new SAR
                sar = (af as f64).mul_add(ep - sar, sar);
                // Make sure the new SAR is within
                // yesterday's and today's range.
                if sar > prevLow {
                    sar = prevLow;
                }
                if sar > newLow {
                    sar = newLow;
                }
            } else {
                // No switch
                // Output the SAR (was calculated in the previous iteration)
                outReal[outIdx] = sar;
                outIdx += 1;
                // Adjust af and ep.
                if newLow < ep {
                    ep = newLow;
                    af += optInAcceleration;
                    if af > optInMaximum {
                        af = optInMaximum;
                    }
                }
                // Calculate the new SAR
                sar = (af as f64).mul_add(ep - sar, sar);
                // Make sure the new SAR is within
                // yesterday's and today's range.
                if sar < prevHigh {
                    sar = prevHigh;
                }
                if sar < newHigh {
                    sar = newHigh;
                }
            }
        }
        (*outNBElement) = outIdx;
        return RetCode::Success;
    }
    /// Unchecked variant of [`Core::sar`], used for internal cross-indicator calls.
    ///
    /// Skips parameter validation and uses unchecked indexing internally. Every argument must
    /// satisfy the constraints documented on [`Core::sar`]; 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::sar`].
    #[inline]
    pub fn sar_unguarded(
        &self,
        mut startIdx: usize,
        endIdx: usize,
        inHigh: &[f64],
        inLow: &[f64],
        mut optInAcceleration: f64,
        mut optInMaximum: f64,
        outBegIdx: &mut usize,
        outNBElement: &mut usize,
        outReal: &mut [f64],
    ) -> RetCode {
        let mut retCode: RetCode = RetCode::Success;
        let mut isLong: usize = 0_usize;
        let mut todayIdx: usize = 0_usize;
        let mut outIdx: usize = 0_usize;
        let mut tempInt: usize = 0_usize;
        let mut newHigh: f64 = 0.0_f64;
        let mut newLow: f64 = 0.0_f64;
        let mut prevHigh: f64 = 0.0_f64;
        let mut prevLow: f64 = 0.0_f64;
        let mut af: f64 = 0.0_f64;
        let mut ep: f64 = 0.0_f64;
        let mut sar: f64 = 0.0_f64;
        let mut ep_temp: [f64; 1 as usize] = [0.0_f64; 1 as usize];
        unsafe {
        assert!(endIdx < inHigh.len());
        assert!(endIdx < inLow.len());
        let _assertLb = self.sar_lookback(optInAcceleration, optInMaximum);
        let _assertStart = if startIdx > _assertLb { startIdx } else { _assertLb };
        assert!(_assertStart > endIdx || endIdx - _assertStart < outReal.len());
        if startIdx < 1 {
            startIdx = 1;
        }
        if startIdx > endIdx {
            (*outBegIdx) = 0;
            (*outNBElement) = 0;
            return RetCode::Success;
        }
        af = optInAcceleration;
        if af > optInMaximum {
            optInAcceleration = optInMaximum;
            af = optInAcceleration;
        }
        let mut _dup_out: usize = 0_usize;
        retCode = self.minus_dm_unguarded(startIdx, startIdx, inHigh, inLow, 1, &mut tempInt, &mut _dup_out, &mut ep_temp);
        if *ep_temp.as_ptr().add(0) > 0_f64 {
            isLong = 0;
        } else {
            isLong = 1;
        }
        if retCode != RetCode::Success {
            (*outBegIdx) = 0;
            (*outNBElement) = 0;
            return retCode;
        }
        (*outBegIdx) = startIdx;
        outIdx = 0;
        todayIdx = startIdx;
        newHigh = *inHigh.as_ptr().add(todayIdx - 1);
        newLow = *inLow.as_ptr().add(todayIdx - 1);
        if isLong == 1 {
            ep = *inHigh.as_ptr().add(todayIdx);
            sar = newLow;
        } else {
            ep = *inLow.as_ptr().add(todayIdx);
            sar = newHigh;
        }
        newLow = *inLow.as_ptr().add(todayIdx);
        newHigh = *inHigh.as_ptr().add(todayIdx);
        while todayIdx <= endIdx {
            prevLow = newLow;
            prevHigh = newHigh;
            newLow = *inLow.as_ptr().add(todayIdx);
            newHigh = *inHigh.as_ptr().add(todayIdx);
            todayIdx += 1;
            if isLong == 1 {
                if newLow <= sar {
                    isLong = 0;
                    sar = ep;
                    if sar < prevHigh {
                        sar = prevHigh;
                    }
                    if sar < newHigh {
                        sar = newHigh;
                    }
                    *outReal.as_mut_ptr().add(outIdx) = sar;
                    outIdx += 1;
                    af = optInAcceleration;
                    ep = newLow;
                    sar = (af as f64).mul_add(ep - sar, sar);
                    if sar < prevHigh {
                        sar = prevHigh;
                    }
                    if sar < newHigh {
                        sar = newHigh;
                    }
                } else {
                    *outReal.as_mut_ptr().add(outIdx) = sar;
                    outIdx += 1;
                    if newHigh > ep {
                        ep = newHigh;
                        af += optInAcceleration;
                        if af > optInMaximum {
                            af = optInMaximum;
                        }
                    }
                    sar = (af as f64).mul_add(ep - sar, sar);
                    if sar > prevLow {
                        sar = prevLow;
                    }
                    if sar > newLow {
                        sar = newLow;
                    }
                }
            } else if newHigh >= sar {
                isLong = 1;
                sar = ep;
                if sar > prevLow {
                    sar = prevLow;
                }
                if sar > newLow {
                    sar = newLow;
                }
                *outReal.as_mut_ptr().add(outIdx) = sar;
                outIdx += 1;
                af = optInAcceleration;
                ep = newHigh;
                sar = (af as f64).mul_add(ep - sar, sar);
                if sar > prevLow {
                    sar = prevLow;
                }
                if sar > newLow {
                    sar = newLow;
                }
            } else {
                *outReal.as_mut_ptr().add(outIdx) = sar;
                outIdx += 1;
                if newLow < ep {
                    ep = newLow;
                    af += optInAcceleration;
                    if af > optInMaximum {
                        af = optInMaximum;
                    }
                }
                sar = (af as f64).mul_add(ep - sar, sar);
                if sar < prevHigh {
                    sar = prevHigh;
                }
                if sar < newHigh {
                    sar = newHigh;
                }
            }
        }
        (*outNBElement) = outIdx;
        return RetCode::Success;
        } // unsafe
    }
}
/***************/
/* End of File */
/***************/
