#!/usr/bin/ksh93

#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#

#
# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

#
# mandelbrotset1 - a simple mandelbrot set generation and
# parallel execution demo
#

# Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant
export PATH=/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin

# Make sure all math stuff runs in the "C" locale to avoid problems
# with alternative # radix point representations (e.g. ',' instead of
# '.' in de_DE.*-locales). This needs to be set _before_ any
# floating-point constants are defined in this script).
if [[ "${LC_ALL}" != "" ]] ; then
    export \
        LC_MONETARY="${LC_ALL}" \
        LC_MESSAGES="${LC_ALL}" \
        LC_COLLATE="${LC_ALL}" \
        LC_CTYPE="${LC_ALL}"
        unset LC_ALL
fi
export LC_NUMERIC=C

function printmsg
{
	print -u2 "$*"
}

function fatal_error
{
	print -u2 "${progname}: $*"
	exit 1
}

# Get terminal size and put values into a compound variable with the integer
# members "columns" and "lines"
function get_term_size
{
	nameref rect=$1
    
	rect.columns=${ tput cols ; } || return 1
	rect.lines=${ tput lines ; }  || return 1
    
	return 0
}

function mandelbrot
{
	nameref result=$1
	float   x=$2
	float   y=$3
	float   xx
	float   yy
	float   x1=$4
	float   y1=$5
	integer iteration=$6
	integer max_iteration=$7
	float   mag

	for (( mag=0 ; mag < max_mag && iteration < max_iteration ; iteration++ )) ; do
		((
			xx=x*x ,
			yy=y*y ,
			mag=xx+yy ,
			y=x*y*2+y1 ,
			x=xx-yy+x1
		))
	done

	(( result=iteration ))

	return 0
}

# build mandelbrot image serially
function loop_serial
{
	integer value
	typeset line=""

	for (( y=y_min ; y < y_max ; y+=stepwidth )) ; do
		for (( x=x_min ; x < x_max ; x+=stepwidth )) ; do
			mandelbrot value ${x} ${y} ${x} ${y} 1 ${symbollistlen}
			line+="${symbollist:value:1}"
		done

		line+=$'\n'
	done
	
	print -r -- "${line}"
	
	return 0
}

# build mandelbrot image using parallel worker jobs
function loop_parallel
{
	integer numjobs=0
	# the following calculation suffers from rounding errors
	integer lines_per_job=$(( ((m_height+(numcpus-1)) / numcpus) ))
	typeset tmpjobdir

	printmsg $"# lines_per_job=${lines_per_job}"
	printmsg $"# numcpus=${numcpus}"

	# "renice" worker jobs
	set -o bgnice

	tmpjobdir="$(mktemp --default=/tmp --directory "mandelbrotset1${PPID}_$$_XXXXXX")" || fatal_error $"Could not create temporary directory."
	trap "rm -r ${tmpjobdir}" EXIT # cleanup

	# try to generate a job identifer prefix which is unique across multiple hosts
	jobident="job_host_$(uname -n)pid_$$_ppid${PPID}"

	printmsg $"## prepare..."
	for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do
		rm -f "${tmpjobdir}/${jobident}_child_$y.joboutput"

		(( numjobs++ ))
	done

	printmsg $"## running ${numjobs} children..."
	for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do
		(
			integer value
			typeset line=""
			# save file name since we're going to modify "y"
			typeset filename="${tmpjobdir}/${jobident}_child_$y.joboutput"

			for (( ; y < y_max && lines_per_job-- > 0 ; y+=stepwidth )) ; do
				for (( x=x_min ; x < x_max ; x+=stepwidth )) ; do
					mandelbrot value ${x} ${y} ${x} ${y} 1 ${symbollistlen}
					line+="${symbollist:value:1}"
				done

				line+=$'\n'
			done
			print -r -- "${line}" >"${filename}"
			
			exit 0
		) &
	done

	printmsg $"## waiting for ${numjobs} children..."
	wait

	printmsg $"## output:"
	for (( y=y_min ; y < y_max ; y+=(stepwidth*lines_per_job) )) ; do
		print -r -- "$( < "${tmpjobdir}/${jobident}_child_$y.joboutput")"
		# EXIT trap will cleanup temporary files
	done

	return 0
}

function usage
{
	OPTIND=0
	getopts -a "${progname}" "${mandelbrotset1_usage}" OPT '-?'
	exit 2
}

# main
builtin basename
builtin cat
builtin rm
builtin uname # loop_parallel needs the ksh93 builtin version to generate unique job file names
builtin mktemp

set -o noglob
set -o nounset

typeset progname="${ basename "${0}" ; }"

float x_max
float x_min
float y_max
float y_min
float m_width
float m_height
float max_mag
float stepwidth
integer numcpus

# terminal size rect
compound termsize=(
	integer columns=-1
	integer lines=-1
)

get_term_size termsize || fatal_error $"Could not get terminal size."

typeset symbollist='    .:0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%#'
typeset symbollistlen=$(( ${#symbollist} - 1))
typeset mode="parallel"

max_mag=400
stepwidth=0.1
numcpus=16

(( m_width=termsize.columns-1 , m_height=termsize.lines-2 ))

typeset -r mandelbrotset1_usage=$'+
[-?\n@(#)\$Id: mandelbrotset1 (Roland Mainz) 2009-06-14 \$\n]
[-author?Roland Mainz <roland.mainz@nrubsig.org>]
[+NAME?mandelbrotset1 - generate mandelbrot set fractals with ksh93]
[+DESCRIPTION?\bmandelbrotset1\b mandelbrot set fractal generator
	which runs either in serial or parallel mode (using multiple worker jobs).]
[w:width?Width of fractal.]:[width]
[h:height?Height of fractal.]:[height]
[s:symbols?Symbols to build the fractal from.]:[symbolstring]
[m:mag?Magnification level.]:[magnificationlevel]
[p:stepwidth?Width per step.]:[widthperstep]
[S:serial?Run in serial mode.]
[P:parallel?Run in parallel mode.]
[M:mode?Execution mode.]:[mode]
[C:numcpus?Number of processors used for parallel execution.]:[numcpus]
[+SEE ALSO?\bjuliaset1\b(1), \bksh93\b(1)]
'

while getopts -a "${progname}" "${mandelbrotset1_usage}" OPT ; do 
#	printmsg "## OPT=|${OPT}|, OPTARG=|${OPTARG}|"
	case ${OPT} in
		w)	m_width="${OPTARG}"	;;
		h)	m_height="${OPTARG}"	;;
		s)	symbollist="${OPTARG}"	;;
		m)	max_mag="${OPTARG}"	;;
		p)	stepwidth="${OPTARG}"	;;
		S)	mode="serial"		;;
		+S)	mode="parallel"		;;
		P)	mode="parallel"		;;
		+P)	mode="serial"		;;
		M)	mode="${OPTARG}"	;;
		C)	numcpus="${OPTARG}"	;;
		*)	usage			;;
	esac
done
shift $((OPTIND-1))

printmsg "# width=${m_width}"
printmsg "# height=${m_height}"
printmsg "# max_mag=${max_mag}"
printmsg "# stepwidth=${stepwidth}"
printmsg "# symbollist='${symbollist}'"
printmsg "# mode=${mode}"

(( symbollistlen=${#symbollist}-1 ))

((
	x_max=m_width*stepwidth/2. ,
	x_min=-x_max ,
	y_max=m_height*stepwidth/2. ,
	y_min=-y_max
))

case "${mode}" in
	parallel)	loop_parallel	; exit $? ;;
	serial)		loop_serial	; exit $? ;;
	*)		fatal_error $"Unknown mode \"${mode}\"." ;;
esac

fatal_error "not reached."
# EOF.