/* Copyright (C) 2000,2004 by Peter Eastman

   This program is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later version.

   This program 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 General Public License for more details. */

package artofillusion.procedural;

import artofillusion.*;
import artofillusion.math.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

/** This is a Module which scales and shifts its input value. */

public class ScaleShiftModule extends Module
{
  private double scale, shift;
  transient double from1, from2, to1, to2;    // TODO(MB) Should be saved

  ValueField scaleField, shiftField;
  ValueField from1Field, from2Field, to1Field, to2Field;


  public ScaleShiftModule(Point position)
  {
    super("\u00D7 1.0 + 0.0", new IOPort [] {new IOPort(IOPort.NUMBER, IOPort.INPUT, IOPort.LEFT, new String [] {"Input", "(0)"})},
      new IOPort [] {new IOPort(IOPort.NUMBER, IOPort.OUTPUT, IOPort.RIGHT, new String [] {"Output"})},
      position);
    scale = 1.0;
    shift = 0.0;
    from1 = 0.0;
    to1   = 0.0;
    from2 = 1.0;
    to2   = 1.0;
  }
  
  /** Get the scale value. */
  
  public double getScale()
  {
    return scale;
  }
  
  /** Set the scale value. */
  
  public void setScale(double val)
  {
    scale = val;
  }
  
  /** Get the shift value. */
  
  public double getShift()
  {
    return shift;
  }
  
  /** Set the shift value. */
  
  public void setShift(double val)
  {
    shift = val;
  }

  /* Calculate the output value. */

  public double getAverageValue(int which, double blur)
  {
    if (linkFrom[0] == null)
      return shift;
    return linkFrom[0].getAverageValue(linkFromIndex[0], blur)*scale + shift;
  }

  /* Calculate the output error. */

  public double getValueError(int which, double blur)
  {
    if (linkFrom[0] == null)
      return 0.0;
    return linkFrom[0].getValueError(linkFromIndex[0], blur)*Math.abs(scale);
  }

  /* The gradient is the sum of the two gradients. */

  public void getValueGradient(int which, Vec3 grad, double blur)
  {
    if (linkFrom[0] == null)
      {
        grad.set(0.0, 0.0, 0.0);
        return;
      }
    else
      linkFrom[0].getValueGradient(linkFromIndex[0], grad, blur);
    grad.set(grad.x*scale+shift, grad.y*scale+shift, grad.z*scale+shift);
  }

  /** Helper class to handle text events during editing. */
  protected class EditListener
  {
    boolean processEvents;
    ValueField[] uppers;
    // ValueField[] lowers;

    public EditListener()
    {
      processEvents = false;
      uppers = new ValueField[] {scaleField, shiftField};
      // lowers = new ValueField[] {from1Field, from2Field, to1Field, to2Field};
    }

    protected boolean isUpper(Object f)
    {
      return Utilities.findIndexIdent(uppers, f) > -1;
//      ValueField field = (ValueField)f;
//      
//      for(int i = 0; i < uppers.length; ++i)
//      {
//        if (field == uppers[i])
//          return true;
//      }
//      return false;
    }

//    public void keyTyped(KeyEvent e)
//    {}
//
//    public void keyReleased(KeyEvent e)
//    {}

    public void keyPressed(KeyPressedEvent e)
    {
      processEvents = true;
    }

    public void valueChanged(ValueChangedEvent evt)
    {
      if (!processEvents)
        return;
      processEvents = false;

      // Read all values
      double scale = scaleField.getValue();
      double shift = shiftField.getValue();
      double from1 = from1Field.getValue();
      double to1   = to1Field.getValue();
      double from2 = from2Field.getValue();
      double to2   = to2Field.getValue();

      if (isUpper(evt.getSource()))
      {
        // User edited upper panel, so change lower accordingly
        to1Field.setValue(from1 * scale + shift);
        to2Field.setValue(from2 * scale + shift);
      }
      else
      {
        // User edited lower panel, so change upper accordingly
        if (from1 == from2)  // TODO(MB) Check for a very small delta instead?
          return;   // can't be calculated

        scale = (to2 - to1) / (from2 - from1);
        shift = to1 - scale * from1;

        scaleField.setValue(scale);
        shiftField.setValue(shift);
      }
    }
  }


  /** Allow the user to set the parameters. */

  public boolean edit(BFrame fr, Scene theScene)
  {
    scaleField = new ValueField(scale, ValueField.NONE, 5);
    shiftField = new ValueField(shift, ValueField.NONE, 5);

    from1Field = new ValueField(from1, ValueField.NONE, 5);
    from2Field = new ValueField(from2, ValueField.NONE, 5);
    
    to1Field = new ValueField(to1, ValueField.NONE, 5);
    to2Field = new ValueField(to2, ValueField.NONE, 5);
    
    RowContainer upper = new RowContainer();
    upper.add(new BLabel(Translate.text("scaleShiftEquation")));
    upper.add(scaleField);
    upper.add(new BLabel(" + "));
    upper.add(shiftField);
    
    FormContainer lower = new FormContainer(2, 3);
    
    lower.add(new BLabel("Input"), 0, 0);
    lower.add(new BLabel("Output"), 1, 0);
    
    lower.add(from1Field, 0, 1);
    lower.add(to1Field, 1, 1);

    lower.add(from2Field, 0, 2);
    lower.add(to2Field, 1, 2);

    BorderContainer content = new BorderContainer();

    content.add(upper, BorderContainer.NORTH);
    content.add(lower, BorderContainer.SOUTH);
    
    EditListener edl = new EditListener();
    //scaleField.addEventLink(ValueChangedEvent.class, edl, "valueChanged");
    
    ValueField[] fvarr = new ValueField[] {scaleField, shiftField, from1Field, from2Field,
      to1Field, to2Field};

    for(int i = 0; i < fvarr.length; ++i)
    {
      fvarr[i].addEventLink(ValueChangedEvent.class, edl, "valueChanged");
      fvarr[i].addEventLink(KeyPressedEvent.class, edl, "keyPressed");
    }
 
    PanelDialog dlg = new PanelDialog(fr, Translate.text("selectScaleShiftProperties"), content);


/*                     TODO(MB): !!! Rewrite to buoy
    EditListener edl = new EditListener();

    // Simple way to add listeners to all fields
    fvarr = new ValueField[] {scaleField, shiftField, from1Field, from2Field,
      to1Field, to2Field};

    for(i = 0; i < fvarr.length; ++i)
    {
      fvarr[i].addTextListener(edl);
      fvarr[i].addKeyListener(edl);
    }


    // Normal fields as usual
    Panel upper = new Panel();

    upper.add(new Label("Output = Input \u00D7"));
    upper.add(scaleField);
    upper.add(new Label(" + "));
    upper.add(shiftField);

    // New fields
    Panel lower = new Panel();
    GridBagConstraints c = new GridBagConstraints();

    lower.setLayout(new GridBagLayout());
    c.weightx = 1.0;
    c.anchor = GridBagConstraints.CENTER;
    c.ipadx = 0;
    c.gridx = 0;
    c.gridy = 0;
    lower.add(new Label(" Input "), c);
    c.gridx = GridBagConstraints.RELATIVE;
    lower.add(new Label(" Output "), c);
    c.fill = GridBagConstraints.HORIZONTAL;
    c.gridy = 1;
    lower.add(from1Field, c);
    lower.add(to1Field, c);
    c.gridy = 2;
    lower.add(from2Field, c);
    lower.add(to2Field, c);

    // Merge together
    Panel p = new Panel();

    p.setLayout(new BorderLayout());
    p.add(upper, BorderLayout.NORTH);
    p.add(lower, BorderLayout.SOUTH);

    PanelDialog dlg = new PanelDialog(fr, "Set Output Parameters:", p);
*/
    if (!dlg.clickedOk())
      return false;
    scale = scaleField.getValue();
    shift = shiftField.getValue();
    from1 = from1Field.getValue();
    to1   = to1Field.getValue();
    from2 = from2Field.getValue();
    to2   = to2Field.getValue();

    name = "\u00D7 "+scale+" + "+shift;
    layout();
    return true;
  }

  /* Create a duplicate of this module. */

  public Module duplicate()
  {
    ScaleShiftModule mod = new ScaleShiftModule(new Point(bounds.x, bounds.y));

    mod.scale = scale;
    mod.shift = shift;
    mod.name = name;
    mod.from1 = from1;
    mod.from2 = from2;
    mod.to1 = to1;
    mod.to2 = to2;
    return mod;
  }

  /* Write out the parameters. */

  public void writeToStream(DataOutputStream out, Scene theScene) throws IOException
  {
    out.writeDouble(scale);
    out.writeDouble(shift);
  }

  /* Read in the parameters. */

  public void readFromStream(DataInputStream in, Scene theScene) throws IOException
  {
    scale = in.readDouble();
    shift = in.readDouble();
    from1 = 0.0;
    from2 = 1.0;
    to1 = from1 * scale + shift;
    to2 = from2 * scale + shift;

    name = "\u00D7 "+scale+" + "+shift;
    layout();
  }
}
