package Molecule;

use Geometry;
use Data::Dumper;
use strict;
my $BONDCUTOFF=1.6;
my $BONDSCUTOFF=2.0;
1;

sub new {
	my $class = shift;
	my $self = { 
		id => "", 
		atoms => [], 
		charge => 0.0, 
		mw => 0.0,
		connect => [],
		numAtoms => 0,
		numBonds => 0,
		numCyc => 0,
		numArCyc => 0,
		atomsRef => {},
		volume => 0,
		formula => ""
		};
	bless $self;
}

sub clone {
	my $self = shift;
	my $clone = Molecule->new;
	$clone->{id} = $self->{id};
	$clone->{charge} = $self->{charge};
	$clone->{mw} = $self->{mw};
	$clone->{numAtoms} = $self->{numAtoms};
	$clone->{volume} = $self->{volume};
	$clone->{formula} = $self->{formula};
	for my $atm (@{$self->{atoms}}) {
		push @{$clone->{atoms}}, $atm->clone;
	}
	for (my $i=0; $i<$self->{numAtoms}; $i++) {
		for (my $j=0; $j<$self->{numAtoms}; $j++) {
			$clone->{connect}->[$i][$j] = $self->{connect}->[$i][$j];
		}
	}
	for my $ref (keys %{$self->{atomsRef}}) {
		$clone->{atomsRef}->{$ref}=$self->{atomsRef}->{$ref};
	}
	return $clone;
}

sub loadPDBSimple {
	my ($class, $id, $text, $ff) = @_;
	my $self = Molecule->new;
	$self->{id} = $id;
	for my $lin (split /\n/, $text) {
		next if (($lin !~ /ATOM/) && ($lin !~ /HETATM/));
		my $atom = Atom->loadAtomPDB ($lin,$ff);
		$self->addAtom($atom);
	};
	return $self;
}
sub loadHETSimple {
	my ($class, $text, $ff) = @_;
	my $self = Molecule->new;
	my %CONN;
	foreach my $lin (split /\n/, $text) {
		if ($lin=~/RESIDUE/) {
			my ($l,$id,$nat) = split ' ', $lin;
			$self->id($id);
		} elsif ($lin =~ /CONECT/) {
			my $atId0=substr($lin,12,5).substr($lin,11,1);
			$atId0=~s/ //g;
			my $atom=Atom->new;
			$atom->id($atId0);
			$atom->type($ff->atType(substr($atId0,0,1)));
			$self->addAtom($atom);
			$CONN{$atId0}=substr($lin,19);
		} elsif ($lin=~ /FORMUL/) {
			$self->{formula} = substr($lin,19)
		}
	}
	$self->{numCyc} = $self->{numBonds} - $self->{numAtoms} + 1;
	foreach my $id (keys %CONN) {
		next if (!$id);
		my $num=eval(substr($CONN{$id},0,1));
		for (my $i=0; $i<$num; $i++) {
			my $idc =substr($CONN{$id},$i*5+2,4).substr($CONN{$id},$i*5+1,1);
			$idc =~ s/ //g;
			$self->makeBond($id,$idc);
		}
	}
#	$self->{numCyc} = $self->{numBonds} - $self->{numAtoms} + 1;
	return $self;
}
	

sub id {
	my ($self, $id) = @_;
	$self->{id} = $id if defined $id;
	return $self->{id};
}

sub atomsId {
	my $self = shift;
	return map ($_->id, @{$self->{atoms}});
}

sub charge {
	my $self = shift;
	return $self->{charge};
}

sub atoms {
	my $self = shift;
	return $self->{atoms};
}

sub mw {
	my $self = shift;
	return $self->{mw};
}

sub volume{
	my $self = shift;
	return $self->{volume};
}

sub formula {
	my ($self, $f) = @_;
	$self->{formula} = $f if $f;
	return $self->{formula}
}
sub numAtoms {
	my $self = shift;
	return $self->{numAtoms};
}
sub numBonds {
	my $self = shift;
	return $self->{numBonds};
}
sub numCyc {
	my $self = shift;
	return $self->{numCyc};
}
sub numArCyc {
	my $self = shift;
	return $self->{numArCyc};
}

sub addAtom {
	my ($self, $atom) = @_;
	push @{$self->{atoms}}, $atom;
	$self->{charge} += $atom->charge;
	$self->{numAtoms}++;
	$self->{mw} += $atom->mw if $atom->type;
	$self->{atomsRef}->{$atom->id}=$self->{numAtoms}-1;
	for (my $i=0; $i < $self->{numAtoms}; $i++) {
		$self->{connect}->[$i][$self->{numAtoms}-1] = 0;
		$self->{connect}->[$self->{numAtoms}-1][$i] = 0;
	};
	return $self;
}


sub bondByDist {
	my $self = shift;
	my $cutoff;
	for (my $i=0; $i < $self->{numAtoms} - 1; $i++) {
		for (my $j=$i+1; $j < $self->{numAtoms}; $j++) {
			my $dist = Geometry::calcDist (
				$self->{atoms}->[$i]->xyz,
				$self->{atoms}->[$j]->xyz);
			if ($self->_getId($i) =~ /^S/ || $self->_getId($j) =~ /^S/) {
				$cutoff = $BONDSCUTOFF;
			}
			else { 
				$cutoff = $BONDCUTOFF;
			};
			$self->_makeBondN($i,$j) if $dist < $cutoff;
		}
	}
	$self->{numCyc} = $self->{numBonds} - $self->{numAtoms} + 1;
}
	
sub writeConnectMatrix {
	my $self = shift;
	print "\t";
	my @atomsId = map ($_->id, @{$self->{atoms}});
	for (my $i=0; $i < $self->{numAtoms}; $i++) {
		print "$atomsId[$i]\t";
	}
	for (my $i=0; $i < $self->{numAtoms}; $i++) {
		print "$atomsId[$i]\t";
		for (my $j=$i+1; $j < $self->{numAtoms}; $j++) {
			print $self->{connect}->[$i][$j]."\t";
		};
		print "\n";
	}
}		
	
sub connectAtoms {
	my ($self, $id) = @_;
	return $self->_mapIds($self->_connectAtomsN($self->_getN($id)));
}	

sub numConnectAtoms {
	my ($self, $id) = @_;
	return scalar $self->connectAtoms($id);
}

sub breakBond {
	my ($self, $id1, $id2) = @_;
	return $self->_breakBondN($self->_getN($id1),$self->_getN($id2));
}
sub makeBond {
	my ($self, $id1, $id2) = @_;
	return $self->_makeBondN($self->_getN($id1),$self->_getN($id2));
}
    
sub cycle {
	my ($self, $id) = @_;
	my $origin = $self->_getN($id);
	my $mol2 = $self->clone;
	my @found;
	my $i=0;
	my @ats=$mol2->_connectAtomsN($origin);
	while ($i < $#ats && !@found) {
		$mol2->_breakBondN($origin,$ats[$i]);
		@found = $mol2->_minPathN($origin,$ats[$i]);
		$mol2->_makeBondN($origin,$ats[$i]);
		$i++;
	};
	return $self->_mapIds(@found);
}

sub checkAromCyc {
	my ($self, @ids) = @_;
	if (!@ids) {return 0};
	my $arom=1;
	my $i=1;
	while (($i<=$#ids) && $arom) {
		next if (substr ($ids[$i],0,1) ne "C");
		$arom = ($self->numConnectAtoms($ids[$i])< 4);
	}
	continue {
		$i++;
	}
	return $arom;
}

sub countAromCyc {
	my $self = shift;
	my @atsId = $self->atomsId;
	my %CYC;
	for (my $i=0; $i<$#atsId-1; $i++) {
		my $atId1=$atsId[$i];
		my @cyc = sort $self->cycle($atId1);
		$CYC{join "-",@cyc}=\@cyc if (@cyc);
	}
	my $numAr=0;
	foreach my $c (keys %CYC) {
	print "$c\n";
		$numAr++ if ($self->checkAromCyc(@{$CYC{$c}}));
	}
	$self->{numArCyc}=$numAr;
	return $self;
}

sub calcVol {
	my $self=shift;
	my $vol=0.0;
	foreach my $atm (@{$self->atoms}) {
		$vol += $atm->{type}->{vol};
	}
	$self->{volume} = $vol-$self->{numBonds}*5.62
	                   -$self->{numArCyc}*14.7
							 -3.8*($self->{numCyc}-$self->{numArCyc});
   return $self;
}
sub inCycle {
	my ($self, $id1, $id2) = @_;
	my @cycIds = $self->cycle($id1);
	my $i=0;
	while (($i<$#cycIds) and ($cycIds[$i] ne $id2)) {$i++};
	return ($cycIds[$i] eq $id2);
};

	

sub minPath {
	my ($self, $id1, $id2) = @_;
	return $self->_minPathN($self->_getN($id1), $self->_getN($id2));
};


sub subMol {
	my ($self,$id) = @_;
	return $self->mapIds($self->_subMolN($self->_getN($id)));
};

sub _subMolN {
	my ($self,$origin) = @_;
	my @cua;
	push @cua, $origin;
	my %ats;
	$ats{$origin}=1;
	while (@cua) {
		my $i = shift @cua;
		for my $j ($self->_connectAtomsN($i)) {
			if (!$ats{$j}) {
				$ats{$j} = 1;
				push @cua, $j;
			};
		};
	};
	return keys %ats;
};

sub getFragments {
	my ($self, $id1, $id2) = @_;
	my $at1 = $self->_getN($id1);
	my $at2 = $self->_getN($id2);	
	my $mol = $self->clone;
	if ($mol->{connect}->[$at1][$at2]) {
		$mol->_breakBondN($at1,$at2);
		return ([$self->_mapIds($mol->_subMolN($at1))], [$self->_mapIds($mol->_subMolN($at2))]);
	}
	else
	{
		return ();
	}
}

sub bound {
	my ($self, $id1, $id2) = @_;
	my $i = $self->_getN($id1);
	my $j = $self->_getN($id2);
	return $self->{connect}->[$i][$j];
}		
		
sub _getId {
	my ($self, $i) =  @_;
	return $self->{atoms}->[$i]->id;
};
				

sub _getN {
	my ($self, $id) = @_;
	return $self->{atomsRef}->{$id};
}

sub _connectAtomsN {
	my ($self, $n) = @_;
	my @ats=();
	for (my $i=0; $i<$self->{numAtoms}; $i++) {
		push @ats, $i if $self->{connect}->[$n][$i];
	};
	return @ats;
}

sub _numConnectAtomsN {
	my ($self, $i) = @_;
	return scalar $self->_connectAtomsN($i);
}

sub _calcCharge {
	my $self = shift;
	$self->{charge}= 0.0;
	for my $at  (@{$self->{atoms}}) {
		$self->{charge} += $at->charge;
	}
	return $self->{charge};
}

sub _breakBondN {
	my ($self, $i, $j) = @_;
	if ($self->{connect}) {
		$self->{connect}->[$i][$j]=0;
		$self->{connect}->[$j][$i]=0;
		$self->{numBonds}--;
		$self->{numCyc}--;
	}
	return $self;
}

sub _makeBondN {
	my ($self, $i, $j) = @_;
	if (!$self->{connect}->[$i][$j]) {
		$self->{connect}->[$i][$j]=1;
		$self->{connect}->[$j][$i]=1;
		$self->{numBonds}++;
		$self->{numCyc}++;
	}
	return $self;
}

sub _minPathN {
	my ($self, $origin, $target) = @_;
	my @ord;
	my @prev;
	for (my $i=0; $i<$self->{numAtoms}; $i++) {
		$ord[$i]=0;
		$prev[$i]=-1;
	};
	$ord[$origin] = 1;
	my $found = $ord[$target];
	my @cua;
	push @cua, $origin;
	while (!$found && @cua) {
		my $i=shift @cua;
		if ($ord[$i]) {
			foreach my $atN ($self->_connectAtomsN($i)) {
				if (!$ord[$atN]) {
					$ord[$atN] = $ord[$i] + 1;
					$prev[$atN] = $i;
					push @cua, $atN;
				}
			}
		}
		$found = $ord[$target];
	};
	if (!$found) {
		return ()
	}
	else {
		my @path;
		my $i;
		$i=$target;
		while ($i != $origin && $i != -1) {
			push @path, $i;
			$i=$prev[$i];
		};
		push @path,$origin;
		return reverse @path;
	}
}

sub _mapIds {
	my ($self, @ats) = @_;
	return map($self->_getId($_), @ats);
};
