/*
 *                    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.survival.cox;

import org.biojava.nbio.survival.cox.stats.ChiSq;
import org.biojava.nbio.survival.kaplanmeier.figure.ExpressionFigure;
import org.biojava.nbio.survival.kaplanmeier.figure.KaplanMeierFigure;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;

//import org.biojava.nbio.survival.cox.comparators.SurvivalInfoComparator;

/**
 * Holds the results of a cox analysis where calling dump(), toString() will give an output similar to R
 * @author Scooter Willis <willishf at gmail dot com>
 */
public class CoxInfo {

	private WaldTestInfo waldTestInfo = null;
	String message = "";
	Integer maxIterations = null;
	Double eps = null;
	Double toler = null;
	CoxMethod method;
	private double[][] imat = null; //:the variance matrix at beta=final
	private double[][] naive_imat = null; //the original variance matrix used in residuals
	double[] u = new double[0]; //:score vector
	int iterations = 0; //:actual number of iterations used
	int flag = 0; // success flag  1000  did not converge 1 to nvar: rank of the solution
	double logTest = 0;
	double logTestpval = 0;
	double loglikInit = 0; //loglik at beta=initial values, at beta=final
	double loglikFinal = 0;
	Double scoreLogrankTest;
	private Double rscore = null; //robust score
	private Double rscoreLogrankTestpvalue = null;
	private double degreeFreedom;
	private Double scoreLogrankTestpvalue;
	int numSamples = 0;
	int numEvents = 0;
	private LinkedHashMap<String, String> metaDataFilter = null;
	private LinkedHashMap<String, CoxCoefficient> coefficientsList = new LinkedHashMap<String, CoxCoefficient>();
	LinkedHashMap<Double, Double> baselineSurvivorFunction = new LinkedHashMap<Double, Double>();
	ArrayList<SurvivalInfo> survivalInfoList = new ArrayList<SurvivalInfo>();
	/**
	 *
	 */
	public KaplanMeierFigure kmf = null;
	/**
	 *
	 */
	public ExpressionFigure ef = null;

	/**
	 *
	 * @return
	 */
	public ArrayList<SurvivalInfo> getSurvivalInfoList() {
		return survivalInfoList;
	}

	/**
	 *
	 * @param var
	 * @throws Exception
	 */
	public void setVariance(double[][] var) throws Exception {

		//   if (Math.abs(var[0][1] - var[1][0]) > .0000000000001) { //in the CoxCC correction looks like a precision error keeps these from being equal 10-19
		//       throw new Exception("Expecting diagonal to be equal");
		//   }
		imat = new double[var.length][var[0].length];
		for (int i = 0; i < var.length; i++) {
			for (int j = 0; j < var[0].length; j++) {
				imat[i][j] = var[i][j];
			}
		}
		calcSummaryValues();
	}

	/**
	 *
	 * @return
	 */
	public double[][] getVariance() {
		double[][] var = new double[imat.length][imat[0].length];
		for (int i = 0; i < var.length; i++) {
			for (int j = 0; j < var[0].length; j++) {
				var[i][j] = imat[i][j];
			}
		}

		return var;
	}

	/**
	 *
	 * @param var
	 * @throws Exception
	 */
	public void setNaiveVariance(double[][] var) throws Exception {
//        if (var[0][1] != var[1][0]) {
//            throw new Exception("Expecting diagonal to be equal");
//        }


		naive_imat = new double[var.length][var[0].length];
		for (int i = 0; i < var.length; i++) {
			for (int j = 0; j < var[0].length; j++) {
				naive_imat[i][j] = var[i][j];
			}
		}

		calcSummaryValues();
	}

	/**
	 *
	 * @return
	 */
	public double[][] getNaiveVariance() {
		double[][] var = new double[imat.length][imat[0].length];
		for (int i = 0; i < var.length; i++) {
			for (int j = 0; j < var[0].length; j++) {
				var[i][j] = naive_imat[i][j];
			}
		}

		return var;

	}

	/**
	 *
	 * @param data
	 */
	public void setSurvivalInfoList(ArrayList<SurvivalInfo> data) {
		survivalInfoList = data;
		numSamples = data.size();

		for (SurvivalInfo si : data) {
			if (si.getStatus() == 1) {
				numEvents++;
			}
		}
	}

	/**
	 *
	 * @return
	 */
	public double[] getWeighted() {
		double[] weighted = new double[survivalInfoList.size()];
		int p = 0;
		for (SurvivalInfo si : this.survivalInfoList) {
			weighted[p] = si.getWeight();
			p++;
		}
		return weighted;
	}

	/**
	 *
	 * @return
	 */
	public double[][] getVariableResiduals() {
		ArrayList<String> variables = new ArrayList<String>(coefficientsList.keySet());
		double[][] rr = new double[survivalInfoList.size()][variables.size()];
		int p = 0;
		for (SurvivalInfo si : this.survivalInfoList) {
			int i = 0;
			for (String v : variables) {
				rr[p][i] = si.getResidualVariable(v);
				i++;
			}
			p++;
		}

		return rr;
	}

	/**
	 *
	 * @param rr
	 */
	public void setVariableResiduals(double[][] rr) {
		ArrayList<String> variables = new ArrayList<String>(coefficientsList.keySet());

		int p = 0;
		for (SurvivalInfo si : this.survivalInfoList) {
			int i = 0;
			for (String v : variables) {
				si.setResidualVariable(v, rr[p][i]);
				i++;
			}
			p++;
		}

	}

	/**
	 *
	 * @return
	 */
	public int getNumberCoefficients() {
		return coefficientsList.size();
	}

	/**
	 *
	 * @param name
	 * @return
	 */
	public CoxCoefficient getCoefficient(String name) {
		return coefficientsList.get(name);
	}

	/**
	 *
	 * @param name
	 * @param coefficient
	 */
	public void setCoefficient(String name, CoxCoefficient coefficient) {
		coefficientsList.put(name, coefficient);
	}

	/**
	 *
	 * @param header
	 * @param beginLine
	 * @param beginCell
	 * @param endCell
	 * @param endLine
	 * @return
	 */
	public String getCoefficientText(boolean header, String beginLine, String beginCell, String endCell, String endLine) {
		String o = "";
		if (header) {
			String robust = "";
			if (naive_imat != null) {
				robust = beginCell + "robust se" + endCell;
			}
			o = o + beginLine + beginCell + fmtpl("", 9) + endCell + beginCell + fmtpl("coef", 9) + endCell + beginCell + fmtpl("se(coef)", 9) + endCell + robust + beginCell + fmtpl("z", 9) + endCell + beginCell + fmtpl("p-value", 9) + endCell + beginCell + fmtpl("HR", 9) + endCell + beginCell + fmtpl("lower .95", 9) + endCell + beginCell + fmtpl("upper .95", 9) + endCell + endLine;
		}//Coefficients,Coe,StdErr,HR,p-value,HR Lo 95%,HR Hi 95%

		for (CoxCoefficient coe : coefficientsList.values()) {
			String robust = "";
			String stderror = "";
			if (naive_imat != null) {
				stderror = beginCell + fmt(coe.getRobustStdError(), 5, 9) + endCell;
				robust = beginCell + fmt(coe.getStdError(), 5, 9) + endCell;
			} else {
				stderror = beginCell + fmt(coe.getStdError(), 5, 9) + endCell;
			}
			o = o + beginLine + beginCell + fmtpr(coe.getName(), 9) + endCell + beginCell + fmt(coe.getCoeff(), 5, 9) + stderror + robust + endCell + beginCell + fmt(coe.getZ(), 5, 9) + endCell + beginCell + fmt(coe.getPvalue(), 6, 9) + endCell + beginCell + fmt(coe.getHazardRatio(), 3, 9) + endCell + beginCell + fmt(coe.getHazardRatioLoCI(), 3, 9) + endCell + beginCell + fmt(coe.getHazardRatioHiCI(), 3, 9) + endCell + endLine;
		}
		return o;
	}

	/**
	 *
	 * @param d
	 * @param precision
	 * @param pad
	 * @return
	 */
	public static String fmt(Double d, int precision, int pad) {
		if(d == null)
			return "";
		if(d == Double.NaN)
			return "";
		String value = "";
		DecimalFormat dfe = new DecimalFormat("0.00E0");
		String dpad = "0.";
		double p = 1.0;
		for (int i = 0; i < (precision); i++) {
			dpad = dpad + "0";
			p = p / 10.0;
		}
		DecimalFormat df = new DecimalFormat(dpad);
		if (Math.abs(d) >= p) {
			value = df.format(d);
		} else {
			value = dfe.format(d);
		}
		int length = value.length();
		int extra = pad - length;
		if (extra > 0) {
			for (int i = 0; i < extra; i++) {
				value = " " + value;
			}
		}
		return value;
	}

	/**
	 *
	 */
	private void calcSummaryValues() {

		//beta

		ArrayList<String> variables = new ArrayList<String>(coefficientsList.keySet());
		for (int i = 0; i < variables.size(); i++) {
			String variable = variables.get(i);
			CoxCoefficient coe = coefficientsList.get(variable);
			coe.setStdError(Math.sqrt(imat[i][i])); //values can be updated to reflect new error
			if (naive_imat != null) {
				coe.setRobustStdError(Math.sqrt(naive_imat[i][i]));
			}
			coe.setZ(coe.getCoeff() / coe.getStdError());
			coe.setPvalue(ChiSq.norm(Math.abs(coe.getCoeff() / coe.getStdError())));
			//z <- qnorm((1 + conf.int)/2, 0, 1)
			double z = 1.959964;
			coe.setHazardRatioLoCI(Math.exp(coe.getCoeff() - z * coe.getStdError()));
			coe.setHazardRatioHiCI(Math.exp(coe.getCoeff() + z * coe.getStdError()));
		}

		logTest = -2 * (loglikInit - loglikFinal);
		logTestpval = ChiSq.chiSq(logTest, (int) degreeFreedom);

		scoreLogrankTestpvalue = ChiSq.chiSq(scoreLogrankTest, (int) degreeFreedom);
		if (rscore != null) {
			rscoreLogrankTestpvalue = ChiSq.chiSq(rscore, (int) degreeFreedom);
		}
	}

	/**
	 *
	 */
	public void dump() {

		//need an ordered list for comparing to R dumps

//        ArrayList<SurvivalInfo> orderedSurvivalInfoList = new ArrayList<SurvivalInfo>(survivalInfoList);
//        SurvivalInfoComparator sicSort = new SurvivalInfoComparator();
		//       Collections.sort(orderedSurvivalInfoList,sicSort);


		System.out.println();
		System.out.println("$coef");
		for (CoxCoefficient coe : coefficientsList.values()) {
			System.out.print(coe.getCoeff() + " ");
		}
		System.out.println();
		System.out.println("$means");

		for (CoxCoefficient coe : coefficientsList.values()) {
			System.out.print(coe.getMean() + " ");
		}
		System.out.println();
		System.out.println("$u");

		for (double d : u) {
			System.out.print(d + " ");
		}

		System.out.println();
		System.out.println("$imat");
		for (int i = 0; i < imat.length; i++) {
			for (int j = 0; j < imat[0].length; j++) {
				System.out.print(imat[i][j] + " ");
			}
			System.out.println();
		}

		if (this.naive_imat != null) {
			System.out.println("$naive_imat");
			for (int i = 0; i < naive_imat.length; i++) {
				for (int j = 0; j < naive_imat[0].length; j++) {
					System.out.print(naive_imat[i][j] + " ");
				}
				System.out.println();
			}
		}

		System.out.println();
		System.out.println("$loglik");

		System.out.println(loglikInit + " " + loglikFinal);

		System.out.println();
		System.out.println("$sctest");

		System.out.println(this.scoreLogrankTest);

		System.out.println("$iter");
		System.out.println(this.iterations);

		System.out.println("$flag");
		System.out.println(flag);

		System.out.println();
//        if (false) {
//            System.out.println("ID      LP       Score      Residuals");
//            for (SurvivalInfo si : orderedSurvivalInfoList) {
//                System.out.println(si.getOrder() + " " + si.getLinearPredictor() + " " + si.getScore() + " " + si.getResidual());
//
//            }
//            System.out.println();
//            ArrayList<String> variables = new ArrayList<String>(coefficientsList.keySet());
//            System.out.print("Sample");
//            for (String v : variables) {
//                System.out.print("    " + v);
//            }
//            System.out.println("rr");
//            for (SurvivalInfo si : orderedSurvivalInfoList) {
//                System.out.print(si.getOrder());
//                for (String v : variables) {
//                    System.out.print("   " + si.getResidualVariable(v));
//                }
//                System.out.println();
//            }
//        }

	}

	/**
	 *
	 * @param d
	 * @param pad
	 * @return
	 */
	public String fmtpr(String d, int pad) {
		int length = d.length();
		int extra = pad - length;
		if (extra < 0) {
			extra = 0;
		}
		String v = d;
		for (int i = 0; i < extra; i++) {
			v = v + " ";
		}
		return v;
	}

	/**
	 * Pad left a string with spaces
	 *
	 * @param d
	 * @param pad
	 * @return
	 */
	public String fmtpl(String d, int pad) {
		int length = d.length();
		int extra = pad - length;
		if (extra < 0) {
			extra = 0;
		}
		String v = d;
		for (int i = 0; i < extra; i++) {
			v = " " + v;
		}
		return v;
	}

	@Override
	public String toString() {
		return toString("", " ", "\r\n");
	}

	/**
	 *
	 * @param beginLine
	 * @param del
	 * @param endLine
	 * @return
	 */
	public String toString(String beginLine, String del, String endLine) {



		String o = beginLine + fmtpl("", 9) + fmtpl("Avg", 9) + fmtpl("SD", 9) + endLine;
		for (CoxCoefficient coe : coefficientsList.values()) {
			o = o + beginLine + fmtpr(coe.getName(), 9) + fmt(coe.getMean(), 4, 9) + fmt(coe.getStandardDeviation(), 4, 9) + endLine;

		}

		o = o + beginLine + endLine;



		o = o + beginLine + "n= " + this.numSamples + ", number of events=" + this.numEvents + endLine;
		o = o + getCoefficientText(true, beginLine, del, "", endLine);

		o = o + beginLine + endLine;

		if (baselineSurvivorFunction.size() > 0) {
			o = o + beginLine + "Baseline Survivor Function (at predictor means)" + endLine;
			for (Double time : baselineSurvivorFunction.keySet()) {
				Double mean = baselineSurvivorFunction.get(time);
				o = o + beginLine + fmt(time, 4, 10) + fmt(mean, 4, 10) + endLine;
			}
		}

		o = o + beginLine + endLine;
		o = o + beginLine + "Overall Model Fit" + endLine;
		o = o + beginLine + "Iterations=" + iterations + endLine;


		o = o + beginLine + "Likelihood ratio test = " + fmt(this.logTest, 2, 0) + " df=" + this.degreeFreedom + " p-value=" + fmt(this.logTestpval, 7, 0) + endLine;

		o = o + beginLine + "Wald test             = " + fmt(waldTestInfo.getTest(), 2, 0) + " df=" + waldTestInfo.getDf() + " p-value=" + fmt(waldTestInfo.getPvalue(), 7, 0) + endLine;
		o = o + beginLine + "Score (logrank) test  = " + fmt(scoreLogrankTest, 2, 0) + " df=" + ((int) (this.degreeFreedom)) + " p-value=" + fmt(this.scoreLogrankTestpvalue, 7, 0);

		if (this.rscore != null) {
			o = o + ",  Robust = " + fmt(rscore, 2, 0) + " p-value=" + fmt(rscoreLogrankTestpvalue, 7, 0);

		}

		o = o + endLine;

		//       o = o + "Rank of solution flag=" + flag + "\r\n";
		//       o = o + "Log lik Initial=" + loglikInit + "\r\n";
		//       o = o + "Log lik Final=" + loglikFinal + "\r\n";
		o = o + beginLine + "Method=" + method.name() + endLine;




		return o;
	}

	/**
	 * @return the scoreLogrankTest
	 */
	public double getChiSquare() {
		return scoreLogrankTest;
	}

	/**
	 * @return the degreeFreedom
	 */
	public double getDegreeFreedom() {
		return degreeFreedom;
	}

	/**
	 * @return the scoreLogrankTestpvalue
	 */
	public double getOverallModelFitPvalue() {
		return scoreLogrankTestpvalue;
	}

	/**
	 * @return the rscore
	 */
	public Double getRscore() {
		return rscore;
	}

	/**
	 * @param rscore the rscore to set
	 */
	public void setRscore(Double rscore) {
		this.rscore = rscore;
		if (rscore != null) {
			rscoreLogrankTestpvalue = ChiSq.chiSq(rscore, (int) degreeFreedom);
		}
	}

	/**
	 * @return the rscoreLogrankTestpvalue
	 */
	public Double getRscoreLogrankTestpvalue() {
		return rscoreLogrankTestpvalue;
	}

	/**
	 * @param rscoreLogrankTestpvalue the rscoreLogrankTestpvalue to set
	 */
	public void setRscoreLogrankTestpvalue(Double rscoreLogrankTestpvalue) {
		this.rscoreLogrankTestpvalue = rscoreLogrankTestpvalue;
	}

	/**
	 * @return the scoreLogrankTest
	 */
	public Double getScoreLogrankTest() {
		return scoreLogrankTest;
	}

	/**
	 * @param scoreLogrankTest the scoreLogrankTest to set
	 */
	public void setScoreLogrankTest(Double scoreLogrankTest) {
		this.scoreLogrankTest = scoreLogrankTest;
	}

	/**
	 * @return the scoreLogrankTestpvalue
	 */
	public Double getScoreLogrankTestpvalue() {
		return scoreLogrankTestpvalue;
	}

	/**
	 * @param scoreLogrankTestpvalue the scoreLogrankTestpvalue to set
	 */
	public void setScoreLogrankTestpvalue(Double scoreLogrankTestpvalue) {
		this.scoreLogrankTestpvalue = scoreLogrankTestpvalue;
	}

	/**
	 * @return the metaDataFilter
	 */
	public LinkedHashMap<String, String> getMetaDataFilter() {
		return metaDataFilter;
	}

	/**
	 * @param metaDataFilter the metaDataFilter to set
	 */
	public void setMetaDataFilter(LinkedHashMap<String, String> metaDataFilter) {
		this.metaDataFilter = metaDataFilter;
	}

	/**
	 * @return the coefficientsList
	 */
	public LinkedHashMap<String, CoxCoefficient> getCoefficientsList() {
		return coefficientsList;
	}

	/**
	 * @return the waldTestInfo
	 */
	public WaldTestInfo getWaldTestInfo() {
		return waldTestInfo;
	}

	/**
	 * @return the imat
	 */
	public double[][] getImat() {
		return imat;
	}

	/**
	 * @return the naive_imat
	 */
	public double[][] getNaive_imat() {
		return naive_imat;
	}

	/**
	 * @param degreeFreedom the degreeFreedom to set
	 */
	public void setDegreeFreedom(double degreeFreedom) {
		this.degreeFreedom = degreeFreedom;
	}

	/**
	 * @param waldTestInfo the waldTestInfo to set
	 */
	public void setWaldTestInfo(WaldTestInfo waldTestInfo) {
		this.waldTestInfo = waldTestInfo;
	}
}
