package metab;
import metab.*;
import java.io.*;


/**
 * The <code>NMSubject</code> class handles the bulk of the computations for the
 * compartmental model.  An instance of the class is representative of one human
 * subject.  The subject contains exactly one <code>Stomach</code>, <code>Intestine</code>,
 * and <code>LeanBodyMass</code> compartment.  See the constructor for the information
 * required for using this class.  Values for the bitFlag argurment need to be
 * or'd ( | ) together, with one option set for each category. The categories are
 * food present in the stomach, drinking history, and units of measurement.  When
 * _METRIC is selected as the units of measurement, cm and kg should be used for
 * the height and weight arguments, and when _IMPERIAL is selected, in and lbs
 * should be used.  When returning the bitFlag, the user should check to see if
 * the _ERROR bit is checked.  If so, then the user constructed the object incorrectly
 * and should re-initialize it appropriately.  This class keeps track of BAC during
 * each minute time interval and final BAC should be determined from the array
 * determined by the getBAC method.
 * @author Levi Blackstone, Matthew Woller
 * @version 1.29
 * @see metab.Stomach
 * @see metab.Intestine
 * @see metab.LeanBodyMass
 * @see metab.Subject
 * @see metab.Drink
 * @see metab.DrinkTime
 * @see metab.TDSubject
 */
public class NMSubject implements Serializable {

	/**
	 * Standard value representing a light meal was eaten recently.
	 */
	public static final int _LIGHT_MEAL = 1;
	/**
	 * Standard value representing an average meal was eaten recently.
	 */
	public static final int _AVERAGE_MEAL = 1 << 1;
	/**
	 * Standard value representing a large meal was eaten recently.
	 */
	public static final int _LARGE_MEAL = 1 << 2;
	/**
	 * Standard value representing no meal was eaten recently.
	 */
	public static final int _NO_MEAL = 1 << 3;
	/**
	 * Standard value representing that the subject is a light drinker.
	 */
	public static final int _LIGHT_DRINKER = 1 << 4;
	/**
	 * Standard value representing that the subject is a moderate drinker.
	 */
	public static final int _MODERATE_DRINKER = 1 << 5;
	/**
	 * Standard value representing that the subject is a heavy drinker.
	 */
	public static final int _HEAVY_DRINKER = 1 << 6;
	/**
	 * Standard value representing that input units are in metric units.
	 */
	public static final int _METRIC = 1 << 7;
	/**
	 * Standard value representing that input units are in Imperial units.
	 */
	public static final int _IMPERIAL = 1 << 8;
	/**
	 * Standard value representing that an error occured while retrieving info.
	 */
	public static final int _ERROR = 1 << 9;
	/**
	 * Amount of liquid exiting the stomach through pyloric sphincter (L/min).
	 */
	private static final double squirtVolume = 0.0111;
	/**
	 * Amount of liquid exiting the intestine through the villi (L/min).
	 */
	private static final double absorbVolume = 0.01;
	/**
	 * Amount of gastric fluid being produced in the stomach (L/min).
	 */
	private static final double newFluid = .002;

	/**
	 * Bitflag representing all set switches.
	 */
	private int bitFlag;
	/**
	 * Age in years.
	 */
	private int age;
	/**
	 * Represents number of minutes after the specified amount at which point
	 * the BAC would become zero.
	 */
	private int timeForZeroEthanol;
	/**
	 * Total number of minutes in drinking interval.
	 */
	private int totalMinutes;
	/**
	 * Array of the value for the BAC at every time interval.
	 */
	private double BACvalues[];
	/**
	 * Height (cm).
	 */
	private double height;
	/**
	 * Weight (kg).
	 */
	private double weight;
	/**
	 * Represents the highest BAC achieved during the time interval.
	 */
	private double peakBAC;
	/**
	 * Represents the total amount of ethanol to be introduced (g).
	 */
	private double totalEthanol;
	/**
	 * Represents the total amount of liquid to be introduced (L).
	 */
	private double totalVolume;
	/**
	 * Represents the total water content of the body (L).
	 */
	private double TBW;
	/**
	 * Represents the amount of ethanol to be introduced each cycle (g/min).
	 */
	private double passingEthanol;
	/**
	 * Represents the amount of liquid to be introduced each cycle (L/min).
	 */
	private double passingVolume;
	/**
	 * Represents the gender of the subject ('M' or 'F').
	 */
	private char gender;
	/**
	 * Represents the name and/or description of the subject.
	 */
	private String subjectName;
	/**
	 * Array of all the drinks consumed by the subject.
	 */
	private Drink drinks[];
	/**
	 * Represents the time that the subject started consuming their drinks.
	 */
	private DrinkTime start;
	/**
	 * Represents the time that the subject stopped consuming their drinks.
	 */
	private DrinkTime end;
	/**
	 * Represents the subject's stomach.
	 */
	private Stomach stomach;
	/**
	 * Represents the subject's small intestine.
	 */
	private Intestine intestine;
	/**
	 * Represents the subject's non-fatty body mass.
	 */
	private LeanBodyMass body;


	/**
	 * Constructor for the New Model subject class.
	 * @param age <code>int</code> representing the age of the subject in years.
	 * @param height <code>double</code> representing the height of the subject.
	 * @param gender <code>char</code> representing the gender of the subject.
	 * @param weight <code>double</code> representing the weight of the subject.
	 * @param drinks <code>Drink</code> array representing all the drinks drank
	 * 				 by the subject.
	 * @param start <code>DrinkTime</code> representing the time that the
	 * 				subject started drinking.
	 * @param end <code>DrinkTime</code> representing the time that the subject
	 * 				stopped drinking.
	 * @param subjectName <code>String</code> representing the subjects name and
	 * 				/ or description.
	 * @param bitFlag <code>int</code> representing all set switches.
	 */
	NMSubject(int age, double height, char gender, double weight, Drink [] drinks,
			  DrinkTime start, DrinkTime end, String subjectName, int bitFlag){

		this.age = age;
		this.gender = gender;
		this.drinks = drinks;
		this.start = start;
		this.end = end;
		this.subjectName = subjectName;
		this.bitFlag = bitFlag;
		this.peakBAC = 0.00;

		if ((bitFlag & _METRIC) != 0) {
			this.weight = weight;
			this.height = height;
		}
		else if ((bitFlag & _IMPERIAL) != 0) {
			this.weight = weight / 2.20462262; // lb to kg
			this.height = height * 2.54; // in to cm
		}

		if (this.gender == 'M'){
            this.TBW = (-0.09516 * (this.age)) + (0.1074 * (this.height)) + (0.3362 * (this.weight)) + 2.447;
        }
        else if (this.gender == 'F'){
            this.TBW = (0.1069 * (this.height)) + (0.2466 * (this.weight)) - 2.097;
        }
//System.out.println(this.TBW);

		for (int i=0; i < drinks.length; i++){
			this.totalVolume += drinks[i].getVolume() * 0.0295735297; // Liters
			this.totalEthanol += (drinks[i].getVolume() * (drinks[i].getConcentration()/100))
								 * 23.36; // Grams
		}
//System.out.println(this.totalVolume);
//System.out.println(this.totalEthanol);

		// if drinking period crosses 12 o' clock
		if ((end.getAMorPM() == DrinkTime._AM && start.getAMorPM() == DrinkTime._PM) ||
			(end.getAMorPM() == DrinkTime._PM && start.getAMorPM() == DrinkTime._AM)) {
			this.totalMinutes = (((12 - start.getHours()) * 60) + (0 - start.getMinutes()))
								+ (end.getHours() * 60) + end.getMinutes();
		} else {
			if (start.getHours() == 12)
                this.totalMinutes = (end.getHours() * 60) + (end.getMinutes() - start.getMinutes());
            else {
                this.totalMinutes = (end.getHours() - start.getHours()) * 60
								+ (end.getMinutes() - start.getMinutes());
			}
		}

		this.BACvalues = new double[this.totalMinutes];

		this.passingEthanol = this.totalEthanol / this.totalMinutes;
		this.passingVolume = this.totalVolume / this.totalMinutes;
//System.out.println(this.passingEthanol);
//System.out.println(this.passingVolume);

	}

	/**
	 * Retrieves the age of the subject.
	 * @return <code>int</code> representing the subject's age (yrs).
	 */
	public int getAge() {

		return this.age;

	}

	/**
	 * Retrieves the list of BACvalues recorded while the model was running.
	 * One value will have been recorded for each minute the subject was drinking.
	 * @return Array of <code>double</code>s containing the chronological list of
	 * BAC values.
	 */
	public double [] getBAC() {

		double BAC[] = new double[this.BACvalues.length];
		System.arraycopy(this.BACvalues, 0, BAC, 0, this.BACvalues.length);
		return BAC;

	}

	/**
	 * Retrieves the options set in the subject's bit flag.
	 * @return <code>int</code> representing the bit flag.
	 */
	public int getBitF() {

		return this.bitFlag;

	}

	/**
	 * Retrieves the list of drinks consumed by the subject.
	 * @return Array of <code>Drink</code>s containing the drink objects.
	 */
	public Drink [] getDrinks() {

		Drink drinks[] = new Drink[this.drinks.length];
		System.arraycopy(this.drinks, 0, drinks, 0, this.drinks.length);
		return drinks;

	}

	/**
	 * Retrieves the time when the subject stopped drinking.
	 * @return <code>DrinkTime</code> representing the end time.
	 */
	public DrinkTime getEndTime() {

		return this.end.getCopy();

	}

	/**
	 * Retrieves the height of the subject depending on the system of
	 * measurement selected.
	 * @return <code>double</code> representing the height (cm or in).
	 */
	public double getHeight() {

		if ((bitFlag & _METRIC) != 0) {
			return this.height;
		} else if ((bitFlag & _IMPERIAL) != 0) {
			return this.height / 2.54; // cm to in
		} else {
			return 0;
		}

	}
	/**
	 * Retrieves the gender of the subject.
	 * @return <code>char</code> representing gender ('M' or 'F').
	 */
	public char getGender() {

		return this.gender;

	}

	/**
	 * Retrieves the total number of minutes for the drinking time.
	 * @return <code>int</code> representing the total number of minutes.
	 */
	public int getMinutes() {

		return totalMinutes;

	}

	/**
	 * Retrieves the name and description of the subject.
	 * @return <code>String</code> containing the subject name.
	 */
	public String getName() {

		return this.subjectName;

	}

	/**
	 * Retrieves the peak BAC achieved during the drinking period.
	 * @return <code>double</code> representing the peak BAC.
	 */
	public double getPeakBAC() {

		return this.peakBAC;

	}

	/**
	 * Retrieves the time when the subject stopped drinking.
	 * @return <code>DrinkTime</code> representing the end time.
	 */
	public DrinkTime getStartTime() {

		return this.start.getCopy();

	}

	/**
	 * Retrieves the Total Body Water (TBW) for the subject.
	 * @return <code>double</code> representing the TBW.
	 */
    public double getTBW() {

	   return this.TBW;

	}

	/**
	 * Retrieves the weight of the subject depending on the system of
	 * measurement selected.
	 * @return <code>double</code> representing the weight (kg or lb).
	 */
	public double getWeight() {

		if ((bitFlag & _METRIC) != 0) {
			return this.weight;
		} else if ((bitFlag & _IMPERIAL) != 0) {
			return this.weight * 2.20462262; // cm to in
		} else {
			return 0;
		}

	}

	/**
	 * Checks to see if a new peak BAC has been reached.
	 */
	private void peakBAC() {

		if (this.peakBAC < (body.getContents() / this.TBW) / 10) {
			this.peakBAC = (body.getContents() / this.TBW) / 10;
		}

	}

	/**
	 * Runs the compartmental model.  This method should only be run if all
	 * values have already been declared and initialized, otherwise, unexpected
	 * results may occur.
	 * @return <code>boolean</code> value that represents whether the method
	 * 			executed successfully.  <code>true</code> if successful,
	 * 			otherwise, unsuccessful.
	 */
    public boolean runModel() {

		/*
		 * Initializes the stomach compartment.  Initial volume is equal to the
		 * gastric fluid already present (depending on meal).
		 */
		stomach = new Stomach(0.00,
			((bitFlag & NMSubject._NO_MEAL) != 0) ? .150 :
			((bitFlag & NMSubject._LIGHT_MEAL) != 0) ? .600 :
			((bitFlag & NMSubject._AVERAGE_MEAL) != 0) ? 1.000 :
			((bitFlag & NMSubject._LARGE_MEAL) != 0) ? 1.400 : 0.00);
		intestine = new Intestine(0.000, 0.000);
		int drinkingHistory = ((bitFlag & NMSubject._LIGHT_DRINKER) != 0) ? NMSubject._LIGHT_DRINKER :
		                      ((bitFlag & NMSubject._MODERATE_DRINKER) != 0) ? NMSubject._MODERATE_DRINKER :
		                      ((bitFlag & NMSubject._HEAVY_DRINKER) != 0) ? NMSubject._HEAVY_DRINKER : NMSubject._MODERATE_DRINKER;
		body = new LeanBodyMass(0.000, this.TBW, drinkingHistory);

		int min = 0;
		while (min < this.totalMinutes) {

			double firstPass = stomach.computeMMMetab() * .04607 * stomach.getLiquid(); // grams of ethanol metabolized.
			double toIntestine = stomach.computeEthanolRelease(); // grams of ethanol leaving through pyloric sphincter.
			double toBody = intestine.computeEthanolAbsorption(); // grams of ethanol leaving through villi
			double fromBody = body.computeMMMetab() * .04607 * this.TBW; // grams of ethanol metabolized.
//System.out.println(this.passingEthanol);
//System.out.println(this.passingVolume);
//System.out.println(firstPass);
//System.out.println(squirtVolume);
//System.out.println(toIntestine);
//System.out.println(stomach.getLiquid());
//System.out.println(intestine.getLiquid());
//System.out.println(stomach.getContents());
//System.out.println(intestine.getContents());
//System.out.println(intestine.getContents());
//System.out.println(body.getContents());

			// adjust stomach transfer values.
			stomach.addContents(this.passingEthanol);
			stomach.addLiquid(this.passingVolume);
			stomach.addContents(-firstPass);
			stomach.addLiquid(-this.squirtVolume);
			stomach.addContents(-toIntestine);

			// adjust intestine transfer values.
			intestine.addContents(toIntestine);
			intestine.addLiquid(this.squirtVolume);
			intestine.addContents(-toBody);
			intestine.addLiquid(-this.absorbVolume);

			// adjust body transfer values.
			body.addContents(toBody);
			body.addContents(-fromBody);

			BACvalues[min] = (body.getContents() / this.TBW) / 10;
			min++;
			this.peakBAC();

		}

    	return true;

    }

}