#!/usr/bin/perl

BEGIN {
use lib 'd:/Programs/Perl/lib';
use lib '/home/tkamiya/bin/lib';
my $BaseDir = $ENV{'TkPerlDir'};
#print "\nBaseDir: $BaseDir\n";
@INC = ("$BaseDir/lib", "$BaseDir/VNL", "d:/Programs/Perl/lib", @INC);
}

use strict;
#use warnings;

use MyApplication;
use Sci::ChemicalReaction;
use Sci qw($e $kB $NA);
use Sci::Optimize;

my $DebugMode = 1;
my $UseEval   = 0;
my $ErrorStop = 0;

my $IniFile  = "cmd.ini";
$IniFile = $ARGV[0] if(defined $ARGV[0]);
my $HelpFile = "cmdhelp.txt";

my $Opt = new Optimize;

#my $Method = "PDL::Simplex";
my $Method = "Amoeba::Simplex";
#my $Method = "ModifiedNewton";
#my $Method = "LinearOptimization"; # 線形パラメータのみ。

my $EPS            = 1.0e-9;
my $nMaxIter       = 100000;
my $iPrintLevel    = 1;
my $iPrintInterval = 100;
my $iIteration     = 0;
my $KScale0        = 0.1;
my $KScale         = 1.0;

my $PrintDetail = 0;

#=============================================
# Create ChemicalReaction object
#=============================================
my $R = new ChemicalReaction([], '', "Compound");
$R->SetDebugMode($DebugMode);

#=============================================
# Execute IniFile
#=============================================
my $CmdLine;

my $in = new JFile;
if(!$in->Open($IniFile, "r")) {
	print "Error: Can not open [$IniFile].\n";
	return;
}
while(1) {
	$CmdLine = $in->ReadLine();
	last if(!defined $CmdLine);
	my $ret = &ExecuteAline($CmdLine);
	if($ret eq 'exit') {
		exit;
	}
	if($ret eq 'next') {
		next;
	}
}
$in->Close();

#=============================================
# Loop
#=============================================
while(1) {
	print ">>";
	$CmdLine = <>;
	next if($CmdLine eq '');
	my $ret = &ExecuteAline($CmdLine);
	if($ret eq 'exit') {
		exit;
	}
	if($ret eq 'next') {
		next;
	}
}

exit;

#=============================================
# Subroutines
#=============================================
sub eval
{
	my (@args) = @_;
	Utils::DelSpace($CmdLine);
	my ($cmd, $command) = ($CmdLine =~ /^(\S+)\s+(.*)$/);
#print "com: $command\n";
	print "[$command]=" . eval($command) . "\n";
}

sub speculate
{
	my (@args) = @_;
	for(my $i = 0 ; $i < @args ; $i++) {
		$R->ShowSpeculatedSpecies($args[$i]);
	}
}

sub e
{
	my (@args) = @_;
	if($args[0] =~ /=>?/) {
		&caleform(@args);
	}
	else {
		&calecoh(@args);
		&caletot(@args);
	}
}

sub ecoh
{
	my (@args) = @_;
	return &calecoh(@args);
}

sub calecoh
{
	my (@args) = @_;
	for(my $i = 0 ; $i < @args ; $i++) {
		my $Ecoh = $R->CalCohesiveEnergy($args[$i], 1, $ErrorStop);
		my $EcohkJ = $Ecoh * $e * $NA * 1.0e-3;
		printf "  Ecoh=%12.6g kJ/mol\n", $EcohkJ;
	}
}

sub etot
{
	my (@args) = @_;
	return &caletot(@args);
}

sub caletot
{
	my (@args) = @_;

	Utils::DelSpace($CmdLine);
	my ($cmd, $Reaction) = ($CmdLine =~ /^(\S+)\s+(.*)$/);

	my $Etot = $R->CalTotalEnergy($Reaction, 1, $ErrorStop);
	my $EtotkJ = $Etot * $e * $NA * 1.0e-3;
	printf "  Etot=%12.6g kJ/mol\n", $EtotkJ;
}

sub eform
{
	my (@args) = @_;
	return &caleform(@args);
}

sub caleform
{
	my (@args) = @_;
	Utils::DelSpace($CmdLine);
	my ($cmd, $Reaction) = ($CmdLine =~ /^(\S+)\s+(.*)$/);
	my ($EForm, $EFormInitial, $EFormFinal) = $R->CalFormationEnergy($Reaction, 1, $ErrorStop);
	my $EFormkJ = $EForm * $e * $NA * 1.0e-3;
	printf "  EForm=%12.6g kJ/mol\n", $EFormkJ;
}

sub analyze
{
	my (@args) = @_;

	Utils::DelSpace($CmdLine);
	my ($cmd, $Reaction) = ($CmdLine =~ /^(\S+)\s+(.*)$/);

	$R->ClearHashes();
	$R->Analyze($Reaction);
	if(!$R->CheckMassBalance(1)) {
		print("Mass balance violated\n");
	}
	else {
#		my ($EForm, $EFormInitial, $EFormFinal) = $R->CalEForm(PrintLevel => 1);
		my ($EForm, $EFormInitial, $EFormFinal) = $R->CalEForm($Reaction, 1);
		print "EForm: $EForm eV ($EFormFinal - $EFormInitial)\n";
	}
}

sub extract
{
	my (@args) = @_;

print "Extract possible compounds for [", join(',', @args), "]\n";
#	my $pCompoundDB = $R->GetCompoundDB();
	my $pCompoundDB = $R->GetCompositionCompoundDB();
	my @PossibleCompounds = $R->ExtractPossibleCompounds([@args], [keys %$pCompoundDB], $pCompoundDB);
	print "To be considered: Atom\n";
	foreach my $c (sort @PossibleCompounds) {
		next if($pCompoundDB->{$c}->{Type} ne 'Atom');
		printf "  %-2s %6.3f eV", $c, $pCompoundDB->{$c}->{Emol};
		print  " [$pCompoundDB->{$c}->{Compound}][$pCompoundDB->{$c}->{Ref}]\n";
	}
	print "To be considered: Elementary\n";
	foreach my $c (sort @PossibleCompounds) {
		next if($pCompoundDB->{$c}->{Type} ne 'Elementary');
		printf "  %-2s %6.3f eV", $c, $pCompoundDB->{$c}->{Emol};
		print  " $pCompoundDB->{$c}->{Type} [$pCompoundDB->{$c}->{Compound}][$pCompoundDB->{$c}->{Ref}]\n";
	}
	print "To be considered: Compound\n";
	foreach my $c (sort @PossibleCompounds) {
		next if($pCompoundDB->{$c}->{Type} ne 'Compound');
		printf "  %s %10.3f eV", $c, $pCompoundDB->{$c}->{Emol};
		print  " $pCompoundDB->{$c}->{Type} [$pCompoundDB->{$c}->{Compound}][$pCompoundDB->{$c}->{Ref}]\n";
	}
}

sub extract1
{
	my (@args) = @_;
	my $pDB = $R->GetDB();
	foreach my $key (sort keys %$pDB) {
		my $IsHit = 1;
		my ($pe, $pn) = $R->CompoundToElements($pDB->{$key}->{SortedComposition});
		for(my $j = 0 ; $j < @$pe ; $j++) {
			my $IsIncluded = 0;
			for(my $i = 0 ; $i < @args ; $i++) {
				if($args[$i] eq $pe->[$j]) {
					$IsIncluded = 1;
					last;
				}
			}
			if(!$IsIncluded) {
				$IsHit = 0;
				last;
			}
		}

		next if(!$IsHit);

		my $d = $pDB->{$key};
		print "[$key] [$d->{Compound}]: $d->{SortedComposition}: $d->{Etot}/$d->{Z} = $d->{Emol} eV [$d->{Ref}]\n";
	}
}

sub search
{
	my (@args) = @_;
	my $pDB = $R->GetDB();
print "Seach in DB [mode=", $R->GetMode(), "]\n";
	foreach my $key (sort keys %$pDB) {
		my $IsHit = 1;
		for(my $i = 0 ; $i < @args ; $i++) {
			if($pDB->{$key}->{SortedComposition} !~ /$args[$i]/) {
				$IsHit = 0;
				last;
			}
		}
		next if(!$IsHit);

		my $d = $pDB->{$key};
		print "[$key] [$d->{Compound}]: $d->{SortedComposition}: $d->{Etot}/$d->{Z} = $d->{Emol} eV [$d->{Ref}]\n";
	}
}

sub compare
{
	my (@args) = @_;

	for(my $i = 0 ; $i < @args ; $i++) {
		$args[$i] = $R->SortChemicalFormula($args[$i]);
	}

	my %Hit;
	for(my $i = 0 ; ; $i++) {
		my $db = $R->GetCSVDB($i);
		last if(!$db);

		my $pSortedComposition = $db->GetData("SortedComposition");
		$pSortedComposition = $db->GetData("Composition") if(!$pSortedComposition);
		my $pCompound = $db->GetData("Compound");
		my $pType     = $db->GetData("Type");
		my $pEtot     = $db->GetData("Etot");
		my $pZ        = $db->GetData("Z");
		my $pEmol     = $db->GetData("Emol");
		my $pRef      = $db->GetData("Ref");

		for(my $j = 0 ; $j < @$pSortedComposition ; $j++) {
			my $SC = $R->SortChemicalFormula($pSortedComposition->[$j]);
			my $IsHit = 1;
			for(my $l = 0 ; $l < @args ; $l++) {
#print "$i:$j: $SC <=> $args[$l]\n";
				if($SC !~ /$args[$l]/) {
					$IsHit = 0;
					last;
				}
			}
#print "   Hit: $IsHit\n";
			next if(!$IsHit);

			my %db = (
				SortedComposition => $SC,
				Compound          => $pCompound->[$j],
				Etot              => $pEtot->[$j],
				Z                 => $pZ->[$j],
				Emol              => $pEmol->[$j],
				Ref               => $pRef->[$j],
				);
			$db{Emol} = $db{Etot} / $db{Z} if(!defined $db{Emol});

#print "   set $pCompound->[$j]: $pRef->[$j]\n";
			$Hit{"$SC:$pRef->[$j]"} = \%db;
		}
	}
	foreach my $key (sort keys %Hit) {
#print "$key: $Hit{$key}{Etot}\n";
		print "$Hit{$key}{Compound}: $Hit{$key}{Etot}/$Hit{$key}{Z} = $Hit{$key}{Emol} eV: $key\n";
	}
}

sub showmode
{
	my ($mode, @args) = @_;
	print "Mode: ", $R->GetMode(), "\n";
}

sub set
{
	my ($arg, @args) = @_;
	my ($target, $f) = ($arg =~ /^\s*(.*?)=(.*)\s*$/);
	if(lc $target eq 'useeval') {
print "set UseEval=$f\n";
		$UseEval = $f;
	}
	if(lc $target eq 'debugmode') {
print "set DebugMode=$f\n";
		$DebugMode = $f;
	}
	if(lc $target eq 'mode') {
		&setmode(@args);
	}
}

sub setmode
{
	my ($mode, @args) = @_;
	if($mode =~ /compos/i) {
		$mode = 'Composition';
	}
	elsif($mode =~ /compou/i) {
		$mode = 'Compound';
	}
	else {
		print "Error: Invalid mode [$mode]: should be 'compound' or 'composition' \n";
		return;
	}
	print "SetMode $mode\n";
	$R->SetMode($mode);
}

sub printdata
{
	my (@args) = @_;
	my $pDB = $R->GetDB();
	for(my $i = 0 ; $i < @args ; $i++) {
		my $c = $R->SortChemicalFormula($args[$i]);
		my $data = $pDB->{$c};
		print "Data for [$args[$i]]\n";
		if(!$data) {
			print "  Data not found.\n";
			next;
		}
		foreach my $key (sort keys %$data) {
			print "  $key: $data->{$key}\n";
		}
	}
}

sub readdb
{
	my (@args) = @_;
	$R->ReadDBFiles([@args]);
}

sub listdb
{
	my (@args) = @_;
	my %db = $R->GetDBListHash();
	print "List Databases\n";
	foreach my $key (sort keys %db) {
		print "  $key\n";
	}
}

sub printdb
{
	my (@args) = @_;
	my %db = $R->GetDBListHash();
	for(my $i = 0 ; $i < @args ; $i++) {
		print "Print DB data [$args[$i]]\n";
		my $db = $db{lc $args[$i]};
		next if(!defined $db);

		if($args[$i] =~ /^CSV/i) {
			my $pCompound = $db->GetData("Compound");
			my $pType     = $db->GetData("Type");
			my $pEmol     = $db->GetData("Emol");
			my $pRef      = $db->GetData("Ref");
			$db = {};
			for(my $i = 0 ; $i < @$pCompound ; $i++) {
				$db->{$pCompound->[$i]}{Compound} = $pCompound->[$i];
				$db->{$pCompound->[$i]}{Type}     = $pType->[$i];
				$db->{$pCompound->[$i]}{Emol}     = $pEmol->[$i];
				$db->{$pCompound->[$i]}{Ref}      = $pRef->[$i];
			}
		}

		foreach my $key (sort keys %$db) {
			print "  $key: $db->{$key}{Compound} [$db->{$key}{Type}] $db->{$key}{Emol} eV ($db->{$key}{Ref})\n";
		}
	}
}

sub printdbdata
{
	my (@args) = @_;
	print "Print DB data\n";
	$R->PrintDBData(StopByError => 0);
}

sub help
{
	my (@args) = @_;
	my $in = new JFile;
	if(!$in->Open($HelpFile, "r")) {
		print "Error: Can not open [$HelpFile].\n";
		return;
	}
	while(1) {
		my $line = $in->ReadLine();
		last if(!defined $line);
		print $line;
	}
	$in->Close();
}

sub ExecuteAline
{
	my ($line) = @_;

	return 'next' if($line =~ /^#/);

	my ($cmd, @args) = Utils::Split("\\s+", $line);
	return 'exit' if($cmd =~ /^(quit|exit|bye)$/i);
	return 'next' if($cmd eq '');

	my $lccmd = lc $cmd;
	if(!main->can($lccmd)) {
		print "Error: [$cmd] is not defined\n";
		return 'next';
	}
no strict;
	if(UseEval) {
		eval('&$lccmd(@args)');
	}
	else {
		&$lccmd(@args);
	}
use strict;
}
