/*
 * @(#)ORonnModel.java	1.0 June 2010
 *
 * Copyright (c) 2010 Peter Troshin
 *
 *        BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.nbio.ronn;

import org.biojava.nbio.ronn.ModelLoader.Model;
import org.biojava.nbio.ronn.ModelLoader.Threshold;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.Locale;



/**
 * Fully re-factored version of RONN model. Based on the code in C version of
 * RONN.
 *
 * @author Peter Troshin
 * @version 1.0
 * @since 3.0.2
 */
public final class ORonnModel {

	private static final Logger logger = LoggerFactory.getLogger(ORonnModel.class);

	/**
	 * Order probability, corresponds to disorder as 1-order
	 */
	private final float disorder_weight;

	private final static int AA_ALPHABET = 19;
	private final static int maxR = 110;
	//private final static float coef = 1.0f;
	/**
	 * Holds encoded query sequence
	 */
	private final short[] seqAA;
	/**
	 * Holds query sequence
	 */
	private final char[] query;

	private final Model model;

	/**
	 * Disorder scores for all residues
	 */
	private float[] scores = null;

	final float[] detect() {

	scores = new float[query.length];
	int sResidue;
	int dIndex;
	int r;
	float est, fOrder, pDisor, fDisor;
	final float[][] Z = new float[seqAA.length][ORonnModel.maxR];
	final int[] Q = new int[seqAA.length];
	final Threshold thold = new ModelLoader.Threshold(model.modelNum);

	/*
	 * 19 looks like a size of the sliding window. So for any sequences
	 * shorted than 19 AA the score will be NaN. Original RONN segfault in
	 * such condition
	 */
	for (sResidue = 0; sResidue <= query.length - ORonnModel.AA_ALPHABET; sResidue++) {
		est = 0.0f;

		for (dIndex = 0; dIndex < model.numOfDBAAseq; dIndex++) {
		final float[] rho = align(sResidue, dIndex);// search for the
		// maximum alignment between ith peptide from the
		// query and the dIndex-th database sequence
		est += model.W[dIndex] * Math.exp((rho[1] - rho[0]) / rho[0]);
		}

		fOrder = (float) (Math.exp(-0.5 * Math.pow(est - thold.mu0, 2.0)
			/ thold.sigma0) / (Math.sqrt(6.28) * thold.sigma0));

		fDisor = (float) (Math.exp(-0.5 * Math.pow(est - thold.mu1, 2.0)
			/ thold.sigma1) / (Math.sqrt(6.28) * thold.sigma1));

		pDisor = (float) (disorder_weight * fDisor / ((1.0 - disorder_weight)
			* fOrder + disorder_weight * fDisor));
		for (r = sResidue; r < sResidue + ORonnModel.AA_ALPHABET; r++) {
		Z[r][Q[r]] = pDisor;
		Q[r]++;
		}
	}

	for (sResidue = 0; sResidue < query.length; sResidue++) {
		est = 0.0f;
		float[] zRow = Z[sResidue];
		int numOfIterations = Q[sResidue];
		for (r = 0; r < numOfIterations; r++) {
		est += zRow[r];
		}
		scores[sResidue] = est / numOfIterations;
	}
	return scores;
	}

	public void getScores(final File outfile) throws FileNotFoundException {
	final PrintWriter output = new PrintWriter(outfile);
	if (scores == null) {
		synchronized (this) {
		if (scores == null) {
			detect();
		}
		}
	}
	for (int i = 0; i < scores.length; i++) {
		output.printf(Locale.US, "%c\t%f\n", query[i], scores[i]);
	}
	output.close();
	}

	// sResidue query sequence index and dIndex database sequence index
	private final float[] align(final int sResidue, final int dIndex) {
	int dResidue, r;
	float maxScore = -1000000;
	float rho1 = 0;
	int maxIdx = 0;
	float rho0 = 0;
	short[] dbAARow = model.dbAA[dIndex];
	int numOfIterations = model.Length[dIndex] - ORonnModel.AA_ALPHABET;
	for (dResidue = 0; dResidue <= numOfIterations; dResidue++) {
		// go though the database sequence for maximised alignment
		rho1 = 0.0f;
		for (r = 0; r < ORonnModel.AA_ALPHABET; r++) {
		// go through the query sequence for one alignment
		rho1 += RonnConstraint.Blosum62[seqAA[sResidue + r]][dbAARow[dResidue
			+ r]];
		}
		if (rho1 > maxScore) {
		maxScore = rho1;
		maxIdx = dResidue;
		}
	}
	for (r = 0; r < ORonnModel.AA_ALPHABET; r++) {
		rho0 += RonnConstraint.Blosum62[dbAARow[maxIdx + r]][dbAARow[maxIdx
			+ r]];
	}
	return new float[] { rho0, maxScore };
	}

	public ORonnModel(final String sequence, final Model model,
		final float disorder) {
	this.disorder_weight = disorder;
	this.model = model;
	query = sequence.toCharArray();
	seqAA = new short[query.length];
	assert model != null;
	assert model.numOfDBAAseq > 0;
	for (int sResidue = 0; sResidue < sequence.length(); sResidue++) {
		seqAA[sResidue] = RonnConstraint.INDEX[query[sResidue] - 'A'];
		if ((seqAA[sResidue] < 0) || (seqAA[sResidue] > 19)) {
			logger.error("seqAA[sResidue]={}({})", seqAA[sResidue], query[sResidue]);
			System.exit(1);
		}
	}
	}
}
