FlowStatistics.java [src/java/cfa] Revision: 94672bd672801bb1026090f5bf92961096fa875a  Date: Fri Jan 30 15:43:14 MST 2015
package cfa;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
* Last Updated: 29-January-2015
* @author Tyler Wible
* @since 29-June-2011
*/
public class FlowStatistics {
    //Gets
    public String getFlowStatistics_summary() {
        return "flow_statistics.csv";
    }
    /**
     * Calculates the statistics (min, max) for various numbers of consecutive 
     * days of daily data and other hydrologic indicators of alteration.
     * Additionally, this calculates the same statistics for each year within 
     * the dataset
     * @param mainFolder  the file path where the summary will be saved
     * @param stationID  the station ID for the current station, used to label the output
     * @param stationName  the station name for the current station, used to label the  output
     * @param flowData  the String[][] containing sorted data for the time series 
     * (column 1 = dates (yyyy-mm-dd) column 2 = value
     * @param highPercentile  the percentile value for defining a "high flow pulse"
     * typically greater than 75% = 0.75
     * @param lowPercentile  the percentile value for defining a "low flow pulse"
     * typically lower than 25% = 0.25
     * @param m  used to calculate an "m-day-average" flow
     * @param n  used to calculate an "m-day-average" low flow which has a
     * linearly interpolated recurrence interval of "n" years
     * @param showMonthlyTF  a flag to show monthly statistics (min/max/etc.) or not (which reduces the total output of the tool)
     * @param seasonBegin  the month and date (MM-dd) of the start of a seasonal analysis to be averaged over the period of data
     * @param seasonEnd  the month and date (MM-dd) of the end of a seasonal analysis to be averaged over the period of data
     * @param period1Begin  the begin date of the first period of analysis (yyyy-MM-dd), if blank ("") then no period 1 analysis is performed
     * @param period1End  the end date of the first period of analysis (yyyy-MM-dd), if blank ("") then no period 1 analysis is performed
     * @param period2Begin  the begin date of the second period of analysis (yyyy-MM-dd), if blank ("") then no period 2 analysis is performed
     * @param period2End  the end date of the first period of analysis (yyyy-MM-dd), if blank ("") then no period 2 analysis is performed
     * @param period3Begin  the begin date of the third period of analysis (yyyy-MM-dd), if blank ("") then no period 3 analysis is performed
     * @param period3End  the end date of the first period of analysis (yyyy-MM-dd), if blank ("") then no period 3 analysis is performed
     * @return  an Object[] where returnArray[0] = flow value of "m-day-average 
     * n-year recurrence low flow"
     * returnValue[1] = the error message (if any) associated with calculating 
     * the mQn flow
     * @throws IOException
     * @throws ParseException
     */
    public Object[] calculateAllStatisticsSummaries(String mainFolder,
                                                    String stationID,
                                                    String stationName,
                                                    String[][] flowData,
                                                    double highPercentile,
                                                    double lowPercentile,
                                                    int m,
                                                    int n,
                                                    boolean showMonthlyTF,
                                                    String seasonBegin,
                                                    String seasonEnd,
                                                    String period1Begin,
                                                    String period1End,
                                                    String period2Begin,
                                                    String period2End,
                                                    String period3Begin,
                                                    String period3End) throws IOException, ParseException{
        //Get today's date for output purposes
        Date currentDate = new Date();
        SimpleDateFormat desiredDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        String today = desiredDateFormat.format(currentDate);
        stationName = stationName.replace(",", "");
        
        //Initialize the summary result table
        int summarySize = 60;
        if(showMonthlyTF) summarySize = 180;
        
        String[][] statsSummaryTable = new String[summarySize][1];
        statsSummaryTable[0][0] = "Flow Statistics for " + stationID + "; " + stationName + "; created on " + today;
        statsSummaryTable[1][0] = "Analysis Period (calendar year)";
        statsSummaryTable[2][0] = "Maximum [cfs]";
        statsSummaryTable[3][0] = "Minimum [cfs]";
        statsSummaryTable[4][0] = "Upper Quartile [cfs]";
        statsSummaryTable[5][0] = "Lower Quartile [cfs]";
        statsSummaryTable[6][0] = "Median [cfs]";
        statsSummaryTable[7][0] = "Average [cfs]";
        statsSummaryTable[8][0] = "Standard Deviation [cfs]";
        statsSummaryTable[9][0] = "Variance";
        statsSummaryTable[10][0] = "Skewness";
        statsSummaryTable[11][0] = "Coefficient of Variation";
        statsSummaryTable[12][0] = "Maximum (1-day) [cfs]";
        statsSummaryTable[13][0] = "Date of Maximum (1-day)";
        statsSummaryTable[14][0] = "Minimum (1-day) [cfs]";
        statsSummaryTable[15][0] = "Date of Minimum (1-day)";
        statsSummaryTable[16][0] = "Maximum (3-day) [cfs]";
        statsSummaryTable[17][0] = "Dates of Maximum (3-day)";
        statsSummaryTable[18][0] = "Minimum (3-day) [cfs]";
        statsSummaryTable[19][0] = "Dates of Minimum (3-day)";
        statsSummaryTable[20][0] = "Maximum (7-day) [cfs]";
        statsSummaryTable[21][0] = "Dates of Maximum (7-day)";
        statsSummaryTable[22][0] = "Minimum (7-day) [cfs]";
        statsSummaryTable[23][0] = "Dates of Minimum (7-day)";
        statsSummaryTable[24][0] = "Minimum (7-day) / Annual Average  [cfs]";
        statsSummaryTable[25][0] = "Maximum (30-day) [cfs]";
        statsSummaryTable[26][0] = "Dates of Maximum (30-day)";
        statsSummaryTable[27][0] = "Minimum (30-day) [cfs]";
        statsSummaryTable[28][0] = "Dates of Minimum (30-day)";
        statsSummaryTable[29][0] = "Maximum (90-day) [cfs]";
        statsSummaryTable[30][0] = "Dates of Maximum (90-day)";
        statsSummaryTable[31][0] = "Minimum (90-day) [cfs]";
        statsSummaryTable[32][0] = "Dates of Minimum (90-day)";
        statsSummaryTable[33][0] = "Number of Zero Flow Days";
        statsSummaryTable[34][0] = "Number of Flow Reversals";
        statsSummaryTable[35][0] = "Number of Flow Rises";
        statsSummaryTable[36][0] = "Number of Flow Falls";
        statsSummaryTable[37][0] = "Number of High Pulses (> " + highPercentile + " percentile)";
        statsSummaryTable[38][0] = "Threshold for High Pulses (> " + highPercentile + " percentile) [cfs]";
        statsSummaryTable[39][0] = "Average Duration of High Pulses (> " + highPercentile + " percentile) [days]";
        statsSummaryTable[40][0] = "Number of Low Pulses (< " + lowPercentile + " percentile)";
        statsSummaryTable[41][0] = "Threshold for Low Pulses (< " + lowPercentile + " percentile) [cfs]";
        statsSummaryTable[42][0] = "Average Duration of Low Pulses (< " + lowPercentile + " percentile) [days]";
        statsSummaryTable[43][0] = "Average Positive Difference Between Consecutive Days [cfs]";
        statsSummaryTable[44][0] = "Average Negative Difference Between Consecutive Days [cfs]";
        statsSummaryTable[45][0] = "Temporal Centriod of Discharge [Day of Calendary Year]";
        statsSummaryTable[46][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Maximum) [cfs]";
        statsSummaryTable[47][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Minimum) [cfs]";
        statsSummaryTable[48][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Upper Quartile) [cfs]";
        statsSummaryTable[49][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Lower Quartile) [cfs]";
        statsSummaryTable[50][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Median) [cfs]";
        statsSummaryTable[51][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Average) [cfs]";
        statsSummaryTable[52][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Standard Deviation) [cfs]";
        statsSummaryTable[53][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Variance)";
        statsSummaryTable[54][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Skewness)";
        statsSummaryTable[55][0] = "Season " + seasonBegin + " to " + seasonEnd + " (Coefficient of Variation)";
        
        if(showMonthlyTF){
            statsSummaryTable[56][0] = "January (Maximum) [cfs]";
            statsSummaryTable[57][0] = "January (Minimum) [cfs]";
            statsSummaryTable[58][0] = "January (Upper Quartile) [cfs]";
            statsSummaryTable[59][0] = "January (Lower Quartile) [cfs]";
            statsSummaryTable[60][0] = "January (Median) [cfs]";
            statsSummaryTable[61][0] = "January (Average) [cfs]";
            statsSummaryTable[62][0] = "January (Standard Deviation) [cfs]";
            statsSummaryTable[63][0] = "January (Variance)";
            statsSummaryTable[64][0] = "January (Skewness)";
            statsSummaryTable[65][0] = "January (Coefficient of Variation)";
            statsSummaryTable[66][0] = "February (Maximum) [cfs]";
            statsSummaryTable[67][0] = "February (Minimum) [cfs]";
            statsSummaryTable[68][0] = "February (Upper Quartile) [cfs]";
            statsSummaryTable[69][0] = "February (Lower Quartile) [cfs]";
            statsSummaryTable[70][0] = "February (Median) [cfs]";
            statsSummaryTable[71][0] = "February (Average) [cfs]";
            statsSummaryTable[72][0] = "February (Standard Deviation) [cfs]";
            statsSummaryTable[73][0] = "February (Variance)";
            statsSummaryTable[74][0] = "February (Skewness)";
            statsSummaryTable[75][0] = "February (Coefficient of Variation)";
            statsSummaryTable[76][0] = "March (Maximum) [cfs]";
            statsSummaryTable[77][0] = "March (Minimum) [cfs]";
            statsSummaryTable[78][0] = "March (Upper Quartile) [cfs]";
            statsSummaryTable[79][0] = "March (Lower Quartile) [cfs]";
            statsSummaryTable[80][0] = "March (Median) [cfs]";
            statsSummaryTable[81][0] = "March (Average) [cfs]";
            statsSummaryTable[82][0] = "March (Standard Deviation) [cfs]";
            statsSummaryTable[83][0] = "March (Variance)";
            statsSummaryTable[84][0] = "March (Skewness)";
            statsSummaryTable[85][0] = "March (Coefficient of Variation)";
            statsSummaryTable[86][0] = "April (Maximum) [cfs]";
            statsSummaryTable[87][0] = "April (Minimum) [cfs]";
            statsSummaryTable[88][0] = "April (Upper Quartile) [cfs]";
            statsSummaryTable[89][0] = "April (Lower Quartile) [cfs]";
            statsSummaryTable[90][0] = "April (Median) [cfs]";
            statsSummaryTable[91][0] = "April (Average) [cfs]";
            statsSummaryTable[92][0] = "April (Standard Deviation) [cfs]";
            statsSummaryTable[93][0] = "April (Variance)";
            statsSummaryTable[94][0] = "April (Skewness)";
            statsSummaryTable[95][0] = "April (Coefficient of Variation)";
            statsSummaryTable[96][0] = "May (Maximum) [cfs]";
            statsSummaryTable[97][0] = "May (Minimum) [cfs]";
            statsSummaryTable[98][0] = "May (Upper Quartile) [cfs]";
            statsSummaryTable[99][0] = "May (Lower Quartile) [cfs]";
            statsSummaryTable[100][0] = "May (Median) [cfs]";
            statsSummaryTable[101][0] = "May (Average) [cfs]";
            statsSummaryTable[102][0] = "May (Standard Deviation) [cfs]";
            statsSummaryTable[103][0] = "May (Variance)";
            statsSummaryTable[104][0] = "May (Skewness)";
            statsSummaryTable[105][0] = "May (Coefficient of Variation)";
            statsSummaryTable[106][0] = "June (Maximum) [cfs]";
            statsSummaryTable[107][0] = "June (Minimum) [cfs]";
            statsSummaryTable[108][0] = "June (Upper Quartile) [cfs]";
            statsSummaryTable[109][0] = "June (Lower Quartile) [cfs]";
            statsSummaryTable[100][0] = "June (Median) [cfs]";
            statsSummaryTable[111][0] = "June (Average) [cfs]";
            statsSummaryTable[112][0] = "June (Standard Deviation) [cfs]";
            statsSummaryTable[113][0] = "June (Variance)";
            statsSummaryTable[114][0] = "June (Skewness)";
            statsSummaryTable[115][0] = "June (Coefficient of Variation)";
            statsSummaryTable[116][0] = "July (Maximum) [cfs]";
            statsSummaryTable[117][0] = "July (Minimum) [cfs]";
            statsSummaryTable[118][0] = "July (Upper Quartile) [cfs]";
            statsSummaryTable[119][0] = "July (Lower Quartile) [cfs]";
            statsSummaryTable[120][0] = "July (Median) [cfs]";
            statsSummaryTable[121][0] = "July (Average) [cfs]";
            statsSummaryTable[122][0] = "July (Standard Deviation) [cfs]";
            statsSummaryTable[123][0] = "July (Variance)";
            statsSummaryTable[124][0] = "July (Skewness)";
            statsSummaryTable[125][0] = "July (Coefficient of Variation)";
            statsSummaryTable[126][0] = "August (Maximum) [cfs]";
            statsSummaryTable[127][0] = "August (Minimum) [cfs]";
            statsSummaryTable[128][0] = "August (Upper Quartile) [cfs]";
            statsSummaryTable[129][0] = "August (Lower Quartile) [cfs]";
            statsSummaryTable[130][0] = "August (Median) [cfs]";
            statsSummaryTable[131][0] = "August (Average) [cfs]";
            statsSummaryTable[132][0] = "August (Standard Deviation) [cfs]";
            statsSummaryTable[133][0] = "August (Variance)";
            statsSummaryTable[134][0] = "August (Skewness)";
            statsSummaryTable[135][0] = "August (Coefficient of Variation)";
            statsSummaryTable[136][0] = "September (Maximum) [cfs]";
            statsSummaryTable[137][0] = "September (Minimum) [cfs]";
            statsSummaryTable[138][0] = "September (Upper Quartile) [cfs]";
            statsSummaryTable[139][0] = "September (Lower Quartile) [cfs]";
            statsSummaryTable[140][0] = "September (Median) [cfs]";
            statsSummaryTable[141][0] = "September (Average) [cfs]";
            statsSummaryTable[142][0] = "September (Standard Deviation) [cfs]";
            statsSummaryTable[143][0] = "September (Variance)";
            statsSummaryTable[144][0] = "September (Skewness)";
            statsSummaryTable[145][0] = "September (Coefficient of Variation)";
            statsSummaryTable[146][0] = "October (Maximum) [cfs]";
            statsSummaryTable[147][0] = "October (Minimum) [cfs]";
            statsSummaryTable[148][0] = "October (Upper Quartile) [cfs]";
            statsSummaryTable[149][0] = "October (Lower Quartile) [cfs]";
            statsSummaryTable[150][0] = "October (Median) [cfs]";
            statsSummaryTable[151][0] = "October (Average) [cfs]";
            statsSummaryTable[152][0] = "October (Standard Deviation) [cfs]";
            statsSummaryTable[153][0] = "October (Variance)";
            statsSummaryTable[154][0] = "October (Skewness)";
            statsSummaryTable[155][0] = "October (Coefficient of Variation)";
            statsSummaryTable[156][0] = "November (Maximum) [cfs]";
            statsSummaryTable[157][0] = "November (Minimum) [cfs]";
            statsSummaryTable[158][0] = "November (Upper Quartile) [cfs]";
            statsSummaryTable[159][0] = "November (Lower Quartile) [cfs]";
            statsSummaryTable[160][0] = "November (Median) [cfs]";
            statsSummaryTable[161][0] = "November (Average) [cfs]";
            statsSummaryTable[162][0] = "November (Standard Deviation) [cfs]";
            statsSummaryTable[163][0] = "November (Variance)";
            statsSummaryTable[164][0] = "November (Skewness)";
            statsSummaryTable[165][0] = "November (Coefficient of Variation)";
            statsSummaryTable[166][0] = "December (Maximum) [cfs]";
            statsSummaryTable[167][0] = "December (Minimum) [cfs]";
            statsSummaryTable[168][0] = "December (Upper Quartile) [cfs]";
            statsSummaryTable[169][0] = "December (Lower Quartile) [cfs]";
            statsSummaryTable[170][0] = "December (Median) [cfs]";
            statsSummaryTable[171][0] = "December (Average) [cfs]";
            statsSummaryTable[172][0] = "December (Standard Deviation) [cfs]";
            statsSummaryTable[173][0] = "December (Variance)";
            statsSummaryTable[174][0] = "December (Skewness)";
            statsSummaryTable[175][0] = "December (Coefficient of Variation)";
        }
        statsSummaryTable[statsSummaryTable.length - 4][0] = "Flow Statistics based on Indicators of Hydrologic Alteration from:";
        statsSummaryTable[statsSummaryTable.length - 3][0] = "B.D. Richter; J.V. Baumgartner; J. Powell; D.P. Braun. 1996. 'A Method For Assessing Hydrologic Aleration Within Ecosystems.' Conservation Biology 10(4): 1163-1174.";
        statsSummaryTable[statsSummaryTable.length - 2][0] = "B.D. Richter; J.V. Baumgartner; R. Wigington; D.P Braun. 1997. 'How Much Water Does A River Need?' Freshwater Biology. 37: 231-249.";
        statsSummaryTable[statsSummaryTable.length - 1][0] = "B.D. Richter; J.V. Baumgartner; D.P. Braun; J. Powell. 1998. 'A Spatial Assessment Of Hydrologic Alteration Within A River Network.' Regul. Rivers: Res. Mgmt. 14: 329-340.";
        
        //Calculate all data statistics
        //These are not annual statistics because the data can include more than 1 year
        String beginDate = flowData[0][0];
        String endDate = flowData[flowData.length - 1][0];
        Object[] resultArray = calculateFlowStatistics(statsSummaryTable, 
                                                       flowData,
                                                       highPercentile,
                                                       lowPercentile,
                                                       m,
                                                       "All: " + beginDate + " to " + endDate,
                                                       showMonthlyTF,
                                                       seasonBegin,
                                                       seasonEnd);
        statsSummaryTable = (String[][]) resultArray[0];
        //double mQnFlow_temp = (Double) resultArray[1];
        
        //Calculate data statistics for each period
        Arrays.sort(flowData, new DateComparator());
        if(!period1Begin.isEmpty() && !period1End.isEmpty()){
            Date period1Begin_date = desiredDateFormat.parse(period1Begin);
            Date period1End_date = desiredDateFormat.parse(period1End);
            
            ArrayList<String> period1_dates = new ArrayList<String>();
            ArrayList<String> period1_flows = new ArrayList<String>();
            for(int i=0; i<flowData.length; i++){
                Date newDate = desiredDateFormat.parse(flowData[i][0]);
                if(newDate.compareTo(period1Begin_date) >= 0 && newDate.compareTo(period1End_date) <= 0){
                    period1_dates.add(flowData[i][0]);
                    period1_flows.add(flowData[i][1]);
                }
            }
            String[][] period1Data = new String[period1_dates.size()][2];
            for(int i=0; i<period1_dates.size(); i++){
                period1Data[i][0] = period1_dates.get(i);
                period1Data[i][1] = period1_flows.get(i);
            }
            
            //Calculate Period 1 data statistics
            resultArray = calculateFlowStatistics(statsSummaryTable, 
                                                  period1Data, 
                                                  highPercentile, 
                                                  lowPercentile, 
                                                  m, 
                                                  "Period-1 Data: " + period1Begin + " to " + period1End, 
                                                  showMonthlyTF,
                                                  seasonBegin,
                                                  seasonEnd);
            statsSummaryTable = (String[][]) resultArray[0];
            //double mQnFlow_temp = (Double) resultArray[1];
        }
        if(!period2Begin.isEmpty() && !period2End.isEmpty()){
            Date period2Begin_date = desiredDateFormat.parse(period2Begin);
            Date period2End_date = desiredDateFormat.parse(period2End);
            
            ArrayList<String> period2_dates = new ArrayList<String>();
            ArrayList<String> period2_flows = new ArrayList<String>();
            for(int i=0; i<flowData.length; i++){
                Date newDate = desiredDateFormat.parse(flowData[i][0]);
                if(newDate.compareTo(period2Begin_date) >= 0 && newDate.compareTo(period2End_date) <= 0){
                    period2_dates.add(flowData[i][0]);
                    period2_flows.add(flowData[i][1]);
                }
            }
            String[][] period2Data = new String[period2_dates.size()][2];
            for(int i=0; i<period2_dates.size(); i++){
                period2Data[i][0] = period2_dates.get(i);
                period2Data[i][1] = period2_flows.get(i);
            }
            
            //Calculate Period 2 data statistics
            resultArray = calculateFlowStatistics(statsSummaryTable, 
                                                  period2Data, 
                                                  highPercentile, 
                                                  lowPercentile, 
                                                  m, 
                                                  "Period-2 Data: " + period2Begin + " to " + period2End, 
                                                  showMonthlyTF,
                                                  seasonBegin,
                                                  seasonEnd);
            statsSummaryTable = (String[][]) resultArray[0];
            //double mQnFlow_temp = (Double) resultArray[1];
        }
        if(!period3Begin.isEmpty() && !period3End.isEmpty()){
            Date period3Begin_date = desiredDateFormat.parse(period3Begin);
            Date period3End_date = desiredDateFormat.parse(period3End);
            
            ArrayList<String> period3_dates = new ArrayList<String>();
            ArrayList<String> period3_flows = new ArrayList<String>();
            for(int i=0; i<flowData.length; i++){
                Date newDate = desiredDateFormat.parse(flowData[i][0]);
                if(newDate.compareTo(period3Begin_date) >= 0 && newDate.compareTo(period3End_date) <= 0){
                    period3_dates.add(flowData[i][0]);
                    period3_flows.add(flowData[i][1]);
                }
            }
            String[][] period3Data = new String[period3_dates.size()][2];
            for(int i=0; i<period3_dates.size(); i++){
                period3Data[i][0] = period3_dates.get(i);
                period3Data[i][1] = period3_flows.get(i);
            }
            
            //Calculate Period 3 data statistics
            resultArray = calculateFlowStatistics(statsSummaryTable, 
                                                  period3Data, 
                                                  highPercentile, 
                                                  lowPercentile, 
                                                  m, 
                                                  "Period-3 Data: " + period3Begin + " to " + period3End, 
                                                  showMonthlyTF,
                                                  seasonBegin,
                                                  seasonEnd);
            statsSummaryTable = (String[][]) resultArray[0];
            //double mQnFlow_temp = (Double) resultArray[1];
        }
        
        //Calculate Flow statistics for each year in time period
        DoubleArray doubleArray =  new DoubleArray();
        ArrayList<Double> mQnFlows = new ArrayList<Double>();
        boolean moreYears = flowData.length > 0;
        String currentYear = flowData[0][0].substring(0,4);
        String finalYear = flowData[flowData.length - 1][0].substring(0,4);
        while(moreYears){
            //Get current year's data and calculate it's statistics
            String[][] partialData = doubleArray.getYearsData(flowData, currentYear);
            resultArray = calculateFlowStatistics(statsSummaryTable, 
                                                  partialData,
                                                  highPercentile,
                                                  lowPercentile,
                                                  m,
                                                  currentYear,
                                                  showMonthlyTF,
                                                  seasonBegin,
                                                  seasonEnd);
            statsSummaryTable = (String[][]) resultArray[0];
            double mQnFlow_temp = (Double) resultArray[1];
            mQnFlows.add(mQnFlow_temp);
            
            int nextYear = Integer.parseInt(currentYear) + 1;
            if(finalYear.compareToIgnoreCase(String.valueOf(nextYear)) >= 0){
                currentYear = String.valueOf(nextYear);
            }else{
                moreYears = false;
            }
        }
        
        //Call file writer for outputs fo flow statistics
        writeStatsSummaryFile(mainFolder, statsSummaryTable);
        
        //Calculate mQn (7Q10) Statistics
        double mQnFlow = 0;
        if(m != 0 && n != 0){
            mQnFlow = doubleArray.calculateReturnPeriod(mQnFlows, n);
        }
        String errorMessage = "";
        if(mQnFlow == 0){
            errorMessage = "There is no " + m + " consecutive day flow recorded so there was no " + m + "Q" + n + " flow value calculated.";
        }
        
        
        Object[] returnArray = {mQnFlow, errorMessage};
        return returnArray;
    }
    /**
     * calculates flow statistics for the provided flowData and appends the column 
     * of results to the statsSummaryTable under the provided header
     * @param statsSummaryTable  the summary table to add to
     * @param flowData  the String[][] containing sorted data for the time series 
     * (column 1 = dates (yyyy-mm-dd) column 2 = value
     * @param highPercentile  the percentile value for defining a "high flow pulse"
     * typically greater than 75% = 0.75
     * @param lowPercentile  the percentile value for defining a "low flow pulse"
     * typically lower than 25% = 0.25
     * @param m  used to calculate an "m-day-average" minimum flow
     * @param dataHeader  the text header for this column of result statistics in the statsSummaryTable (i.e. 'All Data' or '1998')
     * @param showMonthlyTF  a flag to show monthly statistics (min/max/etc.) or not (which reduces the total output of the tool)
     * @param seasonBegin  the month and date (MM-dd) of the start of a seasonal analysis to be averaged over the period of data
     * @param seasonEnd  the month and date (MM-dd) of the end of a seasonal analysis to be averaged over the period of data
     * @return  an Object[] containing the updated statsSummaryTable at returnObject[0] and the m-day-average minimum-flow at returnObject[1]
     * @throws IOException 
     */
    private Object[] calculateFlowStatistics(String[][] statsSummaryTable,
                                             String[][] flowData,
                                             double highPercentile,
                                             double lowPercentile,
                                             int m,
                                             String dataHeader,
                                             boolean showMonthlyTF,
                                             String seasonBegin,
                                             String seasonEnd) throws IOException{
        //Determine statistics
        DoubleMath doubleMath = new DoubleMath();
        DoubleArray doubleArray = new DoubleArray();
        
        //Sort Data consecutively
        Arrays.sort(flowData, new DateComparator());
        
        //Get/Store data for seasonal statistics
        String[][] seasonalData_String = doubleArray.getSeasonalData(flowData, seasonBegin, seasonEnd);
        ArrayList<Double> seasonal_data = new ArrayList<Double>();
        for(int i=0; i<seasonalData_String.length; i++){
            double value = Double.parseDouble(seasonalData_String[i][1]);
            seasonal_data.add(value);
        }
        
        //Set up monthly data arrays
        ArrayList<Double> jan_data = new ArrayList<Double>();
        ArrayList<Double> feb_data = new ArrayList<Double>();
        ArrayList<Double> mar_data = new ArrayList<Double>();
        ArrayList<Double> apr_data = new ArrayList<Double>();
        ArrayList<Double> may_data = new ArrayList<Double>();
        ArrayList<Double> jun_data = new ArrayList<Double>();
        ArrayList<Double> jul_data = new ArrayList<Double>();
        ArrayList<Double> aug_data = new ArrayList<Double>();
        ArrayList<Double> sep_data = new ArrayList<Double>();
        ArrayList<Double> oct_data = new ArrayList<Double>();
        ArrayList<Double> nov_data = new ArrayList<Double>();
        ArrayList<Double> dec_data = new ArrayList<Double>();
        
        //Calculate 1-day statistics
        ArrayList<Double> allData = new ArrayList<Double>();
        ArrayList<Double> diffPositive = new ArrayList<Double>();
        ArrayList<Double> diffNegative = new ArrayList<Double>();
        double max_1day = -9999;
        double min_1day = 9999;
        double oldValue = 0;
        String max_1day_date = "-1";
        String min_1day_date = "-1";
        boolean increasing = false;
        int ctr_zero = 0, ctr_rises = 0, ctr_falls = 0, ctr_reversals = 0;
        String date1 = "1900-01-01";
        double centroidSum = 0, sum = 0;
        for(int i=0; i<flowData.length; i++){
            String date2 = flowData[i][0];
            double month = Double.parseDouble(flowData[i][0].substring(5,7));
            int month_int =  (int) month;
            double value = Double.parseDouble(flowData[i][1]);
            allData.add(value);
            
            //Store data for monthly averages
            switch (month_int) {
                case 1:  jan_data.add(value); break;
                case 2:  feb_data.add(value); break;
                case 3:  mar_data.add(value); break;
                case 4:  apr_data.add(value); break;
                case 5:  may_data.add(value); break;
                case 6:  jun_data.add(value); break;
                case 7:  jul_data.add(value); break;
                case 8:  aug_data.add(value); break;
                case 9:  sep_data.add(value); break;
                case 10:  oct_data.add(value); break;
                case 11:  nov_data.add(value); break;
                case 12:  dec_data.add(value); break;
                default: break;
            }
            
            //Calculate Max
            if(max_1day < value){
                max_1day_date = date2;
                max_1day = value;
            }
            
            //Calculate Min
            if(min_1day > value){
                min_1day_date = date2;
                min_1day = value;
            }
            
            //Count zero flow days
            if(value == 0){
                ctr_zero++;
            }
            
            //Calculate difference
            if(doubleArray.checkSubsequentDates(date1, date2)){
                double diff = value - oldValue;
                if(diff > 0){
                    diffPositive.add(diff);
                }else if(diff < 0){
                    diffNegative.add(diff);
                }

                //Calculate flow reversals for consecutive days only
                if(value > oldValue){
                    if(!increasing){
                        ctr_reversals++;
                        ctr_rises++;
                        increasing = true;
                    }
                }else if(value < oldValue){
                    if(increasing){
                        ctr_reversals++;
                        ctr_falls++;
                        increasing = false;
                    }
                }
            }
            
            //Calculate centriod values
            double currentYear_double = Double.parseDouble(flowData[i][0].substring(0,4));
            double currentMonth_double = Double.parseDouble(flowData[i][0].substring(5,7));
            double currentDay_double = Double.parseDouble(flowData[i][0].substring(8));
            int currentYear = (int) currentYear_double;
            int currentMonth = (int) currentMonth_double;
            int currentDay = (int) currentDay_double;
            Calendar currentDate = new GregorianCalendar(currentYear, currentMonth - 1, currentDay);
            int dayOfYear = currentDate.get(Calendar.DAY_OF_YEAR);
            centroidSum = centroidSum + dayOfYear * value;
            sum = sum + value;
            
            //Reset yesterday's flow value
            oldValue = value;
            date1 = date2;
        }
        double mean_all = doubleMath.meanArithmetic(allData);
        double centroid = centroidSum / sum;
        
        //Calculate 3-day statistics
        Object[] resultArray = doubleArray.getMdayData(flowData, 3, "arithmetic");
        ArrayList<String> average_3day_date = (ArrayList<String>) resultArray[0];
        ArrayList<Double> average_3day = (ArrayList<Double>) resultArray[1];
        double max_3day = doubleMath.max(average_3day);
        double min_3day = doubleMath.min(average_3day);
        String max_3day_date = getDateOfValue(average_3day_date, average_3day, max_3day);
        String min_3day_date = getDateOfValue(average_3day_date, average_3day, min_3day);
        
        //Calculate 7-day statistics
        resultArray = doubleArray.getMdayData(flowData, 7, "arithmetic");
        ArrayList<String> average_7day_date = (ArrayList<String>) resultArray[0];
        ArrayList<Double> average_7day = (ArrayList<Double>) resultArray[1];
        double max_7day = doubleMath.max(average_7day);
        double min_7day = doubleMath.min(average_7day);
        double min_7day_ave = doubleMath.round(min_7day/mean_all,3);
        String max_7day_date = getDateOfValue(average_7day_date, average_7day, max_7day);
        String min_7day_date = getDateOfValue(average_7day_date, average_7day, min_7day);
        
        //Calculate 30-day statistics
        resultArray = doubleArray.getMdayData(flowData, 30, "arithmetic");
        ArrayList<String> average_30day_date = (ArrayList<String>) resultArray[0];
        ArrayList<Double> average_30day = (ArrayList<Double>) resultArray[1];
        double max_30day = doubleMath.max(average_30day);
        double min_30day = doubleMath.min(average_30day);
        String max_30day_date = getDateOfValue(average_30day_date, average_30day, max_30day);
        String min_30day_date = getDateOfValue(average_30day_date, average_30day, min_30day);
        
        //Calculate 90-day statistics
        resultArray = doubleArray.getMdayData(flowData, 90, "arithmetic");
        ArrayList<String> average_90day_date = (ArrayList<String>) resultArray[0];
        ArrayList<Double> average_90day = (ArrayList<Double>) resultArray[1];
        double max_90day = doubleMath.max(average_90day);
        double min_90day = doubleMath.min(average_90day);
        String max_90day_date = getDateOfValue(average_90day_date, average_90day, max_90day);
        String min_90day_date = getDateOfValue(average_90day_date, average_90day, min_90day);
        
        //Calculate mQn (7Q10)Statistics
        double min_Mday = 0;
        if(m != 0){
            resultArray = doubleArray.getMdayData(flowData, m, "arithmetic");
            ArrayList<Double> average_Mday = (ArrayList<Double>) resultArray[1];
            min_Mday = doubleMath.min(average_Mday);
        }
        
        //Calculate Pulse Information
        double highLimit = doubleMath.Percentile_function(allData, highPercentile);
        double lowLimit = doubleMath.Percentile_function(allData, lowPercentile);
        ArrayList<Double> highPulses = new ArrayList<Double>();
        ArrayList<Double> lowPulses = new ArrayList<Double>();
        int ctr_highPulse = 0, ctr_lowPulse = 0;
        double duration_highPulse = 0, duration_lowPulse = 0;
        boolean highPulseTF = false, lowPulseTF = false;
        oldValue = 0;
        date1 = "";
        for(int i=0; i<flowData.length; i++){
            //Check for consecutive days of flow data
            String date2 = flowData[i][0];
            double value = Double.parseDouble(flowData[i][1]);
            if(i>0){
                if(doubleArray.checkSubsequentDates(date1, date2)){
                    //Calculate high pulse information
                    if(value > highLimit){
                        if(!highPulseTF){
                            //If on a new highPulse add to the counters
                            ctr_highPulse++;
                            duration_highPulse = 1;
                            highPulseTF = true;
                        }else{
                            //If still on a highPulse, add to the counter
                            duration_highPulse = duration_highPulse + 1;
                        }
                    }else{
                        //If no longer on a highPulse, store the results, reset the counters
                        if(highPulseTF){
                            highPulses.add(duration_highPulse);
                            highPulseTF = false;
                        }
                    }

                    //Calculate low pulse information
                    if(value < lowLimit){
                        if(!lowPulseTF){
                            //If on a new lowPulse add to the counters
                            ctr_lowPulse++;
                            duration_lowPulse = 1;
                            lowPulseTF = true;
                        }else{
                            //If still on a lowPulse, add to the counter
                            duration_lowPulse = duration_lowPulse + 1;
                        }
                    }else{
                        //If no longer on a lowPulse, store the results, reset the counters
                        if(lowPulseTF){
                            lowPulses.add(duration_lowPulse);
                            lowPulseTF = false;
                        }
                    }
                }else{
                    //If no longer on consecutive days, store the results (if previously on a pulse) and reset the counters
                    if(highPulseTF){
                        highPulses.add(duration_highPulse);
                        highPulseTF = false;
                    }
                    if(lowPulseTF){
                        lowPulses.add(duration_lowPulse);
                        lowPulseTF = false;
                    }
                }
            }
            //Reset yesterday's flow value
            oldValue = value;
            date1 = date2;
        }
        
        
        //Build Flow Statistics Summary for output
        int summarySize = 60;
        if(showMonthlyTF) summarySize = 180;
        String[] additionalSummary = new String[summarySize];
        additionalSummary[0] = "";//blank
        additionalSummary[1] = dataHeader;//Method
        additionalSummary[2] = String.valueOf(doubleMath.round(doubleMath.max(allData), 3));//Maximum, overall
        additionalSummary[3] = String.valueOf(doubleMath.round(doubleMath.min(allData), 3));//Minimum, overall
        additionalSummary[4] = String.valueOf(doubleMath.round(doubleMath.Percentile_function(allData,0.75), 3));//Upper Quartile, overall
        additionalSummary[5] = String.valueOf(doubleMath.round(doubleMath.Percentile_function(allData,0.25), 3));//Lower Quartile, overall
        additionalSummary[6] = String.valueOf(doubleMath.round(doubleMath.Median(allData), 3));//Median, overall
        additionalSummary[7] = String.valueOf(doubleMath.round(doubleMath.meanArithmetic(allData), 3));//Average, overall
        additionalSummary[8] = String.valueOf(doubleMath.round(doubleMath.StandardDeviationSample(allData), 3));//Standard Deviation, overall
        additionalSummary[9] = String.valueOf(doubleMath.round(doubleMath.VarianceSample(allData), 3));//Variance, overall
        additionalSummary[10] = String.valueOf(doubleMath.round(doubleMath.SkewnessSample(allData), 3));//Skewness, overall
        additionalSummary[11] = String.valueOf(doubleMath.round(doubleMath.CoefficientOfVariation(allData), 3));//Coefficient of Variation, overall
        additionalSummary[12] = String.valueOf(doubleMath.round(max_1day,3));//Maximum (1-day)
        additionalSummary[13] = max_1day_date;//Date of Maximum (1-day)
        additionalSummary[14] = String.valueOf(doubleMath.round(min_1day,3));//Minimum (1-day)
        additionalSummary[15] = min_1day_date;//Date of Minimum (1-day)
        additionalSummary[16] = String.valueOf(doubleMath.round(max_3day,3));//Maximum (3-day)
        additionalSummary[17] = max_3day_date;//Dates of Maximum (3-day)
        additionalSummary[18] = String.valueOf(doubleMath.round(min_3day,3));//Minimum (3-day)
        additionalSummary[19] = min_3day_date;//Dates of Minimum (3-day)
        additionalSummary[20] = String.valueOf(doubleMath.round(max_7day,3));//Maximum (7-day)
        additionalSummary[21] = max_7day_date;//Dates of Maximum (7-day)
        additionalSummary[22] = String.valueOf(doubleMath.round(min_7day,3));//Minimum (7-day)
        additionalSummary[23] = min_7day_date;//Dates of Minimum (7-day)
        additionalSummary[24] = String.valueOf(doubleMath.round(min_7day_ave,3));//Minimum (7-day)
        additionalSummary[25] = String.valueOf(doubleMath.round(max_30day,3));//Maximum (30-day)
        additionalSummary[26] = max_30day_date;//Dates of Maximum (30-day)
        additionalSummary[27] = String.valueOf(doubleMath.round(min_30day,3));//Minimum (30-day)
        additionalSummary[28] = min_30day_date;//Dates of Minimum (30-day)
        additionalSummary[29] = String.valueOf(doubleMath.round(max_90day,3));//Maximum (90-day)
        additionalSummary[30] = max_90day_date;//Dates of Maximum (90-day)
        additionalSummary[31] = String.valueOf(doubleMath.round(min_90day,3));//Minimum (90-day)
        additionalSummary[32] = min_90day_date;//Dates of Minimum (90-day)
        additionalSummary[33] = String.valueOf(ctr_zero);//Number of Zero Flow Days
        additionalSummary[34] = String.valueOf(ctr_reversals);//Number of Flow Reversals
        additionalSummary[35] = String.valueOf(ctr_rises);//Number of Flow Rises
        additionalSummary[36] = String.valueOf(ctr_falls);//Number of Flow Falls
        additionalSummary[37] = String.valueOf(ctr_highPulse);//Number of High Pulses
        additionalSummary[38] = String.valueOf(doubleMath.round(highLimit,1));//Threshold for High Pulses
        additionalSummary[39] = String.valueOf(doubleMath.round(doubleMath.meanArithmetic(highPulses), 3));//Average Duration of High Pulses
        additionalSummary[40] = String.valueOf(ctr_lowPulse);//Number of Low Pulses
        additionalSummary[41] = String.valueOf(doubleMath.round(lowLimit,1));//Threshold for Low Pulses
        additionalSummary[42] = String.valueOf(doubleMath.round(doubleMath.meanArithmetic(lowPulses), 3));//Average Duration of Low Pulses
        additionalSummary[43] = String.valueOf(doubleMath.round(doubleMath.meanArithmetic(diffPositive),3));//Average Positive Difference Between Consecutive Days
        additionalSummary[44] = String.valueOf(doubleMath.round(doubleMath.meanArithmetic(diffNegative),3));//Average Negative Difference Between Consecutive Days
        additionalSummary[45] = String.valueOf(doubleMath.round(centroid,2));//Temporal centroid of annual discharge (Julian day, not water-year day)
        
        //Add seasonal stats summary
        int index = 46;
        resultArray = addSimpleStatsSummary(additionalSummary, seasonal_data, index);
        additionalSummary = (String[]) resultArray[0];
        index = (int) resultArray[1];
        
        if(showMonthlyTF){
            //January
            resultArray = addSimpleStatsSummary(additionalSummary, jan_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //February
            resultArray = addSimpleStatsSummary(additionalSummary, feb_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //March
            resultArray = addSimpleStatsSummary(additionalSummary, mar_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //April
            resultArray = addSimpleStatsSummary(additionalSummary, apr_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //May
            resultArray = addSimpleStatsSummary(additionalSummary, may_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //June
            resultArray = addSimpleStatsSummary(additionalSummary, jun_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //July
            resultArray = addSimpleStatsSummary(additionalSummary, jul_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //August
            resultArray = addSimpleStatsSummary(additionalSummary, aug_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //September
            resultArray = addSimpleStatsSummary(additionalSummary, sep_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //October
            resultArray = addSimpleStatsSummary(additionalSummary, oct_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //November
            resultArray = addSimpleStatsSummary(additionalSummary, nov_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            //December
            resultArray = addSimpleStatsSummary(additionalSummary, dec_data, index);
            additionalSummary = (String[]) resultArray[0];
            index = (int) resultArray[1];
            
            //References section blanks
            additionalSummary[index] = "";//blank
            additionalSummary[index + 1] = "";//blank
            additionalSummary[index + 2] = "";//blank
            additionalSummary[index + 3] = "";//blank
        }else{
            //References section blanks
            additionalSummary[index] = "";//blank
            additionalSummary[index + 1] = "";//blank
            additionalSummary[index + 2] = "";//blank
            additionalSummary[index + 3] = "";//blank
        }
        
        //Add these statistics to the existing results
        statsSummaryTable = doubleArray.appendcolumn_Matrix(statsSummaryTable, additionalSummary);
        
        Object[] returnArray = {statsSummaryTable, min_Mday};
        return returnArray;
    }
    /**
     * 
     * @param statsSummaryTable
     * @param data
     * @param index
     * @return 
     */
    private Object[] addSimpleStatsSummary(String[] statsSummaryTable, ArrayList<Double> data, int index){
        DoubleMath doubleMath = new DoubleMath();

        statsSummaryTable[index] = String.valueOf(doubleMath.round(doubleMath.max(data), 3));//Maximum
        statsSummaryTable[index + 1] = String.valueOf(doubleMath.round(doubleMath.min(data), 3));//Minimum
        statsSummaryTable[index + 2] = String.valueOf(doubleMath.round(doubleMath.Percentile_function(data, 0.75), 3));//Upper Quartile
        statsSummaryTable[index + 3] = String.valueOf(doubleMath.round(doubleMath.Percentile_function(data,0.25), 3));//Lower Quartile
        statsSummaryTable[index + 4] = String.valueOf(doubleMath.round(doubleMath.Median(data), 3));//Median
        statsSummaryTable[index + 5] = String.valueOf(doubleMath.round(doubleMath.meanArithmetic(data), 3));//Average
        statsSummaryTable[index + 6] = String.valueOf(doubleMath.round(doubleMath.StandardDeviationSample(data), 3));//Standard Deviation
        statsSummaryTable[index + 7] = String.valueOf(doubleMath.round(doubleMath.VarianceSample(data), 3));//Variance
        statsSummaryTable[index + 8] = String.valueOf(doubleMath.round(doubleMath.SkewnessSample(data), 3));//Skewness
        statsSummaryTable[index + 9] = String.valueOf(doubleMath.round(doubleMath.CoefficientOfVariation(data), 3));//January (Coefficient of Variation
        
        index = index + 10;
        Object[] returnArray = {statsSummaryTable, index};
        return returnArray;
    }
    /**
     * Loop through the provided data array and gets the corresponding index to 'value' and returns that index from the dates array
     * @param dates  a list of dates
     * @param data  a list of values corresponding to the dates in the above list
     * @param value  a value
     * @return the date corresponding to the provided value
     */
    private String getDateOfValue(ArrayList<String> dates, ArrayList<Double> data, double value){
        String dateOfValue = "null";
        for(int i=0; i<dates.size(); i++){
            if(data.get(i) == value){
                dateOfValue = dates.get(i);
                break;
            }
        }
        return dateOfValue;
    }
    /**
     * Writes the provided array to a CSV result file
     * @param mainFolder  the folder location for the result file
     * @param statsSummary  the desired contents of the file
     * @throws IOException 
     */
    public void writeStatsSummaryFile(String mainFolder, String[][] statsSummary) throws IOException{
        //Open a file writer for the summary of the flow statistcs per year
        String path = mainFolder + File.separator + getFlowStatistics_summary();
        FileWriter newFile =  new FileWriter(path, false);
        PrintWriter writer = new PrintWriter(newFile);
        
        for(int i=0; i<statsSummary.length; i++){
            String currentLine = statsSummary[i][0];
            for(int j=1; j<statsSummary[i].length; j++){
                currentLine = currentLine + "," + statsSummary[i][j];
            }
            writer.printf("%s" + "\r\n", currentLine);
        }
        
        //Close file writer
        newFile.close();
        writer.close();
        System.out.println("Text File located at:\t" + path);
    }
}