%  -*- Mode: latex; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
%
%
% hereapplies.sty
%
% A LaTeX package for cross-linking concepts to their applications
%
% https://github.com/madmurphy/hereapplies.sty
%
% Version 0.5.0
%
% Copyright (C) 2022 madmurphy <madmurphy333@gmail.com>
%
% **Here Applies** is free software: you can redistribute it and/or modify it
% under the terms of the GNU Affero General Public License as published by the
% Free Software Foundation, either version 3 of the License, or (at your
% option) any later version.
%
% **Here Applies** 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 Affero General Public License
% for more details.
%
% You should have received a copy of the GNU Affero General Public License
% along with this program. If not, see <http://www.gnu.org/licenses/>.
%
%
%
% Example usage:
%
%     \documentclass{article}
%
%     \usepackage{hereapplies}
%
%     \begin{document}
%
%     \title{Some title}
%
%     \author{Some author}
%
%     \maketitle
%
%     This is concept one. To find this concept applied, please
%     see \whereapplies{conceptOne}.
%
%     This is concept two. To find this concept applied, please
%     see \whereapplies{conceptTwo}.\newpage
%
%     \hereapplies{conceptOne}This is page \thepage. As you can see, ``concept
%     one'' applies here.\newpage
%
%     \hereapplies{conceptTwo}This is page \thepage. As you can see, ``concept
%     two'' applies here.\newpage
%
%     \hereapplies{conceptOne,conceptTwo}This is page \thepage. As you
%     can see, both ``concept one'' and ``concept two'' apply here.\newpage
%
%     \hereapplies{conceptTwo}This is page \thepage. As you can see, ``concept
%     two'' applies here.\newpage
%
%     \hereapplies[myref]{conceptOne}This is page \thepage. As you can
%     see, ``concept one'' applies here. This point in the document is
%     labeled \texttt{myref}.
%
%     \end{document}
%
%
\ProvidesPackage{hereapplies}
\RequirePackage{hyperref}
\RequirePackage{refcount}
%
%
%
%         PRIVATE ENVIRONMENT
%         ===================
%
%
% Assign a unique number to each identifier
\newcounter{@ha@identifier@counter}
% Assign a unique number to each unnamed occurrence of an identifier
\newcounter{@ha@unlabeled@counter}
%
%
% Macro `\@ha@makeoccurrencelist{hypermacro}{labels}`
% *****************************************************************************
%
% Generate the list of page numbers with page range support
%
% This macro is for internal purposes only. When invoked, it scans the
% comma-separated list of labels provided (`labels`) and checks which labels
% refer to duplicate page numbers and which page numbers can be grouped
% together.
%
% The `hypermacro` argument is the macro from the `hyperref` package that will
% process the label name. This should be either `T@pageref` -- equivalent to
% `\pageref` -- or `@pagerefstar` -- equivalent to `\pageref*`.
%
% The `labels` must be a comma-separated list of labels.
%
\newcommand{\@ha@makeoccurrencelist}[2]{%
	% Reset the current page number
	\def\@ha@tmp@@currp{-1}%
	% Reset the current range offset
	\def\@ha@tmp@@prangeoffs{-1}%
	% Iterate through the `labels` argument
	\@for\@ha@tmp@@thislabel:=#2\do{%
		% Store the page number associated with this label
		\edef\@ha@tmp@@nextp{\getpagerefnumber{\@ha@tmp@@thislabel}}%
		% Check that we are not on the same page as in the last iteration
		\unless\ifnum\@ha@tmp@@currp=\@ha@tmp@@nextp%
			% This is not the same page as in the last iteration
			% Is this the first page in which this identifier appears?
			\unless\ifnum\@ha@tmp@@currp=-1%
				% This is not the first page in which this identifier appears
				% Does this page follow immediately the previous label's page?
				\ifnum\numexpr\@ha@tmp@@currp+1=\@ha@tmp@@nextp%
					% This page follows immediately the previous label's page
					% Are these the first contiguous pages that we encounter?
					\ifnum\@ha@tmp@@prangeoffs=-1%
						% These are the first contiguous pages we encounter
						% Store the first page number of the pair
						\edef\@ha@tmp@@prangeoffs{\@ha@tmp@@currp}%
						% Store the first label of the pair
						\edef\@ha@tmp@@currrangelbl{\@ha@tmp@@currlbl}%
					\fi%
				\else%
					% This page is far from the previous label's page
					% Was the previous page part of a contiguous range?
					\ifnum\@ha@tmp@@prangeoffs=-1%
						% The previous page was a standalone page
						% Print "[p], "
						{\csname #1\endcsname{\@ha@tmp@@currlbl}, }%
					\else%
						% The previous page was part of a contiguous range
						% Print "[p--q], "
						{\csname #1\endcsname{\@ha@tmp@@currrangelbl}--\csname
							#1\endcsname{\@ha@tmp@@currlbl}, }%
						% Reset the current range offset
						\def\@ha@tmp@@prangeoffs{-1}%
					\fi%
				\fi%
			\fi%
			% Prepare the next page number
			\edef\@ha@tmp@@currp{\@ha@tmp@@nextp}%
			% Prepare the next label
			\edef\@ha@tmp@@currlbl{\@ha@tmp@@thislabel}%
		\fi%
	}%
	% Print the last page number
	% Is there at least one page to print?
	\unless\ifnum\@ha@tmp@@currp=-1%
		% There is at least one page to print
		% Was the previous page part of a contiguous range?
		\ifnum\@ha@tmp@@prangeoffs=-1%
			% The previous page was a standalone page
			% Print "[p], "
			{\csname #1\endcsname{\@ha@tmp@@currlbl}}%
		\else%
			% The previous page was part of a contiguous range
			% Print "[p--q], "
			{\csname #1\endcsname{\@ha@tmp@@currrangelbl}--\csname
				#1\endcsname{\@ha@tmp@@currlbl}}%
		\fi%
	\fi%
}
%
%
% Macro `\@ha@newidentifier{identifier}`
% *****************************************************************************
%
% Initialize a new identifier
%
% This macro is for internal purposes only. When invoked, it sets up the helper
% macros, counters and auxiliary files needed for keeping track of an
% identifier. If the identifier was already initialized the macro will be no
% op.
%
% The `identifier` argument may contain only Latin letters and the "at" sign
% (`@`). This string remains confined within the internal scope of the package
% and does not create conflicts with possible macros or labels of the same
% name.
%
\newcommand{\@ha@newidentifier}[1]{%
	% Was this identifier already initialized?
	\unless\ifcsname @ha@prop@@count@#1\endcsname%
		% The identifier was never initialized
		% Set the output to "??" - it will be updated by the .haN file
		\expandafter\gdef\csname @ha@prop@@uoutput@#1\endcsname{\textbf{??}}%
		% Set the starred output to "??" - it will be updated by the .haN file
		\expandafter\gdef\csname @ha@prop@@soutput@#1\endcsname{\textbf{??}}%
		% Use "p." for the preamble when there is only one occurrence
		\expandafter\gdef\csname @ha@prop@@preamble@#1\endcsname{p.~}%
		% Increase the counter of identifiers
		\stepcounter{@ha@identifier@counter}%
		% Store the current value of the counter of identifiers
		\expandafter\xdef\csname
			@ha@prop@@count@#1\endcsname{\the@ha@identifier@counter}%
		% Initialize the .haN file
		% Previous versions had this line, which created unwanted whitespaces:
		%\@starttoc{ha\csname @ha@prop@@count@#1\endcsname}%
		% I am thankful to David Carlisle for the following replacement:
		{\endlinechar=\m@ne\@starttoc{ha\csname @ha@prop@@count@#1\endcsname}}%
		% Store all the occurrences when the document reaches the end
		\AtEndDocument{%
			% Make sure that there are occurrences
			\ifcsname @ha@prop@@labels@#1\endcsname%
				% There are occurrences
				% Set the .haN file as output
				\addtocontents{ha\csname @ha@prop@@count@#1\endcsname}{%
					% Write "p./pp. \pageref..." to the output
					\gdef\expandafter\protect\csname @ha@prop@@uoutput@#1\endcsname{%
						% Preamble (either "p." or "pp.")
						\csname @ha@prop@@preamble@#1\endcsname%
						% `\T@pageref` is a synonym of `\pageref`
						\expandafter\protect\csname
							@ha@makeoccurrencelist\endcsname{T@pageref}{\csname
								@ha@prop@@labels@#1\endcsname}%
					}%
					% Write "p./pp. \pageref*..." to the starred output
					\gdef\expandafter\protect\csname @ha@prop@@soutput@#1\endcsname{%
						% Preamble (either "p." or "pp.")
						\csname @ha@prop@@preamble@#1\endcsname%
						% `\@pagerefstar` is a synonym of `\pageref*`
						\expandafter\protect\csname
							@ha@makeoccurrencelist\endcsname{@pagerefstar}{\csname
								@ha@prop@@labels@#1\endcsname}%
					}%
				}%
			\fi%
		}%
	\fi%
}
%
%
% Macro: `\@ha@getpropat{field}{identifier}`
% *****************************************************************************
%
% Get the value of an identifier's property
%
% This macro is for internal purposes (but nothing forbids invoking it
% directly). If the identifier was never initialized the macro will initialize
% it.
%
% Possible values for the `field` argument are: `count`, `labels`, `preamble`,
% `soutput`, `uoutput`.
%
% The `identifier` argument may contain only Latin letters and the "at" sign
% (`@`). This string remains confined within the internal scope of the package
% and does not create conflicts with possible macros or labels of the same
% name.
%
\newcommand{\@ha@getpropat}[2]{%
	% Make sure that the identifier is initialized
	\@ha@newidentifier{#2}%
	% Print the identifier's property
	\csname @ha@prop@@#1@#2\endcsname%
}
%
%
% Macro: `\starred@labeled@hereapplies{label}{identifiers}`
% *****************************************************************************
%
% Equivalent to `\hereapplies*`, but the two arguments are both mandatory
%
% This macro is for internal purposes (but nothing forbids invoking it
% directly). See the documentation of `\hereapplies` for more information.
%
\newcommand{\starred@labeled@hereapplies}[2]{%
	% Assign a label to this occurrence
	\label{#1}%
	% Iterate through the comma-separated list `identifiers`
	\@for\@ha@tmp@@id:=#2\do{%
		% Make sure that the identifier is initialized
		{\edef\tmp{\noexpand\@ha@newidentifier{\@ha@tmp@@id}}\tmp}%
		% Is this the first time this identifier is mentioned?
		\ifcsname @ha@prop@@labels@\@ha@tmp@@id\endcsname%
			% This is *not* the first time
			\expandafter\g@addto@macro\csname
				@ha@prop@@labels@\@ha@tmp@@id\endcsname{,#1}%
			% Use "pp." for the preamble when there are multiple occurrences
			\expandafter\gdef\csname
				@ha@prop@@preamble@\@ha@tmp@@id\endcsname{pp.~}%
		\else%
			% This is the first time
			\expandafter\gdef\csname
				@ha@prop@@labels@\@ha@tmp@@id\endcsname{#1}%
		\fi%
	}%
}
%
%
% Macro: `\starred@hereapplies[label]{identifiers}`
% *****************************************************************************
%
% Equivalent to `\hereapplies*`
%
% This macro is for internal purposes (but nothing forbids invoking it
% directly). See the documentation of `\hereapplies` for more information.
%
\newcommand{\starred@hereapplies}[2][]{%
	% Check whether the macro has been called with one or two arguments
	\ifx&#1&%
		% The macro has been called with only one argument
		% Assign a unique number to the unnamed occurrence
		\stepcounter{@ha@unlabeled@counter}%
		% Call `\starred@hereapplies` with an opaque label
		{\edef\tmp{\noexpand\starred@labeled@hereapplies{%
			hereapplies:unnamed\the@ha@unlabeled@counter}{#2}%
		}\tmp}%
	\else%
		% The macro has been called with two arguments
		\starred@labeled@hereapplies{#1}{#2}%
	\fi%
}
%
%
%
%         PUBLIC ENVIRONMENT
%         ===================
%
%
% Macro: `\hereapplies[label]{identifiers}`
% *****************************************************************************
%
% Notify the document that one or more identifiers apply to a particular point
% and add a label to it
%
% If the optional argument is passed the label created will be named
% accordingly, otherwise an opaque name will be assigned to it. This argument
% may contain only what is legal for `\pageref`.
%
% The `identifiers` argument must be a comma-separated list of identifiers.
% Each of these may contain only Latin letters and the "at" sign (`@`). These
% strings remain confined within the internal scope of the package and do not
% create conflicts with possible macros or labels of the same names.
%
% The starred version of this macro (`\hereapplies*`) does not invoke the
% `\phantomsection` directive.
%
\newcommand{\hereapplies}{%
	% Check if a star is present in the invocation of the command
	\@ifstar{\starred@hereapplies}{\phantomsection\relax\starred@hereapplies}%
}
%
%
% Macro: `\whereapplies{identifier}`
% *****************************************************************************
%
% Print all the occurrences of an identifier in the form "p. ..." or "pp. ..."
% with page range support
%
% The `identifier` argument may contain only Latin letters and the "at"
% sign (`@`). This string remains confined within the internal scope of the
% package and does not create conflicts with possible macros or labels of the
% same name.
%
% If the same `identifier` is not passed to `\hereapplies` at least once
% throughout the document, `\whereapplies` will print "??".
%
% The starred version of this macro (`\whereapplies*`) will use `\pageref*`
% instead of `\pageref` for generating the page list.
%
\newcommand{\whereapplies}{%
	% Check if a star is present in the invocation of the command
	\@ifstar{\@ha@getpropat{soutput}}{\@ha@getpropat{uoutput}}%
}

