/*******************************************************************************
 * Copyright (c) 2010, 2011 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 ******************************************************************************/
package org.eclipse.equinox.bidi.custom;

import org.eclipse.equinox.bidi.advanced.IStructuredTextExpert;
import org.eclipse.equinox.bidi.advanced.StructuredTextEnvironment;
import org.eclipse.equinox.bidi.internal.StructuredTextImpl;

/**
 * Generic handler to be used as superclass (base class) for specific structured
 * text handlers.
 * <p>
 * Here are some guidelines about how to write structured text handlers.
 * <ul>
 * <li>Handler instances may be accessed simultaneously by several threads. They
 * should have no instance variables.</li>
 * <li>This class provides common logic in code which can be invoked by any
 * {@link StructuredTextTypeHandler structured text handler}. This common logic
 * uses handler methods to query the characteristics of the specific handler:
 * <ul>
 * <li>the separators which separate the structured text into tokens. See
 * {@link #getSeparators}.</li>
 * <li>the direction which governs the display of tokens one after the other.
 * See {@link #getDirection}.</li>
 * <li>the number of special cases which need to be handled by code specific to
 * that handler. See {@link #getSpecialsCount}.</li>
 * </ul>
 * </li>
 * <li>Before starting deeper analysis of the submitted text, the common logic
 * gives to the handler a chance to shorten the process by invoking its
 * {@link #skipProcessing} method.</li>
 * <li>The common logic then analyzes the text to segment it into tokens
 * according to the appearance of separators (as retrieved using
 * {@link #getSeparators}).</li>
 * <li>If the handler indicated a positive number of special cases as return
 * value from its {@link #getSpecialsCount} method, the common logic will
 * repeatedly invoke the handler's {@link #indexOfSpecial} method to let it
 * signal the presence of special strings which may further delimit the source
 * text.</li>
 * <li>When such a special case is signaled by the handler, the common logic
 * will call the handler's {@link #processSpecial} method to give it the
 * opportunity to handle it as needed. Typical actions that the handler may
 * perform are to add directional marks unconditionally (by calling
 * {@link #insertMark} or conditionally (by calling
 * {@link #processSeparator}).</li>
 * </ul>
 */
public class StructuredTextTypeHandler {

	final private String separators;

	/**
	 * Creates a new instance of the StructuredTextTypeHandler class.
	 */
	public StructuredTextTypeHandler() {
		separators = ""; //$NON-NLS-1$
	}

	/**
	 * Creates a new instance of the StructuredTextTypeHandler class.
	 * 
	 * @param separators string consisting of characters that split the text into
	 *                   fragments.
	 */
	public StructuredTextTypeHandler(String separators) {
		this.separators = separators;
	}

	/**
	 * Locates occurrences of special strings within a structured text and returns
	 * their indexes one after the other in successive calls.
	 * <p>
	 * This method is called repeatedly if the number of special cases returned by
	 * {@link #getSpecialsCount} is greater than zero.
	 * </p>
	 * <p>
	 * A handler handling special cases must override this method.
	 * </p>
	 * 
	 * @param expert     IStructuredTextExpert instance through which this handler
	 *                   is invoked. The handler can use IStructuredTextExpert
	 *                   methods to query items stored in the expert instance, like
	 *                   the current {@link StructuredTextEnvironment environment}.
	 * @param text       the structured text string before addition of any
	 *                   directional formatting characters.
	 * @param charTypes  an object whose methods can be useful to the handler.
	 * @param offsets    an object whose methods can be useful to the handler.
	 * @param caseNumber number of the special case to locate. This number varies
	 *                   from 1 to the number of special cases returned by
	 *                   {@link #getSpecialsCount} for this handler. The meaning of
	 *                   this number is internal to the class implementing
	 *                   <code>indexOfSpecial</code>.
	 * @param fromIndex  the index within <code>text</code> to start the search
	 *                   from.
	 * 
	 * @return the position where the start of the special case corresponding to
	 *         <code>caseNumber</code> was located. The method must return the first
	 *         occurrence of whatever identifies the start of the special case
	 *         starting from <code>fromIndex</code>. The method does not have to
	 *         check if this occurrence appears within the scope of another special
	 *         case (e.g. a comment starting delimiter within the scope of a literal
	 *         or vice-versa). <br>
	 *         If no occurrence is found, the method must return -1.
	 *
	 * @throws IllegalStateException If not overridden, this method throws an
	 *                               <code>IllegalStateException</code>. This is
	 *                               appropriate behavior (and does not need to be
	 *                               overridden) for handlers whose number of
	 *                               special cases is zero, which means that
	 *                               <code>indexOfSpecial</code> should never be
	 *                               called for them.
	 */
	public int indexOfSpecial(IStructuredTextExpert expert, String text, StructuredTextCharTypes charTypes,
			StructuredTextOffsets offsets, int caseNumber, int fromIndex) {
		// This method must be overridden by all subclasses with special cases.
		throw new IllegalStateException("A handler with specialsCount > 0 must have an indexOfSpecial() method."); //$NON-NLS-1$
	}

	/**
	 * Handles special cases specific to this handler. It is called when a special
	 * case occurrence is located by {@link #indexOfSpecial}.
	 * <p>
	 * If a special processing cannot be completed within a current call to
	 * <code>processSpecial</code> (for instance, a comment has been started in the
	 * current line but its end appears in a following line),
	 * <code>processSpecial</code> should specify a final state by calling
	 * {@link IStructuredTextExpert#setState(Object)}. The meaning of this state is
	 * internal to the handler.
	 * <p>
	 * On a later call, <code>processSpecial</code> will be called with
	 * <code>-1</code> for parameter <code>separLocation</code>. It should then
	 * retrieve the last state by calling {@link IStructuredTextExpert#getState()}
	 * and clear the state by calling {@link IStructuredTextExpert#clearState()}.
	 * After that, it should perform whatever initializations are required depending
	 * on the last state.
	 * </p>
	 * <p>
	 * A handler handling special cases (with a number of special cases greater than
	 * zero) must override this method.
	 * </p>
	 * 
	 * @param expert        IStructuredTextExpert instance through which this
	 *                      handler is invoked. The handler can use
	 *                      IStructuredTextExpert methods to query items stored in
	 *                      the expert instance, like the current
	 *                      {@link StructuredTextEnvironment environment}.
	 * @param text          the structured text string before addition of any
	 *                      directional formatting characters.
	 * @param charTypes     an object whose methods can be useful to the handler.
	 * @param offsets       an object whose methods can be useful to the handler.
	 * @param caseNumber    number of the special case to handle.
	 * @param separLocation the position returned by {@link #indexOfSpecial}. After
	 *                      calls to {@link IStructuredTextExpert#leanToFullText}
	 *                      and other methods of {@link IStructuredTextExpert} which
	 *                      set a non-null final state, <code>processSpecial</code>
	 *                      is called when initializing the processing with value of
	 *                      <code>separLocation</code> equal to <code>-1</code>.
	 *
	 * @return the position after the scope of the special case ends. For instance,
	 *         the position after the end of a comment, the position after the end
	 *         of a literal. <br>
	 *         A value greater or equal to the length of <code>text</code> means
	 *         that there is no further occurrence of this case in the current
	 *         structured text.
	 *
	 * @throws IllegalStateException If not overridden, this method throws an
	 *                               <code>IllegalStateException</code>. This is
	 *                               appropriate behavior (and does not need to be
	 *                               overridden) for handlers whose number of
	 *                               special cases is zero, which means that
	 *                               <code>processSpecial</code> should never be
	 *                               called for them.
	 */
	public int processSpecial(IStructuredTextExpert expert, String text, StructuredTextCharTypes charTypes,
			StructuredTextOffsets offsets, int caseNumber, int separLocation) {
		// This method must be overridden by all subclasses with any special case.
		throw new IllegalStateException("A handler with specialsCount > 0 must have a processSpecial() method."); //$NON-NLS-1$
	}

	/**
	 * Specifies that a mark character must be added before the character at the
	 * specified position of the <i>lean</i> text when generating the <i>full</i>
	 * text. This method can be called from within {@link #indexOfSpecial} or
	 * {@link #processSpecial} in extensions of
	 * <code>StructuredTextTypeHandler</code>. The mark character will be LRM for
	 * structured text with a LTR base direction, and RLM for structured text with
	 * RTL base direction. The mark character is not added physically by this
	 * method, but its position is noted and will be used when generating the
	 * <i>full</i> text.
	 *
	 * @param text      is the structured text string received as parameter to
	 *                  <code>indexOfSpecial</code> or <code>processSpecial</code>.
	 * @param charTypes is a parameter received by <code>indexOfSpecial</code> or
	 *                  <code>processSpecial</code>.
	 * @param offsets   is a parameter received by <code>indexOfSpecial</code> or
	 *                  <code>processSpecial</code>.
	 * @param offset    position of the character in the <i>lean</i> text. It must
	 *                  be a non-negative number smaller than the length of the
	 *                  <i>lean</i> text. For the benefit of efficiency, it is
	 *                  better to insert multiple marks in ascending order of the
	 *                  offsets.
	 */
	public static final void insertMark(String text, StructuredTextCharTypes charTypes, StructuredTextOffsets offsets,
			int offset) {
		offsets.insertOffset(charTypes, offset);
	}

	/**
	 * Adds a directional mark before a separator if needed for correct display,
	 * depending on the base direction of the text and on the class of the
	 * characters in the <i>lean</i> text preceding and following the separator
	 * itself. This method can be called from within {@link #indexOfSpecial} or
	 * {@link #processSpecial} in extensions of
	 * <code>StructuredTextTypeHandler</code>.
	 * <p>
	 * The logic implemented in this method considers the text before
	 * <code>separLocation</code> and the text following it. If, and only if, a
	 * directional mark is needed to insure that the two parts of text will be laid
	 * out according to the base direction, a mark will be added when generating the
	 * <i>full</i> text.
	 * </p>
	 * 
	 * @param text          is the structured text string received as parameter to
	 *                      <code>indexOfSpecial</code> or
	 *                      <code>processSpecial</code>.
	 * @param charTypes     is a parameter received by <code>indexOfSpecial</code>
	 *                      or <code>processSpecial</code>.
	 * @param offsets       is a parameter received by <code>indexOfSpecial</code>
	 *                      or <code>processSpecial</code>.
	 * @param separLocation offset of the separator in the <i>lean</i> text. It must
	 *                      be a non-negative number smaller than the length of the
	 *                      <i>lean</i> text.
	 */
	public static final void processSeparator(String text, StructuredTextCharTypes charTypes,
			StructuredTextOffsets offsets, int separLocation) {
		StructuredTextImpl.processSeparator(text, charTypes, offsets, separLocation);
	}

	/**
	 * Indicates the separators to use for the current handler. This method is
	 * invoked before starting the processing.
	 * <p>
	 * If no separators are specified, this method returns an empty string.
	 * </p>
	 * 
	 * @param expert IStructuredTextExpert instance through which this handler is
	 *               invoked. The handler can use IStructuredTextExpert methods to
	 *               query items stored in the expert instance, like the current
	 *               {@link StructuredTextEnvironment environment}.
	 *
	 * @return a string grouping one-character separators which separate the
	 *         structured text into tokens.
	 */
	public String getSeparators(IStructuredTextExpert expert) {
		return separators;
	}

	/**
	 * Indicates the base text direction appropriate for an instance of structured
	 * text. This method is invoked before starting the processing.
	 * <p>
	 * If not overridden, this method returns {@link IStructuredTextExpert#DIR_LTR
	 * DIR_LTR}.
	 * </p>
	 * 
	 * @param expert IStructuredTextExpert instance through which this handler is
	 *               invoked. The handler can use IStructuredTextExpert methods to
	 *               query items stored in the expert instance, like the current
	 *               {@link StructuredTextEnvironment environment}.
	 * @param text   the structured text string to process.
	 *
	 * @return the base direction of the structured text. This direction may not be
	 *         the same depending on the environment and on whether the structured
	 *         text contains Arabic or Hebrew letters.<br>
	 *         The value returned is either {@link IStructuredTextExpert#DIR_LTR
	 *         DIR_LTR} or {@link IStructuredTextExpert#DIR_RTL DIR_RTL}.
	 */
	public int getDirection(IStructuredTextExpert expert, String text) {
		return IStructuredTextExpert.DIR_LTR;
	}

	/**
	 * Indicates the base text direction appropriate for an instance of structured
	 * text. This method is invoked before starting the processing.
	 * <p>
	 * If not overridden, this method returns {@link IStructuredTextExpert#DIR_LTR
	 * DIR_LTR}.
	 * </p>
	 * 
	 * @param expert    IStructuredTextExpert instance through which this handler is
	 *                  invoked. The handler can use IStructuredTextExpert methods
	 *                  to query items stored in the expert instance, like the
	 *                  current {@link StructuredTextEnvironment environment}.
	 * @param text      is the structured text string to process.
	 * @param charTypes is a parameter received by <code>indexOfSpecial</code> or
	 *                  <code>processSpecial</code>.
	 *
	 * @return the base direction of the structured text. This direction may not be
	 *         the same depending on the environment and on whether the structured
	 *         text contains Arabic or Hebrew letters.<br>
	 *         The value returned is either {@link IStructuredTextExpert#DIR_LTR
	 *         DIR_LTR} or {@link IStructuredTextExpert#DIR_RTL DIR_RTL}.
	 */
	public int getDirection(IStructuredTextExpert expert, String text, StructuredTextCharTypes charTypes) {
		return IStructuredTextExpert.DIR_LTR;
	}

	/**
	 * Indicates the number of special cases handled by the current handler. This
	 * method is invoked before starting the processing. If the number returned is
	 * zero, {@link #indexOfSpecial} and {@link #processSpecial} will not be
	 * invoked.
	 * <p>
	 * If not overridden, this method returns <code>zero</code>.
	 * </p>
	 * 
	 * @param expert IStructuredTextExpert instance through which this handler is
	 *               invoked. The handler can use IStructuredTextExpert methods to
	 *               query items stored in the expert instance, like the current
	 *               {@link StructuredTextEnvironment environment}.
	 *
	 * @return the number of special cases for the associated handler. Special cases
	 *         exist for some types of structured text handlers. They are
	 *         implemented by overriding methods
	 *         {@link StructuredTextTypeHandler#indexOfSpecial} and
	 *         {@link StructuredTextTypeHandler#processSpecial}. Examples of special
	 *         cases are comments, literals, or anything which is not identified by
	 *         a one-character separator.
	 */
	public int getSpecialsCount(IStructuredTextExpert expert) {
		return 0;
	}

	/**
	 * Checks if there is a need for processing structured text. This method is
	 * invoked before starting the processing. If the handler returns
	 * <code>true</code>, no directional formatting characters are added to the
	 * <i>lean</i> text and the processing is shortened.
	 * <p>
	 * If not overridden, this method returns <code>false</code>.
	 * </p>
	 * 
	 * @param expert    IStructuredTextExpert instance through which this handler is
	 *                  invoked. The handler can use IStructuredTextExpert methods
	 *                  to query items stored in the expert instance, like the
	 *                  current {@link StructuredTextEnvironment environment}.
	 * @param text      is the structured text string to process.
	 * @param charTypes is a parameter received by <code>indexOfSpecial</code> or
	 *                  <code>processSpecial</code>.
	 *
	 * @return a flag indicating if there is no need to process the structured text
	 *         to add directional formatting characters.
	 */
	public boolean skipProcessing(IStructuredTextExpert expert, String text, StructuredTextCharTypes charTypes) {
		return false;
	}

	@Override
	public String toString() {
		return super.toString() + " [" + separators + "]"; //$NON-NLS-1$ //$NON-NLS-2$
	}
}
