
package polarDust.calculator;

/*
 * Copyright (c) 1996 Sorin Lazareanu, All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies.
 *
 */

import java.awt.*;

/**
 * NumDisplay01 is a component capable of displaying a 12 digit
 * decimal number in a floating point calculator specific format.
 * If the number overflows the limit of 10e13 - 1, a flashing error message is 
 * displayed. On the left side it has letter M as a memory content flag.
 *
 * <br><br><a href=../source/polarDust/calculator/NumDisplay01.java>
 * The source code.</a>
 *
 * @version 0.9, 1996.06.04
 * @author Sorin Lazareanu
 */

public class NumDisplay01
	extends Canvas
	implements
		CalcDisplay,
		Runnable,
		CalcConstants {											   	
	static final long lMaxDigitsFactor = 100000000000L;  // 10e11d
	static final int iMaxDigits = 12;
	boolean bM = false;
	boolean bNewThread = true, bSuspended = true;
	boolean bTick = true;
	boolean bError = false;
	int iLastOp = 0;
	Thread thisThread = new Thread(this, "Blinking Display");
	String text= "            0.";
	Dimension bufferDimension = new Dimension(0, 0);
	Image bufferImage;
	Graphics bufferGraphics;
	PrinterPanel01 printer;

	/**
	* Constructs a new display.
	*/
	public NumDisplay01() {
		super();
	}

	/**
	* Constructs a new display, driver for a printer.
	* @param aPrinter the calculator printer attached.
	* @see PrinterPanel01
	*/
	public NumDisplay01(PrinterPanel01 aPrinter) {
		super();
		printer = aPrinter;
	}

	/**
	* @return the factor for a 12 digits display is 10e11
	*/
	public long maxDigitsFactor() {
		return lMaxDigitsFactor;
	}

	/**
	* Displays the parameter. No output to calculator printer.
	* @param dReg1 the number to be displayed.
	* @return the number as represented on the display component.
	*/
	public double show(double dReg1) {
		double dResult;
		dResult = showNumber(dReg1, -1);
		return dResult;
	}

	/**
	* Displays the parameter with output to calculator printer if exists.
	* @param dReg1 the number to be displayed.
	* @return the number as represented on the display component.
	*/
	public double assign(double dReg1) {
		double dResult;
		if (printer != null)
			printer.showResult(text);
		dResult = showNumber(dReg1, -1);
		return dResult;
	}

	/**
	* Oputputs the operator to the calculator printer.
	* @param oper the representation of the operator to be printed.
	*/
	public void showOp(String oper) {
		if (printer != null)
			printer.showOp(oper);
	}

	/**
	* Displays the parameter. No output to calculator printer.
	* @param dReg1 the number to be displayed.
	* @param iDecimals the number of digits after the decimal point.
	* @return the number as represented on the display component.
	*/
	public double showWithDecimals(double dReg1, int iDecimals) {
		double dResult;
		dResult = showNumber(dReg1, iDecimals);
		return dResult;
	}

	/**
	* Displays the parameter with output to calculator printer.
	* @param dReg1 the number to be displayed.
	* @param iDecimals the number of digits after the decimal point.
	* @return the number as represented on the display component.
	*/
	public double assignWithDecimals(double dReg1, int iDecimals) {
			double dResult;
		if (printer != null)
			printer.showResult(text);
		dResult = showNumber(dReg1, iDecimals);
		return dResult;
	}

	/**									   
	* Converts a double to a string format suitable for a NumDisplay
	* @param dReg1 the number to be displayed.
	* @param iDecimals the number of digits after the decimal point.
	* @return the representation.
	*/
	protected synchronized String toFloatString(double dReg1, int iDecimals){
		int i;
		char cDec[];
		double dAbs = Math.abs(dReg1);
		long lInt, lDec, lI = 1;
		StringBuffer num = new StringBuffer(""), dec;
		StringBuffer paddedNum = new StringBuffer("");

		iDecimals = Math.min(iMaxDigits, iDecimals);
		if (dAbs >= lMaxDigitsFactor * 10) {
			num.append("Error");
		}
		else {
			num.append(dReg1 >= 0?"":"-");
			lInt = (long)dAbs;
			num.append(lInt + ".");
			while (dAbs > lI) lI *= 10L;
				lDec = Math.round((dAbs - lInt + 1) * lMaxDigitsFactor * 100 / lI);
			// * 1000 - 10 from (... + 1)
			//        - 10 increase the internal precision by 1 digit ( not finished)
			lDec = lDec / 10 + ((lDec - lDec / 10 * 10 > 5)?1:0);
			dec = new StringBuffer(new Long(lDec).toString());
			while (
				dec.length() > 1 &&
				dec.charAt( dec.length() - 1 ) == '0' &&
				(dec.length() - 1) > iDecimals
				) {
				dec.setLength( Math.max(1, dec.length() - 1 ));
			}
			if (iDecimals != 0 && dec.length() > 1) {
				cDec = new char[ dec.length() - 1 ];
				dec.getChars(1, dec.length(), cDec, 0);
				num.append(cDec);
			}
			}
		i = iMaxDigits + 2 - num.length();
		while (i-- > 0) {
			paddedNum.append(' ');
		};
		paddedNum.append(num);
		return new String(paddedNum);
	}

	protected double showNumber(double dReg1, int iDecimals){
		String num  = toFloatString(dReg1, iDecimals);
		text = num;
		if (num.endsWith("Error")) {
			bError = true;
			if (printer != null) printer.showResult(text);
			if (bNewThread) {
				start();
				bNewThread = false;
			}
			resume();
		} else {
			bError = false;
			if (!bNewThread) suspend();
		}
		repaint();
		try {
			dReg1 = new Double(num).doubleValue();
		} catch (NumberFormatException e){ dReg1 = 0d; }
		return dReg1;
	}

	/**
	* @return the last operation generated an error (overflow).
	*/
	public boolean isError() {
	return bError;
	}

	/**
	* @param a number
	* @return the parameter has a zero value representation on display.
	*/
	public boolean isNotZero(double dReg) {;
		String num  = toFloatString(dReg, -1);
		try {
			dReg = new Double(num).doubleValue();
		} catch (NumberFormatException e) { dReg = 0d; };
		return dReg == 0d;
	}

	/**
	* Shows the letter M if the parameter is not represented as a zero value.
	*/
	public double showM(double dRegM) {
		String num  = toFloatString(dRegM, -1);
		try {
			dRegM = new Double(num).doubleValue();
		} catch (NumberFormatException e) {
			text = num;
			dRegM = 0d;
		}
		bM = dRegM != 0d;
		repaint();
		return dRegM;
	}

	/**
	* Sends a string to be displayed.
	*/
	public void setNumber( String aString ) {
		text = aString;
		repaint();
	}

	public Dimension preferredSize() {
		return new Dimension(155, 32);
	}

	public Dimension minimumSize() {
		return preferredSize();
	}

	/**
	* Start the thread for blinking.
	*/
	public synchronized void start() {
		if (!isAlive()) {
			thisThread.setDaemon(true);
			thisThread.start();
			bSuspended = false;
		}
	}

	/**
	* Stops the thread for blinking.
	*/
	public void stop() {
		if (isAlive()) {
			thisThread.stop();
			bNewThread = false;
		};
	}

	/**
	* Resume or suspends the thread for blinking.
	*/
	public void toggleRunnable() {
		if (isAlive()) {
			try {
				if (bSuspended) resume();
				else suspend();
			} catch (IllegalThreadStateException e) { }
		}
	}

	public void suspend() {
		if (isAlive()) {
			try { //catch does'n work - no exception thrown ver1.0
				thisThread.suspend();
				bSuspended = true;
				bTick = true;
				repaint();
			} catch (IllegalThreadStateException e) { }
		}
	}

	public boolean isAlive() {
		return thisThread.isAlive();
	}

	public void resume() {
		if (isAlive()) {
			try { //catch does'n work - no exception thrown ver1.0
				thisThread.resume();
				bSuspended = false;
			} catch (IllegalThreadStateException e) { }
	 	}
	}

	public void run() {
		long lTickDuration = 250L;
		long lLastmS = System.currentTimeMillis();

		while (thisThread != null) {
			try {
				if (bufferGraphics != null) {
					bTick = !bTick;
				}
				thisThread.sleep(
					lTickDuration -
					Math.max(
						0L,
						Math.min(
							lTickDuration,
							System.currentTimeMillis() - lLastmS
						)
					)
				);
				lLastmS = System.currentTimeMillis();
				// Should repaint() be called only when Screen Updater thread is running ?
				// No specs. on Screen Updater.
				// The thread must be stopped before System.exit(...).
				repaint();
			} catch (InterruptedException e) {
				break;
			}

		};
	}

	public void paint(Graphics aGraphics) {
		update(aGraphics);
	}

	public synchronized void update(Graphics aGraphics) {
		int iWidth = size().width;
		int iHeight = size().height;
		int i, iBeginX, iBeginY;
		int iStringWidth, iFontHeight;
		int iFontNewSize;
		int iMaxScreenWidth, iMaxScreenHeight;
		int iScreenWidth, iScreenHeight;
		StringBuffer aStringBuffer = new StringBuffer("");
		String aString;
		FontMetrics currentFontMetrics;
		
		aStringBuffer.append(bM?'M':' ');
		i = iMaxDigits + 2 - (bTick?text.length():0);
		while (i-- > 0)
			aStringBuffer.append(' ');
		aStringBuffer.append(bTick?text:"");
		aString = new String(aStringBuffer);
		iMaxScreenWidth = iWidth - 4;
		iMaxScreenHeight = iHeight - 4;
		if (
			bufferDimension.width != iWidth ||
			bufferDimension.height != iHeight
		) {
			bufferDimension = size();
			bufferImage = createImage(iWidth, iHeight);
			bufferGraphics = bufferImage.getGraphics();
			// min. font size = 15, min. string width = 136 (15chars)
			iStringWidth = bufferGraphics.getFontMetrics().stringWidth(aString);
			iFontHeight = bufferGraphics.getFontMetrics().getAscent();
			iFontNewSize = bufferGraphics.getFont().getSize() *
				Math.min(iMaxScreenWidth / iStringWidth, iMaxScreenHeight / iFontHeight) + 5;
			do { // we must loop here since the font size is not a precise defined measure.
				iFontNewSize--;
				bufferGraphics.setFont(new Font("Courier", Font.BOLD, iFontNewSize));
				currentFontMetrics = bufferGraphics.getFontMetrics();
				iStringWidth = currentFontMetrics.stringWidth(aString); //15chars
				iFontHeight = currentFontMetrics.getAscent();
			} while (iStringWidth > iMaxScreenWidth || iFontHeight > iMaxScreenHeight);
			setFont(new Font("Courier", Font.BOLD, iFontNewSize));
			aGraphics.setFont(new Font("Courier", Font.BOLD, iFontNewSize));
		}
		currentFontMetrics = bufferGraphics.getFontMetrics();
		bufferGraphics.setColor(getBackground());
		bufferGraphics.fillRect(0, 0, iWidth, iHeight);
		bufferGraphics.setColor(Color.gray);
		bufferGraphics.fillRoundRect(0, 0, iWidth - 1, iHeight - 1, 15, 15);
		bufferGraphics.setColor(Color.black);
		bufferGraphics.drawRoundRect(0, 0, iWidth - 1, iHeight - 1, 15, 15);
		bufferGraphics.drawRoundRect(1, 1, iWidth - 3, iHeight - 3, 14, 14);
		iScreenWidth = currentFontMetrics.stringWidth(aString) + 2;
		iScreenHeight = currentFontMetrics.getAscent()+ 2;
		iBeginX = (iWidth + 1 - iScreenWidth) / 2;  // + 1 for rounding
		iBeginY = (iHeight + 1 - iScreenHeight ) / 2; // + 1 for rounding
		bufferGraphics.setColor(Color.black);
		bufferGraphics.fillRoundRect(
			iBeginX,
			iBeginY,
			iScreenWidth,
			iScreenHeight,
			13,
			13
		);
		bufferGraphics.setColor(Color.green.brighter());
		bufferGraphics.drawString(
			aString,
			iBeginX + 3,
			(iHeight + getFontMetrics(getFont()).getAscent() + 1) / 2 - 2
		);
		aGraphics.drawImage(bufferImage, 0, 0, this);
	}
	
}


