package edu.ncsu.csc.itrust.action;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

import edu.ncsu.csc.itrust.beans.CDCStatsBean;
import edu.ncsu.csc.itrust.dao.DAOFactory;
import edu.ncsu.csc.itrust.dao.mysql.CDCBmiStatsDAO;
import edu.ncsu.csc.itrust.dao.mysql.CDCHeadCircStatsDAO;
import edu.ncsu.csc.itrust.dao.mysql.CDCHeightStatsDAO;
import edu.ncsu.csc.itrust.dao.mysql.CDCStatsDAO;
import edu.ncsu.csc.itrust.dao.mysql.CDCWeightStatsDAO;
import edu.ncsu.csc.itrust.exception.DBException;

/**
 * UploadReferenceTablesAction is an action class that is used for uploading reference tables. It reads 
 * in csv files and parses and loads them to a specified reference table. Contains methods for storing 
 * csv files for weight, height, bmi, and head circumference statistics. Each store method also verifies 
 * whether the passed in file is correctly formatted.
 *
 */
public class UploadReferenceTablesAction {
	/**
	 * Regex pattern to match the header format of a CDC health stats csv. Checks that header contains 
	 * at least sex, age, L, M, and S fields
	 */
	private static final String headerFormat = "Sex,Agemos,L,M,S.*";
	/**
	 * Regex pattern to match each row of data in a CDC health stats csv. Checks that header contains at 
	 * least sex, age, L, M, and S fields
	 */
	private static final String statsDataFormat = "\\d,\\d+\\.?\\d?(,-?\\d+\\.\\d+){3}(,-?\\d+\\.\\d+)*";
	
	private CDCWeightStatsDAO weightStatsDAO;
	private CDCHeightStatsDAO heightStatsDAO;
	private CDCHeadCircStatsDAO headCircStatsDAO;
	private CDCBmiStatsDAO bmiStatsDAO;
	
	/**
	 * Constructor for UploadReferenceTablesAction. Reads in DAOFactory and initializes 
	 * CDCStatsDAO fields with the factory.
	 * @param factory the DAOfactory to use for database transactions
	 */
	public UploadReferenceTablesAction(DAOFactory factory) {
		weightStatsDAO = new CDCWeightStatsDAO(factory);
		heightStatsDAO = new CDCHeightStatsDAO(factory);
		headCircStatsDAO = new CDCHeadCircStatsDAO(factory);
		bmiStatsDAO = new CDCBmiStatsDAO(factory);
	}
	
	/**
	 * Parses a CSV file and stores the statistics for average patient weights
	 * @param weightCSV InputStream object with weight statistics csv file
	 * @return true if data is stored correctly in the database.
	 * 		   false if csv file is of the incorrect format and cannot be stored
	 */
	public boolean storeWeightStats(InputStream weightCSV) {
		return storeCDCStats(weightCSV, weightStatsDAO);
	}
	
	/**
	 * Parses a CSV file and stores the statistics for average patient heights/lengths
	 * @param heightCSV InputStream object with height statistics csv file
	 * @return true if data is stored correctly in the database.
	 * 		   false if csv file is of the incorrect format and cannot be stored
	 */
	public boolean storeHeightStats(InputStream heightCSV) {
		return storeCDCStats(heightCSV, heightStatsDAO);
	}
	
	/**
	 * Parses a CSV file and stores the statistics for average patient head circumferences
	 * @param  @param headCircCSV InputStream object with head circumference statistics csv file
	 * @return true if data is stored correctly in the database.
	 * 		   false if csv file is of the incorrect format and cannot be stored
	 */
	public boolean storeHeadCircStats(InputStream headCircCSV) {
		return storeCDCStats(headCircCSV, headCircStatsDAO);
	}
	
	/**
	 * Parses a CSV file and stores the statistics for average patient BMIs
	 * @param bmiCSV InputStream object with bmi statistics csv file
	 * @return true if data is stored correctly in the database.
	 * 		   false if csv file is of the incorrect format and cannot be stored
	 */
	public boolean storeBMIStats(InputStream bmiCSV) {
		return storeCDCStats(bmiCSV, bmiStatsDAO);
	}
	
	/**
	 * Parses a csv file and sends it to the CDCStatsDAO that is passed in to store the data. 
	 * First verifies whether the csv file is formatted correctly. If the file is of the correct
	 * format then the file is parsed and passed into the specified CDCStatsDAO for storing.
	 * @param healthStatsCSV InputStream with the csv file containing the health statistics to 
	 * store in the database
	 * @param dao the CDCStatsDAO to use to store the data from the csv file
	 * @return true if data is stored correctly in the database.
	 * 		   false if csv file is of the incorrect format and cannot be stored
	 */
	@SuppressWarnings("resource")
	private boolean storeCDCStats(InputStream healthStatsCSV, CDCStatsDAO dao) {
		BufferedInputStream csvFile = new BufferedInputStream(healthStatsCSV);
		try {
			csvFile.mark(csvFile.available() + 1);
			//If the csv file cannot be verified, close the InputStream and return false
			if (!verifyCDCStatsCSV(csvFile)) {
				csvFile.close();
				return false;
			}
			csvFile.reset();
		} catch (IOException e) {
			return false;
		}
		
		//Create scanner to read each line of data in the csv
		Scanner csvScanner = new Scanner(csvFile, "UTF-8");
		//Scanner to parse through each row of data
		Scanner rowScanner = null;
		//String for saving a row of data
		String row = "";
		
		//Scan and throw away the header line.
		csvScanner.nextLine();
		
		//CDCStatsBean for storing health metric statistics taken from the csv file
		CDCStatsBean statsBean = null;
		
		try {
			while (csvScanner.hasNextLine()) {
				//Create new CDCStatsBean for storing a new row of data
				statsBean = new CDCStatsBean();
				
				//Read a row of data
				row = csvScanner.nextLine();
				
				//Read the next row if row is a header
				if (row.matches(headerFormat))
					continue;
				
				//Create scanner to parse each row by using commas as the delimiter
				rowScanner = new Scanner(row).useDelimiter(",");
				//Get the sex field
				statsBean.setSex(rowScanner.nextInt());
				//Get the age field
				statsBean.setAge(rowScanner.nextFloat());
				//Get the L field
				statsBean.setL(rowScanner.nextDouble());
				//Get the M field
				statsBean.setM(rowScanner.nextDouble());
				//Get the S field
				statsBean.setS(rowScanner.nextDouble());
				
				//Insert CDCStatsBean into the database
				dao.storeStats(statsBean);
			}
		} catch (DBException e) {
			//If there is a DBException close the InputStream and 
			//return false since not all the data has been added correctly
			try {
				healthStatsCSV.close();
			} catch (IOException e1) {
				//Still return false if I/O error occurs
			}
			return false;
		}
		
		return true;	
	}
	
	/**
	 * Verifies that a health stats csv file is of the correct format. Checks that the header is formatted
	 * correctly, and then checks that each data row contains only integers, doubles, and commas
	 * @param healthStatsCSV InputStream with the csv file whose format needs to be checked
	 * @return true if the csv file is correctly formatted. false otherwise.
	 */
	@SuppressWarnings("resource")
	private boolean verifyCDCStatsCSV(InputStream healthStatsCSV) {
		//Create scanner to read each line of data in the csv
		Scanner csvScanner = new Scanner(healthStatsCSV, "UTF-8");
		//String for holding a line of data from the CSV file
		String line = "";
		
		//Read the header line
		if (csvScanner.hasNextLine()) {
			line = csvScanner.nextLine();
		} else {
			csvScanner.close();
			//If the csv file does not contain a line, the csv file is incorrect
			return false;
		}
		//If the header line is incorrectly formatted then the csv file is incorrect
		if (!line.matches(headerFormat)) {
			csvScanner.close();
			return false;
		}
		
		//Verify that each consecutive line follows correct formatting
		while (csvScanner.hasNextLine()) {
			line = csvScanner.nextLine();
			//If none of the lines match a data line format nor a header line
			//format then the csv file is incorrect
			if (!line.matches(statsDataFormat) && !line.matches(headerFormat)) {
				csvScanner.close();
				return false;
			}
		}
		
		//The csv file passes the verification process it is deemed correct
		return true;
	}
}