#!/usr/bin/perl -w
use strict;
#use List::MoreUtils qw / pairwise/;
use File::Temp qw/ tempfile tempdir /;
use File::Copy;
use Getopt::Long;

my $conf = {"cleanup" => 0, "oute"=> "energy", "name" => "XYZ"};
my $RUN_PREFIX = ""; #"mpirun --host node10,node11 -np 8";
my $siesta = "siesta";
my $PSEUDODIR = "/home/gkolesov/run/Pseudo/GGA";
$PSEUDODIR    = $ENV{PSEUDODIR}     if $ENV{PSEUDODIR};
$PSEUDODIR    = $ENV{SIESTA_PSEUDO} if $ENV{SIESTA_PSEUDO};

my @argv_orig = @ARGV;
GetOptions(
        "c|cleanup!"    => \$conf->{cleanup},
        "v|verbose!"    => \$conf->{verbose},
        "n|dry!"        => \$conf->{dry},
        "o|oute=s"      => \$conf->{oute},
        "x|name=s"      => \$conf->{name},
        "e|cmd=s"       => \$siesta,
        "p|ppdir=s"     => \$PSEUDODIR,
        "r|runprefix=s" => \$RUN_PREFIX,
        "h|help!"       => \&print_usage_and_die,
        );


if(@ARGV>0){
    my $remotecmd = pop @argv_orig;
    print STDERR "ARGV: @ARGV\n";
    $remotecmd  = "@ARGV" if @ARGV>1;
    #$remotecmd = "node" unless defined $remotecmd;

    exit( mysystem("$remotecmd runstruct_siesta @argv_orig") );

}


my $anumber = atom_number_table();

my ($lvec,$atoms) = parse_strout("str.out");
my $ddd =1;

# make_fdf("/tmp/y", $lvec, $atoms);
my $E=run_siesta($lvec,$atoms);
if($E==99999.){
    open(my $fw, '>', 'error') || die "Can not open file for writing: $!";
    close $fw;
}

print "E = $E\n" if $conf->{verbose};
unless($conf->{dry}){
    open(my $fw, '>', $conf->{oute}) || die "Can not open file: $!";
    print $fw $E;
    close $fw;
}

sub make_fdf{
    my ($fname,$lvec,$atoms) = @_;

    my %h=();
    my $k = 1;
    foreach my $a (@$atoms){
        $h{$a->{type}} = $k++ unless defined $h{$a->{type}};
    }

    my $acnt = scalar keys %h;

    open(my $fw, '>', $fname) || die "Can not open file for writing: $!";
    print $fw siesta_head();
    print $fw "SystemLabel $conf->{name}\n";
    print $fw "LatticeConstant 1.0 Ang\n";
    print $fw "Number_of_species $acnt\n";
    print $fw "NumberOfAtoms ",scalar @$atoms,"\n\n";
    print $fw "%block Chemical_Species_label\n";
    foreach my $t (sort {$h{$a} <=> $h{$b}} keys %h){
        printf $fw "  %-2d %-3d %s\n", $h{$t}, $anumber->{$t}, $t;
    }
    print $fw "%endblock Chemical_Species_label\n\n";
    print $fw "%block LatticeVectors\n";
    foreach my $v (@$lvec){
        printf $fw "  % -3.8f % -3.8f % -3.8f\n", @$v;
    }
    print $fw "%endblock LatticeVectors\n\n";
    
    print $fw "%block AtomicCoordinatesAndAtomicSpecies\n";
    foreach my $a (@$atoms){
        printf $fw "  % -3.8f % -3.8f % -3.8f %d\n", @{$a->{coord}},
            $h{$a->{type}};
    }
    print $fw "%endblock AtomicCoordinatesAndAtomicSpecies\n";

    close $fw;
}




sub get_energy{
    my ($siestaout) = @_;

    open(my $fh, $siestaout) || die "Can not open file: $!";
    my $readtot = 0;
    my $energy = 99999.;
    while(<$fh>){
        if($readtot){
            if(/^siesta.*?Total\s*=\s*(\S+)/i){
                $energy = $1;
                last;
            }
            next;
        }
        $readtot=1 if /^siesta:\s*Final\s+energy/;

    }
    close $fh;
    return $energy;
}

sub run_siesta{
    my ($lvec,$atoms) = @_;
    my $tdir = "siesta.$conf->{name}"; #tempdir(DIR => "./", CLEANUP => $conf->{cleanup});
    mkdir $tdir || die "Can not mkdir $tdir : $!";
    chdir $tdir || die "Can not chdir into dir : $!";

    my $fname   = "$conf->{name}.fdf";
    my $siestaout = "siesta.out";
    make_fdf($fname, $lvec, $atoms);

    my %atype = map {$_->{type} => 1}  @$atoms;
    foreach my $a (keys %atype){
        if(-e "$PSEUDODIR/$a.psf"){
            copy("$PSEUDODIR/$a.psf", "$a.psf");
        }
        else{
            die "No pseudopotential for element $a is found.\n";
        }
    }

    my $cmd = "$RUN_PREFIX $siesta <  $fname > $siestaout";

    mysystem($cmd);

    my $E = get_energy($siestaout);

    
    chdir "../" || die "Can not chdir ../ : $!";
    if($conf->{cleanup}){
        system("/bin/rm -rf $tdir");
    }

    return $E;

}


sub mysystem{
    if($conf->{dry} || $conf->{verbose}){
        print STDOUT "@_\n";
        return system("/bin/true",3) if $conf->{dry} && $conf->{verbose};
        return 0 if $conf->{dry};
    }
    
    return system(@_);
}


sub parse_strout{
    my ($file) = @_;

    open(my $fh, $file) || die "Can not open file: $!";
    my @cvec = map {$_=<$fh>; [split] } (0..2);
    my @lvec = map {$_=<$fh>; [split] } (0..2);

    my @clvec = map {cvec2cart($_,\@cvec)} @lvec;

    my @atoms = ();
    while(<$fh>){
        my @t = split /[\s,]+/;
        my $a = pop @t;
        my $v = cvec2cart(\@t,\@cvec);
        push @atoms, {type => $a, coord => $v};

    }
    close $fh;

    return (\@clvec,\@atoms);
}

sub cvec2cart{
    my ($v,$cvec) = @_;
    local $^W = 0;

    #my @t=pairwise { [map {$a *$_} @$b] } @$v,@$cvec;
    my @t = ();
    for(my $i=0; $i<@$v; $i++){
        my $a = $v->[$i];
        my $b = $cvec->[$i];
        push @t, [map {$a * $_} @$b];
    }

    my @l = ();
    for(my $i=0; $i<3; $i++){
        do { $l[$i]+=$_->[$i] } foreach @t;
    }

    return \@l;

}

sub atom_number_table{
    my %t = (
            "H"   => 1  ,
            "He"  => 2  ,
            "Li"  => 3  ,
            "Be"  => 4  ,
            "B"   => 5  ,
            "C"   => 6  ,
            "N"   => 7  ,
            "O"   => 8  ,
            "F"   => 9  ,
            "Ne"  => 10 ,
            "Na"  => 11 ,
            "Mg"  => 12 ,
            "Al"  => 13 ,
            "Si"  => 14 ,
            "P"   => 15 ,
            "S"   => 16 ,
            "Cl"  => 17 ,
            "Ar"  => 18 ,
            "K"   => 19 ,
            "Ca"  => 20 ,
            "Sc"  => 21 ,
            "Ti"  => 22 ,
            "V"   => 23 ,
            "Cr"  => 24 ,
            "Mn"  => 25 ,
            "Fe"  => 26 ,
            "Co"  => 27 ,
            "Ni"  => 28 ,
            "Cu"  => 29 ,
            "Zn"  => 30 ,
            "Ga"  => 31 ,
            "Ge"  => 32 ,
            "As"  => 33 ,
            "Se"  => 34 ,
            "Br"  => 35 ,
            "Kr"  => 36 ,
            "Rb"  => 37 ,
            "Sr"  => 38 ,
            "Y"   => 39 ,
            "Zr"  => 40 ,
            "Nb"  => 41 ,
            "Mo"  => 42 ,
            "Tc"  => 43 ,
            "Ru"  => 44 ,
            "Rh"  => 45 ,
            "Pd"  => 46 ,
            "Ag"  => 47 ,
            "Cd"  => 48 ,
            "In"  => 49 ,
            "Sn"  => 50 ,
            "Sb"  => 51 ,
            "Te"  => 52 ,
            "I"   => 53 ,
            "Xe"  => 54 ,
            "Cs"  => 55 ,
            "Ba"  => 56 ,
            "La"  => 57 ,
            "Ce"  => 58 ,
            "Pr"  => 59 ,
            "Nd"  => 60 ,
            "Pm"  => 61 ,
            "Sm"  => 62 ,
            "Eu"  => 63 ,
            "Gd"  => 64 ,
            "Tb"  => 65 ,
            "Dy"  => 66 ,
            "Ho"  => 67 ,
            "Er"  => 68 ,
            "Tm"  => 69 ,
            "Yb"  => 70 ,
            "Lu"  => 71 ,
            "Hf"  => 72 ,
            "Ta"  => 73 ,
            "W"   => 74 ,
            "Re"  => 75 ,
            "Os"  => 76 ,
            "Ir"  => 77 ,
            "Pt"  => 78 ,
            "Au"  => 79 ,
            "Hg"  => 80 ,
            "Tl"  => 81 ,
            "Pb"  => 82 ,
            "Bi"  => 83 ,
            "Po"  => 84 ,
            "At"  => 85 ,
            "Rn"  => 86 ,
            "Fr"  => 87 ,
            "Ra"  => 88 ,
            "Ac"  => 89 ,
            "Th"  => 90 ,
            "Pa"  => 91 ,
            "U"   => 92 ,
            "Np"  => 93 ,
            "Pu"  => 94 ,
            "Am"  => 95 ,
            "Cm"  => 96 ,
            "Bk"  => 97 ,
            "Cf"  => 98 ,
            "Es"  => 99 ,
            "Fm"  => 100,
            "Md"  => 101,
            "No"  => 102,
            "Lr"  => 103,
            "Rf"  => 104,
            "Db"  => 105,
            "Sg"  => 106,
            "Bh"  => 107,
            "Hs"  => 108,
            "Mt"  => 109,
            "Ds"  => 110,
            "Rg"  => 111,
            "Cn"  => 112,
            "Uut" => 113,
            "Fl"  => 114,
            "Uup" => 115,
            "Lv"  => 116,
            "Uus" => 117,
            "Uuo" => 118
    );
    return \%t;
}

sub siesta_head{
    foreach my $head ("siesta.wrap", "../siesta.wrap","../../siesta.wrap"){
	if(-f $head){
	    open(my $fh, $head) || die "Can not open $head: $!";
	    local $/ = undef;
	    my $t =  <$fh>;
	    close $fh;
	    return $t;
	}
    }

    return <<SHEAD;
DM.MixingWeight 0.01
DM.Tolerance 0.001
DM.NumberPulay 5
# DM.Require.Energy.Convergence .true.
# DM.Energy.Tolerance 0.01
PAO.BasisSize dz
SolutionMethod diagon
AtomicCoordinatesFormat Ang
MaxSCFIterations 1000
SpinPolarized False
MeshCutoff 4081.709348 eV
LatticeConstant 1.0 Ang


%block kgrid_Monkhorst_Pack
5 0 0 0.5
0 5 0 0.5
0 0 5 0.5
%endblock kgrid_Monkhorst_Pack

MD.TypeOfRun            CG          # Type of dynamics:
MD.VariableCell         .false.
#MD.NumCGsteps           50
#MD.NumCGsteps           1

WriteKbands    .false.
WriteBands     .false.
UseSaveData True
DM.UseSaveDM True
SHEAD
}

sub print_usage_and_die{
    print STDERR <<USAGE;
    $0 [OPTION] [remotecmd host]
    -v,  --verbose
    -n,  --dry          Dry mode. Doesn't actually do anything, just
                        says what it would do 
    -c,  --cleanup      Remove siesta working directory (default=false)
    -o,  --oute         File to write energy value into (default=energy)
    -x,  --name         Siesta system name label and file prefix (default=XYZ)
    -e,  --cmd          Siesta executable name (default=siesta)
    -p,  --ppdir        Pseudopotential directory; can be set also with
                        environment variables PSEUDODIR or SIESTA_PSEUDO
    -r,  --runprefix    siesta run prefix, e.g. "mpirun -np 4"
    -h,  --help         print this message

AUTHOR:
    Grigori Kolesov gkolesov@uwyo.edu

EXAMPLE:
    pollmach runstruct_siesta -e siesta.static -p ~/run/Pseudo/GGA 

    If siesta.wrap file exists in the current directory or in ../ , or in 
    ../../ the script will use it as a siesta input fdf file (and will add the
    geometry and atomic specie setup), otherwise the default at the bottom of
    this script will be used.

    Note that the siesta.wrap file should not contain LatticeConstant and
    SystemLabel fields which are also setup by the script.

    If the ~/.machines.rc file is set up and contains something like
    ssh node1  sar -u  1 1 | getvalue "Average:        all" + node -s node1
    the script should be able to run siesta via "node" script / ssh.

USAGE
    exit(0);
}
