<?xml version="1.0" encoding="utf-8"?>
<!--
  Copyright (C) 2014-2023 Ryan Specialty, LLC.

  This file is part of tame-core.

  tame-core is free software: you can redistribute it and/or modify it
  under the terms of the GNU General Public License as
  published by the Free Software Foundation, either version 3 of the
  License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-->
<package xmlns="http://www.lovullo.com/rater"
  xmlns:c="http://www.lovullo.com/calc"
  xmlns:t="http://www.lovullo.com/rater/apply-template"
  core="true"
  desc="Numeric computations dealing with rounding">

  <import package="../base" />


  <!-- template version of round function, supporting arbitrary nodes -->
  <template name="_round_" desc="Round to the nearest integer">
    <param name="@values@" desc="Calculation to round (single node)" />
    <param name="@precision@" desc="Level of precision after decimal point">
      <text>0</text>
    </param>

    <c:apply name="round_real" label="Rounded value">
      <c:arg name="round_real_val">
        <c:sum>
          <param-copy name="@values@" />
        </c:sum>
      </c:arg>

      <c:arg name="round_real_n">
        <c:expt>
          <c:const value="10" desc="Decimal base" />
          <c:const value="@precision@" desc="Exponent" />
        </c:expt>
      </c:arg>
    </c:apply>
  </template>


  <!--
    Let expression exposing one or more of ceiling, floor, and nearest
    integer of given name

    This is our Swiss Army knife of rounding values.

    The values @high@, @low@, and @nearest@ (can specify all or just one)
    represent the names of the variables to store, respectively, the
    ceiling, floor, and nearest integer to the value @name@. Those values
    are then accessible within the scope of the let expression.

    An @exp@ can be used to provide a base-10 exponent to move the decimal
    place (negative is right, positive left) before performing rounding.

    The @step@ allows specifying an arbitrary value to round toward. For
    example, a @step@ of 1500 rounds in 1500 increments. This may be used
    with @exp@, in which case the step will apply to @name@ after its value
    has been exponentiated.
  -->
  <template name="_let-round_"
               desc="Produce floor and ceiling values">
    <param name="@values@"
              desc="Calculation subject to let expression" />

    <param name="@name@"
              desc="Value to round" />

    <param name="@index@"
              desc="Optional value index">
      <text></text>
    </param>

    <param name="@high@"
              desc="Name to yield ceil'd value into" />

    <param name="@low@"
              desc="Name to yield floor'd value into" />

    <param name="@nearest@"
              desc="Name to yield nearest integer to value into" />


    <param name="@exp@"
              desc="Offset digits before rounding">
      <text>#0</text>
    </param>

    <param name="@step@"
              desc="Arbitrary step">
      <text>#1</text>
    </param>



    <c:let>
      <c:values>
        <c:value name="__div"
                 type="float"
                 desc="Exponential/step divisor">
          <c:product>
            <c:expt>
              <c:const value="10"
                       desc="Decimal base" />
              <c:value-of name="@exp@" />
            </c:expt>

            <c:value-of name="@step@" />
          </c:product>
        </c:value>
      </c:values>

      <c:let>
        <c:values>
          <c:value name="__adjusted"
                   type="float"
                   desc="Value adjusted for 10^@exp@ or step @step@">
            <c:quotient>
              <c:value-of name="@name@" index="@index@" />
              <c:value-of name="__div" />
            </c:quotient>
          </c:value>
        </c:values>

        <c:let>
          <c:values>
            <if name="@high@">
              <c:value name="@high@" type="float"
                       desc="Ceiling of adjusted {@name@}">
                <c:product>
                  <c:ceil>
                    <c:value-of name="__adjusted" />
                  </c:ceil>

                  <c:value-of name="__div" />
                </c:product>
              </c:value>
            </if>

            <if name="@low@">
              <c:value name="@low@" type="float"
                       desc="floor of adjusted {@name@}">
                <c:product>
                  <c:floor>
                    <c:value-of name="__adjusted" />
                  </c:floor>

                  <c:value-of name="__div" />
                </c:product>
              </c:value>
            </if>

            <if name="@nearest@">
              <c:value name="@nearest@" type="float"
                       desc="nearest integer to adjusted {@name@}">
                <c:product>
                  <t:round>
                    <c:value-of name="__adjusted" />
                  </t:round>

                  <c:value-of name="__div" />
                </c:product>
              </c:value>
            </if>
          </c:values>

          <!-- body subject to let expression -->
          <param-copy name="@values@" />
        </c:let>
      </c:let>
    </c:let>
  </template>


  <!-- ceil if > 0.5, otherwise floor; round() in most languages -->
  <!-- TODO: support left and right delimiters; do \floor \ceil -->
  <function name="round" desc="Round to the nearest integer">
    <param name="roundval" type="float" desc="Value to round to nearest integer" />

    <!-- By raising by 0.5 and taking the floor() of the resulting value, we
         can produce values consistent with common round() implementations. It
         may be easier to think of this as ceil( val - 0.4 ), but representing
         subtraction is more verbose than addition, so a floor() implementation
         is used instead. -->
    <c:floor>
      <c:sum>
        <c:value-of name="roundval" />
        <c:const value="0.5" desc="Raises value in a manner that it can be properly rounded by a floor" />
      </c:sum>
    </c:floor>
  </function>


  <function name="round_real" desc="Round to the nearest 1/n">
    <param name="round_real_val" type="float"   desc="Value to round" />
    <param name="round_real_n"   type="integer" desc="Round to 1/n" />

    <!-- we can achieve this by multiplying by N, rounding, and then dividing
         by the same N -->
    <c:quotient>
      <c:apply name="round">
        <c:arg name="roundval">
          <c:product>
            <c:value-of name="round_real_val" />
            <c:value-of name="round_real_n" />
          </c:product>
        </c:arg>
      </c:apply>

      <c:value-of name="round_real_n" />
    </c:quotient>
  </function>


  <!-- this is simply a shorthand for round_real -->
  <function name="round_cents" desc="Round to the nearest penny">
    <param name="round_cents_val" type="float" desc="Monetary value" />

    <c:apply name="round_real">
      <c:arg name="round_real_n">
        <c:const value="100" desc="Round to the nearest 100th" />
      </c:arg>

      <c:arg name="round_real_val">
        <c:value-of name="round_cents_val" />
      </c:arg>
    </c:apply>
  </function>


  <template name="_ceil-n_" desc="Ceiling on the last n digits">
    <param name="@values@" desc="Calculation to use (single node)" />
    <param name="@digits@" desc="Level of precision after decimal point" />

    <c:product>
      <c:ceil>
        <c:quotient>
          <param-copy name="@values@" />
          <c:expt>
            <c:const value="10" desc="Decimal base" />
            <c:const value="@digits@" desc="Number of digits" />
          </c:expt>
        </c:quotient>
      </c:ceil>

      <c:expt>
        <c:const value="10" desc="Decimal base" />
        <c:const value="@digits@" desc="Number of digits" />
      </c:expt>
    </c:product>
  </template>
</package>

