package com.actelion.research.chem.interactionstatistics;
import com.actelion.research.chem.Molecule;
import com.actelion.research.chem.Molecule3D;
import com.actelion.research.chem.PeriodicTable;
import com.actelion.research.chem.StereoMolecule;


public class InteractionAtomTypeCalculator {
	
	
	
	public enum FunctionalGroup {NITRO("NO2",1),ESTER("COOR",2),CARBOXYL("COO",3),
			SULFONAMIDE("HNSO2R",4),SULFONATE("SO3",5), PHOSPHONATE("PO3",6),SULFOXIDE("SO",7),
			SULFONE("SO2",8),AMIDE("HNCO",9),AMIDINE("N=C-N",10),GUANIDINE("N-C(-N)=N",11),N_SP2_TAUT("N=C-N(Ar)",12),
			SP2_AMINE("C=CNX2",13),ENOL("C=COH",14);
	
			private String s;
			private int id;
			
			FunctionalGroup(String s, int id){
				this.s = s;
				this.id = id;
			}
			
			public String getString() {return s;}
			
			public int getId() {return id;}

		@Override
		public String toString() {
			final StringBuilder sb = new StringBuilder("FunctionalGroup{");
			sb.append("s='").append(s).append('\'');
			sb.append(", id=").append(id);
			sb.append('}');
			return sb.toString();
		}
	}
	

	
	public enum AtomPropertyMask { ATOMIC_NO(0x0000007F),HYBRID(0x00000180),NONHNEIGHBOURS(0x00000E00),AROM(0x00001000),
		STABILIZED(0x00002000), FUNCTIONAL_GROUP(0x000FC000), BASIC(0x000001FF), EXTENDED(0x00003FFF),SPECIFIC(0x000FCFFF);

		private final int mask;
		AtomPropertyMask(int mask) { this.mask = mask;}
		public int getMask() { return mask;}
	}
	


	
	public enum AtomPropertyShift{HYBRID_SHIFT(7), NEIGHBOURS_SHIFT(9), FUNCTIONAL_GROUP_SHIFT(16);
		private final int shift;
		AtomPropertyShift(int shift) { this.shift = shift;}
		public int getShift() { return shift;}
	}

	
	public enum AtomFlagCount{FUNC_GROUP_FLAG_COUNT(20),BASIC_ATOM_FLAG_COUNT(12), EXTENDED_ATOM_FLAG_COUNT(14);
		private final int count;
		AtomFlagCount(int count) { this.count = count;}
		public int getCount() { return count;}
	}
	
	
	private static int getFunctionalGroupValue(StereoMolecule complex, int atom) {
		return getFunctionalGroup(complex,atom)<<AtomPropertyShift.FUNCTIONAL_GROUP_SHIFT.getShift();
		
	}
	
	private static int getFunctionalGroup(StereoMolecule complex,int atom) {
		complex.ensureHelperArrays(Molecule.cHelperNeighbours);
		int atomNo = complex.getAtomicNo(atom);
		int neighbours = 0;
		int doubleBonds = 0;
		int tripleBonds = 0;
		

		for(int i=0; i<complex.getConnAtoms(atom); i++) {
			int order = complex.getConnBondOrder(atom, i);
			if(order==2) doubleBonds++;
			else if(order==3) tripleBonds++;
			neighbours++;
		}

		if(atomNo==8) { //oxygen
			if(neighbours==1) {
				if(getPaths(complex,atom,7,-1,8,-1)>0)return FunctionalGroup.NITRO.id;
				//int nPOs = getPaths(complex,a,15,-1,8,-1);
				int nPs = getNeighbours(complex,atom,15,-1);
				if(nPs>0) {
					int p = complex.getConnAtom(atom, 0);
					int nPOXs = getNeighbours(complex,p,8,-1);
					int nPORs = getPaths(complex,p,8,-1,-1,-1);
					int nPOs = nPOXs-nPORs;
					if(nPOs==1) return 0;
					else if(nPOs>=2) return FunctionalGroup.PHOSPHONATE.id;
				}
				int nSs = getNeighbours(complex,atom,16,-1);
				if(nSs>0) {
					int s = complex.getConnAtom(atom, 0);
					int nSNs = getPaths(complex,atom,16,-1,7,1);
					int nSOXs = getNeighbours(complex,s,8,-1);
					int nSORs = getPaths(complex,s,8,-1,-1,-1);
					int nSOs = nSOXs-nSORs;
					if (nSOs>0 && nSNs>0) return FunctionalGroup.SULFONAMIDE.id;
					else if (nSOs==1) return FunctionalGroup.SULFOXIDE.id;
					else if (nSOs==2) return FunctionalGroup.SULFONE.id;
					else if (nSOs==3) return FunctionalGroup.SULFONATE.id;
				}
			}
			int nCOs = getPaths(complex,atom,6,-1,8,-1);
				
			if(nCOs>0) {
				for(int i=0;i<complex.getConnAtoms(atom);i++) {
					int aa = complex.getConnAtom(atom,i);
					if(complex.getAtomicNo(aa)!=6)continue;
					for(int j=0;j<complex.getConnAtoms(aa);j++) {
						int aaa = complex.getConnAtom(aa, j);
						if(aaa==atom)continue;
						else if (complex.getAtomicNo(aaa)==8) { // C(=O)O
							int dBds = complex.getBondOrder(complex.getBond(aa,aaa))> 1 ? 1:0;
							if(doubleBonds+dBds>0) {
								if(complex.getNonHydrogenNeighbourCount(aaa)==1 && neighbours==1) return FunctionalGroup.CARBOXYL.id;
								else if (complex.getAllConnAtoms(aaa)==1 && neighbours>1) return FunctionalGroup.ESTER.id;
								else if (complex.getAllConnAtoms(aaa)>1 && neighbours==1) return FunctionalGroup.ESTER.id;
							}
							
						}
				
			}
				}
			}
			
			if(doubleBonds<1 && complex.getNonHydrogenNeighbourCount(atom)==1) {
				if(getPaths(complex, atom, 6, 1, 6, 2)>0) {
					return FunctionalGroup.ENOL.id;
			}
			}
				
					
			

				
			if(doubleBonds==1) { //sp2 oxygen
				if(getPaths(complex,atom,6,-1,7,1)>0) { //amide
					return FunctionalGroup.AMIDE.id;
				}

							
			}
			return 0;
			}
		else if(atomNo==7) { //nitrogen
			if(neighbours==4) return 0;
			if(complex.isAromaticAtom(atom)) { //aromatic nitrogen
				/**
				//check for imidazole 
				if(neighbours==2) { //only secondary nitrogens 
					
					imidazole:for(int i=0; i<complex.getAllConnAtoms(a); i++) {
						int aa = complex.getConnAtom(a, i);
						if(!complex.isRingBond(complex.getBond(a, aa)) || complex.getAtomicNo(aa)!=6 || !complex.isAromaticAtom(aa))continue imidazole;
						for(int j=0; j<complex.getAllConnAtoms(a); j++) {
							int aaa = complex.getConnAtom(aa, j);
							if(aaa==a) continue;
							if(complex.getAtomicNo(aaa)==7 && complex.isAromaticAtom(aaa) && complex.getConnAtoms(aaa)==2) return N_IMIDAZOLE; 
						}
					}
				}
				*/
				if(neighbours<=2) { //check for generic tautomeric  nitrogens in aromatic rings
					for(int i=0; i<complex.getAllConnAtoms(atom); i++) {
						int aa = complex.getConnAtom(atom, i);
						if(!complex.isAromaticAtom(aa))continue;
						for(int j=0; j<complex.getConnAtoms(aa); j++) {
							int aaa = complex.getConnAtom(aa, j);
							if(!complex.isAromaticAtom(aaa))continue;
							if(aaa==atom) continue;
							if(complex.getAtomicNo(aaa)==7 && complex.getConnAtoms(aaa)<=2) return FunctionalGroup.N_SP2_TAUT.id;
					}
				}
				
				}

				return 0;
			}
			else { //nitrogen is not aromatic
				if(getNeighbours(complex,atom,8,-1)>=2) return FunctionalGroup.NITRO.id;
				if(neighbours==1 && tripleBonds>0) return 0;
				if(getPaths(complex,atom,6,1,8,2)>0) { //amide
					return FunctionalGroup.AMIDE.id;
				}
				if(getPaths(complex,atom,16,1,8,2)>0) {
					return FunctionalGroup.SULFONAMIDE.id;
				}
			    
				boolean isAmidine = false; 
				
				guanidine:for(int i=0; i<complex.getAllConnAtoms(atom); i++) {
					int aa = complex.getConnAtom(atom, i);
					if(complex.getAtomicNo(aa)==6 && complex.getConnAtoms(aa)==3 && getNeighbours(complex, aa, 7, 2)>0) {
						for(int j=0; j<complex.getAllConnAtoms(aa); j++) {
							int aaa = complex.getConnAtom(aa, j);
							if(atom==aaa)continue;
							if(complex.getAtomicNo(aaa)!=7 || complex.isAromaticAtom(aaa)) continue guanidine;
							else isAmidine = true;
						}
						return FunctionalGroup.GUANIDINE.id;

					}
				}
				if(isAmidine) {
					return FunctionalGroup.AMIDINE.id;
				}
				if(doubleBonds==0 && complex.isFlatNitrogen(atom)) {
					return FunctionalGroup.SP2_AMINE.id;
				}
		}
		}
		return 0;
	}
	
	private static int getHybridizationValue(StereoMolecule mol, int atom) {
		if(mol.getAtomicNo(atom) == 6 || mol.getAtomicNo(atom) == 7 || mol.getAtomicNo(atom) == 8) {
		int pi = mol.getAtomPi(atom);
		int sp = (pi == 2) ? 1 : (pi == 1 || mol.getAtomicNo(atom) == 5)  ? 2 : 3;
		
		return sp << AtomPropertyShift.HYBRID_SHIFT.getShift();
		}
		else return 0;

	}
	
	
	
	private static int getAtomicNoValue(StereoMolecule mol, int atom) {
		int atomicNo = mol.getAtomicNo(atom);
		return atomicNo;
		
	}
	

	
	private static int getAromValue(StereoMolecule mol, int atom) {
		int arom = mol.isAromaticAtom(atom) ? 1 : 0;
		int type = 0;
		if(arom>0)
			type = AtomPropertyMask.AROM.getMask();
		
		return type;
	}
	

	private static int getStabilizationValue(StereoMolecule mol, int atom) {
		int stab = mol.isStabilizedAtom(atom) ? 1 : 0;
		int type = 0;
		if(stab>0) 
			type = AtomPropertyMask.STABILIZED.getMask();
		
		return type;
	}
	

	  
	/**
	* get connected atoms of specified element that are bonded to the atom of interest with the specific bond order
	* -1 for any bond order
	* @param mol
	* @param a
	* @param aaAtomicNo
	* @param aaBondOrder
	* @return
	*/
	private static int getNeighbours(StereoMolecule mol,int a, int aaAtomicNo, int aaBondOrder) { 
		int n = 0;
		for(int i=0;i<mol.getAllConnAtoms(a);i++) {
			int aa = mol.getConnAtom(a, i);
			int bondOrder = mol.getBondOrder(mol.getBond(a, aa));
			if(aaAtomicNo>=0 && mol.getAtomicNo(aa)!=aaAtomicNo) continue;
			if(aaBondOrder>0 && bondOrder!=aaBondOrder) continue;
			n++;
		}
		return n;
		
		}



	private static int getPaths(StereoMolecule mol,int a, int aaAtomicNo, int aaBondOrder,int aaaAtomicNo, int aaaBondOrder) { 
		int n = 0;
		for(int i=0;i<mol.getAllConnAtoms(a);i++) {
			int aa = mol.getConnAtom(a, i);
			int bondOrder1 = mol.getBondOrder(mol.getBond(a, aa));
			if(aaAtomicNo>0 &&mol.getAtomicNo(aa)!=aaAtomicNo) continue;
			if(aaBondOrder>0 && bondOrder1!=aaBondOrder) continue;
			for(int j=0;j<mol.getAllConnAtoms(aa);j++) {
				int aaa = mol.getConnAtom(aa, j);
				if(aaa==a)continue;
				int bondOrder2 = mol.getBondOrder(mol.getBond(aa, aaa));
				if(aaaAtomicNo>0 && mol.getAtomicNo(aaa)!=aaaAtomicNo) continue;
				if(aaaBondOrder>0 && bondOrder2!=aaaBondOrder) continue;
				n++;
				
			}
		}
		return n;
		
		}
	
	
	
	public static int getAtomType(StereoMolecule mol, int atom) {
		int type = 0;
		type += getFunctionalGroupValue(mol,atom);
		type += getHybridizationValue(mol,atom);
		type += getStabilizationValue(mol,atom);
		type += getAromValue(mol,atom);
		type += getAtomicNoValue(mol,atom);

		return type;	
	}
	
	public static int getAtomType(FunctionalGroup fg, int atomicNo, boolean isAromatic, int hybridization,
			boolean isStabilized) {
		int type = 0;
		if(fg!=null) {
			type+=fg.id<<AtomPropertyShift.FUNCTIONAL_GROUP_SHIFT.getShift();
		}
		type+=hybridization << AtomPropertyShift.HYBRID_SHIFT.getShift();
		if(isStabilized)
				type += AtomPropertyMask.STABILIZED.getMask();
		if(isAromatic)
			type += AtomPropertyMask.AROM.getMask();
		
		type+= atomicNo;
		
		return type;
	}
	
	public static String getString(int atomType) {
		StringBuilder sb = new StringBuilder();
		sb.append(PeriodicTable.symbol(atomType & AtomPropertyMask.ATOMIC_NO.getMask()));
		int hybrid = (atomType & AtomPropertyMask.HYBRID.getMask())>>AtomPropertyShift.HYBRID_SHIFT.getShift();
		if(hybrid>0) {
			sb.append("." + hybrid);
		}
		int neighbours = (atomType & AtomPropertyMask.NONHNEIGHBOURS.getMask())>>AtomPropertyShift.NEIGHBOURS_SHIFT.getShift();
		sb.append("-" + neighbours + "-");
		String stab = (atomType & AtomPropertyMask.STABILIZED.getMask())>0 ? ".St" : "" ;
		sb.append(stab);
		String arom = (atomType & AtomPropertyMask.AROM.getMask())>0 ? ".Ar" : "" ;
		sb.append(arom);
		int fgValue = (atomType & AtomPropertyMask.FUNCTIONAL_GROUP.getMask())>>AtomPropertyShift.FUNCTIONAL_GROUP_SHIFT.getShift();
		for(FunctionalGroup fg : FunctionalGroup.values()) {
			if (fg.getId()==fgValue) {
				sb.append("(");
				sb.append(fg.getString());
				sb.append(")");
			}
		}
		return sb.toString();
		
	}

	public static int getAtomicNumber(int atomType){
		return atomType & AtomPropertyMask.ATOMIC_NO.getMask();
	}

	public static boolean isCarbonInteraction(int atomType){
		int atNo = getAtomicNumber(atomType);
		return (atNo==6)?true:false;
	}

	public static boolean isAromatic(int atomType){
		return ((atomType & AtomPropertyMask.AROM.getMask()) > 0) ? true : false;
	}


	public static void setInteractionTypes(Molecule3D mol) {
		for (int i = 0; i < mol.getAtoms(); i++) {
			int atomType = getAtomType(mol, i);
			mol.setInteractionAtomType(i, atomType);
		}
	}
	
	public static int getGenericDonor() {//represents a hydroxyl group
		return getAtomType(null, 8, false, 3,
			false);
	}
	
	public static int getGenericAcceptor() {//represents a hydroxyl group
		return getAtomType(null, 8, false, 3,
			false);
	}
	
	public static int getGenericPosCharge() {//represents sp3 primary amine
		return getAtomType(null, 7, false, 3,
			false);
	}
	
	public static int getGenericNegCharge() {//represents a carboxylate oxygen
		return getAtomType(FunctionalGroup.CARBOXYL, 8, false, 2,
			false);
	}
	
}
	
