#!/usr/bin/perl

use strict;

use constant {
    HEATFCAL    => 0,
    HEATFJUL    => 1,
    NUMATOMS    => 2,
    CARTESIAN   => 3,
    NUMFREQ     => 4,
    FREQUENCIES => 5,
    CALCPERIND  => 6,
    GRADIENTS   => 7,
    NUMCONF     => 8,
    DIPXYZ      => 9,
    EEL         => 10,
};

my($CALS_TO_JOULES)=4.1868;

my (%defaults) = (
    'COEFS_TEMPLATE' => "template.coefs",
    'MOPAC_TEMPLATE' => "template.mop",
    'MOPAC_MOP'      => "mopac_input.mop",
    'EXTERNAL_INPUT' => "mopac.input",
    'EXTERNAL_FIT'   => "mopac.fit",
    'EXTRACTED_DATA' => "extracted.data",
    'CONDITIONS_FIT' => "conditions.txt",
    'TOOLS_OUTPUT'   => "no",
);

my (
    $CoefsTemplate, $MopacTemplate, $MopacMop,
    $ExternalInput, $ExternalFit,   $ToolsOutput,
    $MopacOut,      $Extracted,     $ConditionsFit,
);

my (@mopErrors) = (
    "TOO MANY ITERATIONS IN LAMDA BISECT",
    "CALCULATION IS TERMINATED TO AVOID ZERO DIVIDE",
    "GRADIENT IS TOO LARGE TO ALLOW FORCE MATRIX TO BE CALCULATED",
    "THIS IS A FATAL ERROR, RUN STOPPED IN GMETRY",
    "TS FAILED TO LOCATE TRANSITION STATE",
    "A FAILURE HAS OCCURRED, TREAT RESULTS WITH CAUTION!!",
    "EXCESS NUMBER OF OPTIMIZATION CYCLES",
    "SHEPHERD NON RECOVERABLE ERROR"
);

my (@mopSTOPErrors) = ("EXTERNAL file: '.*' does not exist!");

#data needed
my (%DataNeeded) = (
    'heat' => 0,
    'coor' => 0,
    'grad' => 0,
    'freq' => 0,
    'eest' => 0,
);

my ($site) = " \\*\\* Site#:";

my ($ncalcindv);
my ($indv) = $ARGV[0];
my ( $i, $j );
my ($indcalc);
my ($eachone);
my (%toprocess);
my ($okeys) = 0;

#Interesting variables
my ($NumberOfAtoms)          = 0;
my ($NumberOfConfigurations) = 0;

#buffer filled with the input file
my ($intext);

#buffer filled with the output
my ($outtext) = "";
my (@name);
my ($FOUT);
my ($debug) = 0;

#z-matrix stuff
my (@Points);
my (%Zmat);

if ( $indv == 0 ) {
    @name = split( "./", $0 );
    print "\n";
    print " @name v0.2 (c)GAFit toolkit - 2017\n";
    print "    extracts data from mopac output\n";
    print "    usage: @name #vectors \n\n";
    exit;
}

if ( -e '__STOP__' ) {
    print "extractor: __STOP__ exists!\n";
    exit;
}

InitFileNames();
if ($debug) {
    print "Extractor: Coefs Template:  $CoefsTemplate,\n";
    print "           Mopac Template:  $MopacTemplate, \n";
    print "           Mopac input:     $MopacMop,\n";
    print "           Gafit E input:   $ExternalInput,\n";
    print "           Gafit E fit:     $ExternalFit\n";
    print "           Extracted data:  $Extracted\n";
    print "           Conditions file: $ConditionsFit\n";
}

WhichDataIsNeeded();

unlink($Extracted);

$intext = LoadText($MopacOut);

foreach $i (@mopSTOPErrors) {
    if ( $intext =~ m/($i)/ ) {
        open( $FOUT, ">", "__STOP__" ) or die $!;
        print $FOUT "test \"$i\" failed\n";
        close($FOUT);
    }
}

$ncalcindv = CountCalcs($intext);

print "Calcs per individual: $ncalcindv\n" if ($debug);

PrintOutput( 0, 0, CALCPERIND, $ncalcindv );

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

    $indcalc = OneCalc( $i + 1, $ncalcindv, $intext );
    $toprocess{$i}{'All'} = $indcalc;
    for ( $j = 0 ; $j < $ncalcindv ; $j++ ) {
        $eachone = OneCalc( $j + 1, 1, $indcalc );
        if ( HasError($eachone) ) {
            $toprocess{$i}{'Error'} = 1;
            last;
        }
        else {
            $toprocess{$i}{$j} = $eachone;
            $toprocess{$i}{'Error'} = 0;
        }
    }
}

for ( $i = 0 ; $i < $indv ; $i++ ) {
    if ( !$toprocess{$i}{'Error'} ) {
        $okeys++;
        for ( $j = 0 ; $j < $ncalcindv ; $j++ ) {

            GetHeatFormation( $i, $j, $toprocess{$i}{$j} )
              if ( $DataNeeded{'heat'} );

            if ( $DataNeeded{'coor'} || $DataNeeded{'grad'} ) {
                GetNumAtoms( $i, $j, $toprocess{$i}{$j} );

                if ( $DataNeeded{'coor'} ) {
                    GetCartesianCoordinates( $i, $j, $toprocess{$i}{$j} );
                    GetInternalCoordinates( $i, $j, $toprocess{$i}{$j} )
                      if (
                        !GetCartesianCoordinates( $i, $j, $toprocess{$i}{$j} )
                      );
                }
                GetGradients( $i, $j, $toprocess{$i}{$j} )
                  if ( $DataNeeded{'grad'} );
            }
            GetFrequencies( $i, $j, $toprocess{$i}{$j} )
              if ( $DataNeeded{'freq'} );

            if ( $DataNeeded{'eest'} ) {
                GetNumConfigurations( $i, $j, $toprocess{$i}{$j} );
                GetDipXYZeel( $i, $j, $toprocess{$i}{$j} );
            }
        }
    }
}

open( $FOUT, ">", $Extracted ) or die $!;
print $FOUT $outtext;
close($FOUT);

#if ( $okeys != $indv ) {
print "extractor correct/total:$okeys/$indv\n" if ( $ToolsOutput eq 'yes' );

#}

#====================================
#
sub WhichDataIsNeeded {
    my ($text) = LoadText($ConditionsFit);

    $DataNeeded{'heat'} = 1
      if ( $text =~ m/heat/i
        || $text =~ m/delt/i );

    $DataNeeded{'coor'} = 1
      if ( $text =~ m/dist/i
        || $text =~ m/angl/i
        || $text =~ m/dihe/i );

    $DataNeeded{'freq'} = 1
      if ( $text =~ m/freq/i );

    $DataNeeded{'grad'} = 1
      if ( $text =~ m/grad/i );

    $DataNeeded{'eest'} = 1
      if ( $text =~ m/dipx/i
        || $text =~ m/dipy/i
        || $text =~ m/dipz/i
        || $text =~ m/eel/i );
}

sub GetDipXYZeel {
    my ( $i, $j, $text ) = @_;
    my (@data);
    my ($F);
    my ($done)   = 0;
    my ($begin)  = 0;
    my ($number) = 0;

    my ($singlet) = 1;
    my ($doublet) = 1;
    my ($triplet) = 1;
    my ($type)    = 0;
    my ($order)   = 0;

    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        if ( !$begin ) {
            if ( $_ =~ m/EFFECT\s+OF\s+DIPOLE\s+OPERATOR\s+ON\s+STATES/ ) {
                $begin = 1;
                next;
            }
            else { next; }
        }
        if ( $_ =~ m/STATE\s+X\s+Y\s+Z/ ) {
            next;
        }
        if ( $_ =~ m/The\s+\"\+\"\s+symbol\s+indicates\s+the\s+root\s+used\./ )
        {
            last;
        }
        chomp;
        @data = split;
        if (@data) {
            if ( $number < $NumberOfConfigurations ) {
                $done = 1;

                PrintOutput( $i, $j, DIPXYZ, join( " ", @data ) );
                $number++;
                next;
            }
            if (   $number >= $NumberOfConfigurations
                && $number < $NumberOfConfigurations * 2 )
            {
                @data = @data[ 0 .. 6 ];
                $type = 0;

   #                $data[0] =~ s/\+//;
   #		print "$data[0] - $data[1] - $data[2] - $data[3] - $data[4] - $data[5]\n";
                if ( $data[4] =~ m/SINGLET/ ) {
                    $type  = 1;
                    $order = $singlet;
                    $singlet++;
                }
                if ( $data[4] =~ m/DOUBLET/ ) {
                    $type  = 2;
                    $order = $doublet;
                    $doublet++;
                }

                if ( $data[4] =~ m/TRIPLET/ ) {
                    $type  = 3;
                    $order = $triplet;
                    $triplet++;
                }
                PrintOutput( $i, $j, EEL, join( " ", $type, $order, $data[1] ) )
                  if ( $type != 0 );
                $number++;
                next;
            }
        }
    }
    close($F);
    return $done;
}

sub GetNumConfigurations {
    my ( $i, $j, $text ) = @_;
    my (@cols);
    my ($F);
    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        chomp;
        if ( $_ =~ m/NO\s+OF\s+CONFIGURATIONS\s+CONSIDERED/ ) {
            @cols = split( "=", $_ );
            @cols = split( " ", $cols[1] );
            PrintOutput( $i, $j, NUMCONF, $cols[0] );
            $NumberOfConfigurations = $cols[0];
            last;
        }
    }
    close($F);
}

sub GetFrequencies {
    my ( $i, $j, $text ) = @_;
    my ($freqnum) = 0;
    my (@cols);
    my ( $k, $freq );
    my ($F);
    $k = 0;
    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        $freqnum++ if ( $_ =~ m/FREQ\./ );
    }
    close($F);
    PrintOutput( $i, $j, NUMFREQ, $freqnum );
    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        chomp;
        if ( $_ =~ m/FREQ\./ ) {
            @cols = split;
            $k++;
            $freq = "$k $cols[1]";
            PrintOutput( $i, $j, FREQUENCIES, $freq );
        }
    }
    close($F);
}

sub GetGradients {
    my ( $i, $j, $text ) = @_;
    my ($getit) = 0;
    my (@data);
    my ($F);
    my ($done)   = 0;
    my ($begin)  = 0;
    my ($number) = 0;

    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        if ( !$begin ) {
            if ( $_ =~ m/COMPUTATION\s+TIME/ ) { $begin = 1; }
            else                               { next; }
        }
        chomp;
        if ( $_ =~ m/FINAL\s+POINT\s+AND\s+DERIVATIVES/ ) {
            $getit = 1;
        }
        $getit = 0 if ( $number >= $NumberOfAtoms * 3 );
        if ($getit) {
            @data = split;

            # a hack to separate negative gradient from value
            if ( $data[6] =~ m/KCAL\/ANGSTROM/ ) {
                $data[6] = -( split( "-", $data[5] ) )[-1];
            }
            if (@data) {
                $done = 1;
                if (  !( $data[0] eq "PARAMETER" or $data[0] eq "FINAL" )
                    && ( $data[3] eq "CARTESIAN" ) )
                {
                    PrintOutput( $i, $j, GRADIENTS,
                        join( " ", $data[0], $data[6] ) );
                    $number++;
                }
            }
        }
    }
    close($F);
    return $done;
}

sub GetCartesianCoordinates {
    my ( $i, $j, $text ) = @_;
    my ($first) = 1;
    my ($getit) = 0;
    my (@data);
    my ($F);
    my ($done)   = 0;
    my ($begin)  = 0;
    my ($number) = 0;

    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        if ( !$begin ) {
            if ( $_ =~ m/COMPUTATION\s+TIME/ ) { $begin = 1; }
            else                               { next; }
        }
        chomp;
        if ( $_ =~ m/CARTESIAN\s+COORDINATES/ ) {
            if   ($first) { $getit = 1; }
            else          { $first = 0; }
        }

        $getit = 0 if ( $number >= $NumberOfAtoms );
        if ($getit) {
            @data = split;
            if (@data) {
                $done = 1;
                if ( !( $data[0] eq "CARTESIAN" or $data[0] eq "NO." ) ) {
                    PrintOutput( $i, $j, CARTESIAN, join( " ", @data ) );
                    $number++;
                }
            }
        }
    }
    close($F);
    return $done;
}

sub GetInternalCoordinates {
    my ( $i, $j, $text ) = @_;
    my ($first) = 1;
    my ($getit) = 0;
    my (@data);
    my ($F);
    my ($done)  = 0;
    my ($begin) = 0;
    my ($ii);
    my ($natom) = 0;
    my ( $index, $sym, $len, $bangle, $twist, $na, $nb, $nc );

    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        if ( !$begin ) {
            if ( $_ =~ m/COMPUTATION\s+TIME/ ) { $begin = 1; }
            else                               { next; }
        }
        chomp;
        if ( $_ =~
            m/\s+ATOM\s+CHEMICAL\s+BOND\s+LENGTH\s+BOND\s+ANGLE\s+TWIST\s+ANGLE/
          )
        {
            if   ( !$first ) { $getit = 1; }
            else             { $first = 0; }
        }
        $getit = 0
          if ( $_ =~ m/Empirical\s+Formula:/ );
        if ($getit) {
            $_ =~ s/\*//g;
            @data = split;
            if (@data) {
                $done = 1;
                if (
                    !(
                           $data[0] eq "ATOM"
                        or $data[0] eq "NUMBER"
                        or $data[0] eq "(I)"
                    )
                  )
                {
                    print join( " ", @data ), "\n" if ($debug);
                    ( $index, $sym, $len, $bangle, $twist, $na, $nb, $nc ) =
                      @data;
                    addZ( $index, $sym, $len, $bangle, $twist, $na, $nb, $nc );
                    $natom++;
                }
            }
        }
    }
    close($F);
    for ( $ii = 0 ; $ii <= $natom ; $ii++ ) {
        computeXYZ($ii);
        if ($debug) {
            printZ($ii);
            PrintPoint($ii);
        }
    }
    for ( $ii = 1 ; $ii <= $natom ; $ii++ ) {
        PrintOutput(
            $i, $j,
            CARTESIAN,
            join( " ",
                $ii,               $Points[$ii]->[0], $Points[$ii]->[1],
                $Points[$ii]->[2], $Points[$ii]->[3] )
        );
    }
    return $done;
}

sub GetNumAtoms {
    my ( $i, $j, $text ) = @_;
    my (@cols);
    my ($F);
    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        chomp;
        if ( $_ =~ m/Empirical\s+Formula:/ ) {
            @cols = split( "=", $_ );
            @cols = split( " ", $cols[1] );
            PrintOutput( $i, $j, NUMATOMS, $cols[0] );
            $NumberOfAtoms = $cols[0];
            last;
        }
    }
    close($F);
}

sub GetHeatFormation {
    my ( $i, $j, $text ) = @_;
    my (@cols);
    my ($F);
    open( $F, "<", \$text ) or die $!;
    while (<$F>) {
        chomp;
        if ( $_ =~ m/FINAL\s+HEAT\s+OF\s+FORMATION\s+=/ ) {
            @cols = split( "=", $_ );
            @cols = split( " ", $cols[1] . " " . $cols[2] );
	    if( $cols[2] =~ m/\*\*\*/ )
	    	{
#	  due the fact tha some times the heat of formation
#	  in Joules overflow the field length, we calculate
#	  it from calories
		  $cols[2]=$cols[0]*$CALS_TO_JOULES;
		}
            PrintOutput( $i, $j, HEATFCAL, $cols[0] );
            PrintOutput( $i, $j, HEATFJUL, $cols[2] );
        }
    }
    close($F);
}

sub PrintOutput {
    my ( $individual, $calculation, $datatype, $data ) = @_;
    $outtext .= "$individual $calculation $datatype\n$data\n";
}

sub HasError {
    my ($text) = @_;
    my ($err);
    foreach $err (@mopErrors) {
        return 1 if ( $text =~ m/($err)/ );
    }
    return 0;
}

sub OneCalc {
    my ( $n, $groupping, $intext ) = @_;
    my ($counter) = 0;
    my ($text)    = "";
    my ($F);

    open( $F, "<", \$intext ) or die $!;
    while (<$F>) {
        use integer;
        $counter++ if ( $_ =~ m/($site)/ );
        $text .= $_ if ( ( ( $counter - 1 ) / $groupping + 1 ) == $n );
        last if ( ( ( $counter - 1 ) / $groupping + 1 ) > $n );
    }
    close($F);
    return $text;
}

sub CountCalcs {
    my ($text)    = @_;
    my ($counter) = 0;
    my ($F);
    open( $F, "<", \$text ) or die $!;
    while (<$F>) { $counter++ if ( $_ =~ m/($site)/ ) }
    close($F);
    return int( $counter / $indv );
}

sub LoadText {
    my ($file) = @_;
    my ($text);
    print "file:$file\n" if ($debug);
    open( F, "<", $file ) or die $!;
    while (<F>) {
        $text .= $_;
    }
    close(F);
    return $text;
}

sub InitFileNames {
    my (@tmp);

    $CoefsTemplate = SetEnv('COEFS_TEMPLATE');
    $MopacTemplate = SetEnv('MOPAC_TEMPLATE');
    $MopacMop      = SetEnv('MOPAC_MOP');
    $ExternalInput = SetEnv('EXTERNAL_INPUT');
    $ExternalFit   = SetEnv('EXTERNAL_FIT');
    $Extracted     = SetEnv('EXTRACTED_DATA');

    # there is no environment variable for this setting
    $ConditionsFit = SetEnv('CONDITIONS_FIT');
    $ToolsOutput   = lc SetEnv('TOOLS_OUTPUT');
    @tmp           = split( /\./, $MopacMop );
    pop(@tmp);
    push( @tmp, "out" );
    $MopacOut = join( ".", @tmp );
}

sub SetEnv {
    my ($par) = $_[0];

    if ( !$ENV{$par} ) {
        return $defaults{$par};
    }
    else {
        return $ENV{$par};
    }
}

#########################

sub addZ {
    my ( $index, $sym, $len, $angle, $twist, $na, $nb, $nc ) = @_;
    $Zmat{$index}{'Symbol'}    = $sym;
    $Zmat{$index}{'Len'}       = $len;
    $Zmat{$index}{'BondAngle'} = $angle;
    $Zmat{$index}{'Dihedral'}  = $twist;
    $Zmat{$index}{'Na'}        = $na;
    $Zmat{$index}{'Nb'}        = $nb;
    $Zmat{$index}{'Nc'}        = $nc;
}

sub printZ {
    my ($index) = @_;
    print $Zmat{$index}{'Symbol'}, " ",
      $Zmat{$index}{'Len'},       " ",
      $Zmat{$index}{'BondAngle'}, " ",
      $Zmat{$index}{'Dihedral'},  " ",
      $Zmat{$index}{'Na'},        " ",
      $Zmat{$index}{'Nb'},        " ",
      $Zmat{$index}{'Nc'},        "\n ";
}

sub addPoint {
    my ( $i, $symb, $x, $y, $z ) = @_;
    $Points[$i] = ( [ $symb, $x, $y, $z ] );
}

sub PrintPoint {
    my ($i) = @_;
    print
"Points[$i]=$Points[$i][0] ($Points[$i][1],$Points[$i][2],$Points[$i][3])\n";
}

sub computeXYZ {
    my ($index) = @_;
    my ( $nc, $nb, $na, $an, $da, $len );
    my ( @plano, @axis, @v, $norm );
    $na  = $Zmat{$index}{'Na'};
    $nb  = $Zmat{$index}{'Nb'};
    $nc  = $Zmat{$index}{'Nc'};
    $an  = $Zmat{$index}{'BondAngle'};
    $da  = $Zmat{$index}{'Dihedral'};
    $len = $Zmat{$index}{'Len'};

    if ( $index == 0 ) {
        addPoint( 0, 'None', 0, 0, 0 );
    }
    elsif ( $index == 1 ) {
        addPoint( 1, $Zmat{1}{'Symbol'}, 0, 0, 0 );
    }
    elsif ( $index == 2 ) {
        addPoint( 2, $Zmat{2}{'Symbol'}, $Zmat{2}{'Len'}, 0, 0 );
    }
    elsif ( $index == 3 ) {
        my ($xyz3) = xyzRotZ( point2xyz($nb), point2xyz($na), $len, $an );
        addPoint( 3, $Zmat{3}{'Symbol'}, ref2array($xyz3) );
    }
    else {
        my ($xyzN) =
          xyzRotXYZ( point2xyz($nc), point2xyz($nb), point2xyz($na), $len, $an,
            $da );
        addPoint( $index, $Zmat{$index}{'Symbol'}, ref2array($xyzN) );
    }
}

#########################
sub xyzRotXYZ {

    #$pa $pb $pc points A-B-C
    #$long distance C-X
    #$angle angle B-C-X
    #$torsion dihedral A-B-C-X
    my ( $pa, $pb, $pc, $long, $angle, $torsion ) = @_;
    my ($iangle)  = 180 - $angle;
    my ($axisdie) = vAB( $pb, $pc );
    my ($plane)   = vCROSSv( vAB( $pa, $pb ), $axisdie );
    my ($pdie)    = Rotation( $axisdie, $torsion, $plane );
    my ($vec)     = Rotation( $pdie, $iangle, $axisdie );
    $vec = vScale( $long, $vec );
    $vec = vSUMv( $pc, $vec );
    return $vec;
}

sub xyzRotZ {

    #$pa origin point A
    #$pb destination point B
    #len distance B-X
    #rotation $angle A-B-X
    #rotation axis = Z axis
    my ( $pa, $pb, $len, $angle ) = @_;
    my ($axis) = [ 0, 0, 1 ];
    return xyzRotationAngle( $pa, $pb, $len, $angle, $axis );
}

sub xyzRotationAngle {

    #$pa origin point A
    #$pb destination point B
    #len distance B-X
    #rotation $angle A-B-X
    #rotation axis = $axis
    my ( $pa, $pb, $len, $angle, $axis ) = @_;
    $angle = 180 - $angle;
    my ($vab) = vAB( $pa, $pb );
    my ($res) = Rotation( $axis, $angle, $vab );
    $res = vScale( $len, $res );
    $res = vSUMv( $pb, $res );
    return $res;
}

sub Rotation {
    my ( $axis, $angle, $vec ) = @_;
    my ( $qrot, $qrotinv, $result, $qvec );
    $qrot    = qRotation( $angle, $axis );
    $qrotinv = qInverse($qrot);
    $qvec    = [ 0, $vec->[0], $vec->[1], $vec->[2] ];
    $result  = qMultiply( $qrot, $qvec );
    $result  = qMultiply( $result, $qrotinv );
    $result  = vZ($result);
    print "Warning: q0 != 0!!!" if ( $result->[0] != 0 );
    return [ $result->[1], $result->[2], $result->[3] ];
}

###quaternions stuff

sub qMultiply {
    my ( $a, $b ) = @_;
    return [
        $a->[0] * $b->[0] -
          $a->[1] * $b->[1] -
          $a->[2] * $b->[2] -
          $a->[3] * $b->[3],
        $a->[0] * $b->[1] +
          $a->[1] * $b->[0] +
          $a->[2] * $b->[3] -
          $a->[3] * $b->[2],
        $a->[0] * $b->[2] -
          $a->[1] * $b->[3] +
          $a->[2] * $b->[0] +
          $a->[3] * $b->[1],
        $a->[0] * $b->[3] +
          $a->[1] * $b->[2] -
          $a->[2] * $b->[1] +
          $a->[3] * $b->[0]
    ];
}

sub qRotation {
    my ( $angle, $vec ) = @_;
    my ( $i, @vv );
    $angle = deg2rad($angle);
    $vec   = vNormalize($vec);
    for ( $i = 0 ; $i < 3 ; $i++ ) {
        $vv[$i] = $vec->[$i] * sin( $angle / 2 );
    }
    return [ cos( $angle / 2 ), $vv[0], $vv[1], $vv[2] ];
}

sub qInverse {
    my ($qvec) = @_;
    return [ $qvec->[0], -$qvec->[1], -$qvec->[2], -$qvec->[3] ];
}

######point/vector stuff

sub point2xyz {
    my ($p) = @_;
    return [ $Points[$p]->[1], $Points[$p]->[2], $Points[$p]->[3] ];
}

sub ref2array {
    my ($ref) = @_;
    return ( $ref->[0], $ref->[1], $ref->[2] );
}

sub vABC {
    my ( $a, $b, $c ) = @_;
    my ( $v1, $v2 ) = ( vAB( $a, $b ), vAB( $b, $c ) );
    return vCROSSv( $v1, $v2 );
}

sub vAB {
    my ( $a, $b ) = @_;
    return [ $b->[0] - $a->[0], $b->[1] - $a->[1], $b->[2] - $a->[2] ];
}

sub vScale {
    my ( $sc, $v ) = @_;
    my ( $i, $vv );
    $vv = vNormalize($v);
    for ( $i = 0 ; $i < 3 ; $i++ ) {
        $vv->[$i] = $vv->[$i] * $sc;
    }
    return $vv;
}

sub vNormalize {
    my ($n) = @_;
    my ( $i, @nn );
    my ($mod) = vModule($n);
    for ( $i = 0 ; $i < 3 ; $i++ ) {
        $nn[$i] = $n->[$i] / $mod;
    }
    return [@nn];
}

sub vModule {
    my ($n) = @_;
    my ( $i, $sum );
    $sum = 0;
    for ( $i = 0 ; $i < 3 ; $i++ ) {
        $sum += ( $n->[$i] )**2;
    }
    return sqrt($sum);
}

sub vANGv {
    my ( $v, $w ) = @_;
    return rad2deg( acos( vDOTv( $v, $w ) / ( vModule($v) * vModule($w) ) ) );
}

sub vDOTv {
    my ( $v, $w ) = @_;
    my ($res) = 0;
    $res = $v->[0] * $w->[0] + $v->[1] * $w->[1] + $v->[2] * $w->[2];
    return $res;
}

sub vCROSSv {
    my ( $v, $w ) = @_;
    my (@vec);
    $vec[0] = $v->[1] * $w->[2] - $v->[2] * $w->[1];
    $vec[1] = $v->[2] * $w->[0] - $v->[0] * $w->[2];
    $vec[2] = $v->[0] * $w->[1] - $v->[1] * $w->[0];
    return [@vec];
}

sub vSUMv {
    my ( $v, $w ) = @_;
    my ( $i, @vec );
    for ( $i = 0 ; $i < 3 ; $i++ ) { $vec[$i] = $v->[$i] + $w->[$i]; }
    return [@vec];
}

### precision stuff
### cut down to 0 values < 1.e-6

sub vZ {
    my ($vec) = @_;
    my ( $i, @vv );
    for ( $i = 0 ; $i <= $#{$vec} ; $i++ ) {
        $vv[$i] = Z( $vec->[$i] );
    }
    return [@vv];
}

sub Z {
    my ($val) = @_;
    return 0 if ( abs($val) < 1.e-6 );
    return $val;
}

#### trigronometric pack ####
sub deg2rad { ( $_[0] / 180 ) * pi() }
sub rad2deg { ( $_[0] / pi() ) * 180 }
sub acos    { atan2( sqrt( 1 - $_[0] * $_[0] ), $_[0] ) }
sub asin    { atan2( $_[0], sqrt( 1 - $_[0] * $_[0] ) ) }
sub tan     { sin( $_[0] ) / cos( $_[0] ) }
sub atan    { atan2( $_[0], 1 ) }
sub pi      { return 4 * atan2( 1, 1 ) }

