package com.actelion.research.chem.phesa.pharmacophore.pp;

import com.actelion.research.chem.Coordinates;
import com.actelion.research.chem.PeriodicTable;
import com.actelion.research.chem.StereoMolecule;
import com.actelion.research.chem.alignment3d.transformation.Transformation;
import com.actelion.research.chem.alignment3d.transformation.TransformationSequence;
import com.actelion.research.chem.phesa.EncodeFunctions;
import com.actelion.research.chem.phesa.Gaussian3D;
import com.actelion.research.chem.phesa.MolecularVolume;

import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;

/** 
 * @version: 1.0, February 2018
 * Author: J. Wahl
 * basic class to describe Gaussian functions used for the calculation of Molecular Volumes
 * Gaussian functions have a center (3D coordinates), a width and a height 
 * this class provides functionalities for calculating higher order overlaps of Gaussians

*/

public class PPGaussian extends Gaussian3D {
	IPharmacophorePoint pp;

	
	public PPGaussian(int atomicNo,IPharmacophorePoint pp){
		super(pp.getCenterID(),atomicNo,pp.getCenter(), 1.0);
		this.pp = pp;

		
	}
	
	public PPGaussian(PPGaussian original){
		super(original.atomId,original.atomicNo,new Coordinates(original.center),original.weight);
		this.pp = original.pp.copyPharmacophorePoint();
		

	}
	
	private PPGaussian(String encodedGaussian, StereoMolecule mol) {
		decode(encodedGaussian,mol);
	}
	
	public static PPGaussian fromString(String encodedGaussian, StereoMolecule mol) {
		return new PPGaussian(encodedGaussian, mol);
	}
	
	public Coordinates getRotatedDirectionality(double[][] rotMatrix, double scaleFactor) {
		Coordinates directMod = pp.getRotatedDirectionality(rotMatrix,scaleFactor);
		return directMod;
	}
	
	public double getVectorSimilarity(PPGaussian ppGauss2,Coordinates directionalityMod) {
		
		return this.pp.getVectorSimilarity(ppGauss2.getPharmacophorePoint(),directionalityMod);

		
	}

	public double getVectorSimilarity(PPGaussian ppGauss2) {
		return getVectorSimilarity(ppGauss2,ppGauss2.getPharmacophorePoint().getDirectionality());
		
	}
	
	public IPharmacophorePoint getPharmacophorePoint() {
		return pp;
	}
	
	@Override
	public void setAtomId(int atomID) {
		this.pp.setCenterID(atomID);
	}
	
	@Override
	public int getAtomId() {
		return this.pp.getCenterID();
	}

	public double getSimilarity(PPGaussian ppGauss2, Coordinates directionality) {
		double ppSimilarity = 1.0;
		double vectorSim = getVectorSimilarity(ppGauss2,directionality);
		double similarity = (Math.max(0, vectorSim)+2*ppSimilarity)/3.0;
		return similarity;
	}
	
	public double getSimilarity(PPGaussian ppGauss2) {
		
		return getSimilarity(ppGauss2, ppGauss2.getPharmacophorePoint().getDirectionality());
		
	}
	
	public double getInteractionSimilarity(PPGaussian ppGauss2) {

		return pp.getSimilarity(ppGauss2.pp);
	}
	
	@Override 
	
	public void setCenter(Coordinates center) {
		this.center = center;
		this.pp.getCenter().x = center.x;
		this.pp.getCenter().y = center.y;
		this.pp.getCenter().z = center.z;
	}
	


	@Override
	public String encode() { //encodes all information of an atomicGaussian using the Base64 encoder
		Encoder encoder = Base64.getEncoder();
		StringBuilder molVolString = new StringBuilder();
		molVolString.append(Integer.toString(atomicNo));
		molVolString.append(" ");
		molVolString.append(encoder.encodeToString(EncodeFunctions.doubleToByteArray(weight)));
		molVolString.append(" ");
		molVolString.append(pp.encode());

		return molVolString.toString();
	}
	

	public void decode(String string64, StereoMolecule mol)  {
		Decoder decoder = Base64.getDecoder();
		String[] strings = string64.split(" ");
		if(strings.length==1) { // no pharmacophore information encoded
			return;
		}
		atomicNo = Integer.decode(strings[0]);
		weight = EncodeFunctions.byteArrayToDouble(decoder.decode(strings[1].getBytes()));
		StringBuilder sb = new StringBuilder();
		for(int i=2;i<strings.length;i++) {
			sb.append(strings[i]);
			sb.append(" ");
		}
		pp = PharmacophorePointFactory.fromString(sb.toString(), mol);
		center = pp.getCenter();
		alpha = calculateWidth(); //the width of the Gaussian depends on the atomic radius of the atom
		volume = calculateVolume();
		coeff = calculateHeight();
		this.atomId = pp.getCenterID();
	}

	@Override
	public double calculateHeight() {
		return MolecularVolume.p;
	}
	
	@Override 
	public void transform(Transformation transformation) {
		if(!(transformation instanceof TransformationSequence)) {
			this.pp.applyTransformation(transformation);
			this.center = pp.getCenter();
		}
		else {
			TransformationSequence seq = (TransformationSequence) transformation;
			for(Transformation trans : seq.getTransformations()) {
				this.pp.applyTransformation(trans);
				this.center = pp.getCenter();
			}
			}
		}
	
	@Override
	public void updateCoordinates(Coordinates[] coords) {
		pp.updateCoordinates(coords);
		center = pp.getCenter();
	}

	
	@Override
	public void updateAtomIndeces(int[] map) {
		atomId = map[atomId];
		pp.updateAtomIndices(map);
	}

	@Override
	public double calculateWidth() {
		double vdwR = PeriodicTable.getElement(atomicNo).getVDWRadius();
		return MolecularVolume.alpha_pref/(vdwR*vdwR);
	}
}

