/*
 *                    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 java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;

/**
 * Information needed to represent a survival curve
 *
 * @author Scooter Willis 
 */
public class StrataInfo {

	private ArrayList<Double> time = new ArrayList<>();
	private ArrayList<Integer> status = new ArrayList<>();
	private ArrayList<Double> nevent = new ArrayList<>();
	private ArrayList<Double> ncens = new ArrayList<>();
	private ArrayList<Double> nrisk = new ArrayList<>();
	private ArrayList<Double> weight = new ArrayList<>();
	private ArrayList<Double> surv = new ArrayList<>();
	private ArrayList<Double> varhaz = new ArrayList<>();
	private ArrayList<Double> stderr = new ArrayList<>();
	private ArrayList<Double> stdlow = new ArrayList<>();
	private ArrayList<Double> upper = new ArrayList<>();
	private ArrayList<Double> lower = new ArrayList<>();
	private LinkedHashMap<Double, Integer> ndead = new LinkedHashMap<>();
	DecimalFormat df = new DecimalFormat("#.######");
	DecimalFormat dfe = new DecimalFormat("0.000000E0");

	/**
	 * Need to find the actual time for the nearest time represented as a
	 * percentage Would be used to then look up the number at risk at that
	 * particular time
	 *
	 * @param timePercentage
	 * @return
	 */
	public Double getNearestTime(double timePercentage) {
		//the arrays should be sorted by time so this step is probably not needed
		Double minTime = null;
		Double maxTime = null;
		for (Double t : time) {
			if (minTime == null || t < minTime) {
				minTime = t;
			}
			if (maxTime == null || t > maxTime) {
				maxTime = t;
			}
		}
		Double timeRange = maxTime - minTime;
		Double targetTime = minTime + timePercentage * timeRange;
		Double previousTime = null;
		for (Double t : time) {
			if (previousTime == null || t <= targetTime) {
				previousTime = t;
			} else {
				return previousTime;
			}
		}
		return previousTime;
	}

	/**
	 * Selection of number of risk will depend on the precision and rounding of
	 * time in the survival table. If you are asking for 12 and entry exists for
	 * 11.9999999 then 12 is greater than 11.99999 unless you round.
	 *
	 * @param t
	 * @return
	 */
	public Double getNearestAtRisk(double t) {
		Integer index = 0;
/*       String timeValue = t + "";
		String format = "#";
		int numDecimals = 0;
		int decimalIndex = timeValue.indexOf(".");
		if (decimalIndex > 0) {
			for (int i = timeValue.length() - 1; i > decimalIndex; i--) {
				if (timeValue.charAt(i) == '0' && numDecimals == 0) {
					continue;
				}
				if (i == decimalIndex - 1) {
					format = format + ".#";
				} else {
					format = format + "#";
				}
			}
		}
 */
		DecimalFormat newFormat = new DecimalFormat("#.#"); //used to round on expected precision of time. Not correct but trying to match the other packages

		for (int i = 0; i < time.size(); i++) {
			Double compareTime = time.get(i);
		  //  compareTime = new Double(Math.round(compareTime)); //this is rounding up so that we stop on the first match trying to get this to match another report. Not correct or the other report is wrong
			compareTime = Double.valueOf(newFormat.format(compareTime));
			if (compareTime < t) {
				index = i + 1 ;
			} else if(compareTime == t){
				index = i;
				break;
			}else {
				break;
			}
		}

		//http://www.inside-r.org/packages/cran/rms/docs/survplot
		//per validation using survplot from RMS package and ggkm they select the next
		//time in the future which doesn't seem to be correct as the next time represents
		//knowledge about the future but maybe nrisk at that point in time is defined
		//as the nrisk prior to that time. This appears to be the case where at time 0
		//you would expect that everyone is at risk and you should report that time which
		//is the case in survplot. Added in index = 0 or if the time you are requesting has
		//an exact match
		//survplot(kma,n.risk=TRUE,time.inc=1090)
		//ggkm(kma,timeby=1090)
	   //     if(index != 0 && time.get(index) != t){
	   //      index++;
	   //     }
		if (index >= nrisk.size()) {
			return null;
		} else {
			return nrisk.get(index);
		}
	}

	/**
	 *
	 * @param d
	 * @return
	 */
	public String f(Double d) {
		String v = df.format(d);
		int l = 10 - v.length();
		for (int i = 0; i < l; i++) {
			v = v + " ";
		}
		return v;
	}

	@Override
	public String toString() {
		String o = "";
		o = o + "n=" + nevent.size() + "\r\n";
		o = o + "     time      nevent     ncens     nrisk     weight     surv   varhaz    stderr    stdlow    lower    upper\r\n";
		for (int i = 0; i < nevent.size(); i++) {
			//    if(nevent.get(i) == 0)
			//        continue;
			o = o + (i + 1) + "    " + f(time.get(i)) + " " + f(nevent.get(i)) + " " + f(ncens.get(i)) + " " + f(nrisk.get(i)) + " " + f(weight.get(i)) + " " + f(surv.get(i)) + " " + (varhaz.get(i)) + "  " + stderr.get(i) + "  " + stdlow.get(i) + "  " + lower.get(i) + "  " + upper.get(i) + "\r\n";

		}
		o = o + "\r\n";
		//   for(Integer i : ndead.values()){
		//       o = o + i + "\r\n";
		//   }

		return o;
	}

	/**
	 * @return the time
	 */
	public ArrayList<Double> getTime() {
		return time;
	}

	/**
	 * @return the surv
	 */
	public ArrayList<Double> getSurv() {
		return surv;
	}

	/**
	 * @return the stderr
	 */
	public ArrayList<Double> getStderr() {
		return stderr;
	}

	/**
	 * @return the upper
	 */
	public ArrayList<Double> getUpper() {
		return upper;
	}

	/**
	 * @return the lower
	 */
	public ArrayList<Double> getLower() {
		return lower;
	}

	/**
	 * @return the status
	 */
	public ArrayList<Integer> getStatus() {
		return status;
	}

	/**
	 * @return the nevent
	 */
	public ArrayList<Double> getNevent() {
		return nevent;
	}

	/**
	 * @return the ncens
	 */
	public ArrayList<Double> getNcens() {
		return ncens;
	}

	/**
	 * @return the nrisk
	 */
	public ArrayList<Double> getNrisk() {
		return nrisk;
	}

	/**
	 * @return the weight
	 */
	public ArrayList<Double> getWeight() {
		return weight;
	}

	/**
	 * @return the ndead
	 */
	public LinkedHashMap<Double, Integer> getNdead() {
		return ndead;
	}

	/**
	 * @return the varhaz
	 */
	public ArrayList<Double> getVarhaz() {
		return varhaz;
	}

	/**
	 * @return the stdlow
	 */
	public ArrayList<Double> getStdlow() {
		return stdlow;
	}

	/**
	 * @param stdlow the stdlow to set
	 */
	public void setStdlow(ArrayList<Double> stdlow) {
		this.stdlow = stdlow;
	}
}
