##################################################################
# Modul für allgemeine Funktionen
#
# Letzte Änderung: 06.01.2012
##################################################################
use POSIX qw /floor ceil/;
use Math::Trig;
use strict;
use warnings;

##################################################################
### Funktionen
### Hilfsfunktionen
##################################################################
sub degtorad {
	my ($wert) = @_;
	return $wert * pi() / 180;
}

sub radtodeg {
	my ($wert) = @_;
	return $wert * 180 / pi();
}

sub abrunden {
	my ( $wert, $schritt ) = @_;
	return $schritt * floor( $wert / $schritt );
}

sub aufrunden {
	my ( $wert, $schritt ) = @_;
	return $schritt * ceil( $wert / $schritt );
}

sub nachkommastellen {
	my ( $wert, $nachkommastelle ) = @_;

	my $nachkommastellentext = "%0." . $nachkommastelle . "f";
	if ( $nachkommastelle >= 0 ) {
		$wert = sprintf( $nachkommastellentext, $wert );
	}
	else { $wert = $wert; }
	return $wert;
}

sub fact {
	my ($n) = shift;
	if ( ( $n == 1 ) || ( $n == 0 ) ) { return 1; }
	elsif ( $n < 0 ) { return "ERROR"; }
	else             { return $n * fact( $n - 1 ); }
}

sub logarithmus { return log( $_[0] ) / log( $_[1] ); }

sub round {
	my ($number) = shift;
	return int( $number + .5 );
}

##################################################################
########### Funktionen zum Berechnen von maximal und minimal
########### Werten im Werte- und Definitionsbereich.
##################################################################
sub calcfunk {
	my ( $funktionsausdruck, $variable, $wert ) = @_;

	my @ergebnis = regelwerk( tokenizer($funktionsausdruck) );

	return rechenbaum_ausrechnen(
		$ergebnis[0]{'Baum'},
		{
			$variable => $wert,
			'pi'      => pi(),
			'e'       => exp(1)
		}
	);

}

# Berechnet den ymin und ymax Wert fuer eine Funktion
sub calcyminmax {
	my ( $func, $xminwert, $xmaxwert, $minmax ) = @_;
	my ( $schritt, $xsuche, $ysuche, $yminwert, $ymaxwert );
	my $schrittanzahl = 500;

	# Suche von ymin, indem der ganze Definitionsbereich durchgegangen wird und
	# der zugehoerige Wertebereich ausgerechnet wird.
	$yminwert = calcfunk( $func, "x", $xminwert );
	$ymaxwert = calcfunk( $func, "x", $xminwert );
	for ( $schritt = 0 ; $schritt < $schrittanzahl ; $schritt++ ) {
		$xsuche = $xminwert +
		  ( $xmaxwert - $xminwert ) * ( $schritt / ( $schrittanzahl - 1 ) );
		$ysuche = calcfunk( $func, "x", $xsuche );
		$yminwert = $ysuche if ( $yminwert > $ysuche );
		$ymaxwert = $ysuche if ( $ymaxwert < $ysuche );
	}
	return $yminwert if ( $minmax eq "min" );
	return $ymaxwert if ( $minmax eq "max" );
	printf( "calcyminmax: Unbekannter Parameter minmax='%s'\n", $minmax );
}

sub calcymin {
	my ( $func, $xminwert, $xmaxwert ) = @_;
	return nachkommastellen( calcyminmax( $func, $xminwert, $xmaxwert, "min" ),
		4 );
}

sub calcymax {
	my ( $func, $xminwert, $xmaxwert ) = @_;
	return nachkommastellen( calcyminmax( $func, $xminwert, $xmaxwert, "max" ),
		4 );
}

# Berechnet den ymin und ymax Wert fuer zwei Funktion
sub calcfymin {
	my ( $func1, $func2, $xminwert, $xmaxwert ) = @_;
	my $ymin1 = calcymin( $func1, $xminwert, $xmaxwert );
	my $ymin2 = calcymin( $func2, $xminwert, $xmaxwert );
	if   ( $ymin1 < $ymin2 ) { return nachkommastellen( $ymin1, 4 ); }
	else                     { return nachkommastellen( $ymin2, 4 ); }
}

sub calcfymax {
	my ( $func1, $func2, $xminwert, $xmaxwert ) = @_;
	my $ymax1 = calcymax( $func1, $xminwert, $xmaxwert );
	my $ymax2 = calcymax( $func2, $xminwert, $xmaxwert );
	if   ( $ymax1 > $ymax2 ) { return nachkommastellen( $ymax1, 4 ); }
	else                     { return nachkommastellen( $ymax2, 4 ); }
}

# Verschiebt den xmin und xmax Wert so, dass Graph komplett in [ymin..ymax] liegt
sub calcxminmax {
	my ( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert, $minmax ) = @_;
	my ( $bereichmin, $bereichmax, $schritt, $xsuche, $ysuche, $schrittanzahl,
		$gueltig );

	$schrittanzahl = 1000;
	$gueltig       = 0;
	$bereichmin    = $xminwert;
	$bereichmax    = $xminwert;

	for ( $schritt = 0 ; $schritt < $schrittanzahl ; $schritt++ ) {
		$xsuche = $xminwert +
		  ( $xmaxwert - $xminwert ) * ( $schritt / ( $schrittanzahl - 1 ) );

		$ysuche = calcfunk( $func, "x", $xsuche );

		if ( ( $ysuche >= $yminwert ) && ( $ysuche <= $ymaxwert ) ) {

			$bereichmin = $xsuche if ( !$gueltig );
			$bereichmax = $xsuche;

			$gueltig = 1;
		}
		else { $gueltig = 0; }
	}

	return nachkommastellen( $bereichmin, 4 ) if ( $minmax eq "min" );
	return nachkommastellen( $bereichmax, 4 )
	  if ( $minmax eq "max" );
	printf( "calcxminmax: Unbekannter Parameter minmax='%s'\n", $minmax );
}

sub calcxmin {
	my ( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert ) = @_;
	return nachkommastellen(
		calcxminmax( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert, "min" ),
		4
	);
}

sub calcxmax {
	my ( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert ) = @_;
	return nachkommastellen(
		calcxminmax( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert, "max" ),
		4
	);
}

# Verschiebt den xmin und xmax Wert so, dass Graph komplett in [zmin..zmax] liegt
sub calcxminmax3d {
	my (
		$func,     $xminwert, $xmaxwert, $yminwert,
		$ymaxwert, $zminwert, $zmaxwert, $minmax
	) = @_;
	my (
		$bereichmin, $bereichmax,    $xsuche,  $ysuche,
		$zsuche,     $schrittanzahl, $gueltig, $dx,
		$dy,         $i,             $j
	);

	$schrittanzahl = 10;
	$gueltig       = 0;
	$bereichmin    = $yminwert;
	$bereichmax    = $yminwert;

	for ( $i = 0 ; $i < $schrittanzahl ; $i++ ) {

		$xsuche = $xminwert +
		  ( $xmaxwert - $xminwert ) * ( $i / ( $schrittanzahl - 1 ) );

		for ( $j = 0 ; $j < $schrittanzahl ; $j++ ) {
			$ysuche = $yminwert +
			  ( $ymaxwert - $yminwert ) * ( $j / ( $schrittanzahl - 1 ) );
			$zsuche = calcfunk3d( $func, "x", $xsuche, "y", $ysuche );
			if ( ( $zsuche >= $zminwert ) && ( $zsuche <= $zmaxwert ) ) {

				$bereichmin = $ysuche if ( !$gueltig );
				$bereichmax = $ysuche;

				$gueltig = 1;
			}
			else { $gueltig = 0; }
		}
	}

	return nachkommastellen( $bereichmin, 4 ) if ( $minmax eq "min" );
	return nachkommastellen( $bereichmax, 4 ) if ( $minmax eq "max" );
	printf( "calcxminmax: Unbekannter Parameter minmax='%s'\n", $minmax );
}

sub calcymin3d {
	my (
		$func,     $xminwert, $xmaxwert, $yminwert,
		$ymaxwert, $zminwert, $zmaxwert
	) = @_;
	return nachkommastellen(
		calcxminmax3d(
			$func,     $xminwert, $xmaxwert, $yminwert,
			$ymaxwert, $zminwert, $zmaxwert, "min"
		),
		4
	);
}

sub calcymax3d {
	my (
		$func,     $xminwert, $xmaxwert, $yminwert,
		$ymaxwert, $zminwert, $zmaxwert
	) = @_;
	return nachkommastellen(
		calcxminmax3d(
			$func,     $xminwert, $xmaxwert, $yminwert,
			$ymaxwert, $zminwert, $zmaxwert, "max"
		),
		4
	);
}

sub calcparaminmax {
	my ( $func1, $func2, $func3, $t1, $t2, $u1, $u2 ) = @_;

	my ( $i, $j );

	if ( $func3 eq "" ) {

		for ( $i = $t1 ; $i < $t2 ; $i = $i + 0.2 ) {
			minmax(
				calcfunk( $func1, "t", $i ),
				calcfunk( $func2, "t", $i ),
				calcfunk( $func1, "t", $i ),
				calcfunk( $func2, "t", $i )
			);

			setachsengraph(
				calcfunk( $func1, "t", $i ),
				calcfunk( $func2, "t", $i ),
				calcfunk( $func1, "t", $i ),
				calcfunk( $func2, "t", $i )
			);
		}
	}
	else {
		if ( $u1 == $u2 ) {
			for ( $i = $t1 ; $i < $t2 ; $i = $i + 0.1 ) {
				minmax3d(
					calcfunk3d( $func1, "t", $i, "u", 0 ),
					calcfunk3d( $func2, "t", $i, "u", 0 ),
					calcfunk3d( $func3, "t", $i, "u", 0 ),
					calcfunk3d( $func1, "t", $i, "u", 0 ),
					calcfunk3d( $func2, "t", $i, "u", 0 ),
					calcfunk3d( $func3, "t", $i, "u", 0 )
				);
				setachsengraph3d(
					calcfunk3d( $func1, "t", $i, "u", 0 ),
					calcfunk3d( $func2, "t", $i, "u", 0 ),
					calcfunk3d( $func3, "t", $i, "u", 0 ),
					calcfunk3d( $func1, "t", $i, "u", 0 ),
					calcfunk3d( $func2, "t", $i, "u", 0 ),
					calcfunk3d( $func3, "t", $i, "u", 0 )
				);

			}
		}
		else {
			for ( $j = $u1 ; $j < $u2 ; $j = $j + 0.1 ) {
				for ( $i = $t1 ; $i < $t2 ; $i = $i + 0.1 ) {

					minmax3d(
						nachkommastellen(
							calcfunk3d( $func1, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func2, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func3, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func1, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func2, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func3, "t", $i, "u", $j ), 4
						)
					);
					setachsengraph3d(
						nachkommastellen(
							calcfunk3d( $func1, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func2, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func3, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func1, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func2, "t", $i, "u", $j ), 4
						),
						nachkommastellen(
							calcfunk3d( $func3, "t", $i, "u", $j ), 4
						)
					);
				}
			}

		}

	}

}

my $n = 0;

# Die Änderungsrate zwischen zwei Punkten wird ermittelt

sub calcaenderungsrate {
	my ( $x1, $y1, $x2, $y2 ) = @_;
	$n++;
	if ( ( $x2 - $x1 ) == 0 ) { return "ERROR"; }

	else { return ( $y2 - $y1 ) / ( $x2 - $x1 ); }
}

sub calcdiff {
	my ( $wert1, $wert2, $abstand ) = @_;
	my $wert = 1;
	if   ( abs( $wert1 * $wert - $wert2 * $wert ) <= $abstand ) { return 1; }
	else                                                        { return 0; }
}
##Berechnungen fuer die Ableitungen
sub calcablxminmax {
	my ( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert, $minmax ) = @_;
	my ( $bereichmin, $bereichmax, $schritt, $xsuche, $ysuche );
	my $schrittanzahl = 1000;
	my $gueltig       = 0;
	$bereichmin = $bereichmax = $xminwert;
	for ( $schritt = 0 ; $schritt < $schrittanzahl ; $schritt++ ) {
		$xsuche = $xminwert +
		  ( $xmaxwert - $xminwert ) * ( $schritt / ( $schrittanzahl - 1 ) );
		$ysuche = calcableitung( $func, $xsuche );
		if ( ( $ysuche >= $yminwert ) && ( $ysuche <= $ymaxwert ) ) {
			$bereichmin = $xsuche if ( !$gueltig );
			$bereichmax = $xsuche;
			$gueltig    = 1;
		}
		else { $gueltig = 0; }
	}
	return nachkommastellen( $bereichmin, 4 )
	  if ( $minmax eq "min" );
	return nachkommastellen( $bereichmax, 4 )
	  if ( $minmax eq "max" );
	printf( "calcablxminmax: Unbekannter Parameter minmax='%s'\n", $minmax );
}

sub calcablxmin {
	my ( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert ) = @_;
	return calcablxminmax( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert,
		"min" );
}

sub calcablxmax {
	my ( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert ) = @_;
	return calcablxminmax( $func, $xminwert, $xmaxwert, $yminwert, $ymaxwert,
		"max" );
}

# Berechnung der Ableitung über den Differenzenquotienten
sub calcableitung {
	my ( $func, $xwert ) = @_;
	my $hwert = 0.00000001;
	my $ywert =
	  ( calcfunk( $func, "x", $xwert + $hwert ) -
		  calcfunk( $func, "x", $xwert ) ) / $hwert;
	return nachkommastellen( $ywert, 4 );
}
##################################################################
########### Berechnung des Funktionswertes t(x), wenn t die
########### Tangente an der Funktion func im Punkte festwert ist
##################################################################
sub calctangente {
	my ( $func, $xwert, $festwert ) = @_;
	return nachkommastellen(
		calcfunk( $func, "x", $festwert ) +
		  ( calcableitung( $func, $festwert ) * ( $xwert - $festwert ) ),
		4
	);
}
##################################################################
########### Berechnung der Schnittpunkte von zwei Funktionen
##################################################################
sub calcschnittpunkte {
	my ( $func1, $func2, $xminwert, $xmaxwert, $linksrechts ) = @_;
	my (
		$xsuche,         $links,         $rechts,  @xpunkte,
		@xselbst,        $schritt,       $ysuche1, $ysuche2,
		$welchegroesser, $schrittanzahl, $zaehler
	);
	$ysuche1 = calcfunk( $func1, "x", getminmaxachsen( "x", "min" ) );
	$ysuche2 = calcfunk( $func2, "x", getminmaxachsen( "x", "min" ) );
	$welchegroesser = ( $ysuche1 > $ysuche2 ) ? 1 : 2;
	$schrittanzahl  = 9000;
	$zaehler        = 0;

	for ( $schritt = 0 ; $schritt < $schrittanzahl ; $schritt++ ) {
		$xsuche = $xminwert +
		  ( $xmaxwert - $xminwert ) * ( $schritt / ( $schrittanzahl - 1 ) );
		$ysuche1 = calcfunk( $func1, "x", $xsuche );
		$ysuche2 = calcfunk( $func2, "x", $xsuche );
		if ( ( $ysuche1 > $ysuche2 ) xor( $welchegroesser == 1 ) ) {
			$welchegroesser = ( $ysuche1 > $ysuche2 ) ? 1 : 2;
			$zaehler++;
			if   ( $zaehler > 1 ) { $rechts = $xsuche; }
			else                  { $links  = $xsuche; $rechts = $xsuche; }
		}
	}
	if ( $zaehler == 0 ) { $links = $xminwert; $rechts = $xmaxwert; }
	return nachkommastellen( $links,  4 ) if ( $linksrechts eq "links" );
	return nachkommastellen( $rechts, 4 ) if ( $linksrechts eq "rechts" );
	printf( "calcschnittpunkte: Unbekannter Parameter linksrechts='%s'\n",
		$linksrechts );
}

sub calcschnittlinks {
	my ( $func1, $func2, $xminwert, $xmaxwert ) = @_;
	calcschnittpunkte( $func1, $func2, $xminwert, $xmaxwert, "links" );
}

sub calcschnittrechts {
	my ( $func1, $func2, $xminwert, $xmaxwert ) = @_;
	calcschnittpunkte( $func1, $func2, $xminwert, $xmaxwert, "rechts" );
}
##################################################################
########### Berechnungen für Vektoren
##################################################################
sub calcendpunktx {
	my ( $x, $laenge, $winkel ) = @_;
	return nachkommastellen( $x + cos( degtorad($winkel) ) * $laenge, 4 );
}

sub calcendpunkty {
	my ( $y, $laenge, $winkel ) = @_;
	return nachkommastellen( $y + sin( degtorad($winkel) ) * $laenge, 4 );
}

sub calcsumlaenge {
	my ( $x1, $y1, $l1, $w1, $l2, $w2 ) = @_;
	my $xsum = calcendpunktx( 0, $l1, $w1 ) + calcendpunktx( 0, $l2, $w2 );
	my $ysum = calcendpunkty( 0, $l1, $w1 ) + calcendpunkty( 0, $l2, $w2 );
	return nachkommastellen( sqrt( ( $xsum * $xsum ) + ( $ysum * $ysum ) ), 4 );
}

# Winkel wird in Grad ausgegeben
sub calcneuerwinkel {
	my ( $x1, $y1, $l1, $w1, $l2, $w2 ) = @_;
	my $x12 = calcendpunktx( $x1, $l1, $w1 );
	my $y12 = calcendpunkty( $y1, $l1, $w1 );
	my $x22 = calcendpunktx( $x12, $l2, $w2 );
	my $y22 = calcendpunkty( $y12, $l2, $w2 );
	my $dx  = $x22 - $x1;
	my $dy  = $y22 - $y1;
	return nachkommastellen( radtodeg( atan2( $dy, $dx ) ), 4 );
}
##################################################################
# Rechnen mit Vektoren
##################################################################
############$winkel2 = $winkel1 + $winkel;#####################################################
# vektordifferenz (A,B) liefert den Vektor von A nach B
sub vektordifferenz {
	my ( $xyoderz, $startx, $starty, $startz, $endex, $endey, $endez ) = @_;
	if    ( $xyoderz eq "x" ) { return $endex - $startx; }
	elsif ( $xyoderz eq "y" ) { return $endey - $starty; }
	elsif ( $xyoderz eq "z" ) { return $endez - $startz; }
	else                      { return 0; }
}

sub vektorprodukt {
	my ( $startx, $starty, $startz, $endex, $endey, $endez ) = @_;
	return $startx * $endex + $starty * $endey + $startz * $endez;
}

sub vektorbetrag {
	my ( $vektorx, $vektory, $vektorz ) = @_;
	return
	  sqrt( $vektorx * $vektorx + $vektory * $vektory + $vektorz * $vektorz );
}

sub vektorwinkel {
	my ( $vektor1x, $vektor1y, $vektor1z, $vektor2x, $vektor2y, $vektor2z ) =
	  @_;
	return nachkommastellen(
		radtodeg(
			acos(
				vektorprodukt(
					$vektor1x, $vektor1y, $vektor1z,
					$vektor2x, $vektor2y, $vektor2z
				  ) / (
					vektorbetrag( $vektor1x, $vektor1y, $vektor1z ) *
					  vektorbetrag( $vektor2x, $vektor2y, $vektor2z )
				  )
			)
		),
		4
	);
}

sub minimum {
	my ( $x, $y, $z ) = @_;
	my $hilf1;
	if   ( $x < $y ) { $hilf1 = $x; }
	else             { $hilf1 = $y; }
	if   ( $hilf1 < $z ) { return $hilf1; }
	else                 { return $z; }
}

sub istzahl {
	my ($zahl) = @_;
	if ( defined $zahl ) {
		if ( $zahl eq "" ) { return 0; }
		elsif ( $zahl =~ /([ a-zaeoeueA-Z!]+)/ ) { return 0; }
		else                                     { return 1; }
	}
	else { return 0; }
}

sub max {
	my $a = shift;
	my $b = shift;
	return $a > $b ? $a : $b;
}

sub min {
	my $a = shift;
	my $b = shift;
	return $a < $b ? $a : $b;
}

sub farbverlauf {
	my ( $farbe, $i ) = @_;
	$farbe =~ s/!//g;
	$farbe =~ s/1//g;
	$farbe =~ s/2//g;
	$farbe =~ s/3//g;
	$farbe =~ s/4//g;
	$farbe =~ s/5//g;
	$farbe =~ s/6//g;
	$farbe =~ s/7//g;
	$farbe =~ s/8//g;
	$farbe =~ s/9//g;
	$farbe =~ s/0//g;
	my $n = abrunden( $n + 100 / $i, 1 );

	return $farbe . "!" . $n;
}

1;
