// File 3DResponse.java
//
// creates the 3D response to a set of poles and zeros
//
// based on the file surface.java as described below
//
// surface.java version 1.10 Oct 14 1996
// surface.java version 1.20 Nov  8 1996
// Copyright (c) 1996 Yanto Suryono. All Rights Reserved
//

// include the necessary system files
//
//import java.awt.*;
//
import java.applet.*;
import javax.swing.*;
import java.awt.Font;
import java.awt.Event;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.AWTEvent;
import java.awt.event.*;

// include the necessary local files
//
//import Constants;
//import PoleZero;

public class ThreeDResponse extends JFrame implements Runnable, Constants {
    
    private SettingPanel setting_panel;            // the setting panel
    private SurfaceCanvas canvas;                  // the drawing canvas
    private Thread thread;                         // current thread
    private int calc_divisions;              // number of divisions to calculate
    
    public  static final String  APP_NAME    = "3D Response ";
    public  static final String  APP_VERSION = "v7.9";
    
    public PoleZero[] pz_array;
    public int design_type = Constants.DEFAULT_DESIGN;
    private double max_frequency = Constants.DEFAULT_SAMPLE_FREQ;
    private double dc_gain = 1;
    
    private static final int INIT_CALC_DIV   = 50;
    private static final int INIT_DISP_DIV   = 50;
    
    // constructor
    //
    public ThreeDResponse(double frequency, PoleZero[] pz_array_a, int type) {
	
	super(APP_NAME);
	JFrame frame = new JFrame();
	pz_array = pz_array_a;
	design_type = type;
	max_frequency = frequency;
	
	setFont(new Font("Helvetica", Font.PLAIN, 11));
	setBackground(Color.lightGray); 
	frame.getContentPane().setLayout(new BorderLayout());
	canvas=new SurfaceCanvas(this);
	canvas.setPreferredSize(canvas.PreferredSize());
	getContentPane().add("Center", canvas ); // Center
	getContentPane().add("East", setting_panel = new SettingPanel(this, INIT_CALC_DIV,INIT_DISP_DIV)); // East
	
	
	if (design_type == Constants.DIGITAL) {
	    setting_panel.setRanges((float)-Constants.DEFAULT_SAMPLE_FREQ/2, 
				    (float)-Constants.DEFAULT_SAMPLE_FREQ/2, 
				    (float)-100, 
				    (float)Constants.DEFAULT_SAMPLE_FREQ/2,
				    (float)Constants.DEFAULT_SAMPLE_FREQ/2, 
				    (float)100);
	}
	else {
	    setting_panel.setRanges((float)-Constants.DEFAULT_SAMPLE_FREQ, 
				    (float)-Constants.DEFAULT_SAMPLE_FREQ, 
				    (float)-100, 
				    (float)Constants.DEFAULT_SAMPLE_FREQ,
				    (float)Constants.DEFAULT_SAMPLE_FREQ, 
				    (float)100);
	}
	canvas.setSize(600,60);
	//frame.setSize(500,50);
	//
	canvas.repaint();
	this.pack();
	repaint();
    }
    
    
    
    // method: set_dc_gain
    //
    // parameters:
    //     double new_dc_gain: (input) dc gain
    // 
    // this method sets the dc gain of the filter
    //
    // returns: boolean
    //
    public boolean set_dc_gain(double new_dc_gain) {
	
	// set the frequency in all of the sub-components
	//
	dc_gain = new_dc_gain;
	
	// exit gracefully
	//
	return true;
	
    }
    
    
    // method: set_design_type 
    //
    // parameters:
    //     integer new_type: (input) design type to change to
    //
    // this is a method to set the design type.  the design type will
    // be set to the value passed in to new_type
    //
    // returns: boolean
    //
    public boolean set_design_type(int new_type) {
	
	// set the type
	//
	design_type = new_type;
	
	// exit gracefully
	//
	return true;
    }
    
    
    // method: set_frequency
    //
    // parameters:
    //     double frequency: (input) frequency input
    // 
    // this method sets the sample frequency or maximum frequency
    //
    // returns: boolean
    //
    public boolean set_frequency(double frequency) {
	
	// set the frequency
	//
	max_frequency = frequency;
	
	// update the setting panel
	//
	if (design_type == Constants.DIGITAL) {
	    setting_panel.setRanges((float)-max_frequency/2, 
				    (float)-max_frequency/2,
				    (float)-50.0, (float)max_frequency/2, 
				    (float)max_frequency/2, (float)50.0);
	}
	else {
	    setting_panel.setRanges((float)-max_frequency, 
				    (float)-max_frequency,
				    (float)-50.0, (float)max_frequency, 
				    (float)max_frequency, (float)50.0);
	}
	
	// exit gracefully
	//
	return true;
	
    }
    
    
    public void setMessage(String message) {
	return;
    }
    
    //
    //
    public void run() {
	float   stepx, stepy, x, y, v;
	float   xi,xx,yi,yx;
	float   min, max;
	int     i,j,k,total;
	double temp_value;
	
	canvas.validate();
	try {
	    xi = Float.valueOf(setting_panel.getXMin()).floatValue();
	    yi = Float.valueOf(setting_panel.getYMin()).floatValue();
	    xx = Float.valueOf(setting_panel.getXMax()).floatValue();
	    yx = Float.valueOf(setting_panel.getYMax()).floatValue();
	    
	    if ((xi >= xx) || (yi >= yx)) throw new NumberFormatException();
	}
	catch(NumberFormatException e) {
	    setMessage("Error in ranges"); 
	    return;
	}
	
	canvas.setRanges(xi,xx,yi,yx); 
	
	thread.yield();
	calc_divisions = setting_panel.getCalcDivisions();
	canvas.setDataAvailability(false); 
	
	stepx = (xx - xi) / calc_divisions; 
	stepy = (yx - yi) / calc_divisions;
	total = (calc_divisions+1)*(calc_divisions+1);
	
	// release memory being used
	
	canvas.setValuesArray(null);
	SurfaceVertex[][] vertex = null;
	
	try {
	    vertex = new SurfaceVertex[1][total];
	}
	catch(OutOfMemoryError e) {
	    setMessage("Not enough memory");
	    return;
	}
	catch(Exception e) {
	    setMessage("Error: " + e.toString());
	    return;
	}
	
	max = Float.POSITIVE_INFINITY;
	min = Float.NEGATIVE_INFINITY;
	
	canvas.destroyImage();
        
	i = 0; j = 0; k = 0; x = xi; y = yi;
	
	float xfactor = 20/(xx-xi);
	float yfactor = 20/(yx-yi);
	
	while (i <= calc_divisions) {
	    
	    while (j <= calc_divisions) {
		thread.yield();
		
		// set the array value to the first calculated value
		//
		if (pz_array[0] != null) {
		    if (design_type == Constants.ANALOG) {
			temp_value = (pz_array[0]).three_d_mag_resp((double)x, (double)y);
		    }
		    else {
			temp_value = (pz_array[0]).three_d_mag_resp((double)x,
								    (double)y,
								    max_frequency);
		    }
		    if (Double.isInfinite(temp_value)) {
			if (temp_value == Double.POSITIVE_INFINITY) {
			    temp_value = Double.MAX_VALUE;
			}
			else {
			    temp_value = -Double.MAX_VALUE;
			}
		    }
		}
		else {
		    temp_value = 1;
		}
		
		// calculate the mag response due to each pole or zero
		// and multiply it with the previous response
		//
		for (int kk = 1; pz_array[kk] != null; kk++) {
		    if (design_type == Constants.ANALOG) {
			temp_value *= (pz_array[kk]).three_d_mag_resp((double)x, (double)y);
		    }
		    else {
			temp_value *= (pz_array[kk]).three_d_mag_resp((double)x,
								      (double)y,
								      max_frequency);
		    }
		    
		    // check for infinite values
		    //
		    if (Double.isInfinite(temp_value)) {
			if (temp_value == Double.POSITIVE_INFINITY) {
			    temp_value = Double.MAX_VALUE;
			}
			else {
			    temp_value = -Double.MAX_VALUE;
			}
		    }
		}
		
		temp_value *= dc_gain;
		
		if (temp_value == 0.0) {
		    temp_value = 20 * Math.log(Double.MIN_VALUE) / Constants.LOGE10;
		}
		else {
		    temp_value = 20 * Math.log(temp_value) / Constants.LOGE10;
		}
		
		v = (float) temp_value;
		
		if (Float.isInfinite(v)) v = Float.NaN;
		if (!Float.isNaN(v)) {
		    if ((v > max) || (Float.isInfinite(max))) max = v; 
		    else
			if ((v < min) || (Float.isInfinite(min))) min = v;
		}
		vertex[0][k] = new SurfaceVertex((x-xi)*xfactor-10,
						 (y-yi)*yfactor-10,v);
		j++; y += stepy;
		k++;
	    }
	    j = 0; y = yi; i++; x += stepx;
	}
	
	
	if (Float.isInfinite(min)) min = Float.NaN;
	if (Float.isInfinite(max)) max = Float.NaN;
	setting_panel.setMinimumResult(Float.toString(min));
	setting_panel.setMaximumResult(Float.toString(max));
	canvas.setValuesArray(vertex);
	canvas.setDataAvailability(true);
	canvas.repaint(); 
    }
    
    /*--------------------------------------------------------------------------------                     
      Handles user actions 
      --------------------------------------------------------------------------------*/  
    
    public void doCalculate() {
	if (!thread.isAlive()) {
	    thread = new Thread(this);
	    thread.start();
	}
    }
    
    public void doStop() {
	if (thread.isAlive()) {
	    //thread.stop();
	    thread = null;
	    try {
		thread.join();
	    }
	    catch (InterruptedException exception) {};
	    setMessage("Interrupted");
	    canvas.setDataAvailability(false);
	    canvas.repaint();
	}
	canvas.interrupt();
    }
    
    public void changeTick() {
	canvas.destroyImage();
	canvas.repaint();
    }
    
    public void changeGrid() {
	canvas.destroyImage();
	canvas.repaint();
    }
    
    
    /*--------------------------------------------------------------------------------                   
      Starts applet  
      --------------------------------------------------------------------------------*/  
    
    public void start() {
	thread = new Thread(this);
	thread.start();
	repaint();
    }
    
    /*--------------------------------------------------------------------------------                     
      Stops applet's activities 
      --------------------------------------------------------------------------------*/  
    
    public void stop() {
	thread = null;
	//if (thread.isAlive()) {
	    //thread.stop();

	//  try {
	//thread.join();
	//  }
	//  catch (InterruptedException exception) {};
	//  setMessage("Stopped");
	//  canvas.setDataAvailability(false);
	//}
	//canvas.interrupt();
    }
    
    /*--------------------------------------------------------------------------------                     
      Returns whether Surface Plotter should delay regeneration 
      until mouse buttons have been released 
      --------------------------------------------------------------------------------*/  
    
    public boolean isExpectDelay() {
	return false;
    }
    
    /*--------------------------------------------------------------------------------                     
      Functions that return/set plotting variables
      These functions are called ONLY from SurfaceCanvas's importVariables() 
      --------------------------------------------------------------------------------*/  
    
    public boolean isBoxed() {
	return setting_panel.isBoxed();
    }
    
    public boolean isMesh() {
	return setting_panel.isMesh();
    }
    
    public boolean isScaleBox() {
	return setting_panel.isScaleBox();
    }
    
    public boolean isDisplayXY() {
	return setting_panel.isDisplayXY();
    }
    
    public boolean isDisplayZ() {
	return setting_panel.isDisplayZ();
    }
    
    public boolean isDisplayGrids() {
	return setting_panel.isDisplayGrids();
    }
    
    public boolean isPlotFunction1() {
	return true;
    }
    
    public boolean isPlotFunction2() {
	return false;
    }
    
    public int getPlotMode() {
	return 2;
    }
    
    public int getCalcDivisions() {
	return calc_divisions;
    }
    
    /*--------------------------------------------------------------------------------                     
      Returns current Z ranges needed by SurfaceCanvas
      --------------------------------------------------------------------------------*/  
    
    public String getZMin() {
	return setting_panel.getZMin();
    }
    
    public String getZMax() {
	return setting_panel.getZMax();
    }
    
    /*--------------------------------------------------------------------------------                     
      Gets / sets number of divisions to display 
      --------------------------------------------------------------------------------*/  
    
    public int getDispDivisions() {
	int plot_density;
	
	plot_density = setting_panel.getDispDivisions();
	if (plot_density > calc_divisions) plot_density = calc_divisions;
	while ((calc_divisions % plot_density) != 0) plot_density++;
	return plot_density;
    }
    
    public void setDispDivisions(String value) {
	setting_panel.setDispDivisions(value);
    }
    
    /*--------------------------------------------------------------------------------                     
      Routines to give Surface Plotter a frame when running as an application
      --------------------------------------------------------------------------------*/  
    
    
    
    // method: set_pz_array
    //
    // parameters:
    //     PoleZero pz_array_a[]: (input) array of poles and zeros
    // 
    // this method sets the pz_array to point to the input variable
    //
    // returns: boolean
    //
    public boolean set_pz_array(PoleZero pz_array_a[]) {
	
	// set the pointer
	//
	pz_array = pz_array_a;
	
	// exit gracefully
	//
	return true;
	
    }
}
