using System;

namespace DataStructures.SegmentTrees
{
    /// <summary>
    ///     This is an extension of a segment tree, which allows applying distributive operations to a subarray
    ///     (in this case multiplication).
    /// </summary>
    public class SegmentTreeApply : SegmentTree
    {
        /// <summary>
        ///     Initializes a new instance of the <see cref="SegmentTreeApply" /> class.
        ///     Runtime complexity: O(n) where n equals the array-length.
        /// </summary>
        /// <param name="arr">Array on which the operations should be made.</param>
        public SegmentTreeApply(int[] arr)
            : base(arr)
        {
            // Initilizes and fills "operand" array with neutral element (in this case 1, because value * 1 = value)
            Operand = new int[Tree.Length];
            Array.Fill(Operand, 1);
        }

        /// <summary>
        ///     Gets an array that stores for each node an operand,
        ///     which must be applied to all direct and indirect child nodes of this node
        ///     (but not to the node itself).
        /// </summary>
        public int[] Operand { get; }

        /// <summary>
        ///     Applies a distributive operation to a subarray defined by <c>l</c> and <c>r</c>
        ///     (in this case multiplication by <c>value</c>).
        ///     Runtime complexity: O(logN) where N equals the initial array-length.
        /// </summary>
        /// <param name="l">Left border of the subarray.</param>
        /// <param name="r">Right border of the subarray.</param>
        /// <param name="value">Value with which each element of the interval is calculated.</param>
        public void Apply(int l, int r, int value)
        {
            // The Application start at node with 1
            // Node with index 1 includes the whole input subarray
            Apply(++l, ++r, value, 1, Tree.Length / 2, 1);
        }

        /// <summary>
        ///     Edits a query.
        /// </summary>
        /// <param name="l">Left border of the query.</param>
        /// <param name="r">Right border of the query.</param>
        /// <param name="a">Left end of the subarray enclosed by <c>i</c>.</param>
        /// <param name="b">Right end of the subarray enclosed by <c>i</c>.</param>
        /// <param name="i">Current node.</param>
        /// <returns>Sum of a subarray between <c>l</c> and <c>r</c> (including <c>l</c> and <c>r</c>).</returns>
        protected override int Query(int l, int r, int a, int b, int i)
        {
            if (l <= a && b <= r)
            {
                return Tree[i];
            }

            if (r < a || b < l)
            {
                return 0;
            }

            var m = (a + b) / 2;

            // Application of the saved operand to the direct and indrect child nodes
            return Operand[i] * (Query(l, r, a, m, Left(i)) + Query(l, r, m + 1, b, Right(i)));
        }

        /// <summary>
        ///     Applies the operation.
        /// </summary>
        /// <param name="l">Left border of the Application.</param>
        /// <param name="r">Right border of the Application.</param>
        /// <param name="value">Multiplier by which the subarray is to be multiplied.</param>
        /// <param name="a">Left end of the subarray enclosed by <c>i</c>.</param>
        /// <param name="b">Right end of the subarray enclosed by <c>i</c>.</param>
        /// <param name="i">Current node.</param>
        private void Apply(int l, int r, int value, int a, int b, int i)
        {
            // If a and b are in the (by l and r) specified subarray
            if (l <= a && b <= r)
            {
                // Applies the operation to the current node and saves it for the direct and indirect child nodes
                Operand[i] = value * Operand[i];
                Tree[i] = value * Tree[i];
                return;
            }

            // If a or b are out of the by l and r specified subarray stop application at this node
            if (r < a || b < l)
            {
                return;
            }

            // Calculates index m of the node that cuts the current subarray in half
            var m = (a + b) / 2;

            // Applies the operation to both halfes
            Apply(l, r, value, a, m, Left(i));
            Apply(l, r, value, m + 1, b, Right(i));

            // Recalculates the value of this node by its (possibly new) children.
            Tree[i] = Operand[i] * (Tree[Left(i)] + Tree[Right(i)]);
        }
    }
}
