/* Spiro by Jimmc
 * Copyright 1996 Jim McBeath
 *
 * Permission to use, copy, and distribute Spiro, in source or binary form,
 * for personal non-commercial use is hereby granted without fee, provided
 * that this copyright notice appears in all copies, and that no charge is
 * associated with such copies.
 *
 * Permission to modify Spiro and make derivative works for personal
 * non-commercial use, and to distribute such modified versions, is hereby
 * granted without fee, provided that the users of such a modified version
 * are clearly notified that the work is a modified version of Spiro and
 * not the original Spiro.  As with unmodified distributions, this copyight
 * notice must appear in all modified copies, and no charge may be associated
 * with such copies.
 *
 * The author makes no representations about the suitability of this software
 * for any purpose.  It is provided "as-is", without any express or implied
 * warranty.  In no event will the author be held liable for any damages
 * arising from the use of this software.
 */
/* SpiroControls - the control panel for Spiro
 *
 * Jim McBeath, May 1996
 */

import java.awt.*;
import java.applet.Applet;
import java.applet.AppletContext;
import java.util.Properties;
import java.io.*;

class SpiroControls extends Panel {
	SpiroCanvas canvas;
	ButtonPanel buttonPanel;
	WheelPanel wheelPanel;
	MiscPanel miscPanel;
	SpiroAdvanced advancedDialog;

	public SpiroControls(SpiroCanvas c, boolean isApplication,
				boolean canLoadFiles) {
		canvas = c;
		GridBagConstraints gbc = new GridBagConstraints();
		GridBagLayout gb = new GridBagLayout();
		setLayout(gb);
		wheelPanel = new WheelPanel(this);
		miscPanel = new MiscPanel(this,canLoadFiles);
		buttonPanel = new ButtonPanel(this,isApplication,canLoadFiles);

		gbc.gridx = 0;
		gbc.gridy = 0;
		gb.setConstraints(buttonPanel,gbc);
		add(buttonPanel);

		gbc.gridy = 1;
		gb.setConstraints(miscPanel,gbc);
		add(miscPanel);

		gbc.gridx = 1;
		gbc.gridy = 0;
		gbc.gridheight = GridBagConstraints.REMAINDER;
		gb.setConstraints(wheelPanel,gbc);
		add(wheelPanel);

		//canvas.repaint();	//draw the canvas outline
		//doesn't work
	}

	protected SpiroState getState() {
		/* return a SpiroState with all of the info
		 * from the control panel */
		SpiroState state;
		state = wheelPanel.getState();
		miscPanel.getState(state);
		if (advancedDialog!=null)
			advancedDialog.getState(state);
		return state;
	}

	protected void setState(SpiroState state) {
		miscPanel.setState(state);
		wheelPanel.setState(state);
		if (advancedDialog!=null)
			advancedDialog.setState(state);
		layout();	/* in case num wheels changed */
		//TBD - need to update screen?
	}

	protected void editAdvanced() {
		if (advancedDialog==null) {
			advancedDialog = new SpiroAdvanced(getFrame(),this);
		}
		advancedDialog.show();
	}

	protected Frame getFrame() {
		return canvas.spiro.getFrame();
	}

	protected boolean hasFrame() {
		return canvas.spiro.hasFrame();
	}

	//Action callbacks for buttons, etc.
	protected void drawCallback() {
		canvas.defaultState = getState();
		int lcm = canvas.defaultState.getLcmSteps();
		int n = canvas.defaultState.steps;
		miscPanel.setActualSteps(lcm,n);
		if (advancedDialog!=null)
			advancedDialog.setActualSteps(lcm,n);
		canvas.runFlag = true;
		canvas.draw();
	}

	protected void stopCallback() {
		canvas.runFlag = false;
	}

	protected void addWheelCallback() {
		wheelPanel.addWheel();
		updateLayout();
	}

	protected void removeWheelCallback() {
		wheelPanel.removeWheel();
		updateLayout();
	}

	protected void updateLayout() {
		if (hasFrame()) {
		    getFrame().pack();
		}
		layout();
		//repaint();
	}

	protected void toggleAnimateCallback() {
		//if animage was turned on, puts up the animate outline
		//if animate was turned off, this clears the screen
		canvas.defaultState = getState();
		canvas.defaultState.steps = -1;
		canvas.draw();
	}

	protected void saveFile() {
		showStatus("");
		//String homedir;
		//homedir = System.getProperty("user.home");
		Properties p = getState().getProperties();
//p.list(System.out);//debug
		//String defaultDirectory = homedir;
		String defaultDirectory = ".";
		String defaultFileName = "default.spi";
			//"spiro" + File.separator + "default.spi";
		String outFileName = getFileName(defaultDirectory,
					defaultFileName,"Save As",
					FileDialog.SAVE);
		if (outFileName==null) {
			showStatus("Cancelled");
			return;
		}
System.out.println("Output file is " + outFileName);
		FileOutputStream outFile;
		try {
			outFile = new FileOutputStream(outFileName);
		} catch (IOException e) {
			showStatus("Can't open output file");
			return;
		}
		String fileComment = "Spiro Parameters";
		p.save(outFile,fileComment);
		try {
			outFile.close();
			showStatus("Saved file");
		} catch (IOException e) {
			showStatus("Error closing file");
		}
	}

	protected void loadFile() {
		showStatus("");
		//String homedir;
		//homedir = System.getProperty("user.home");
		String defaultDirectory = ".";
		String defaultFileName = "default.spi";
			//"spiro" + File.separator + "default.spi";
		String inFileName = getFileName(defaultDirectory,
					defaultFileName,"Load",
					FileDialog.LOAD);
		if (inFileName==null) {
			showStatus("Cancelled");
			return;
		}
//System.out.println("Input file is " + inFileName);
		SpiroState state = SpiroState.loadFile(inFileName);
		if (state==null) {
			showStatus("Can't load file");
			return;
		}
		setState(state);
	}

	protected void loadURL() {
		showStatus("");
		String inFileName = getURLName();
		if (inFileName==null) {
			showStatus("Cancelled");
			return;
		}
//System.out.println("Input file is " + inFileName);
		SpiroState state = SpiroState.loadURL(inFileName);
		if (state==null) {
			showStatus("Can't load URL");
			return;
		}
		setState(state);
	}

	//misc utility routines
	public void errorMessage(String s) {
		canvas.spiro.errorMessage(s);
	}

	public void showStatus(String s) {
		miscPanel.showStatus(s);
/*
//TBD - not very elegant
		try {
			AppletContext c;
			c = canvas.spiro.getAppletContext();
			c.showStatus(s);
		} catch (Exception e) {
			System.out.println(s);
		}
 */
	}

	private String getFileName(String defaultDirectory,
			String defaultFileName, String title, int mode) {
		Frame f = canvas.spiro.getFrame();
		FileDialog dialog = new FileDialog(f,title,mode);
		if (defaultDirectory!=null)
			dialog.setDirectory(defaultDirectory);
		if (defaultFileName!=null)
			dialog.setFile(defaultFileName);
		dialog.show();
		return (dialog.getDirectory() + File.separator +
				dialog.getFile());
	}

	private String getURLName() {
		//TBD - use a simpler dialog
		Frame f = canvas.spiro.getFrame();
		String msg = "Enter URL to load:";
		String title = "Load URL";
		SpiroStringDialog dialog = new SpiroStringDialog(f,title,msg);
		dialog.show();
		Thread.yield();
		String s = dialog.getText();
//Unfortunately, the dialog does not block for input (but FileDialog does),
//so the control flow is not right here.
		return s;
	}

	private void beep() {
//		canvas.spiro.play(canvas.spiro.getCodeBase(),"beep.au");
//TBD - to be reenabled when audio is working on my system!
	}
}

class ButtonPanel extends Panel {
	SpiroControls controls;
	Checkbox advancedCheckbox;

	public ButtonPanel(SpiroControls c, boolean isApplication,
			boolean canLoadFiles) {
		controls = c;
		setLayout(new FlowLayout());
		addButton("Draw");
		addButton("Stop");
		if (canLoadFiles) {
			addButton("Save As");
			addButton("Load File");
		}
		//addButton("Load URL");
		//skip the Load URL button, it's not quite working

		//addButton("Advanced");
		//advancedCheckbox = addCheckbox("Advanced");
		if (isApplication) {
			addButton("Exit");
		}
	}

	private Button addButton(String label) {
		Button button = new Button(label);
		add(button);
		return button;
	}

	private Checkbox addCheckbox(String label) {
		Checkbox cb = new Checkbox(label);
		add(cb);
		return cb;
	}

	public boolean action(Event ev, Object arg) {
		if (ev.target instanceof Button) {
			Button btn = (Button)(ev.target);
			if (ev.arg.equals("Draw")) {
				controls.drawCallback();
				return true;
			}
			if (ev.arg.equals("Stop")) {
				controls.stopCallback();
				return true;
			}
			if (ev.arg.equals("Advanced")) {
				controls.editAdvanced();
				return true;
			}
			if (ev.arg.equals("Save As")) {
				controls.saveFile();
				return true;
			}
			if (ev.arg.equals("Load File")) {
				controls.loadFile();
				return true;
			}
			if (ev.arg.equals("Load URL")) {
				controls.loadURL();
				return true;
			}
			if (ev.arg.equals("Exit")) {
				System.exit(0);
				return true;
			}
		}
		return false;
	}
}

class MiscPanel extends Panel {
	SpiroControls controls;
	TextField colorW;
	TextField stepsW;
	TextField dashW;
	Label actualStepsW;
	Checkbox animateW;
	Checkbox animateCountW;
	TextField chainW;
	//Choice drawModeW;
	ButtonPanel buttons;
	Label statusW;
	GridBuilder gb;

	public MiscPanel(SpiroControls c, boolean canLoadFiles) {
		controls = c;
		gb = new GridBuilder(this);
		gb.addLabel("Color:");
		colorW = gb.addTextField("red",8);
		gb.addLabel("Dashes:");
		dashW = gb.addTextField("",8);
		gb.nextRow();
		gb.addLabel("Steps:");
		stepsW = gb.addTextField("",8);
		actualStepsW = gb.addLabel("         ");
		gb.nextRow();
		if (canLoadFiles) {
		    gb.addLabel("Chain:");
		    chainW = gb.addTextField("",8);
		    /*
		    gb.addLabel("Draw Mode:");
		    drawModeW = gb.addChoice();
		    drawModeW.addItem("Backing");
		    drawModeW.addItem("Direct");
		    drawModeW.addItem("Synchronous");
		    drawModeW.select("Direct");	//set the default
		    */
		    gb.nextRow();
		}
		gb.constraints.gridwidth = 2;
		animateW = gb.addCheckbox("Animate");
		animateCountW = gb.addCheckbox("Animate Count");
		gb.nextRow();
		statusW = gb.addLabel("                            ");
	}

	protected void getState(SpiroState state) {
		state.color = colorW.getText().trim();
		state.animate = animateW.getState();
		state.animateCount = animateCountW.getState();
		String stepStr = stepsW.getText().trim();
		if (stepStr.length()>0)
			state.steps = Integer.parseInt(stepStr);
		else
			state.steps = 0;
		String dashStr = dashW.getText().trim();
		if (dashStr.length()>0) {
			IntVector v = new IntVector(dashStr);
			int a[] = v.getArray();
			state.dashPattern = a;
		} else
			state.dashPattern = null;
		/*
		if (drawModeW!=null) {
			state.drawMode = drawModeW.getSelectedItem();
		} else {
			state.drawMode = "Direct";
		}
		*/
		//state.drawMode = state.animate?"Backing":"Direct";
		state.drawMode = "Direct";
		if (chainW!=null) {
			state.chain = chainW.getText().trim();
		}
	}

	protected void setState(SpiroState state) {
		colorW.setText(state.color);
		animateW.setState(state.animate);
		animateCountW.setState(state.animateCount);
	}

	public void showStatus(String s) {
		statusW.setText(s);
	}

	protected void setActualSteps(int lcmsteps, int usersteps) {
		String lcmstr = Integer.toString(lcmsteps);
		if (usersteps==0)
			actualStepsW.setText(lcmstr);
		else
			actualStepsW.setText("("+lcmstr+")");
	}

	public boolean action(Event ev, Object arg) {
		if (ev.target instanceof Checkbox) {
			Checkbox ckb = (Checkbox)(ev.target);
			if (ckb.getLabel()=="Animate") {
				controls.toggleAnimateCallback();
				return true;
			}
		}
		return false;
	}
}

class WheelPanel extends Panel {
	SpiroControls controls;
	protected static final int MAXNUMWHEELS = 10;
	protected int numwheels = 0;
	WheelControl wheels[] = new WheelControl[MAXNUMWHEELS];
	GridBuilder gb;

	public WheelPanel(SpiroControls c) {
		controls = c;
		gb = new GridBuilder(this);
		addWheelLabels();
		/* Put in the first two wheel panels */
		addWheel(true,"100","1000","0");
		addWheel(true,"20","100","0");
		addWheel(false,"10","25","10");
		addWheel(false,"3","2","1");
	}

	private void addWheelLabels() {
		gb.addLabel("Wheel");
		gb.addLabel("Enable");
		gb.addLabel("Radius");
		gb.addLabel("Teeth");
		gb.addLabel("Offset");
	}

	private void addWheelFields(WheelControl wheelinfo,
			boolean enabled,
			String defaultRadius, String defaultTeeth,
			String defaultOffset) {
		gb.nextRow();
		gb.addLabel("" + (wheelinfo.num+1));
		wheelinfo.enableW = gb.addCheckbox("");
		wheelinfo.enableW.setState(enabled);
		wheelinfo.radiusW = gb.addTextField(defaultRadius,5);
		wheelinfo.teethW = gb.addTextField(defaultTeeth,5);
		wheelinfo.offsetW = gb.addTextField(defaultOffset,5);
	}

	private void addWheel(boolean enabled,
				String defaultRadius, String defaultTeeth,
				String defaultOffset) {
		if (numwheels>=MAXNUMWHEELS) {
//			controls.beep();
			return;	/* don't do anything */
		}
		wheels[numwheels] = new WheelControl(numwheels);
		addWheelFields(wheels[numwheels],enabled,
				defaultRadius,defaultTeeth,defaultOffset);
		numwheels++;
		layout();
/* TBD - we need to make the parent re-layout, but I can't figure out how
 * to do that.  So the user just has to resize his window to make it relayout
 */
//		canvas.draw();
	}

	protected void addWheel() {
		addWheel(false,"0","0","0");
	}

	protected void removeWheel() {
		if (numwheels<=1) {
//			controls.beep();
			return;	/* ignore it */
		}
		Component[] complist = getComponents();
		int n = complist.length;
		remove(complist[n-1]);	/* offset */
		remove(complist[n-2]);	/* teeth */
		remove(complist[n-3]);	/* radius */
		remove(complist[n-4]);	/* enable */
		remove(complist[n-5]);	/* wheel number */
		numwheels--;
		layout();
	}

	protected SpiroState getState() {
		int wheelsEnabled;
		for (wheelsEnabled=0;
				wheelsEnabled<numwheels; wheelsEnabled++) {
			if (!wheels[wheelsEnabled].enableW.getState())
				break;
		}
		SpiroState state = new SpiroState(wheelsEnabled);
		for (int i=0; i<wheelsEnabled; i++) {
			state.wheels[i] = wheels[i].getState();
		}
		return state;
	}

	protected void setState(SpiroState state) {
		if (state.numwheels>=MAXNUMWHEELS)
			state.numwheels = MAXNUMWHEELS-1;	//silent limit
		if (state.numwheels<1)
			state.numwheels = 1;
		while (numwheels<state.numwheels)
			addWheel();
		while (numwheels>state.numwheels)
			removeWheel();
		for (int i=0; i<state.numwheels; i++) {
			wheels[i].setState(state.wheels[i]);
		}
	}

	public boolean action(Event ev, Object arg) {
	    if (ev.target instanceof Checkbox) {
		Checkbox cb = (Checkbox)(ev.target);
		if (cb.getState()) {
		    //Just turned on, make sure all wheels before
		    //it are also turned on
		    boolean enabled = false;
		    for (int i=numwheels-1; i>=0; i--) {
		    	if (wheels[i].enableW.getState())
			    enabled = true;
			if (enabled && !wheels[i].enableW.getState())
			    wheels[i].enableW.setState(enabled);
		    }
		} else {
		    //Just turned off, make sure all wheels after
		    //it are also turned on
		    boolean enabled = true;
		    for (int i=0; i<numwheels; i++) {
		    	if (!wheels[i].enableW.getState())
			    enabled = false;
			if (!enabled && wheels[i].enableW.getState())
			    wheels[i].enableW.setState(enabled);
		    }
		}
		//Make sure the first wheel is always turned on
		if (!wheels[0].enableW.getState())
		    wheels[0].enableW.setState(true);
		//Update the image on the screen if in animate mode
		controls.toggleAnimateCallback();
		return true;
	    }
	    return false;
	}
}

class WheelControl {
	int num;
	Checkbox enableW;
	TextField radiusW;
	TextField teethW;
	TextField offsetW;

	public WheelControl(int n) {
		num = n;
	}

	public boolean isEnabled() {
		return enableW.getState();
	}

	protected WheelState getState() {
		WheelState state = new WheelState(num);
		state.teeth = Integer.parseInt(teethW.getText().trim());
		state.offset = Integer.parseInt(offsetW.getText().trim());
		String rstr = radiusW.getText();
		parseRadius(rstr,state);
		state.reset();
		return state;
	}

	protected void setState(WheelState state) {
		String s;
		if (state.radiusY!=state.radiusX)
			s = state.radiusX + "," + state.radiusY;
		else
			s = Integer.toString(state.radiusX);
		radiusW.setText(s);
		teethW.setText(Integer.toString(state.teeth));
		offsetW.setText(Integer.toString(state.offset));
	}

	static public void parseRadius(String rstr, WheelState state) {
		int cpos = rstr.indexOf(",");
		if (cpos>0) {
			String rstr0 = rstr.substring(0,cpos).trim();
			String rstr1 = rstr.substring(cpos+1).trim();
			state.radiusX = Integer.parseInt(rstr0);
			state.radiusY = Integer.parseInt(rstr1);
		} else {
			state.radiusX = Integer.parseInt(rstr);
			state.radiusY = state.radiusX;
			if (state.radiusX==0)
				state.teeth = 0;	//disabled
		}
	}
}

/* end */
