/*
 * Copyright (c) 1997 - 2016
 * Actelion Pharmaceuticals Ltd.
 * Gewerbestrasse 16
 * CH-4123 Allschwil, Switzerland
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the the copyright holder nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Modest v. Korff
 */

package com.actelion.research.chem.descriptor.flexophore;

import com.actelion.research.calc.ArrayUtilsCalc;
import com.actelion.research.chem.descriptor.flexophore.generator.ConstantsFlexophoreGenerator;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class DistHist implements Serializable {
	
	private static final long serialVersionUID = 17042013;
	
	// Array containing the histogram vectors.
	// Only one array, because of memory reasons.
	protected byte [] arrDistHists;
	
	private int numPPNodes;
	
	private int identifier;
	
	public DistHist() {
		arrDistHists = new byte[0];
		numPPNodes = 0;
		identifier=-1;
	}

	/**
	 * Initializes the distance histograms. Filled with 0.
	 * @param nPPNodes
	 */
	public DistHist(int nPPNodes) {
		initHistogramArray(nPPNodes);
	}

	public DistHist(DistHist distHist) {
		distHist.copy(this);
	}
	/**
	 * 
	 * @param copy: This object is written into copy.
	 */
	public void copy(DistHist copy){
		
		copy.identifier = this.identifier;
		
		if(copy.numPPNodes != this.numPPNodes) {
		
			copy.numPPNodes = this.numPPNodes;
			
			copy.arrDistHists = new byte[this.arrDistHists.length];
		}
		
		System.arraycopy(this.arrDistHists, 0, copy.arrDistHists, 0, this.arrDistHists.length);
	}

	public int getBonds() {
		return ((numPPNodes * numPPNodes)-numPPNodes) / 2;
	}
	
	/**
	 * 
	 * @return number of pharmacophore points.
	 */
	public int getNumPPNodes(){
		return numPPNodes;
	}
	
	public int getSizeBytes(){
		int s = 0;
		
		if(getNumPPNodes()>1) {
			s += arrDistHists.length;
		}
		// size, identifier
		s += (Integer.SIZE / 8)*2;
		
		return s;
	}

	protected void initHistogramArray(int nPPNodes) {

		this.numPPNodes = nPPNodes;

		int nBonds = ((numPPNodes * numPPNodes)-numPPNodes) / 2;
		
		arrDistHists = new byte[nBonds* ConstantsFlexophoreGenerator.BINS_HISTOGRAM];
		
	}
	/**
	 * 
	 * @param indexNode1
	 * @param indexNode2
	 * @param size number of nodes
	 * @return
	 */
	public static final int getIndex(int indexNode1, int indexNode2, int size){
		
		int i1 = Math.min(indexNode1,indexNode2);
		
		int i2 = Math.max(indexNode1,indexNode2);
		
		int h = i1+1;
				
		int index = (i1*size+i2) - ((h*h-h)/2) - h;
		
		return index;
	}
	
	public int getMinDist(int indexAt1, int indexAt2) {
		byte [] arr = getDistHist(indexAt1, indexAt2);
		
		int dist=0;
		
		for (int i = 0; i < arr.length; i++) {
			if(arr[i]>0){
				dist = i; 
				break;
			}
		}
		
		return dist;
	}
	
	/**
	 * Calculates for nodes cluster the node with the minimum rmsd to the cluster center.
	 * @param maxDistance maximum distance that a node will become a cluster member
	 * @return List with cluster.
	 */
	public List<ClusterNode> getClusterCenter(int maxDistance){
		
		List<ClusterNode> liCluster = new ArrayList<ClusterNode>();
		
		// Get all cluster
		for (int i = 0; i < getNumPPNodes(); i++) {
			ClusterNode cluster = new ClusterNode(i);
			
			for (int j = 0; j < getNumPPNodes(); j++) {
				if(i!=j){
					byte [] hist = getDistHist(i, j);
					boolean bInCluster=false;
					for (int k = 0; k < maxDistance+1; k++) {
						if(hist[k]>0){
							bInCluster=true;
							break;
						}
					}
					if(bInCluster){
						cluster.add(j);
					}
					
				}
			}
			if(cluster.isCluster())
				liCluster.add(cluster);
		}
		
		// Calculate RMSD
		for (int i = 0; i < liCluster.size(); i++) {
			ClusterNode cl = liCluster.get(i);
			
			int rmsd = ClusterNode.getRMSD(cl, this);
			
			cl.setRMSD(rmsd);
			
		}
		
		// Equal cluster into one list
		// The cluster with the lowest rmsd is the chosen cluster center.
		List<ClusterNode> liClusterNodeMinRMSD = new ArrayList<ClusterNode>();
		for (int i = 0; i < liCluster.size()-1; i++) {
			ClusterNode cl = liCluster.get(i);
			List<ClusterNode> liEqCluster = new ArrayList<ClusterNode>();
			liEqCluster.add(cl);
			
			for (int j = liCluster.size()-1; j > i; j--) {
				if(cl.equals(liCluster.get(j))){
					liEqCluster.add(liCluster.remove(j));
				}
			}
			
			Collections.sort(liEqCluster);
			liClusterNodeMinRMSD.add(liEqCluster.get(0));
			
		}
		
		
		return liClusterNodeMinRMSD;
	}

	/**
	 * The distnce histograms are stored in a single array.
	 * @param indexAt1
	 * @param indexAt2
	 * @param arrHist
	 */
	public void setDistHist(int indexAt1, int indexAt2, byte [] arrHist) {
		
		if(indexAt1 >= numPPNodes) {
			throw new ArrayIndexOutOfBoundsException(indexAt1);
		} else if(indexAt2 >= numPPNodes)
			throw new ArrayIndexOutOfBoundsException(indexAt2);
		
		int index = getIndex(indexAt1, indexAt2, numPPNodes);
		
		int posStart = index * ConstantsFlexophoreGenerator.BINS_HISTOGRAM;
		
		System.arraycopy(arrHist, 0, arrDistHists, posStart, ConstantsFlexophoreGenerator.BINS_HISTOGRAM);
		
	}
	public byte [] getDistHists() {
		return arrDistHists;
	}
	
	/**
	 * Flat copy 
	 * @param arr
	 * @param size
	 * @param identifier
	 */
	public void setDistHists(byte [] arr, int size, int identifier) {
		arrDistHists=arr;
		this.numPPNodes = size;
		this.identifier = identifier;
	}
	
	/**
	 * 
	 * @param indexAt1
	 * @param indexAt2
	 * @return deep copy.
	 */
	public byte [] getDistHist(int indexAt1, int indexAt2) {
		
		int index = getIndex(indexAt1, indexAt2, numPPNodes);
		
		int posStart = index * ConstantsFlexophoreGenerator.BINS_HISTOGRAM;
		
		byte [] arr = new byte[ConstantsFlexophoreGenerator.BINS_HISTOGRAM];
		
		System.arraycopy(arrDistHists, posStart, arr, 0, ConstantsFlexophoreGenerator.BINS_HISTOGRAM);
		
		return arr;
	}

	public int getIndexPosStartForDistHist(int indexAt1, int indexAt2) {

		int index = getIndex(indexAt1, indexAt2, numPPNodes);

		int posStart = index * ConstantsFlexophoreGenerator.BINS_HISTOGRAM;

		return posStart;
	}

	public byte getValueAtAbsolutePosition(int indexAbsolutePosition){
		return arrDistHists[indexAbsolutePosition];
	}


	public byte [] getDistHist(int indexAt1, int indexAt2, byte [] arr) {
		
		int index = getIndex(indexAt1, indexAt2, numPPNodes);
		
		int posStart = index * ConstantsFlexophoreGenerator.BINS_HISTOGRAM;
		
		System.arraycopy(arrDistHists, posStart, arr, 0, ConstantsFlexophoreGenerator.BINS_HISTOGRAM);
		
		return arr;
	}

	/**
	 * To get the distance in Angstrom the relative distance has to be multiplied with the histogram range <code>CGMult.RANGE_HISTOGRAM</code>.
	 * @param indexAt1
	 * @param indexAt2
	 * @return relative distance 0: only the first bin is occupied, 
	 * 1: the last bin is occupied.
	 */
	public double getRelMaxDistInHist(int indexAt1, int indexAt2) {
		byte [] arr = getDistHist(indexAt1, indexAt2);

		double max = Integer.MIN_VALUE;
		for (int i = arr.length-1; i >= 0; i--) {
			if(arr[i]>0){
				max = i;
				break;
			}
		}
		return max / arr.length;
	}
	
	public double getMaxDistInHist(int indexAt1, int indexAt2) {
		byte [] arr = getDistHist(indexAt1, indexAt2);

		double max = Integer.MIN_VALUE;
		for (int i = arr.length-1; i >= 0; i--) {
			if(arr[i]>0){
				max = i;
				break;
			}
		}
		return max;
	}
	
	/**
	 * To get the distance in Angstrom the relative distance has to be multiplied with the histogram range <code>CGMult.RANGE_HISTOGRAM</code>.
	 * Maximum relative distance between two nodes in the object.
	 * @return
	 */
	public double getRelMaxDistInHist() {
		int size = getNumPPNodes();
		
		double max = 0;
		for (int i = 0; i < size; i++) {
			for (int j = i+1; j < size; j++) {
				double dist = getRelMaxDistInHist(i, j);
				if(dist>max)
					max=dist;
			}
		}
		
		return max;
	}
	
	public String toStringHistsIndexed(){
		
		StringBuffer b = new StringBuffer();
		
		for (int i = 0; i < numPPNodes; i++) {
			for (int j = i+1; j < numPPNodes; j++) {
				byte [] arrHist = getDistHist(i,j);
				
				if(arrHist!=null)
					b.append(i + "\t" + j + "\t[" + ArrayUtilsCalc.toString(arrHist) + "]\n");
			}
		}
		
		return b.toString();
	}
	
	public int getIdentifier() {
		return identifier;
	}
	
	public void setIdentifier(int identifier) {
		this.identifier = identifier;
	}
	

}
