/*
 *  Draco Econometrics
 *  Copyright (C) 2006-2007 Approximatrix, LLC
 *  http://www.approximatrix.com
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.approximatrix.draco.datamodel;

import java.lang.NumberFormatException;

public class ParseDate {

	public static DateObject ParseTextToDate(String text, int type) {

		int year = 0;
		int quarter = 0;
		int month = 0;
		int monthhalf = 0;
		int day = 0;
		int week = 0;
		
		// First separate the string into date components
		String[] date_components = text.split("[\\s , / - :]+");
		
		// Get the year - it must always be present
		year = GetYear(date_components);
		
		// Select the type
		switch(type) {
			case DateObject.DAILY:
				day = GetDay(date_components);
			case DateObject.MONTHLY:
				month = GetMonth(date_components);
				break;
			case DateObject.BIMONTHLY:
				monthhalf = GetMonthHalf(date_components);	
				month = GetMonth(date_components);
				break;
			case DateObject.WEEKLY:
				week = GetWeek(date_components);
				break;
			case DateObject.QUARTERLY:
				quarter = GetQuarter(date_components);
				break;
		}
		
		return new DateObject(type, year, quarter, month, monthhalf, week, day);
	}

	private static int GetYear(String[] date_components) {
		
		// If only one entry, assume it's a year
		if(date_components.length == 1) return new Integer(date_components[0]).intValue();
		
		int[] values = new int[date_components.length];
		for(int i=0;i<values.length;i++) {
			try {
				values[i] = new Integer(date_components[i]).intValue();
			} catch(NumberFormatException nfe) {
				values[i] = Integer.MIN_VALUE;
			}
		}
		
		// Now we have an array of numbers, try and intelligently determine where
		// the year is in the array.  It will probably be an end.
		int[] possibilities = new int[2];
		possibilities[0] = values[0];
		possibilities[1] = values[values.length-1];
		
		// If either is NaN, assume the other is the year
		if(possibilities[0] == Integer.MIN_VALUE) {
			date_components[date_components.length-1] = null;
			return possibilities[1];
		} 
		else if(possibilities[1] == Integer.MIN_VALUE) {
			date_components[0] = null;
			return possibilities[0];
		}
			
		// So both are numbers, if one is greater than 52, it is definitely a year
		// Negative implies BC and no other component could be negative
		if(possibilities[0] > 52 || possibilities[0] < 0) {
			date_components[0] = null;
			return possibilities[0];
		}
		else if(possibilities[1] > 52 || possibilities[1] < 0) {
			date_components[date_components.length-1] = null;
			return possibilities[1];
		}
			
		// Ok, so both posibilities are less than 52, so it could be a day, month, week,
		// or year.  Assume by default it is the last value
		date_components[date_components.length-1] = null;
		return possibilities[1];
		
	}
	
	private static int GetMonth(String[] date_components) {
	
		// At this point it should be the last available value
		String month = null;
		for(int i=0;i<date_components.length;i++) {
			if(date_components[i] != null) {
				month = date_components[i];
				break;
			}
		}
		
		// Might be a number
		try {
			return new Integer(month).intValue();
		} catch(NumberFormatException nfe) {
			// Okay, it isn't a number
		}
		
		int return_val = MonthText.GetMonthFromText(month);
		if(return_val == -1) return_val = Integer.MIN_VALUE;
		
		return return_val;
	
	}
	
	private static int GetMonthHalf(String[] date_components) {
		
		// If there are two entries, we have a complicated situation
		if(date_components.length == 2) {
			
			String valid = null;
			int component = 0;
			
			// Find the valid entry
			if(date_components[0] != null) {
				valid = date_components[0];
				component = 0;
			} else {
				valid = date_components[1];
				component = 1;
			}
			
			if(valid == null) return Integer.MIN_VALUE;
			
			// A period means something different
			if(valid.indexOf('.') != -1) {
				// Split based on the period
				String[] two = valid.split("\\.");
				
				// The second one will be the month half
				date_components[component] = two[0];
				int value = Integer.MIN_VALUE;
				try {
					value = new Integer(two[1]).intValue();
				} catch(NumberFormatException nfe) {
					return Integer.MIN_VALUE;
				}
				
				return DecipherMonthHalf(value);
			} 
			
			// No period - Assume the number is between 1 and 24
			int value = Integer.MIN_VALUE;
			try {
				value = new Integer(valid).intValue();
			} catch(NumberFormatException nfe) {
				return Integer.MIN_VALUE;
			}
			date_components[component] = new Integer(value/2).toString();
			
			return value % 2 + 1;
		} else {
			
			// There are two values left
			int[] values = new int[date_components.length];
			for(int i=0;i<values.length;i++) {
				try {
					values[i] = new Integer(date_components[i]).intValue();
				} catch(NumberFormatException nfe) {
					values[i] = Integer.MIN_VALUE;
				}
			}
			
			// Get the possibilities
			int[] possibilities = new int[2];
			int count = 0;
			for(int i=0;i<values.length && count < 2;i++) {
				if(values[i] != Integer.MIN_VALUE) {
					possibilities[count] = i;
					count++;
				}
			}
			
			// If only one, return it
			if(count == 1) {
				return DecipherMonthHalf(values[possibilities[0]]);
			}
			
			// If two, demand that it's the first
			return DecipherMonthHalf(values[possibilities[0]]);
			
		}
		
		
	}
	
	private static int DecipherMonthHalf(int value) {
		if(value == 0 || value == 1) {
			return 1;
		} else if(value == 5 || value == 2) {
			return 2;
		}
		return Integer.MIN_VALUE;
	}
	
	private static int GetQuarter(String[] date_components) {
		
		// First, remove any text characters from any of the components
		for(int i=0;i<date_components.length;i++) {
			if(date_components[i] != null) date_components[i] = ClearText(date_components[i]);
		}
		
		// Now determine values for each
		int[] values = new int[date_components.length];
		for(int i=0;i<values.length;i++) {
			try {
				values[i] = new Integer(date_components[i]).intValue();
			} catch(NumberFormatException nfe) {
				values[i] = Integer.MIN_VALUE;
			}
		}
		
		// There should be only one valid value at this point
		for(int i=0;i<values.length;i++) {
			if(values[i] != Integer.MIN_VALUE) return new Integer(values[i]).intValue();
		}
		
		// If we are here, something went horribly wrong
		return Integer.MIN_VALUE;
	}
	
	private static int GetWeek(String[] date_components) {	
		// The procedure for getting a week is the same as for getting a quarter
		return GetQuarter(date_components);
	}
	
	private static int GetDay(String[] date_components) {
	
		// Get numeric values
		int[] values = new int[date_components.length];
		int count = 0;
		for(int i=0;i<values.length;i++) {
			try {
				values[i] = new Integer(date_components[i]).intValue();
				count++;
			} catch(NumberFormatException nfe) {
				values[i] = Integer.MIN_VALUE;
			}
		}
		
		// There's a good chance the month is specified as text.  If so,
		// Simply return the only valid number
		if(count == 1) {
			for(int i=0;i<values.length;i++) {
				if(values[i] != Integer.MIN_VALUE) return values[i];
			}
		}
		
		// So two numbers are present and one is the date
		int[] possibilities = new int[2];
		count = 0;
		for(int i=0;i<values.length && count < 2;i++) {
			if(values[i] != Integer.MIN_VALUE) {
				possibilities[count] = i;
				count++;
			}
		}
		
		// If one is greater than twelve, its a day
		if(values[possibilities[0]] > 12) {
			date_components[possibilities[0]] = null;
			return values[possibilities[0]];
		}
		else if(values[possibilities[1]] > 12) {
			date_components[possibilities[1]] = null;
			return values[possibilities[1]];
		}
		
		// Have to rely on the format standard in DateObject
		if(DateObject.getDateFormat() == DateObject.FMT_DAY_FIRST) {
			date_components[possibilities[0]] = null;
			return values[possibilities[0]];
		}
		else if(DateObject.getDateFormat() == DateObject.FMT_MONTH_FIRST) {
			date_components[possibilities[1]] = null;
			return values[possibilities[1]];
		}
		
		// If we are here, there has been an error
		return Integer.MIN_VALUE;
	}
	
	/** Removes any text characters (a-z or A-Z) from a string
	 *
	 * @param original the origininal String
	 * @return a String without any text charatcers
	 */
	private static String ClearText(String original) {
		return original.replaceAll("[a-zA-Z]","");
	}
	
}
