import com.mathwizards.*;
import com.mathwizards.remote.*;
import graph.*;

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import java.rmi.*;
import java.rmi.server.*;

class RPlot extends Panel {

  G2Dint graph;
  Label title;
  Axis    xaxis;
  Axis    yaxis;

  boolean cleared = true;

  public RPlot() {

    super(); 

    graph = new G2Dint();
    graph.drawgrid = false;
    graph.drawzero = false;

    graph.setDataBackground(Color.black);

    setLayout( new BorderLayout() );
    add( "Center", graph);

    xaxis = graph.createXAxis();
    yaxis = graph.createYAxis();

    graph.setSize( new Dimension(300,300));

    Clear();

  }

  void addDataSet( double[] x , double[] y , Color color ) {

    if ( cleared == true ) {
      graph.detachDataSets();
      cleared = false;
    }

    // if x.length != y.length, use
    int length = java.lang.Math.min( x.length , y.length );

    double[] data = new double[ length * 2 ];

    int j = 0;
    for ( int i = 0 ; i < length ; i++ ) {
      j = i * 2;
      data[j]   = x[i];
      data[j+1] = y[i];
    }

    DataSet ds = graph.loadDataSet( data , length );
    ds.linecolor = color;

    xaxis.attachDataSet( ds );
    yaxis.attachDataSet( ds );

    graph.repaint();
  }

  void Clear() {

    cleared = true;

    graph.detachDataSets();
    double[] zero = { 0.0 , 0.0 , 1.0 , 1.0 };
    
    DataSet data = graph.loadDataSet( zero , 2 );
    data.linestyle = DataSet.NOLINE;

    xaxis.attachDataSet(data);
    yaxis.attachDataSet(data);

    graph.repaint();
  }

}
    
public class RemezClientApplet extends Applet implements com.mathwizards.remote.Client, ActionListener , KeyListener {

  static final String remez_func = "function [ rf , rm , b ] = do_remez( order , f , m )\ndisp('Designing filter ...');\nb = remez( order , f , m );\n[ rm , rf ] = freqz( b , 1 , 512 );\nrm = abs( rm );\nrf = rf / pi;\ndisp('Done ...');";
  boolean remez_func_evaluated = false;

  com.mathwizards.remote.Engine server;
  GenericRemoteClient generic_remote_client = new GenericRemoteClient(this);
  
  Object eval_done = new Object();

  String host = null;

  TextArea console = new TextArea();
  TextField prompt = new TextField();
  TextField order_tf = new TextField();

  Panel fm_panel;

  Button design_b = new Button("Design");
  Button clear_b  = new Button("Clear");

  Label order_l     = new Label("filter order: ", Label.RIGHT );

  RPlot plot = new RPlot();
  ScrollPane fm_sp = new ScrollPane( ScrollPane.SCROLLBARS_AS_NEEDED );
  Panel tf_panel = new Panel();

  int nBands = 4;

  TextField[] mag_tf;
  TextField[] freq_tf;


  public void init() {

    console.setEditable(false);
    prompt.addKeyListener( this );
    clear_b.addActionListener( this );
    design_b.addActionListener( this );

    prompt.setBackground( Color.yellow );

    int num_tf = 2 * nBands + 1;

    freq_tf = new TextField[ num_tf ];
    mag_tf  = new TextField[ num_tf ];

    tf_panel.setLayout( new GridLayout( num_tf , 2 ) );

    for ( int i = 0 ; i < num_tf ; i++ ) {
      freq_tf[i] = new TextField();
      mag_tf[i] = new TextField();
      tf_panel.add( freq_tf[i] );
      tf_panel.add( mag_tf[i] );
    }

    freq_tf[0].setText("Freq.");
    freq_tf[0].setBackground( Color.yellow );
    freq_tf[0].setEditable(false);

    mag_tf[0].setText("Mag.");
    mag_tf[0].setBackground( Color.green );
    mag_tf[0].setEditable(false);

    fm_sp.add( tf_panel );

    setLayout( new GridBagLayout() );
    GridBagConstraints gbc = new GridBagConstraints();

    gbc.gridx = 0;
    gbc.gridy = 0;
    gbc.fill = GridBagConstraints.BOTH;
    gbc.gridheight = GridBagConstraints.REMAINDER;
    gbc.weightx = 0.0;
    gbc.weighty = 0.0;
    gbc.insets = new Insets( 2 , 2 , 2 , 2 );

    add( fm_sp , gbc );  // (0,0)

    gbc.gridx = 1;
    gbc.gridheight = 1;
    gbc.gridwidth = 2;

    add( console , gbc ); // (1,0)

    gbc.gridy = 1;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    add( prompt , gbc ); // (1,1)

    gbc.gridwidth = 1;
    gbc.gridy = 2;
    gbc.fill = GridBagConstraints.BOTH;
    add( order_l , gbc ); // (1,2)

    gbc.gridx = 2;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    add( order_tf , gbc ); // (2,2)

    gbc.gridx = 1;
    gbc.gridy = 3;
    gbc.fill = GridBagConstraints.NONE;
    add( design_b , gbc ); // (1,3)

    gbc.gridx = 2;
    gbc.gridy = 3;
    add( clear_b , gbc ); // (2,3)

    gbc.gridx = 3;
    gbc.gridy = 0;
    gbc.fill = GridBagConstraints.BOTH;
    gbc.gridheight = GridBagConstraints.REMAINDER;
    gbc.weightx = 1.0;
    gbc.weighty = 1.0;
    add( plot , gbc );  // (3,0)

    setDefaults();

  }

  void PlotXY( String x , String y , Color color ) {
    
    try {

      DoubleTensor dt_X = server.getDoubleVar( generic_remote_client , x );
      DoubleTensor dt_Y = server.getDoubleVar( generic_remote_client , y );

      if ( dt_X.data.length != dt_Y.data.length )
	return;

      plot.addDataSet( dt_X.data , dt_Y.data , color );

    }
    catch ( VariableNotFoundException e ) {
      console.append( e.toString() );
    }
    catch ( TypeException e ) {
      console.append( e.toString() );
    }
    catch ( RemoteException re ) {
      console.append( re.toString() );
    }
    
  }

  public void actionPerformed( ActionEvent e ) {

    Object source = e.getSource();

    if ( source == clear_b ) {

      plot.Clear();

      order_tf.setText("");

      for ( int i = 1 ; i < 2 * nBands + 1 ; i++ ) {
	freq_tf[i].setText("");
	mag_tf[i].setText("");
      }

    }
    else if ( source == design_b ) {

      if ( !isConnected() ) {
	server = connect( getDocumentBase() );
	remez_func_evaluated = false;
      }

      if ( !isConnected() )
	return;

      plot.Clear();
      console.setText("");

      int num_freqs = getNumNonEmptyEntries( freq_tf );

      if ( num_freqs % 2 != 0 ) {

	console.append("Error: must provide even\n");
	console.append("number of frequency values.\n");
	console.setForeground( Color.red );
	return;

      }

      int num_mags  = getNumNonEmptyEntries( mag_tf );

      if ( num_freqs % 2 != 0 ) {

	console.append("Error: must provide even\n");
	console.append("number of magnitude values.\n");
	console.setForeground( Color.red );
	return;

      }
      
      if ( num_mags != num_freqs ) {

	console.append("Error: must provide the same\n");
	console.append("number of magnitude values as\n");
	console.append("frequency values.");
	console.setForeground( Color.red );
	return;

      }

      if ( num_mags < 4 ) {

	console.append("Error: must specify at least\n");
	console.append("at least two bands (4 values).\n");
	console.setForeground( Color.red );
	return;

      }

      double[] freq = getEntries( freq_tf , num_freqs );
      if ( freq == null )
	return;

      double[] mag = getEntries( mag_tf , num_freqs );
      if ( mag == null )
	return;

      int order = getOrder();
      if ( order == 0 )
	return;

      console.setForeground( Color.black );
      plot.addDataSet( freq , mag , Color.yellow );

      String freq_str = toMVString( freq );
      String mag_str  = toMVString( mag  );

      if ( !remez_func_evaluated ) {
	Eval( remez_func );
	remez_func_evaluated = true;
      }

      String script = 
        "f = " + freq_str + "\n" +
        "m = " + mag_str  + "\n" +
        "[ rf , rm , b ] = do_remez( " + order + " , f , m );";

      Eval( script );
      Eval( "filter_coefficients = b'" );

      PlotXY( "rf" , "rm" , Color.red );

    }

  }

  public void evalDone( String script , int remainingScripts ) throws RemoteException {
    synchronized (eval_done) {
      eval_done.notify();
    }
  }

  // also clears all entries after the first empty one
  private int getNumNonEmptyEntries( TextField[] tf ) {

    int last_nonempty_tf = 0;
    String entry;

    int i = 1;

    for ( ; i < tf.length ; i++ ) {
      entry = tf[i].getText().trim();
      if ( entry.compareTo("") == 0 ) {
	break;
      }
      else
	last_nonempty_tf++;
    }

    for ( ; i < tf.length ; i++ )
      tf[i].setText("");

    return last_nonempty_tf;

  }
   
  // get and validate
  double[] getEntries( TextField[] tf , int num_tf ) {

    double[] entry = new double[ num_tf ];

    for ( int i = 1 ; i < num_tf + 1 ; i++ ) {
      String e = tf[i].getText();

      try {
	entry[i-1] = Double.valueOf( e ).doubleValue();
      }
      catch ( NumberFormatException nfe ) {

	console.append( "Error: '" + entry[i-1] + "' is not a valid\n");
	console.append( "number\n");
	console.setForeground( Color.red );
	tf[i].selectAll();
	tf[i].requestFocus();
	return null;

      }

      if ( entry[i-1] < 0.0 || entry[i-1] > 1.0 ) {

	console.append( "Error: value must be between\n");
	console.append( "0.0 and 1.0.\n");
	console.setForeground( Color.red );
	tf[i].selectAll();
	tf[i].requestFocus();
	return null;

      }
    }

    return entry;
  }

  // get and validate
  int getOrder() {

    String o = order_tf.getText();
    double order;

    try {
      order = Double.valueOf( o ).doubleValue();
    }
    catch ( NumberFormatException nfe ) {

      console.append( "Error: '" + o + "' is not a valid\n");
      console.append( "number\n");
      console.setForeground( Color.red );
      order_tf.selectAll();
      order_tf.requestFocus();
      return 0;

    }

    int trunc_order = (int)order;
      
    if ( trunc_order < 3 ) {

      console.append( "Error: filter order must\n");
      console.append( "be 3 or greater.\n");
      console.setForeground( Color.red );
      order_tf.selectAll();
      order_tf.requestFocus();
      return 0;

    }

    if ( trunc_order - order != 0.0 ) {

      console.append( "Warning: filter order\n");
      console.append( "must be an integer.\n");
      console.append( "Truncating ...\n");
      order_tf.setText( "" + trunc_order );

    }

    return trunc_order;

  }

  private void setDefaults() {

    order_tf.setText("11");

    freq_tf[1].setText("0.0");
    freq_tf[2].setText("0.3");
    freq_tf[3].setText("0.5");
    freq_tf[4].setText("1.0");

    mag_tf[1].setText("1.0");
    mag_tf[2].setText("1.0");
    mag_tf[3].setText("0.0");
    mag_tf[4].setText("0.0");

    host = getDocumentBase().getHost();
    server = null;
    remez_func_evaluated = false;

  }

  private String toMVString( double[] a ) {

    String result = "[ ";

    for ( int i = 0 ; i < a.length ; i++ )
      result += a[i] + " ";

    result += "]';";

    return result;
  }

  public void keyReleased( KeyEvent e ) {
    if ( e.getKeyCode() == KeyEvent.VK_ENTER ) {
      Eval( prompt.getText() );
      prompt.setText("");
    }
  }
    
  public void keyTyped( KeyEvent e ) {}
  public void keyPressed( KeyEvent e ) {}

  public void parseError( String error ) throws RemoteException {}
  public void varChanged( String var ) throws RemoteException {}

  public void quit() throws RemoteException {
    disconnect();
  }

  public void disconnect() {
    generic_remote_client.disconnect();
  }

  boolean isConnected() {
    return generic_remote_client.isConnected();
  }

  public com.mathwizards.remote.Engine connect( URL base ) {
    return generic_remote_client.connect( base );
  }

  public void timeout( double minutes ) {
    console.append("applet time out by server after " +
		   minutes + " minutes of inactivity");
    server = null;
    remez_func_evaluated = false;
  }

  public void displayResult( String str ) throws RemoteException {
    console.append( str );
  }

  public void Eval( String script ) {

    try {

      if ( !isConnected() ) {
	server = connect( getDocumentBase() );
	remez_func_evaluated = false;
      }

      if ( !isConnected() )
	return;

      server.eval( generic_remote_client , script );

      synchronized( eval_done ) {
	try {
	  eval_done.wait(60000L); // wait for a minute
	}
	catch ( Exception e ) {
	  console.append( e.getMessage() + "\n" );
	}
      }
      
    }
    catch ( RemoteException e ) {

      console.append("lost connection to MathXplorer/J server on host " + 
		     host + " ...\n");
      console.append( e.getMessage() +"\n" );
      server = null;
      remez_func_evaluated = false;
    } 
  }

  public void ack() {}

  public void printStatusMessage( String str ) {
    console.append( str );
  }

}

