	load	'macros.dump'
	include	'driver.equ'
	include	'scrap.equ'
	include	'sssc.equ'

	IMPORT	D_CopyStrings
	IMPORT	X_FormatMask

	ENTRY	X_MoveSaneX
	ENTRY	X_CircStr
	ENTRY	X_ErrorStr
	ENTRY	X_NAStr
	ENTRY	X_DecForm
	ENTRY	X_drec
	ENTRY	X_nstring
	ENTRY	X_PoundStr

;----------------------------------------------------------------------------
;  X_FormatValue(Width:w,Format:l,FloatPtr:l),out=(stringwidth:w,
;	  StringPtr:l,color:w)
;

X_FormatValue	PROC	EXPORT

	input	Width:w,Format:l,FloatPtr:l
	output	StringWidth:w,StringPtr:l,Color:w
	local	OldFace:w,FormatWidth:w,DecPlaces:l
	local	InsertChar:w,InsertPos:w,MinusFlag:w,FormatType:w
	local	ParenFlag:w,RedFlag:w,CommaFlag:w,FirstNumber:w
	local	Scratch:w,Gen2Fixed:w,DotPos:w,EPos:w,EndPos:w,ShiftPos:w
	error	theErr
	BEGIN	+b

;  Initialize locals

	stz	theErr
	stz	Color
	stz	FormatWidth

	MoveLong	#X_NString,StringPtr
	stz	MinusFlag
	stz	FormatType
	stzl	DecPlaces
	stz	Gen2Fixed

;  This code will not handle negative width's gracefully

	lda	Width
	and	#$7FFF
	sta	Width

;  To allow scrap conversion to call this routine and suppress parens on negative
;  numbers, the following mask is performed. (The default is to mask out nothing.)

	lda	Format
	and	X_FormatMask
	sta	Format

	lda	Format+2
	and	X_FormatMask+2
	sta	Format+2

;  Fix up the font tables

	Tool	_GetTextFace,out=(OldFace:w)

	lda	Format
	bit	#X_SSCellBold
	beq	getFontID

	lda	OldFace
	ora	#$01
	tool	_SetTextFace,in=(a:w)

getFontID
	Tool	_GetFontID,out=(xy:l)

	tya
	and	#$FF00
	ora	OldFace

	cpx	X_CurFontID
	bne	setupTable

	cmp	X_CurFontID+2
	beq	checkSpecial

setupTable
	jsr	FillTable

;  First check for special strings, like Circ., Error, and N/A.

checkSpecial
	ldy	Format
	ldx	Format+2

	txa			; x = Format+2
	and	#X_SSCellCircular
	bne	doCirc

	tya			; y = Format
	and	#X_SSCellInvalid
	beq	doNormal

	tya			; y = Format
	and	#X_SSCellError
	bne	doError

	MoveLong	#X_NAStr,StringPtr
	bra	doSpecial

doError
	MoveLong	#X_ErrorStr,StringPtr
	bra	redSpecial

doCirc
	MoveLong	#X_CircStr,StringPtr

redSpecial
	MoveWord	#$4444,Color

doSpecial
	brl	DoWidth

;  We can assume that there really is a number to be formatted.  Let's look at the
;  various formatting options.

doNormal
	txa			; x = Format+2
	and	#X_SSCellDecPlace
	sta	DecPlaces

	tya			; y = Format
	and	#X_SSCellComma
	sta	CommaFlag

	tya			; y = Format
	and	#X_SSCellRedNeg
	sta	RedFlag

	tya			; y = Format
	and	#X_SSCellParenNeg
	sta	ParenFlag
	beq	doGenFormats

	MoveWord	X_CParenWidth,FormatWidth

;  We've checked the general formats, now check the exclusive formats.

doGenFormats
	tya			; y = Format
	bit	#X_SSCellFixed
	beq	chkDollar

	MoveWord	#2,FormatType
	bra	doPreFormat

chkDollar
	bit	#X_SSCellDollar
	beq	chkPercent

	MoveWord	#4,FormatType
	bra	doPreFormat

chkPercent
	bit	#X_SSCellPercent
	beq	chkSciNot

	MoveWord	#6,FormatType
	bra	doPreFormat

chkSciNot
	bit	#X_SSCellSciNote
	beq	doPreFormat

	MoveWord	#8,FormatType

;  We have parsed all of the input, and now we want to figure out what we want
;  to ask from SANE.  We will massage the output later.

doPreFormat
	Call	X_MoveSANEX,in=(FloatPtr:l,#X_Float:l)

	ldx	FormatType
	jsr	(PreSaneTable,x)

	tool	FX2DEC,in=(#X_DecForm:l,#X_Float:l,#X_DRec:l)
	tool	FDEC2STR,in=(#X_DecForm:l,#X_DRec:l,#X_NString:l)

;  Check for various conversion errors.

	cmpw	[StringPtr],#$3F01	; '?'
	beq	goTooBig

	and	#$FF
	bne	stringOK		; zero string? -- actually too small

goTooBig
	brl	TooBig

;  Terminate the string with a '.'

stringOK
	inc	a
	tay
	lda	#'.'		; put '.' at end for search
	sta	[StringPtr],y

;  Is the value negative

	ldy	#1
	lda	[StringPtr],y
	and	#$00FF
	cmp	#'-'
	bne	massageString

	inc	MinusFlag
	iny

;  Massage the string according to the formatting desired.

massageString
	sty	FirstNumber		; first digit in string

	ldx	FormatType
	jsr	(PostSaneTable,x)

;  Insert commas.  First find out how may digits are to the left of the
;  decimal pt, then count back in three's and insert commas.

insertCommas
	lda	CommaFlag
	beq	chkNegOptions

	MoveWord	#',',InsertChar	; character to insert
	ldy	FirstNumber
	bra	doDotChk

countLoop				; Find the '.'  That's why we
	iny			; terminated the string with '.'

doDotChk	lda	[StringPtr],y
	and	#$00FF
	cmp	#'.'
	beq	doneCount
	cmp	#'e'
	bne	countLoop

doneCount
	dey

insertCommaLoop			; Backup in three's
	dey
	dey
	dey

	bmi	chkNegOptions

	cpy	FirstNumber
	blt	chkNegOptions

	iny
	sty	InsertPos

	jsr	InsertCharacter

	ldy	InsertPos
	dey
	bra	insertCommaLoop

;  Format the negative options.

chkNegOptions
	lda	MinusFlag
	beq	doWidth

	lda	RedFlag
	beq	chkParens

	MoveWord	#$4444,Color

chkParens
	lda	ParenFlag
	beq	doWidth

	stz	FormatWidth

	ldy	#1
	lda	[StringPtr],y	; put '(' over the existing '-'
	and	#$FF00
	ora	#'('
	sta	[StringPtr],y

	lda	[StringPtr]		; insert ')' at the end.
	and	#$00FF
	inc	a
	sta	InsertPos
	MoveWord	#')',InsertChar

	jsr	InsertCharacter

;  We now have the final string, let's make one last check that it fits.  If it
;  doesn't, return ## as the string.

doWidth
	Tool	_StringWidth,in=(StringPtr:l),out=(StringWidth:w)

	AddWord	a,FormatWidth,StringWidth
	cmp	Width
	blt	Exit
	beq	Exit

;  The string is too big for the given width.  Replace the number with ##.

TooBig
	inc	theErr

	MoveLong	#X_PoundStr,StringPtr
	Tool	_StringWidth,in=(StringPtr:l),out=(StringWidth:w)

Exit
	Tool	_SetTextFace,in=(OldFace:w)

	RETURN

;--------------------------------------------------------------
;  This mini-routine inserts the character in InsertChar at InsertPos,
;  by start at the end, working backwards moving the characters out
;  of the way one position, inserting the character, and inc-ing the len.

InsertCharacter
	lda	[StringPtr]
	and	#$00FF
	tay
	iny

insertCharLoop
	lda	[StringPtr],y
	xba
	sta	[StringPtr],y
	dey
	cpy	InsertPos
	bge	insertCharLoop

	iny
	and	#$FF00
	ora	InsertChar
	sta	[StringPtr],y

	lda	[StringPtr]
	inc	a
	sta	[StringPtr]
	rts

;------------------------------------------
;------------------------------------------
;  This table is used to determine what pre-parsing
;  should be done.

PreSaneTable
	DC.W	doPreGeneral
	DC.W	doPreFixed
	DC.W	doPreDollar
	DC.W	doPrePercent
	DC.W	doPreSciNot


doPrePercent	; Percent is the same as fixed except that
		; the input is multiplied by 100 first.

	Tool	FMULX,in=(#X_Sane100:l,#X_Float:l)

doPreDollar		; Dollar is the same as fixed

doPreFixed
	MoveWord	#1,X_DecForm	; 1 = Fix Decimal style
	MoveWord	DecPlaces,X_DecForm+2	; # of digits right of dec. pt.
	rts

;------------------------------------------
;  In General format, we will make a conservative estimate
;  of how many digits can fit in the given width.  If
;  we can fit a representation of the number in that many
;  digits in a fixed format, do so.  Else do Sci. Not.

doPreGeneral
	stz	DecPlaces		; we use this var. for # of digits

;  First find out if the number is negative, and subtract the appropriate
;  width for the '-' or '()'.  If the number is positive, subtract ')' if necessary.
;  I have special cased '0' for speed and accuracy.  The rest of the opproximating
;  is easier if I use the absolute value of the number.

	Tool	FCPXX,in=(#X_Float:l,#X_Sane0:l)
	FBEQ	doZero
	FBGE	posNumber

	Tool	FABSX,in=(#X_Float:l)

	lda	ParenFlag
	bne	addParens

	SubWord	Width,X_MinusWidth,a
	bra	handledMinus

addParens
	SubWord	Width,X_OParenWidth,a	; X_OParenWidth = '(' + ')'
	bra	handledMinus

;  Zero special case.  Get the number in Fixed Mode and set flag for displaying
;  fixed mode.

doZero
	inc	Gen2Fixed
	MoveWord	#1,X_DecForm	; 1 = Fix Decimal style
	stz	X_DecForm+2		; 0 digits right of dec. pt.
	stz	X_DRec+2		; I need this to be 0.
	rts

;  The number is positive, subtract width of ')' if necessary.

posNumber
	lda	Width

	ldx	ParenFlag
	beq	handledMinus

	SubWord	a,X_CParenWidth,a

;  We have handled any negative formats, now subtract a Decimal Pt, and start
;  with the first digit.  (You can't display a number with no digits.)

handledMinus
	SubWord	a,X_DotWidth,a
	SubWord	a,X_MaxDigitWidth,a
	bmi	gotDigits

	inc	DecPlaces

;  Now loop, subtracting one digit at a time, and seeing if we have exceeded
;  the width.  If comma's, then subtract the comma width every third digit.
;  We will never display more then 17 digits.

digitLoop
	SubWord	a,X_MaxDigitWidth,a
	bmi	gotDigits

	inc	DecPlaces
	ldy	DecPlaces		; only check here, since
	cpy	#17		; this is the inc that would
	beq	gotDigits		; do it.

	SubWord	a,X_MaxDigitWidth,a
	bmi	gotDigits

	inc	DecPlaces

	SubWord	a,X_MaxDigitWidth,a
	bmi	gotDigits

	ldx	CommaFlag
	beq	addPlace

	SubWord	a,X_CommaWidth,a
	bmi	gotDigits

addPlace
	inc	DecPlaces
	bra	digitLoop

;  DecPlaces now holds the max number of digits that we want to display.
;  Can we represent it in a fixed format?

;  First, we want to compare the rounded value with the maximum and minumum
;  display-able number in the given number of digits.

gotDigits
	Call	X_MoveSANEX,in=(#X_Float:l,#X_Float3:l)
	Tool	FRINTX,in=(#X_Float3:l)

;  Raise 10 to the power of DecPlaces, this is the Max number displayable.
;  If our value is more, display in Floating Format.

	Call	X_MoveSANEX,in=(#X_Sane10:l,#X_Float2:l)

	lda	DecPlaces
	Tool	FXPWRI,in=(a:w,#X_Float2:l)

	Tool	FCPXX,in=(#X_Float3:l,#X_Float2:l)
	FBGE	doFloat

;  Raise 1/10 to the power of DecPlaces-1, this is the Min number displayable.
;  If our value is less, display in Floating Format.  (This is why we special
;  cased '0')

	Call	X_MoveSANEX,in=(#X_Sane10th:l,#X_Float2:l)

	lda	DecPlaces
	dec	a
	Tool	FXPWRI,in=(a:w,#X_Float2:l)

	Tool	FCPXX,in=(#X_Float:l,#X_Float2:l)
	FBGE	displayFixed
	brl	doFloat2

;  We now know that we want to display the number in a fixed format.  However, if
;  we want to display a number larger than 1, it is easier to ask for the number in
;  Float format, because we can ask for significant digits rather then number of
;  digits to the right of the decimal.

displayFixed
	inc	Gen2Fixed

	Tool	FCPXX,in=(#X_Float:l,#X_Sane1:l)
	FBLT	setFixedRecord
	brl	setFloat

setFixedRecord
	MoveWord	#1,X_DecForm	; 1 = Fix Decimal style
	dec	DecPlaces
	MoveWord	DecPlaces,X_DecForm+2	; # of digits right of dec. pt.
	brl	donePostGen

;  The number is too big to display as a fixed number.  Let's find out how many
;  digits are needed for the exponent of the Sci. Not. number and subtract that
;  from the number of significant digits requested from SANE.

doFloat
	Tool	FCPXX,in=(#X_Float:l,#X_SaneINF:l)	; if INF, don't loop
	FBNE	findMag
	brl	setFloat

findMag
	Call	X_MoveSANEX,in=(#X_Sane10:l,#X_Float2:l)

magnitudeLoop
	dec	DecPlaces
	Tool	FXPWRI,in=(#10:w,#X_Float2:l)
	Tool	FCPXX,in=(#X_Float:l,#X_Float2:l)
	FBGE	magnitudeLoop
	bra	subTwoMore

;  The number is too small to display as a fixed number.  Let's find out how many
;  digits are needed for the exponent of the Sci. Not. number and subtract that
;  from the number of significant digits requested from SANE.

doFloat2
	Call	X_MoveSANEX,in=(#X_Sane10th:l,#X_Float2:l)

magnitudeLoop2
	dec	DecPlaces
	Tool	FXPWRI,in=(#10:w,#X_Float2:l)
	Tool	FCPXX,in=(#X_Float:l,#X_Float2:l)
	FBLT	magnitudeLoop2

;  We subtract 2 more for the 'e+' or 'e-' in the number.

subTwoMore
	SubWord	DecPlaces,#2,DecPlaces

setFloat
	stz	X_DecForm		; 0 = Floating Pt. style
	MoveWord	DecPlaces,X_DecForm+2	; digits = the number of sig. digits

donePostGen
	Call	X_MoveSANEX,in=(FloatPtr:l,#X_Float:l)	; to original #
	rts

;------------------------------------------

doPreSciNot
	stz	X_DecForm		; 0 = Floating Pt. style
	lda	DecPlaces		; In floating point style,
	inc	a		; digits = the number of 
	sta	X_DecForm+2		; significant digits, so add 1
	rts

;------------------------------------------
;------------------------------------------
;  This table is used to determine what post-SANE
;  massaging should be done.


PostSaneTable
	DC.W	doPostGeneral
	DC.W	doPostFixed
	DC.W	doPostDollar
	DC.W	doPostPercent
	DC.W	doPostSciNot

;------------------------------------------
;  First thing, if the number should be in a fixed
;  format, change the string in the X_DNString.

doPostGeneral
	lda	Gen2Fixed
	beq	stripZeros

;  To create the Fixed format value, we are going to construct the number
;  from the Dec. Rec.  First clear the length byte and set the minus character

	ldx	#1
	MoveWord	#$2D00,X_NString	; hex for '-' in high byte

;  Only count the '-' if the number is negative.

	lda	X_DRec
	beq	minusHandled

	inx

;  Find the length of the String in the Dec Rec.

minusHandled
	lda	X_DRec+4
	and	#$00FF
	inc	a
	sta	Scratch		; pos. of first non-used char.

;  Calculate the position of the '.'

	AddWord	X_DRec+2,Scratch,DotPos	; pos. where dot should go
	bmi	padZeros

	cmp	#2
	blt	padZeros

;  There is at least one digit before the '.'

	ldy	#1
intLoop
	lda	X_DRec+4,y
	sta	X_NString,x
	inx
	iny
	cpy	DotPos
	blt	intLoop

;  If there are no digits after the '.', skip ahead, else set the '.'

	cpy	Scratch
	beq	setLength
	
	MoveWord	#$002E,X_NString:x	; hex for '.'
	inx
	bra	fracLoop

;  There were no digits before the '.', put a '0.' and pad with zeros.

padZeros
	MoveWord	#$2E30,X_NString:x	; hex for '0.'
	inx
	inx

	ldy	DotPos
	dey
	beq	putDigits

	lda	#'0'
padLoop
	sta	X_NString,x
	inx
	iny
	bne	padLoop

putDigits
	ldy	#1

;  Fill in the rest of the digits here

fracLoop
	lda	X_DRec+4,y
	sta	X_NString,x
	inx
	iny
	cpy	Scratch
	blt	fracLoop

;  Set the length byte, and terminate the string with a '.'

setLength
	MoveWord	#$002E,X_NString:x	; hex for '.'
	txa
	dec	a
	ora	X_NString
	sta	X_NString

;  If the string came from SANE and is in Sci. Not. it is possible that
;  there is a space in front of the number.  This is all that the postSciNot
;  code does, so call it. 

stripZeros
	jsr	doPostSciNot

;  If the number has trailing 0's after the decimal pt, strip them.

	lda	[StringPtr]
	and	#$00FF
	sta	EndPos

	lda	X_DecForm+2
	cmp	#1
	beq	doneShift

;  Find the decimal.

	ldy	FirstNumber
genLoop1
	iny
	lda	[StringPtr],y
	and	#$00FF
	cmp	#'.'
	bne	genLoop1

	sty	DotPos

	cpy	EndPos		; is first dot the terminator?
	bge	doneShift

;  Find the end of fraction, terminated by 'e' or '.'

genLoop2
	iny
	lda	[StringPtr],y
	and	#$00FF
	cmp	#'e'
	beq	doneGenLoop2

	cmp	#'.'
	bne	genLoop2

doneGenLoop2
	sty	EPos

;  Go backwards as long as there are 0's

genLoop3
	dey
	lda	[StringPtr],y
	and	#$00FF
	cmp	#'0'
	beq	genLoop3

	cpy	DotPos
	beq	doShift

	iny

;  Remove'em, if any, and remove the decimal if no digits are still to right.

doShift
	sty	ShiftPos

	SubWord	EPos,ShiftPos,Scratch
	beq	doneShift

	lda	X_NString
	tax
	and	#$FF00
	sta	X_NString
	txa
	and	#$00FF
	SubWord	a,Scratch,EndPos
	ora	X_NString
	sta	X_NString

	ldx	EPos
genLoop4
	lda	X_NString,x
	sta	X_NString,y
	cpy	EndPos
	bge	doneShift

	iny
	iny
	inx
	inx
	bra	genLoop4

doneShift
doPostFixed
	rts
;------------------------------------------
;  In Float format strings coming from SANE, there
;  is sometimes a space at the beginning of the  string.
;  Remove it, if it is there.

doPostSciNot
	lda	X_NString
	tax
	and	#$FF00
	cmp	#$2000		; Space in high byte
	bne	doneExpLoop

	txa
	and	#$00FF
	sta	EndPos

	ldy	#0

expLoop
	iny
	lda	X_NString,y
	xba
	sta	X_NString,y
	cpy	EndPos
	blt	expLoop

	dec	X_NString

doneExpLoop
	rts

;------------------------------------------

doPostPercent	; Attach '%' to the end

	lda	[StringPtr]
	and	#$00FF
	inc	a
	sta	InsertPos
	MoveWord	#'%',InsertChar

	jsr	InsertCharacter
	rts

;------------------------------------------

doPostDollar	; Prefix '$' to the beginning

	MoveWord	FirstNumber,InsertPos
	MoveWord	#'$',InsertChar
	jsr	InsertCharacter

	inc	FirstNumber
	rts

;------------------------------------------
;------------------------------------------
;  This mini-routine is used to fill the font
;  width table, used by this routine.  The hope
;  is that X_FormatValue will get called with
;  the same font many times in a row.

FillTable
	stx	X_CurFontID
	sta	X_CurFontID+2

;  Remember the widths of '.', ',', ')', and '(' + ')'.

	Tool	_CharWidth,in=(#$2E:w),out=(X_DotWidth:w)	; '.'
	Tool	_CharWidth,in=(#',':w),out=(X_CommaWidth:w)
	Tool	_CharWidth,in=(#')':w),out=(X_CParenWidth:w)
	Tool	_CharWidth,in=(#'(':w),out=(a:w)
	AddWord	a,X_CParenWidth,X_OParenWidth

;  Use the max of '-' and '+' for the MinusWidth

	Tool	_CharWidth,in=(#'-':w),out=(X_MinusWidth:w)

	Tool	_CharWidth,in=(#'+':w),out=(a:w)
	cmp	X_MinusWidth
	blt	findMaxDigitWidth

	sta	X_MinusWidth

;  Find the widest of all the digits.  'e' counts as a digit for Sci. Not. purposes.

findMaxDigitWidth
	stz	X_MaxDigitWidth
	MoveWord	#'9',Scratch

sizeLoop
	Tool	_CharWidth,in=(a:w),out=(a:w)
	cmp	X_MaxDigitWidth
	blt	chkEnd

	sta	X_MaxDigitWidth

chkEnd
	dec	Scratch
	CmpWord	Scratch,#'0'
	bge	sizeLoop

	Tool	_CharWidth,in=(#'e':w),out=(a:w)
	cmp	X_MaxDigitWidth
	blt	endFill

	sta	X_MaxDigitWidth

endFill
	rts

X_CurFontID		DC.L	-1	; hopefully no FontID of -1 exists!
X_DotWidth		DS.W	1
X_CommaWidth	DS.W	1
X_MinusWidth	DS.W	1
X_CParenWidth	DS.W	1
X_OParenWidth	DS.W	1	; width of both parens; not use w/o ')'
X_MaxDigitWidth	DS.W	1
	
X_Sane0		DC.W	0,0,0,0,0			;0
X_Sane10th		DC.B	$CD,$CC,$CC,$CC,$CC,$CC,$CC,$CC,$FB,$3F  ; 0.1
X_Sane1		DC.B	0,0,0,0,0,0,0,$80,$FF,$3F	;1
X_Sane10		DC.B	0,0,0,0,0,0,0,$A0,$02,$40	;10
X_Sane100		DC.B	0,0,0,0,0,0,0,$C8,$05,$40	;100
X_SaneINF		DC.B	0,0,0,0,0,0,0,0,$FF,$7F		;infinity
X_Float		DS.X	1
X_Float2		DS.X	1
X_Float3		DS.X	1

	ENDP


;--------------------------------------------------------------
;  X_MoveSaneX
;

X_MoveSaneX	PROC

	input	FromPtr:l,ToPtr:l
	BEGIN

	MoveWord	[FromPtr],[ToPtr]
	ldy	#2
	MoveWord	[FromPtr]:y,[ToPtr]:y
	ldy	#4
	MoveWord	[FromPtr]:y,[ToPtr]:y
	ldy	#6
	MoveWord	[FromPtr]:y,[ToPtr]:y
	ldy	#8
	MoveWord	[FromPtr]:y,[ToPtr]:y

	RETURN
	ENDP

;--------------------------------------------------------------

X_SANEData	PROC	EXPORT
	EXPORT	X_decform
	EXPORT	X_nstring
	EXPORT	X_drec
	EXPORT	X_ErrorStr
	EXPORT	X_NAStr
	EXPORT	X_CircStr
	EXPORT	X_PoundStr

X_decform	DC.W	1,17	; sgn, exp
X_nstring	DS.B	256	 ; sig
X_drec	DS.B	260
X_ErrorStr	STR	'Error'
X_NAStr	STR	'N/A'
X_CircStr	STR 	'Circ.'
X_PoundStr	STR	'##'

	ENDP
	END

