<?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/>.
-->

<!--
  Brings c:case statements to life

  The problem with cases is that they are so verbose for simple cases. This
  makes simple cases simple.
-->
<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"
  title="Map Sets">


  The problem with \texttt{case} statements is that they are very
  verbose, which is greatly distracting for smaller cases, and~can
  take away from the actual intent of the~code.  Map~sets make simple
  cases simple by allowing concise map definitions.

  If \ref{_map-set_/@name@} is provided, then each map implicitly
  operates on that reference as its input, unless overridden using
  \ref{_map_/@name@}.  \ref{_map-set_/@default@} can be used to
  provide a constant default if no mapping is otherwise available; see
  also~\ref{_map-else_}.


  <template name="_map-set_" desc="Map a set of values">
    <param name="@name@" desc="Param name" />
    <param name="@index@" desc="Index" />
    <param name="@values@" desc="Criteria (map nodes)" />
    <param name="@label@" desc="Case statement label">
      <!-- default empty -->
      <text></text>
    </param>

    <!-- used in param-meta for _map-else_, so let's make the default
         clear -->
    <param name="@default@" desc="Default value">
      <text></text>
    </param>


    <c:cases label="@label@">
      <param-copy name="@values@">
        <param-meta name="map_param"   value="@name@" />
        <param-meta name="map_index"   value="@index@" />
        <param-meta name="map_default" value="@default@" />
      </param-copy>

      <unless name="@default@" eq="">
        <c:otherwise>
          <c:const value="@default@" desc="No mapping" />
        </c:otherwise>
      </unless>
    </c:cases>
  </template>


  Mappings are defined using \ref{_map_}.  The input defaults to
  the~reference declared by~\ref{_map-set_/@name@},
  if~provided.  \ref{_map_/@name@} takes precedence, even if
  the~former is provided.  Just like \texttt{case}~statements,
  multiple inputs can be provided by specifying multiple references;
  this means that each \ref{_map_/@name@} can differ.\footnote{This
  isn't particularly intuitive or recommended, but it does simplify
  certain tasks enough to maybe justify this type of use.}

  Just like \texttt{case}~statements themselves, maps are
  surjective---the codomain implicitly includes $0$~to handle all
  default cases, unless a default is provided.


  <template name="_map_" desc="Map criteria">
    <param name="@from@" desc="Value to map from" />
    <param name="@value@" desc="Constant to map to" />
    <param name="@to@" desc="Named value (use instead of @value@)" />

    <param name="@to-index@" desc="Named value index">
      <text></text>
    </param>

    <param name="@type@" desc="Constant value type">
      <text>float</text>
    </param>

    <param name="@desc@" desc="Map description">
      <text>Destination mapping</text>
    </param>

    <!-- set by parent map-set -->
    <param name="@name@" desc="Param name">
      <param-inherit meta="map_param" />
    </param>
    <param name="@index@" desc="Index">
      <param-inherit meta="map_index" />
    </param>


    <c:case>
      <!-- index provided -->
      <if name="@index@">
        <c:when name="@name@" index="@index@">
          <c:eq>
            <c:value-of name="@from@" />
          </c:eq>
        </c:when>
      </if>
      <!-- no index provided -->
      <unless name="@index@">
        <c:when name="@name@">
          <c:eq>
            <c:value-of name="@from@" />
          </c:eq>
        </c:when>
      </unless>

      <if name="@value@">
        <c:const value="@value@" desc="@desc@" />
      </if>
      <unless name="@value@">
        <c:value-of name="@to@" index="@to-index@" />
      </unless>
    </c:case>
  </template>


  The default condition can be handled in two different ways: via
  \ref{_map-set_/@default@}, or using \ref{_map-else_}; the latter
  provides more flexibility and generally reads better.  Both cannot
  be used together.


  <template name="_map-else_" desc="Non-matching map criteria">
    <param name="@value@" desc="Constant to map to" />
    <param name="@to@" desc="Named value (use instead of @value@)" />

    <param name="@desc@" desc="Map description">
      <text>Destination mapping</text>
    </param>

    <param name="@to-index@" desc="Named value index">
      <text></text>
    </param>

    <param name="@_map-default@" desc="_map_-specified default">
      <param-inherit meta="map_default" />
    </param>


    <!-- provide a more friendly error; otherwise, they'd get an error
         from having two c:otherwise nodes, which would be confusing -->
    <unless name="@_map-default@" eq="">
      <error>
        A default value was already provided by _map-set_/@default@
      </error>
    </unless>


    <c:otherwise>
      <if name="@value@">
        <c:const value="@value@" desc="@desc@" />
      </if>
      <unless name="@value@">
        <c:value-of name="@to@" index="@to-index@" />
      </unless>
    </c:otherwise>
  </template>
</package>

