//*****************************************************************************
/*
** FILE:   xs_Stylist.java
**
** (c) 1998 Steve Withall.
**
** HISTORY:
**    18Jun98  stevew  Created.
*/
package xs;

import xg.xg_AttList;
import xg.xg_Element;
import xg.xg_Node;
import xg.xg_VerificationException;

import xa.xa_NodeTypeChoiceList;

import eh.eh_Debug;

import com.sun.java.swing.JProgressBar;

import java.io.IOException;
import java.io.Writer;

import java.util.Hashtable;
import java.util.Vector;

//*****************************************************************************
/** The applier of style (as defined in a stylesheet definition supplied) to a
 *  given XML element, outputting results to a given Writer. This class is the
 *  XSL lynchpin: deciding which XSL rule to apply, and then invoking the
 *  appropriate action.
 */
public class xs_Stylist
{
    /** The definition of the stylesheet which is currently being applied. */
    protected xs_StylesheetDefn  TheStylesheetDefn = null;

    //*****************************************************************************
    /** Default constructor.
     */
//    public xs_Stylist()
//    {
//    }

    //*****************************************************************************
    /** Construct an XSL stylist with a stylesheet definition.
     *
     *  @param  InputStylesheetDefn  The definition of the stylesheet to apply
     */
    public xs_Stylist(xs_StylesheetDefn  InputStylesheetDefn)
    {
        TheStylesheetDefn = InputStylesheetDefn;
    }

    //*****************************************************************************
    /** Apply style to the given source element, and write the results to the
     *  InputResultsWriter.
     *
     *  @param      InputSourceElement   The pre-parsed source element
     *  @param      InputResultsWriter   The writer to which to write the results of
     *                                    applying a stylesheet to the source element
     *  @param      InputProgressBar     Bar on which to show progress (as
     *                                    measured by the end offset of each
     *                                    node after it is processed).
     *  @exception  xs_StyleException    Error applying style
     *  @exception  IOException          Error writing to results writer
     */
    public void applyStyle(xg_Element    InputSourceElement,
                           Writer        InputResultsWriter,
                           JProgressBar  InputProgressBar)
                                           throws xs_StyleException, IOException
    {
        eh_Debug.add(7, "xs_Stylist.applyStyle: to source " + InputSourceElement);

        // Apply all appropriate style rules to the element (placing the net
        // result in a 'style="..."' attribute within it). The element's original
        // attributes are passed back for safekeeping, to be restored later.
        xg_AttList      OriginalAttList = applyStyleRules(InputSourceElement);

        // Search through the rules to find the most suitable one.
        xs_RuleElement  SelectedRule    = selectRule(InputSourceElement);

        // Invoke each of the selected rule's actions in turn.
        invokeRuleActions(SelectedRule,
                          InputSourceElement,
                          InputResultsWriter,
                          InputProgressBar);

        // Set the element's attributes back the way they were (if they were changed).
        InputSourceElement.setAttList(OriginalAttList);
    }

    //*****************************************************************************
    /** Apply all appropriate style rules to the element (placing the net result
     *  in a 'style="..."' attribute within it). The element's original attributes
     *  are passed back for safekeeping, to be restored later.
     *
     *  @param      InputSourceElement   The pre-parsed source element
     *  @return                          The element's original attribute list
     *  @exception  xs_StyleException    Error applying style
     */
    protected xg_AttList applyStyleRules(xg_Element    InputSourceElement)
                                                          throws xs_StyleException
    {
        eh_Debug.add(8, "xs_Stylist.applyStyleRules: Apply all relevant style rules to source "
                               + InputSourceElement);

        // Create a list of every style rule which is applicable to this element,
        // and a second list of the best pattern for each such rule.
        Vector  ApplicableStyleRules = new Vector();
        Vector  BestPatterns         = new Vector();
        identifyStyleRules(InputSourceElement, ApplicableStyleRules, BestPatterns);

        xg_AttList  AttListToRestore = null;  // Assume no att list restoring needed
        if (ApplicableStyleRules.size() > 0)
        {
            // Apply the list of applicable style rules to this element.
            AttListToRestore = applyStyleRules(InputSourceElement,
                                               ApplicableStyleRules,
                                               BestPatterns);
        }
        else
            eh_Debug.add(8, "xs_Stylist.applyStyleRules: No style rules apply to source "
                               + InputSourceElement);

        return AttListToRestore;
    }

    //*****************************************************************************
    /** Create a list of every style rule which is applicable to this element, and
     *  a second list of the best pattern for each such rule.
     *
     *  @param      InputSourceElement  The pre-parsed source element
     *  @param      OutputStyleRules    List of style rules to apply
     *  @param      OutputBestPatterns  The best pattern in each of the
     *                                   applicable style rules
     *  @exception  xs_StyleException   Error applying style
     */
    protected void identifyStyleRules(xg_Element  InputSourceElement,
                                      Vector      OutputStyleRules,
                                      Vector      OutputBestPatterns)
                                                          throws xs_StyleException
    {
        Vector               StyleRuleVector    = TheStylesheetDefn.getStyleRules();
        xs_StyleRuleElement  CurrentStyleRule   = null;
        xs_PatternElement    CurrentBestPattern = null;
        for (int RuleIndex = 0;  RuleIndex < StyleRuleVector.size();  RuleIndex++)
        {
            CurrentStyleRule   = (xs_StyleRuleElement)StyleRuleVector.elementAt(RuleIndex);
            CurrentBestPattern = CurrentStyleRule.matchPattern(InputSourceElement);
            if (CurrentBestPattern != null)
            {
                // This style rule has a matching pattern. Record both the style rule
                // and the pattern.
                OutputStyleRules.addElement(CurrentStyleRule);
                OutputBestPatterns.addElement(CurrentBestPattern);
            }
        }
    }

    //*****************************************************************************
    /** Apply all appropriate style rules to the element (placing the net
     *  result in a 'style="..."' attribute within it). The element's original
     *  attributes are passed back for safekeeping, to be restored later.
     *
     *  @param      InputSourceElement  The pre-parsed source element
     *  @param      InputStyleRules     List of style rules to apply
     *  @param      InputBestPatterns   The best pattern in each of the
     *                                   applicable style rules
     *  @return                         The element's original attribute list
     *  @exception  xs_StyleException   Error applying style
     */
    protected xg_AttList applyStyleRules(xg_Element  InputSourceElement,
                                         Vector      InputStyleRules,
                                         Vector      InputBestPatterns)
                                                        throws xs_StyleException
    {
        eh_Debug.add(8, "xs_Stylist.applyStyleRules: Apply selected style rules to source "
                               + InputSourceElement);

        // Store the element's original attribute list, and create a duplicate for
        // us to tamper with as we like without worrying about long term damage.
        xg_AttList  AttListToRestore = InputSourceElement.getAttList();
        xg_AttList  StyledAttList    = AttListToRestore.duplicate();
        InputSourceElement.setAttList(StyledAttList);

        // Apply each the applicable style rules, using precedence determined by
        // each rule's best pattern.
        xs_StyleAttList  NetStyleAttList = buildStyleAttList(InputSourceElement,
                                                             InputStyleRules,
                                                             InputBestPatterns);

        // Merge in any style attributes from the element itself. (This implementation
        // has no knowledge of which attributes are style-related, so merely treats
        // the attributes from the style rules as the only ones of interest.)
        //TBD

        // Convert style attributes into a single 'style="..."' CSS attribute.
        eh_Debug.add(6, "xs_Stylist.applyStyleRules: style = '"
                               + NetStyleAttList.getStyleAttValue() + "' for source "
                               + InputSourceElement);
        StyledAttList.addAttribute(NetStyleAttList.getStyleAtt() );

        return AttListToRestore;
    }

    //*****************************************************************************
    /** Build a composite attribute list of the net result of applying all the
     *  style rules in the InputStyleRules, with the order of
     *  applying them determined by the precedence of each style rule's best
     *  pattern from the InputBestPatterns.
     *
     *  @param      InputSourceElement  The pre-parsed source element
     *  @param      InputStyleRules     List of style rules to apply
     *  @param      InputBestPatterns   The best pattern in each of the
     *                                   applicable style rules
     *  @return                         List of all style attributes from
     *                                   applicable style rules
     *  @exception  xs_StyleException   Error applying style
     */
    protected xs_StyleAttList buildStyleAttList(xg_Element  InputSourceElement,
                                                Vector      InputStyleRules,
                                                Vector      InputBestPatterns)
                                                        throws xs_StyleException
    {
        eh_Debug.add(8, "xs_Stylist.buildStyleAttList: Build net style att list for source "
                               + InputSourceElement);
        xs_StyleAttList      NetStyleAttList  = new xs_StyleAttList();
        xs_StyleRuleElement  CurrentStyleRule = null;

        if (InputStyleRules.size() == 1)
        {
            // There's only one style rule: just copy its attributes.
            CurrentStyleRule = (xs_StyleRuleElement)InputStyleRules.elementAt(0);
            xg_AttList  OnlyAttList = CurrentStyleRule.getAttList();
            NetStyleAttList.duplicateFrom(OnlyAttList);
        }
        else
        {
            // Apply each the applicable style rules, using precedence determined by
            // each rule's best pattern.
            //TBD
            eh_Debug.add(2, "xs_Stylist.buildStyleAttList: *** Code to merge multiple styles not yet implemented ***");
        }

        return NetStyleAttList;
    }

    //*****************************************************************************
    /** Select the rule most suitable to the InputSourceElement. Search through the
     *  rules to find the best one.
     *
     *  @param      InputSourceElement   The pre-parsed source element
     *  @exception  xs_StyleException    Error applying style
     */
    public xs_RuleElement selectRule(xg_Element  InputSourceElement)
                                                         throws xs_StyleException
    {
        eh_Debug.add(8, "xs_Stylist.selectRule: Select the best rule for source "
                               + InputSourceElement);
        Vector             RuleVector     = TheStylesheetDefn.getRules();
        xs_RuleElement     CurrentRule    = null;
        xs_RuleElement     BestRule       = null;
        xs_PatternElement  BestPattern    = null;
        xs_PatternElement  NewBestPattern = null;
        for (int RuleIndex = 0; RuleIndex < RuleVector.size(); RuleIndex++)
        {
            CurrentRule    = (xs_RuleElement)RuleVector.elementAt(RuleIndex);
            NewBestPattern = CurrentRule.matchPattern(BestPattern,
                                                      InputSourceElement);
            if (    NewBestPattern != null
                 && NewBestPattern != BestPattern)
            {
                // This pattern is a match, and a better match than before.
                BestPattern = NewBestPattern;
                BestRule    = CurrentRule;
            }
        }

        if (BestRule == null)
        {
            eh_Debug.add(7, "xs_Stylist.selectRule: no rule is suitable for source "
                                   + InputSourceElement + " - so using default");
            BestRule = new xs_RuleElement();
        }
        else
            eh_Debug.add(7, "xs_Stylist.selectRule: Rule #" + BestRule.getRuleNumber()
                                   + " has been chosen for source " + InputSourceElement);
        return BestRule;
    }

    //*****************************************************************************
    /** Invoke the actions of the InputSelectedRule, to apply style to the given
     *  source element, and write the results to the InputResultsWriter.
     *
     *  @param      InputSelectedRule    The rule whose actions are to be invoked
     *  @param      InputSourceElement   The pre-parsed source element
     *  @param      InputResultsWriter   The writer to which to write the results of
     *                                    applying a stylesheet to the source element
     *  @param      InputProgressBar     Bar on which to show progress (as
     *                                    measured by the end offset of each
     *                                    node after it is processed).
     *  @exception  xs_StyleException    Error applying style
     *  @exception  IOException          Error writing to results writer
     */
    public void invokeRuleActions(xs_RuleElement  InputSelectedRule,
                                  xg_Element      InputSourceElement,
                                  Writer          InputResultsWriter,
                                  JProgressBar    InputProgressBar)
                                     throws xs_StyleException, IOException
    {
        eh_Debug.add(7, "xs_Stylist.invokeRuleActions: Invoke rule #"
                             + InputSelectedRule.getRuleNumber()
                             + " and apply its actions to source "
                             + InputSourceElement);

        Vector   ActionVector      = InputSelectedRule.getActions();
        xg_Node  CurrentRuleAction = null;
        if (ActionVector.size() == 0)
        {
            // This rule has no explicit actions: default to '<children/>.
            CurrentRuleAction = new xs_ChildrenElement();
            invokeRuleAction(CurrentRuleAction,
                             InputSourceElement,
                             InputResultsWriter,
                             InputProgressBar);
        }
        else
        {
            // Invoke each of the selected rule's actions in turn.
            boolean  ActionFoundFlag = false;  // Used to check if a pattern follows an action
            for (int ActionIndex = 0; ActionIndex < ActionVector.size(); ActionIndex++)
            {
                CurrentRuleAction = (xg_Node)ActionVector.elementAt(ActionIndex);
                invokeRuleAction(CurrentRuleAction,
                                 InputSourceElement,
                                 InputResultsWriter,
                                 InputProgressBar);
            }
        }

        if (    InputProgressBar != null
             && InputSourceElement.getEndOffset() > 0)
            InputProgressBar.setValue(InputSourceElement.getEndOffset() );
    }

    //*****************************************************************************
    /** <p>Invoke the InputRuleAction, to apply style to the InputSourceElement,
     *  and write the results to the InputResultsWriter. If InputRuleAction is an
     *  xs_ActionElement, invoke it to perform its specific type of XSL action; if
     *  InputRuleAction is an xg_Element, write its start tag, process its children
     *  (noting that one or more of them may be xs_ActionElement objects), then its
     *  end tag; otherwise just write the content of xs_ActionElement.</p>
     *
     *  <p>This method is invoked recursively, when making our way through the
     *  children of an InputRuleAction.</p>
     *
     *  @param      InputRuleAction      The rule action to be invoked
     *  @param      InputSourceElement   The pre-parsed source element
     *  @param      InputResultsWriter   The writer to which to write the results of
     *                                    applying a stylesheet to the source element
     *  @param      InputProgressBar     Bar on which to show progress (as
     *                                    measured by the end offset of each
     *                                    node after it is processed).
     *  @exception  xs_StyleException    Error applying style
     *  @exception  IOException          Error writing to results writer
     */
    public void invokeRuleAction(xg_Node       InputRuleAction,
                                 xg_Element    InputSourceElement,
                                 Writer        InputResultsWriter,
                                 JProgressBar  InputProgressBar)
                                    throws xs_StyleException, IOException
    {
        eh_Debug.add(7, "xs_Stylist.invokeRuleAction: Invoke action " + InputRuleAction
                             + " for source " + InputSourceElement);

        if (InputRuleAction.getNodeType() == xa_NodeTypeChoiceList.ELEMENT_TYPE)  // DOM type
        {
            if (InputRuleAction instanceof xs_ActionElement)
            {
                // This is a proper XSL action: invoke it, and let it do its stuff.
                xs_ActionElement  RuleAction = (xs_ActionElement)InputRuleAction;
                RuleAction.setStylesheetDefn(TheStylesheetDefn);
                RuleAction.setProgressBar(InputProgressBar);
                RuleAction.invoke(InputSourceElement, InputResultsWriter);
            }
            else
            {
                // This is an ordinary element: write its start tag, process its
                // children, then write its end tag.
                xg_Element  OrdinaryElement = (xg_Element)InputRuleAction;

                //TBD We need to invoke style rules on this element and format a
                //TBD CSS 'style' attribute for it.
                OrdinaryElement.saveStartTag(InputResultsWriter);
                invokeRuleActionChildren(OrdinaryElement,
                                         InputSourceElement,
                                         InputResultsWriter,
                                         InputProgressBar);
                OrdinaryElement.saveEndTag(InputResultsWriter);
            }
        }
        else
            // It must be text or CDATA - or something (such as a comment or PI)
            // that writes no content.
            InputRuleAction.writeContent(InputResultsWriter);
    }

    //*****************************************************************************
    /** Process each of the children InputRuleAction, treating each one as an action
     *  and invoking it.
     *
     *  @param      InputRuleAction      The rule action whose children are to be
     *                                    invoked
     *  @param      InputSourceElement   The pre-parsed source element
     *  @param      InputResultsWriter   The writer to which to write the results of
     *                                    applying a stylesheet to the source element
     *  @param      InputProgressBar     Bar on which to show progress (as
     *                                    measured by the end offset of each
     *                                    node after it is processed).
     *  @exception  xs_StyleException    Error applying style
     *  @exception  IOException          Error writing to results writer
     */
    public void invokeRuleActionChildren(xg_Node       InputRuleAction,
                                         xg_Element    InputSourceElement,
                                         Writer        InputResultsWriter,
                                         JProgressBar  InputProgressBar)
                                            throws xs_StyleException, IOException
    {
        eh_Debug.add(7, "xs_Stylist.invokeRuleActionChildren: Invoke child actions of "
                             + InputRuleAction + " for source " + InputSourceElement);

        for (int ChildIndex = 0; ChildIndex < InputRuleAction.getChildCount(); ChildIndex++)
        {
            invokeRuleAction(InputRuleAction.getChild(ChildIndex),
                             InputSourceElement,
                             InputResultsWriter,
                             InputProgressBar);
        }
    }

    //*****************************************************************************
    /** Get the definition of the stylesheet currently being applied.
     *
     *  @return  The definition of the stylesheet currently being applied
     */
    public xs_StylesheetDefn getStylesheetDefn()
    {
        return TheStylesheetDefn;
    }
}

//*****************************************************************************
