/*******************************************************************************
 * Copyright (c) 2006 Oracle Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Oracle Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.validator.tools;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;

import javax.xml.namespace.QName;

/**
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @author Glenn Mi (glenn.me@oracle.com)
 * 
 * @date Sep 29, 2006
 * 
 */
public class ParserTool {

	private static final Locale INTERNAL_PARSE_LOCALE = Locale.US;

	private static final Locale INTERNAL_CAL_LOCALE = Locale.US;

	private static SimpleDateFormat zulu = new SimpleDateFormat(
			"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", INTERNAL_PARSE_LOCALE); //$NON-NLS-1$

	static {
		zulu.setTimeZone(TimeZone.getDefault());
	}

	/**
	 * Format date time string into the format:
	 *  2005-04-04T14:59:11.302-08:00
	 *  
	 * @param time
	 * @return the formatted date time string.
	 */
	
	public static String getDateTimeString(long time) {
		// 2005-04-04T14:59:11.302-08:00
		SimpleDateFormat xmlDateFormat = new SimpleDateFormat(
				"yyyy-MM-dd'T'HH:mm:ss.SSSZ", INTERNAL_PARSE_LOCALE); //$NON-NLS-1$
		Calendar cal = Calendar.getInstance(INTERNAL_CAL_LOCALE);
		cal.setTime(new Date(time));
		String javaFmt = xmlDateFormat.format(cal.getTime());

		int length = javaFmt.length();
		String dt = javaFmt.substring(0, length - 2);

		StringBuilder retVal = new StringBuilder(dt);
		retVal.append(":"); //$NON-NLS-1$
		retVal.append(javaFmt.substring(length - 2, length));

		return retVal.toString();
	}

	public static String getDateString(long time) {
		Calendar cal = Calendar.getInstance(INTERNAL_CAL_LOCALE);
		cal.setTime(new Date(time));
		SimpleDateFormat xmlDateFormat = new SimpleDateFormat(
				"yyyy-MM-dd", INTERNAL_PARSE_LOCALE); //$NON-NLS-1$
		return xmlDateFormat.format(cal.getTime());
	}

	/**
	 * parse XML duration in the format of P1Y2M3DT10H30M12.3S
	 * 
	 * @param src
	 * @return ?
	 * @throws IllegalArgumentException
	 */

	public static long parseDuration(String src)
			throws IllegalArgumentException {
		int years = 0;
		int months = 0;
		int days = 0;
		int hours = 0;
		int minutes = 0;
		double seconds = 0.;

		int idx = 0;
		int idx2 = 0;
		if (src.charAt(idx) != 'P')
			throw new IllegalArgumentException(
					"Duration has to start with 'P'."); //$NON-NLS-1$
		idx++;

		String dayPart = null;
		String timePart = null;

		idx2 = src.indexOf('T');
		if (idx2 > 0) {
			dayPart = src.substring(idx, idx2);
			timePart = src.substring(idx2 + 1);
		} else {
			dayPart = src.substring(idx);
		}

		try {
			String str;
			if (dayPart != null && dayPart.length() > 0) {
				// parse day part
				idx = 0;

				idx2 = dayPart.indexOf('Y');
				if (idx2 > 0) {
					str = dayPart.substring(idx, idx2);
					years = Integer.parseInt(str);
					idx = idx2 + 1;
				}

				idx2 = dayPart.indexOf('M');
				if (idx2 > 0) {
					str = dayPart.substring(idx, idx2);
					months = Integer.parseInt(str);
					idx = idx2 + 1;
				}

				idx2 = dayPart.indexOf('D');
				if (idx2 > 0) {
					str = dayPart.substring(idx, idx2);
					days = Integer.parseInt(str);
					idx = idx2 + 1;
				}

				if (dayPart.indexOf('H') >= 0 || dayPart.indexOf('S') >= 0)
					throw new NumberFormatException();
			}

			if (timePart != null) {
				// parse time part
				idx = 0;

				idx2 = timePart.indexOf('H');
				if (idx2 > 0) {
					str = timePart.substring(idx, idx2);
					hours = Integer.parseInt(str);
					idx = idx2 + 1;
				}

				idx2 = timePart.indexOf('M');
				if (idx2 > 0) {
					str = timePart.substring(idx, idx2);
					minutes = Integer.parseInt(str);
					idx = idx2 + 1;
				}

				idx2 = timePart.indexOf('S');
				if (idx2 > 0) {
					str = timePart.substring(idx, idx2);
					seconds = Double.parseDouble(str);
				}

				if (timePart.indexOf('Y') >= 0 || timePart.indexOf('D') >= 0)
					throw new NumberFormatException();
			}
		} catch (NumberFormatException e) {
			throw new IllegalArgumentException(
					"duration should be in the format of 'P1Y2M3DT10H30M12.3S'"); //$NON-NLS-1$
		}

		long duration = (long) (((((((years * 365 + months * 30) + days) * 24 + hours) * 60) + minutes) * 60 + seconds) * 1000);
		return duration;
	}

	/**
	 * Parse date string into a Date.
	 * 
	 * @param dateStr
	 * @return the date
	 */

	public static Date parseDate(String dateStr) {
		try {
			if (dateStr.length() < 10)
				throw new NumberFormatException("badDateTime"); //$NON-NLS-1$
			SimpleDateFormat df = new SimpleDateFormat(
					"yyyy-MM-dd", INTERNAL_PARSE_LOCALE); //$NON-NLS-1$
			return dateStr != null ? df.parse(dateStr) : null;
		} catch (Exception e) {
			throw new NumberFormatException(e.toString());
		}
	}

	public static Date parseDateFromLong(String longString) {
		if (longString != null) {
			return new Date(Long.parseLong(longString));
		}
		return null;
	}

	public static Calendar parseDateTimeFromLong(String longString) {
		if (longString != null) {
			Calendar calendar = Calendar.getInstance(INTERNAL_CAL_LOCALE);
			calendar.setTime(new Date(Long.parseLong(longString)));
			return calendar;
		}
		return null;
	}

	public static Calendar parseDateTime(String source) {
		return parseDateAndTime(source);
	}

	/**
	 * The simple deserializer provides most of the stuff. We just need to
	 * override makeValue().
	 * 
	 * @param source
	 *            the source string to parse
	 * @return the Calandar instance that the string was parsed into
	 * 
	 */

	public static Calendar parseDateAndTime(String source) {
		if (source == null || isEmpty(source))
			return null;

		Calendar calendar = Calendar.getInstance(INTERNAL_CAL_LOCALE);
		Date date;
		boolean bc = false;

		// validate fixed portion of format
		if (source.charAt(0) == '+')
			source = source.substring(1);

		if (source.charAt(0) == '-') {
			source = source.substring(1);
			bc = true;
		}
		if (source.length() < 19) {
			throw new NumberFormatException("badDateTime"); //$NON-NLS-1$
		}

		if (source.charAt(4) != '-' || source.charAt(7) != '-'
				|| source.charAt(10) != 'T') {
			throw new NumberFormatException("badDate"); //$NON-NLS-1$
		}

		if (source.charAt(13) != ':' || source.charAt(16) != ':') {
			throw new NumberFormatException("badTime"); //$NON-NLS-1$
		}

		// convert what we have validated so far
		try {
			synchronized (zulu) {
				date = zulu.parse((source.substring(0, 19) + ".000Z")); //$NON-NLS-1$
			}
		} catch (Exception e) {
			throw new NumberFormatException(e.toString());
		}

		int pos = 19;

		// parse optional milliseconds
		if (pos < source.length() && source.charAt(pos) == '.') {
			int milliseconds = 0;
			int start = ++pos;
			while (pos < source.length()
					&& Character.isDigit(source.charAt(pos)))
				pos++;

			String decimal = source.substring(start, pos);
			if (decimal.length() == 3)
				milliseconds = Integer.parseInt(decimal);
			else if (decimal.length() < 3)
				milliseconds = Integer.parseInt((decimal + "000") //$NON-NLS-1$
						.substring(0, 3));
			else {
				milliseconds = Integer.parseInt(decimal.substring(0, 3));
				if (decimal.charAt(3) >= '5')
					++milliseconds;
			}

			// add milliseconds to the current date
			date.setTime(date.getTime() + milliseconds);
		}

		// parse optional timezone
		if (pos + 5 < source.length()
				&& (source.charAt(pos) == '+' || (source.charAt(pos) == '-'))) {
			if (!Character.isDigit(source.charAt(pos + 1))
					|| !Character.isDigit(source.charAt(pos + 2))
					|| source.charAt(pos + 3) != ':'
					|| !Character.isDigit(source.charAt(pos + 4))
					|| !Character.isDigit(source.charAt(pos + 5))) {
				throw new NumberFormatException("badTimezone"); //$NON-NLS-1$
			}

			int hours = (source.charAt(pos + 1) - '0') * 10
					+ source.charAt(pos + 2) - '0';
			int mins = (source.charAt(pos + 4) - '0') * 10
					+ source.charAt(pos + 5) - '0';
			int milliseconds = (hours * 60 + mins) * 60 * 1000;

			// subtract milliseconds from current date to obtain GMT
			// if (source.charAt(pos)=='+') milliseconds=-milliseconds;
			// date.setTime(date.getTime()+milliseconds);
			pos += 6;
		}

		if (pos < source.length() && source.charAt(pos) == 'Z') {
			pos++;
			// calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
		}

		if (pos < source.length())
			throw new NumberFormatException("badChars");

		calendar.setTime(date);

		// support dates before the Christian era
		if (bc) {
			calendar.set(Calendar.ERA, GregorianCalendar.BC);
		}

		return calendar;
	}

	public static float parseFloat(String floatStr) {
		if (!isEmpty(floatStr)) {
			return Float.parseFloat(floatStr);
		}
		return 0f;
	}

	public static double parseDouble(String value) {
		String v = String.valueOf(value);
		if (!isEmpty(v)) {
			return Double.parseDouble(v);
		}
		return 0d;
	}

	public static int parseInt(String value) {
		try {
			if (!isEmpty(value)) {
				value = String.valueOf(adjustResult(value));
				return Integer.parseInt(value);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return 0;

	}

	public static boolean parseBoolean(String value) {
		if (!isEmpty(value)) {
			return new Boolean(value).booleanValue();
		}
		return false;
	}

	public static short parseShort(String value) {
		if (!isEmpty(value)) {
			value = String.valueOf(adjustResult(value));
			return Short.parseShort(value);
		}
		return 0;
	}

	public static byte parseByte(String value) {
		if (!isEmpty(value)) {
			return Byte.parseByte(value);
		}
		return 0;
	}

	public static long parseLong(String value) {
		if (!isEmpty(value)) {
			return Long.parseLong(value);
		}
		return 0l;
	}

	public static char parseChar(String value) {
		if (!isEmpty(value)) {
			char arr[] = value.toCharArray();
			return arr[0];
		}
		return 0;
	}

	public static Object convertToObject(double d) {
		return new Double(d);
	}

	public static Object convertToObject(int d) {
		return new Integer(d);
	}

	public static Object convertToObject(char d) {
		return new Character(d);
	}

	public static Object convertToObject(byte d) {
		return new Byte(d);
	}

	public static Object convertToObject(short d) {
		return new Short(d);
	}

	public static Object convertToObject(long d) {
		return new Long(d);
	}

	public static Object convertToObject(float d) {
		return new Float(d);
	}

	public static Object convertToObject(Object d) {
		return d;
	}

	public static String convertToXMLString(Object value) {
		String xmlString = null;
		if (value != null) {
			// Conversion process:
			// Object -> Object
			// primitve -> String
			//
			Class type = value.getClass();
			if (type.isPrimitive()) {
				xmlString = primitiveToXMLString(value, type);
			} else if (value instanceof QName) {
				// calender
				QName qname = (QName) value;
				if (qname.getPrefix() != null && qname.getPrefix().length() > 0)
					xmlString = qname.getPrefix() + ":" + qname.getLocalPart();
				else
					xmlString = qname.getLocalPart();
			} else if (value instanceof Calendar) {
				// calender
				xmlString = calendarToXMLString((Calendar) value);
			} else if (value instanceof Date) {
				xmlString = dateToXMLString((Date) value);
			}
			// else if (value instanceof DateTimeBase)
			// {
			// xmlString = dateTimeToXMLString( (DateTimeBase) value );
			// }
			// else if (value instanceof Duration)
			// {
			// xmlString = durationToXMLString( (Duration) value );
			// }
			else {
				xmlString = String.valueOf(value);
			}

		}
		return xmlString;
	}

	// public static String durationToXMLString( Duration value )
	// {
	// return value != null ? value.toString() : "";
	// }
	//	    
	// public static String dateTimeToXMLString( DateTimeBase value )
	// {
	// return value != null ? calendarToXMLString( value.toCalendar() ) : "";
	// }
	//	    
	public static String dateToXMLString(Date value) {
		return value != null ? getDateString(value.getTime()) : "";
	}

	public static String calendarToXMLString(Calendar value) {
		return value != null ? getDateTimeString(value.getTime().getTime())
				: "";
	}

	public static String primitiveToXMLString(Object primitiveObject, Class type) {
		String xmlString = null;
		if (type == Double.class) {
			xmlString = ((Double) primitiveObject).toString();
		} else if (type == Integer.class) {
			xmlString = ((Integer) primitiveObject).toString();
		} else if (type == Boolean.class) {
			xmlString = ((Boolean) primitiveObject).toString();
		} else if (type == Float.class) {
			xmlString = ((Float) primitiveObject).toString();
		} else if (type == Byte.class) {
			xmlString = ((Byte) primitiveObject).toString();
		} else if (type == Short.class) {
			xmlString = ((Short) primitiveObject).toString();
		} else if (type == Long.class) {
			xmlString = ((Long) primitiveObject).toString();
		} else if (type == Character.class) {
			xmlString = ((Character) primitiveObject).toString();
		}
		return xmlString;
	}

	public static void main(String[] args) throws Exception {
		// System.out.println(parseDuration(args[0]));

		Date d = new Date();
		String dtStr = getDateTimeString(d.getTime());
		System.out.println("dateTime=" + dtStr);
		Calendar cal = parseDateTime(dtStr);
		System.out.println("cal is: " + cal);
		System.out.println("date is: " + cal.getTime());
		System.out.println("XML Datetime "
				+ getDateTimeString(cal.getTime().getTime()));
		System.out
				.println("XML Date " + getDateString(cal.getTime().getTime()));

	}

	private static Object adjustResult(String result) {
		if (result != null && result.indexOf(".") > 0) {
			String dS = result;

			int fracIndex = dS.indexOf(".");
			String fr = dS.substring(fracIndex + 1, dS.length());
			String in = dS.substring(0, fracIndex);
			// fraction = (n - abs(n))
			// if the fraction is 0 then assume that the result is an integer
			// and construct an integer object and return it.
			//
			// TODO: FIXME Once we have schema types then we should do proper
			// typed return value
			// System.out.println("___________ fraction : " + fr );
			// System.out.println("___________ before decimal : " + in );
			Double d1 = new Double(fr);
			if (d1.doubleValue() == 0) {
				return new Integer(in);
			}
		}
		return result;
	}

	/**
	 * Returns true if the string is either null or contains just whitespace.
	 */

	static boolean isEmpty(String value) {
		if (value == null || value.length() == 0) {
			return true;
		}
		for (int i = 0, j = value.length(); i < j; i++) {
			if (!Character.isWhitespace(value.charAt(i))) {
				return false;
			}
		}
		return true;
	}
}
