/*
 * GPULikelihoodCore.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 *  BEAST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package dr.evomodel.newtreelikelihood;

import dr.app.beagle.evomodel.treelikelihood.AbstractTreeLikelihood;

import java.util.logging.Logger;

/**
 * @author Marc Suchard
 * @author Andrew Rambaut
 * @author Dat Hunyh
 *
 */

public class GPULikelihoodCore extends NativeLikelihoodCore {

	/**
	 * Constructor
	 *
	 * @param treeLikelihood reference back to ATL, should be able to remove at some point
	 * @param gpuInfo number of states
	 */
    public GPULikelihoodCore(int deviceNumber, int stateCount, AbstractTreeLikelihood treeLikelihood, GPUInfo gpuInfo) {

		StringBuffer sb = new StringBuffer();
		sb.append("Constructing GPU likelihood core:\n");
		sb.append("\tGPU Name: "+gpuInfo.getName(deviceNumber)+"\n");
//		sb.append("\tOther info here: "+gpuInfo.memorySize[deviceNumber]+"\n");
		sb.append("If you publish results using this core, please reference Suchard and Rambaut (in preparation)\n");
		Logger.getLogger("dr.evomodel.treelikelihood").info(sb.toString());
		this.treeLikelihood = treeLikelihood;
		this.stateCount = stateCount;
	}

    private int stateCount;

    private AbstractTreeLikelihood treeLikelihood;   // TODO Not needed if everything runs in one thread or I get GPU contexts working

	/** GPU-specific loading **/

	protected static String GPU_LIBRARY_NAME = "GPULikelihoodCore";

	protected static boolean isCompatible(GPUInfo gpuInfo, int[] configuration) {
		return true;
	}


	/** GPU-specific native interface **/

	private static native GPUInfo getGPUInfo();

    public boolean canHandleTipPartials() {
        return true;
    }

    public boolean canHandleTipStates() {
        return false;
    }

	/** Native interface overriding NativeLikelihoodCore **/

	public void initialize(int nodeCount, int stateTipCount, int patternCount, int matrixCount) {
//		System.err.println("stateCOunt = "+stateCount+" in java");
		initialize(nodeCount, stateTipCount, patternCount, matrixCount, stateCount);
	}

    public native void initialize(int nodeCount, int stateTipCount, int patternCount, int matrixCount, int stateCount);

    private native void freeNativeMemory();

    public native void setTipPartials(int tipIndex, double[] partials);

    public native void setTipStates(int tipIndex, int[] states);

	protected native void updateRootFrequencies(double[] frequencies);

	protected native void updateEigenDecomposition(double[][] eigenVectors, double[][] inverseEigenValues, double[] eigenValues);

	protected native void updateCategoryRates(double[] rates);

    protected native void updateCategoryProportions(double[] proportions);

    public native void updateMatrices(int[] branchUpdateIndices, double[] branchLengths, int branchUpdateCount);

    public native void updatePartials(int[] operations, int[] dependencies, int operationCount);

    public native void calculateLogLikelihoods(int rootNodeIndex, double[] outLogLikelihoods);

    public native void storeState();

    public native void restoreState();

    private static GPUInfo gpuInfo = null;

	public static class LikelihoodCoreLoader implements LikelihoodCoreFactory.LikelihoodCoreLoader {

		public String getLibraryName() { return GPU_LIBRARY_NAME; }

		public LikelihoodCore createLikelihoodCore(int[] configuration, AbstractTreeLikelihood treeLikelihood) {
			int stateCount = configuration[0];
			int paddedStateCount = stateCount;
			int deviceNumber = 0;
			if ( stateCount == 4 )
				paddedStateCount = 4;
			else if ( stateCount <= 16 )
				paddedStateCount = 16;
			else if (stateCount <= 32 )
				paddedStateCount = 32;
			else if (stateCount <= 64 )
				paddedStateCount = 64;
			try {
				System.loadLibrary(getLibraryName()+"-"+paddedStateCount);
				if (gpuInfo == null) {
					gpuInfo = GPULikelihoodCore.getGPUInfo();
					if (gpuInfo == null) // No GPU is present
						return null;
					Logger.getLogger("dr.evomodel.treelikelihood").info(gpuInfo.toString());
				}
				if (!GPULikelihoodCore.isCompatible(gpuInfo, configuration)) // GPU is not compatible
					return null;
			} catch (UnsatisfiedLinkError e) { // No library present
				return null;
			}
			return new GPULikelihoodCore(deviceNumber,stateCount, treeLikelihood, gpuInfo);
		}
	}
}