/// Evolver Copyright 2000, Morgan McGuire
/// matrix@alum.mit.edu   http://graphics3d.com/matrix
import javax.swing.*;
import java.lang.reflect.*;
import javax.swing.event.*;
import java.awt.*;
import java.net.*;

/**
 * UI for editing genetic parameters.
 */
public class GeneticConsole extends JPanel
{
  /**
   * The underlying genetic parameters being edited.
   */
  GeneticParameters geneticParameters = null;

  public GeneticConsole(GeneticParameters gp)
  {
    setBackground(Evolver.backgroundColor);
    setPreferredSize(new Dimension(600, 400));

    geneticParameters = gp;
    
    OperatorSlider operatorSlider[] = new OperatorSlider[geneticParameters.operatorDescription.length];

    // Create probability editors for each operator
    for(int o = 0; o < geneticParameters.operatorDescription.length; o++) {
      OperatorSlider ops = new OperatorSlider(geneticParameters, geneticParameters.operatorDescription[o]);

      // Insert into the appropriate place (insertion sort)
      int insertIndex = 0;

      // Find the insertion point
      for(int i = 0; (i < o) && (ops.rank < operatorSlider[i].rank); i++) {
        insertIndex = i + 1;
      }
      
      // Shift elements up
      System.arraycopy(operatorSlider, insertIndex, operatorSlider, insertIndex + 1, o - insertIndex);

      // Insert
      operatorSlider[insertIndex] = ops;
    }

    // Put the editors in the gui
    for(int o = 0; o < operatorSlider.length; o++) {
      add(operatorSlider[o]);
    }
  }

}



/**
 * A slider with a toggle button that connects to a specific operator
 * description.
 */
class OperatorSlider extends JPanel
{
  JSlider slider = null;
  JToggleButton button = null;

  private static ImageIcon offIcon;
  private static ImageIcon onIcon;
  private static ImageIcon onDownIcon;
  
  // Used for grouping sliders
  int rank = 0;

  public OperatorSlider(final GeneticParameters geneticParameters, OperatorDescription instance)
  {
    if (offIcon == null) {
      // Create the icons
      try {
        offIcon = new ImageIcon(new URL("file:button-off.jpg"));
        onIcon = new ImageIcon(new URL("file:button-on.jpg"));
        onDownIcon = new ImageIcon(new URL("file:button-on-down.jpg"));
      } catch (MalformedURLException e) {
        System.err.println("Warning: " + e); 
      }
    }  

    String name = instance.operator.name();

    // Group in order of increasing number of arguments
    rank = instance.operator.getMinNumArguments() * 10 + name.length();
    
    // Put related filters together
    if (name.endsWith("Image")) {
      rank -= 100000;
    }

    if (name.endsWith("Noise")) {
      rank -= 10000;
    }

    if (name.endsWith("Gradient")) {
      rank -= 1000;
    }

    if ((name.length() > 3) && (name.substring(0,3).equals("Bit"))) {
      rank += 1000;
    }

    if (instance.operator instanceof TrigonometricOperator) {
      rank += 2;
    }

    slider = new JSlider(JSlider.VERTICAL);
    slider.setPaintTicks(false);
    slider.setPreferredSize(new Dimension(22, 88));
    slider.setMajorTickSpacing(50);
    slider.setModel(new DoublePercentageFieldAdapter(instance, "frequency"));
    
    slider.addChangeListener(new ChangeListener() {
        public void stateChanged(ChangeEvent event) {
          // Renormalize all probs
          geneticParameters.computeProbabilities();
        }
      });
    slider.setBackground(Evolver.backgroundColor); 
    
    button = new JToggleButton();
    button.setModel(new BooleanFieldAdapter(instance, "enabled"));
    button.addChangeListener(new ChangeListener() {
        public void stateChanged(ChangeEvent event) {
          OperatorSlider.this.slider.setEnabled(OperatorSlider.this.button.isSelected());
          // renormalize all probs
          geneticParameters.computeProbabilities();
        }
      });

    // Turn off rollover effects
    button.setRolloverEnabled(false);
    
    ELOperator operator = instance.operator;

    // Set the icons
    button.setIcon(new OverlayIcon(offIcon, operator));
    button.setSelectedIcon(new OverlayIcon(onIcon, operator));
    button.setPressedIcon(new OverlayIcon(onDownIcon, operator));

    button.setBorderPainted(false);
    button.setFocusPainted(false);
    button.setMargin(new Insets(-4, -4, -4, -4));
    
    slider.setEnabled(button.isSelected());

    setToolTipText(instance.name);
    setBackground(Evolver.backgroundColor);
    slider.setToolTipText(instance.name);
    button.setToolTipText(instance.name);

    setLayout(new BorderLayout());
    add(slider, BorderLayout.CENTER);
    add(button, BorderLayout.SOUTH);
  }
}

/**
 * Creates an icon that renders a base icon with an overlay as
 * specified by an operator's paintIcon method.
 */
class OverlayIcon implements Icon
{
  ImageIcon baseIcon;
  ELOperator operator;

  public OverlayIcon(ImageIcon baseIcon, ELOperator operator)
  {
    this.baseIcon = baseIcon;
    this.operator = operator;
  }

  public int getIconHeight()
  {
    return baseIcon.getIconHeight();    
  }

  public int getIconWidth()
  {
    return baseIcon.getIconWidth();    
  }

  public void paintIcon(Component c, Graphics g, int x, int y)
  {
    baseIcon.paintIcon(c, g, x, y);
    int w = getIconWidth();
    int h = getIconHeight();

    // Inset the operator's icon a bit so it doesn't draw over
    // the button borders rendered by baseIcon.  This is specific
    // to the image that we know is being used for baseIcon by
    // the Evolver code.
    int xInset = (int)Math.round(w / 4) - 1;
    int yInset = (int)Math.ceil(h / 4) - 1;

    operator.paintIcon((Graphics2D)g, x + xInset, y + yInset, w - xInset * 2, h - yInset * 2);
  }
}

