/*
 * Copyright 2005 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.net.ftp.parser;

import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import org.apache.commons.net.ftp.Configurable;
import org.apache.commons.net.ftp.FTPClientConfig;

/**
 * Default implementation of the {@link  FTPTimestampParser  FTPTimestampParser} 
 * interface also implements the {@link  org.apache.commons.net.ftp.Configurable  Configurable}
 * interface to allow the parsing to be configured from the outside.
 *
 * @see ConfigurableFTPFileEntryParserImpl
 * @since 1.4
 */
public class FTPTimestampParserImpl implements
		FTPTimestampParser, Configurable 
{

	
	private SimpleDateFormat defaultDateFormat;
	private SimpleDateFormat recentDateFormat;
	
	
	/**
	 * The only constructor for this class. 
	 */
	public FTPTimestampParserImpl() {
		setDefaultDateFormat(DEFAULT_SDF);
		setRecentDateFormat(DEFAULT_RECENT_SDF);
	}
	
	/** 
	 * Implements the one {@link  FTPTimestampParser#parseTimestamp(String)  method}
	 * in the {@link  FTPTimestampParser  FTPTimestampParser} interface 
	 * according to this algorithm:
	 * 
	 * If the recentDateFormat member has been defined, try to parse the 
	 * supplied string with that.  If that parse fails, or if the recentDateFormat
	 * member has not been defined, attempt to parse with the defaultDateFormat
	 * member.  If that fails, throw a ParseException. 
	 * 
	 * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String)	 
	 */
	/* (non-Javadoc)
	 * 
	 */
	public Calendar parseTimestamp(String timestampStr) throws ParseException {
		Calendar now = Calendar.getInstance();
		now.setTimeZone(this.getServerTimeZone());
		
		Calendar working = Calendar.getInstance();
		working.setTimeZone(this.getServerTimeZone());
		ParsePosition pp = new ParsePosition(0);

		Date parsed = null;
		if (this.recentDateFormat != null) {
			parsed = recentDateFormat.parse(timestampStr, pp);
		}
		if (parsed != null && pp.getIndex() == timestampStr.length()) 
		{
			working.setTime(parsed);
			working.set(Calendar.YEAR, now.get(Calendar.YEAR));
			if (working.after(now)) {
				working.add(Calendar.YEAR, -1);
			}
		} else {
			pp = new ParsePosition(0);
			parsed = defaultDateFormat.parse(timestampStr, pp);
			// note, length checks are mandatory for us since
			// SimpleDateFormat methods will succeed if less than
			// full string is matched.  They will also accept, 
			// despite "leniency" setting, a two-digit number as
			// a valid year (e.g. 22:04 will parse as 22 A.D.) 
			// so could mistakenly confuse an hour with a year, 
			// if we don't insist on full length parsing.
			if (parsed != null && pp.getIndex() == timestampStr.length()) {
				working.setTime(parsed);
			} else {
				throw new ParseException(
					"Timestamp could not be parsed with older or recent DateFormat", 
					pp.getIndex());
			}
		}
		return working;
	}

	/**
	 * @return Returns the defaultDateFormat.
	 */
	public SimpleDateFormat getDefaultDateFormat() {
		return defaultDateFormat;
	}
	/**
	 * @return Returns the defaultDateFormat pattern string.
	 */
	public String getDefaultDateFormatString() {
		return defaultDateFormat.toPattern();
	}
	/**
	 * @param defaultDateFormat The defaultDateFormat to be set.
	 */
	private void setDefaultDateFormat(String format) {
		if (format != null) {
			this.defaultDateFormat = new SimpleDateFormat(format);
			this.defaultDateFormat.setLenient(false);
		}
	} 
	/**
	 * @return Returns the recentDateFormat.
	 */
	public SimpleDateFormat getRecentDateFormat() {
		return recentDateFormat;
	}
	/**
	 * @return Returns the recentDateFormat.
	 */
	public String getRecentDateFormatString() {
		return recentDateFormat.toPattern();
	}
	/**
	 * @param recentDateFormat The recentDateFormat to set.
	 */
	private void setRecentDateFormat(String format) {
		if (format != null) {
			this.recentDateFormat = new SimpleDateFormat(format);
			this.recentDateFormat.setLenient(false);
		}
	}
	
	/**
	 * @return returns an array of 12 strings representing the short
	 * month names used by this parse.
	 */
	public String[] getShortMonths() {
		return defaultDateFormat.getDateFormatSymbols().getShortMonths();
	}
	
	
	/**
	 * @return Returns the serverTimeZone used by this parser.
	 */
	public TimeZone getServerTimeZone() {
		return this.defaultDateFormat.getTimeZone();
	}
	/**
	 * sets a TimeZone represented by the supplied ID string into all
	 * of the parsers used by this server.
	 * @param serverTimeZone Time Id java.util.TimeZone id used by
	 * the ftp server.  If null the client's local time zone is assumed.
	 */
	private void setServerTimeZone(String serverTimeZoneId) {
		TimeZone serverTimeZone = TimeZone.getDefault();
		if (serverTimeZoneId != null) {
			serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId);
		}
		this.defaultDateFormat.setTimeZone(serverTimeZone);
		if (this.recentDateFormat != null) {
			this.recentDateFormat.setTimeZone(serverTimeZone);
		}			
	}
	
	/**
	 * Implementation of the {@link  Configurable  Configurable}
	 * interface. Configures this <code>FTPTimestampParser</code> according
	 * to the following logic:
	 * <p>
	 * Set up the {@link  FTPClientConfig#setDefaultDateFormatStr(java.lang.String) defaultDateFormat}
	 * and optionally the {@link  FTPClientConfig#setRecentDateFormatStr(String) recentDateFormat}
	 * to values supplied in the config based on month names configured as follows:
	 * </p><p><ul>
	 * <li>If a {@link  FTPClientConfig#setShortMonthNames(String) shortMonthString}
	 * has been supplied in the <code>config</code>, use that to parse  parse timestamps.</li> 
	 * <li>Otherwise, if a {@link  FTPClientConfig#setServerLanguageCode(String) serverLanguageCode}
	 * has been supplied in the <code>config</code>, use the month names represented 
	 * by that {@link  FTPClientConfig#lookupDateFormatSymbols(String) language}
	 * to parse timestamps.</li>
	 * <li>otherwise use default English month names</li>
	 * </ul></p><p>
	 * Finally if a {@link  org.apache.commons.net.ftp.FTPClientConfig#setServerTimeZoneId(String) serverTimeZoneId}
	 * has been supplied via the config, set that into all date formats that have 
	 * been configured.  
	 * </p> 
	 */
	public void configure(FTPClientConfig config) {
		DateFormatSymbols dfs = null;
		
		String languageCode = config.getServerLanguageCode();
		String shortmonths = config.getShortMonthNames();
		if (shortmonths != null) {
			dfs = FTPClientConfig.getDateFormatSymbols(shortmonths);
		} else if (languageCode != null) {
			dfs = FTPClientConfig.lookupDateFormatSymbols(languageCode);
		} else {
			dfs = FTPClientConfig.lookupDateFormatSymbols("en");
		}
		
		
		String recentFormatString = config.getRecentDateFormatStr();
		if (recentFormatString == null) {
		    this.recentDateFormat = null;
		} else {
			this.recentDateFormat = new SimpleDateFormat(recentFormatString, dfs);
			this.recentDateFormat.setLenient(false);
		}
			
		String defaultFormatString = config.getDefaultDateFormatStr();
		if (defaultFormatString == null) {
			throw new IllegalArgumentException("defaultFormatString cannot be null");
		}
		this.defaultDateFormat = new SimpleDateFormat(defaultFormatString, dfs);
		this.defaultDateFormat.setLenient(false);
		
		setServerTimeZone(config.getServerTimeZoneId());
	}
}
