/**
 * 
 */
package gov.nasa.rmt.estimate;

import static org.junit.Assert.assertTrue;

import org.junit.Test;
import java.lang.Math;
;
/**
 * @author julianr
 * Class for COCOMO attributes and estimation
 * 
 */
public class Cocomo {
	public static final int COCOMONUMATTRS = 25;

	public static final String[] COCOMOATTRS = { "A", "B", "KSLOC", "ACAP",
			"AEXP", "CPLX", "DATA", "DOCU", "FLEX", "LTEX", "PCAP", "PCON",
			"PEXP", "PMAT", "PREC", "PVOL", "RELY", "RESL", "RUSE", "SCED",
			"SITE", "STOR", "TEAM", "TIME", "TOOL" };
	
	public static String[] tuningtable = {
		"A", "3.0", "B", "1.0", 
		"time","NA","NA","1.00","1.11", "1.29","1.63",
		"stor","NA","NA","1.00","1.05","1.17","1.46",
		"data","NA","0.90","1.00","1.14","1.28","NA",
		"pvol","NA","0.87","1.00","1.15","1.30","NA",
		"ruse","0.95","1.00","1.07","1.15","1.24","NA",
		"rely","0.82","0.92","1.00","1.10","1.26","NA",
		"docu","0.81","0.91","1.00","1.11","1.23","NA",
		"acap","1.42","1.19","1.00","0.85","0.71","NA",
		"pcap","1.34","1.15","1.00","0.88","0.76","NA",
		"pcon","1.29","1.12","1.00","0.90","0.81","NA",
		"aexp","1.22","1.10","1.00","0.88","0.81","NA",
		"pexp","1.19","1.09","1.00","0.91","0.85","NA",
		"ltex","1.20","1.09","1.00","0.91","0.84","NA",
		"tool","1.17","1.09","1.00","0.90","0.78","NA",
		"sced","1.43","1.14","1.00","1.00","1.00","NA",
		"cplx","0.73","0.87","1.00","1.17","1.34","1.74",
		"site","1.22","1.09","1.00","0.93","0.86","0.80",
		"prec","6.20","4.96","3.72","2.48","1.24","0.0",
		"flex","5.07","4.05","3.04","2.03","1.01","NA",
		"resl","7.07","5.65","4.24","2.83","1.41","0.0",
		"team","5.48","4.38","3.29","2.19","1.01","NA",
		"pmat","7.80","6.24","4.68","3.12","1.56","NA"};
		
		public Cocomo tunings;
	
		// Double value representing an invalid tuning parameter
		public static final Double NA=-99942.42; 
		
	public String name;

	public Attribute[] attrs;

	public class Attribute {
		public Object attr;
		
		public Attribute() {
		}
	}
	
	public class CocValue extends Attribute {
		Double value;
		
		public CocValue(double v) {
			value = new Double(v);
		}
		
		public String toString() {
			return(value.toString());
		}
	}
	
	public class CocRange extends Attribute {
		MinMax value;
		
		public CocRange(MinMax m) {
			value = m;
		}
		
		public String toString() {
				return(value.toString());
		}
	}
	
	public class TuningRow extends Attribute {
		Double[] values;
		
		public TuningRow() {
			values = new Double[6];
		}
		
		public void set(int x, Double v) {
			values[x]=v;
		}
		
		public String toString() {
			String result="{ ";
			for(Double v: values) result+=v+" ";
			return(result + "}");
		}
	}
	
	public Cocomo() {
	}
	
	public Cocomo(String _name) {
		name = _name;
		attrs = new Attribute[COCOMONUMATTRS];
	}

	public Cocomo(String _name, Attribute[] _attrs) {
		name = _name;
		attrs = _attrs;
	}

	/**
	 *
	 * @param attr String name of COCOMO attribute
	 * @param value MinMax value to set that COCOMO attribute
	 * @return updated COCOMO attributes (this)
	 */
	public Cocomo set(String attrname, Attribute value) throws AttributeNotKnownException {
		Boolean foundAttribute = false;
		
		for (int i = 0; i < COCOMONUMATTRS; i++) {
			if (attrname.equals(COCOMOATTRS[i])) {
				foundAttribute = true;
				attrs[i] = value;
			}
		}
		if (!foundAttribute) throw new AttributeNotKnownException(attrname);
		return (this);
	}

	/**
	 *
	 * @param attr String name of COCOMO attribute
	 * @return value of that COCOMO attribute - can be any valid Attribute subtype.
	 */
	public Attribute get(String attrname) throws AttributeNotKnownException {
		Boolean foundAttribute = false;
		
		Attribute result = new Attribute();
		for (int i = 0; i < COCOMONUMATTRS; i++) {
			if (attrname.equals(COCOMOATTRS[i])) {
				foundAttribute = true;
				result = attrs[i];
			}
		}
		if (!foundAttribute) throw new AttributeNotKnownException(attrname);
		return (result);
	}

	public String toString() {
		String result = "Name: "+name+", ";

		for (int i = 0; i < COCOMONUMATTRS; i++) {
			result += COCOMOATTRS[i] + ": " + attrs[i] + ", ";
		}
		return (result);
	}

	public Cocomo defaultTunings() {
		tunings = new Cocomo("Tunings from Menzies XOMO file");
		
		/* Set A and B values */
		
		tunings.set(tuningtable[0],new CocValue(Double.parseDouble(tuningtable[1])));
		tunings.set(tuningtable[2],new CocValue(Double.parseDouble(tuningtable[3])));
		
		for (int i = 0; i < 22; i++) {
			TuningRow row = new TuningRow();
			String attrname=tuningtable[i*7+4].toUpperCase();
			for(int j=0;j<6;j++) {
				String e = tuningtable[i*7+4+j+1];
				if (e.equals("NA")) row.set(j,Cocomo.NA);
				else row.set(j,Double.parseDouble(e)); 
			}
			tunings.set(attrname,row);
		}
		return(tunings);
	}
	
	class AttributeNotKnownException extends IndexOutOfBoundsException {	
		public AttributeNotKnownException(String s) {
			super(s);
		}
	}
	
	class ParameterOutofRangeException extends IndexOutOfBoundsException {	
		public ParameterOutofRangeException(String s) {
			super(s);
		}
	}
	
	
	/* Look up value of attribute attrname in this project in the table of tuning 
	 * parameters. Throw an exception if attribute does not have a numeric value
	 * in the correct range. 
	 * 
	 * If an attribute has a non-integer value, interpolate between its floor and
	 * ceiling. */
	public double lookup(String attrname) throws AttributeNotKnownException, ParameterOutofRangeException {
		double lowervalue=-1;
		double uppervalue=-1;
		double result=-1;
		
		Boolean attrexists = false;
		for (int i = 0; i < COCOMONUMATTRS; i++) {
			if (attrname.equals(COCOMOATTRS[i])) {
				attrexists = true;
				CocValue attrvalue = (CocValue)attrs[i];
				Double attrdouble = attrvalue.value;
				TuningRow row = (TuningRow)(tunings.attrs)[i];
				if ((attrdouble < 1.0) || (attrdouble > 6.0))
					throw new ParameterOutofRangeException(attrname+"="+attrdouble);
				else {
						int lower = attrdouble.intValue();
						double delta = attrdouble-(double)lower;
						lowervalue = (row.values)[6-lower];
						
						/* Get next value up if attrdouble is non-integral
						 * so that we can interpolate.
						 */
						if (delta>0) {
							uppervalue = (row.values)[5-lower];
						}
						
						/* Throw exception if values of parameter which we look up
						 * in tuning table are Cocomo.NA, which signifies a parameter
						 * which cannot take that value.
						 */
						if ((lowervalue == Cocomo.NA) || (uppervalue == Cocomo.NA))
							throw new ParameterOutofRangeException(attrname+"="+attrdouble);
						
						/* Calculate result, interpolating if attrdouble is non-integral */
						result=lowervalue+delta*(uppervalue-lowervalue);
				}
			}
		}
		if (!attrexists) throw new AttributeNotKnownException(attrname);
		
		return(result);	
	}
	
	/* Return (double) product of effort multipliers in this project. Exceptions
	 * may be thrown when looking up attribute values. */
	public double ems() {
			double result=1.0;
			String emattrs[] = {"RELY", "DATA", "CPLX", "RUSE", "DOCU", "TIME",
					"STOR", "PVOL", "ACAP", "PCAP", "PCON", "AEXP", "PEXP",
					"LTEX", "TOOL", "SITE", "SCED"};
			
			for(String em: emattrs) {
				result = result*lookup(em);
			}
			
			return(result);
	}
	
	/* Return (double) sum of scale factors in this project. Exceptions
	 * may be thrown when looking up attribute values. */
	public double sfs() {
		double result=0.0;
		String sfattrs[] = {"PREC", "FLEX", "RESL", "TEAM", "PMAT"};
		
		for(String sf: sfattrs)
			result = result+lookup(sf);
		
		return(result);
	}	
	
	public double a() {
		Attribute av = tunings.get("A");
		return(((CocValue)av).value.doubleValue());
	}
	
	public double b() {
		return(((CocValue)tunings.get("B")).value.doubleValue());
	}
	
	public double ksloc() {
		return(((CocValue)get("KSLOC")).value.doubleValue());
	}
	
	public double estimate() {
		return(a()*ems()*Math.pow(ksloc(),b()+0.01*sfs()));
	}

	@Test public void testCOCOMO1() {
		Cocomo cev = new Cocomo("CEV");
		cev.defaultTunings();
		System.out.println(cev.tunings);

		cev.set("KSLOC",new CocValue(100.0));
		cev.set("PREC",new CocValue(1.0));    /* NB VL=1.0, XH=6.0 */
		cev.set("RESL",new CocValue(1.0));
		cev.set("TEAM",new CocValue(4.0));
		cev.set("PMAT",new CocValue(4.0));
		cev.set("FLEX",new CocValue(4.0));
		cev.set("RELY",new CocValue(2.0));
		cev.set("DATA",new CocValue(3.0));
		cev.set("DOCU",new CocValue(5.0));
		cev.set("CPLX",new CocValue(1.0));
		cev.set("RUSE",new CocValue(3.0));
		cev.set("TIME",new CocValue(4.0));
		cev.set("STOR",new CocValue(4.0));
		cev.set("PVOL",new CocValue(4.0));
		cev.set("ACAP",new CocValue(5.0));
		cev.set("AEXP",new CocValue(3.0));
		cev.set("PCAP",new CocValue(4.0));
		cev.set("PEXP",new CocValue(5.0));
		cev.set("LTEX",new CocValue(4.7));
		cev.set("PCON",new CocValue(3.0));
		cev.set("TOOL",new CocValue(2.0));
		cev.set("SCED",new CocValue(3.0));
		cev.set("SITE",new CocValue(6.0));

		double acap_l=cev.lookup("ACAP");
		double a=cev.a();
		System.out.println("A="+a);
		
		double estimate=cev.estimate();
		System.out.println("COCOMO II Estimate = "+estimate);
		System.out.println("ACAP = "+acap_l);
		System.out.println("CEV attributes = "+cev);
		String expectedResult="Name: CEV, A: null, B: null, KSLOC: 100.0, ACAP: 5.0, AEXP: null, CPLX: 1.0";
		assertTrue(((cev.toString()).substring(1,42)).equals(expectedResult.substring(1,42)));
		assertTrue(acap_l==1.19);
	}
	
}

