/**************************************************************************
* Copyright (c) 2001, 2005 David J. Eck                                   *
*                                                                         *
* Permission is hereby granted, free of charge, to any person obtaining   *
* a copy of this software and associated documentation files (the         *
* "Software"), to deal in the Software without restriction, including     *
* without limitation the rights to use, copy, modify, merge, publish,     *
* distribute, sublicense, and/or sell copies of the Software, and to      *
* permit persons to whom the Software is furnished to do so, subject to   *
* the following conditions:                                               *
*                                                                         *
* The above copyright notice and this permission notice shall be included *
* in all copies or substantial portions of the Software.                  *
*                                                                         *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,         *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF      *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  *
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY    *
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,    *
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE       *
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                  *
*                                                                         *
* ----                                                                    *
* (Released under new license, April 2012.)                               *
*                                                                         *
*             David J. Eck                                                *
*             Department of Mathematics and Computer Science              *
*             Hobart and William Smith Colleges                           *
*             300 Pulteney Street                                         *
*             Geneva, NY 14456                                            *
*             eck@hws.edu                                                 *
*             http://math.hws.edu/eck                                     *
**************************************************************************/

package edu.hws.jcm.awt;

import java.awt.*;
import edu.hws.jcm.data.*;

/**
 * A DisplayLabel is a label that can display numbers embedded in
 * strings.  The text for the label can include '#' characters, which
 * are substituted by values of specified Value objects.  (A doubled
 * ## is interpreted as a single literal # to be displayed rather than
 * substituted.)  You should provide as many Values as there
 * are #'s in the text.  However, no errors are generated if this
 * is not the case.  Extra expressions are ignored; extra #'s are
 * shown as "undefined" in the display.  In fact, DisplayLabels
 * do not ever generat JCMErrors.  Note that Value objects include
 * objects of type Constant, Variable, and Expression, for example.  Value
 * is just an interface defined in package edu.hws.jcm.data. 
 *
 * <p>The values displayed in a DisplayLabel are recomputed when
 * the Label's compute() method is called.  Usually, this is
 * done by a Controller that the DisplayLabel is registered with.
 * See the Controller class for more information.
 */
public class DisplayLabel extends Label implements Computable {

   /**
    * Unsubstituted text for display.
    */
   protected String text;
   
   /**
    * Desired maximum number of characters in displayed numbers.   
    */
   protected int numSize = 10;

   /**
    * Value objects whose values will be
    * substituted for #'s in text.   
    */
   protected Value[] values;

   /**                            
    * Create a label with no expressions set up to display a
    * single number.  Initial value is "undefined";  Use the
    * setValue() method to set the value to be displayed.
    */
   public DisplayLabel() {
      this(null,(Value[])null);
   }
   
   /**
    * Convenience method for making a DisplayLabel with just one value to display.
    *
    * @param text Text to display.  It shoud contain a single '#', which will be substituted by the value.
    * @param val a Value object whose value is substituted for the # in the text.
    */
   public DisplayLabel(String text, Value val) {
      this(text, (val == null)? null : new Value[] { val });
   }

   /** 
    * Create a DisplayLabel to display one or more values.
    * Text and vals can be null.  If not, text should have
    * as many (single) #'s as there are expressions.  The
    * values of the Value objects are substituted for the 
    * #'s in the display.
    *
    * @param text The text to display.  If this is null, it is set to "#".
    * @param vals The Value object(s) whose values are substituted for #'s in the text.  If this is null,
    *             the values shoud be set later by calling the setValues() method.
    */
   public DisplayLabel(String text, Value[] vals) {
      this.text = (text == null)? "#" : text;
      setValues(vals);
   }

   /**   
    * The compute method recalculates the displayed Values
    * and changes the text of the label to show the new values.
    * This is usually called by a Controller.
    */
   public void compute() {
      super.setText(getSubstitutedText());
   }

   /**   
    * Get the array of Value objects whose values are displayed
    * in this DisplayLabel.
    */
   public Value[] getValues() {
      return values;
   }
   
   /**
    *  A convenience method that can be used when the display string contains
    *  just a single #.  This sets the Value object whose value is substituted
    *  for that #.
    */
   public void setValue(Value val) {
      if (val == null)
         values = null;
      else
         values = new Value[] { val };
      super.setText(getSubstitutedText());
   }

   /**   
    * Set the array of Value objects whose values are displayed
    * in this DisplayLabel, and change the display to show
    * the new values.  (The contents of the array, vals, are
    * copied into a newly created array.)
    */
   public void setValues(Value[] vals) {
      if (vals == null)
         values = null;
      else {
         values = new Value[vals.length];
         System.arraycopy(vals,0,values,0,vals.length);
      }
      super.setText(getSubstitutedText());
   }

   /**   
    * Set the desired maximum number of characters in displayed numbers.
    * Actual size might be larger.  Value is clamped to the range
    * 6 to 25.
    */
   public void setNumSize(int size) {
      numSize = Math.min(Math.max(size,6),25);
   }
   
   /**
    * Return the desired maximum number of characters in displayed numbers.
    */
   public int getNumSize() {
      return numSize;
   }

   /**   
    * Return the basic text, including the #'s where Values
    * are inserted in the displayed text.  Note that the
    * getText() method from the Label class will return the actual 
    * displayed text, including the substitited values.
    */
   public String getBaseText() {
      return text;
   }

   /**   
    * Compute the string that is obtained by substituting values for #'s in text.
    * Will NOT throw any errors. (Any errors that occur when the
    * Value objects are evaluated are caught and translated
    * into "undefined" values.)
    */
   private String getSubstitutedText() {
      StringBuffer b = new StringBuffer();
      int valCt = 0;
      for (int i = 0; i < text.length(); i++) {
         if (text.charAt(i) == '#') {
            if (i != text.length() - 1 && text.charAt(i+1) == '#') {
               b.append('#');
               i++;
            }
            else if (values == null || valCt >= values.length)
                  b.append("undefined");
            else {
               try {
                  b.append(NumUtils.realToString(values[valCt].getVal(),numSize));
               }
               catch (JCMError e) {
                  b.append("undefined");
               }
               valCt++;
            }
         }
         else
            b.append(text.charAt(i));
      }
      return b.toString();
   }

   /**   
    * Set text for display -- text should include as many (single) #'s
    * as there are values to display.
    */
   public void setText(String text) {
      this.text = text;
      super.setText(getSubstitutedText());
   }

   /**   
    * Return the preferred size of this DisplayLabel.
    * Allow space for up to numSize (or 8, whichever is larger) characters for 
    * each (single) # in the text.  This is not meant to be called directly.
    */
   public Dimension getPreferredSize() {
      Dimension size = super.getPreferredSize();
      int ct = 0;  // Number of (single) #'1 in the text. 
      if (text == null || text.length() == 0)
         ct = 1;
      else {
         for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            if (ch == '#')
               if (i < text.length() - 1 && text.charAt(i+1) == '#')
                  i++;
               else
                  ct++;  
         }
      }
      FontMetrics fm = getFontMetrics(getFont());
      int perChar = fm.charWidth('0');
      int w = 10 + (int)(perChar * Math.max(8,numSize) * ct + fm.stringWidth(text));  // allowing extra space for numbers
      return new Dimension(w,size.height);
   }
   
} // end class DisplayLabel

