/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.ode.utils;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.StringTokenizer;

public class RelativeDateParser {
    private static final char YEAR_TEMPLATE_MODIFIER = 'y';

    private static final char MONTH_TEMPLATE_MODIFIER = 'M';

    private static final char WEEK_TEMPLATE_MODIFIER = 'w';

    private static final char DAY_TEMPLATE_MODIFIER = 'd';

    private static final char HOUR_TEMPLATE_MODIFIER = 'h';

    private static final char MINUTE_TEMPLATE_MODIFIER = 'm';

    private static final char SECOND_TEMPLATE_MODIFIER = 's';

    /**
     * This array of strings contains all supported "modifiers" for method <code>parseRelativeDate</code>.
     * NOTE: An order of elements in this array is important and used in algorithm of above method!
     */
    private static final char[] TEMPLATE_MODIFIERS_LIST = new char[] {
        YEAR_TEMPLATE_MODIFIER, MONTH_TEMPLATE_MODIFIER,
        WEEK_TEMPLATE_MODIFIER, DAY_TEMPLATE_MODIFIER,
        HOUR_TEMPLATE_MODIFIER, MINUTE_TEMPLATE_MODIFIER,
        SECOND_TEMPLATE_MODIFIER, };

    /**
     * Returns a date which is less than current system date/time on a specified number of days, years, minutes, weeks
     * etc.<p/>
     * A common template which is valid for this method is like that:<br/>
     * <strong>XXXXy XXXXM XXXXw XXXXd XXXXh XXXXm XXXXs</strong>, where:
     * <pre>
     * XXXX - any positive number composed of four or less digits, but not less that one digit
     * y - this symbol represents how many years must be subtracted from current system date
     * M - this symbol represents how many months must be subtracted from current system date
     * w - this symbol represents how many weeks must be subtracted rom current system date
     * d - this symbol represents how many days must be subtracted from current system date
     * h - this symbol represents how many hours must be subtracted from current system date
     * m - this symbol represents how many minutes must be subtracted from current system date
     * s - this symbol represents how many seconds must be subtracted from current system date
     * </pre>
     * Above symbols are called "modifiers".
     * <p><em>Modifiers are case sensitive</em>.
     * Modifiers separated by single space character or sequence of spaces.
     * Each modifier is optional, but an order of modifiers is matter and must be exactly the same
     * how its specified in template.</p>
     * <p/>
     * Here is a few examples of date templates and its meanings:<br/>
     * <code>"4h 3m 23s"</code> - means 4 hours, 3 minutes and 23 seconds ago from current system date<br/>
     * <code>"1y 2m 3w"</code> - means 1 year, 2 months and 3 weeks ago from current system date<br/>
     * <code>"1m 1w 13h 5m"</code> - means 1 month, 1 week, 13 hours and 5 minutes ago from current system date<br/>
     *
     * @param dateTemplate a template of a date we want to be calculated
     * @return a resulted <code>Date</code> object that is applies specified template
     * @throws java.text.ParseException if some problems were occurred while parsing specified date template
     */
    public static Date parseRelativeDate(String dateTemplate)
        throws java.text.ParseException {
      int[] agoValues = new int[TEMPLATE_MODIFIERS_LIST.length];
      int currentModifierPointer = 0;

      StringTokenizer tokens = new StringTokenizer(dateTemplate.trim());
      while (tokens.hasMoreTokens()) {
        String token = tokens.nextToken();
        if (token.length() < 2 || token.length() > 5) {
          throw new ParseException("Invalid token length. Token: "
              + token, dateTemplate.indexOf(token));
        }

        int modValue;
        try {
          modValue = Integer.parseInt(token.substring(0,
              token.length() - 1));
          if (modValue <= 0) {
            throw new ParseException(
                "Modifier value must be a positive number. Token: "
                    + token, dateTemplate.indexOf(token));
          }
        } catch (NumberFormatException nfe) {
          throw new ParseException("Can't parse integer value. Token: "
              + token, dateTemplate.indexOf(token));
        }

        char mod = token.charAt(token.length() - 1);
        while (true) {
          if (currentModifierPointer >= TEMPLATE_MODIFIERS_LIST.length) {
            throw new ParseException(
                "Incorrect modifier at this position. Token: "
                    + token, dateTemplate.indexOf(token)
                    + token.length() - 1);
          } else if (mod != TEMPLATE_MODIFIERS_LIST[currentModifierPointer]) {
            currentModifierPointer++;
            //continue;
          } else if (agoValues[currentModifierPointer] != 0) {
            throw new ParseException(
                "Dublicated modifier found. Token: " + token,
                dateTemplate.indexOf(token) + token.length() - 1);
          } else {
            agoValues[currentModifierPointer++] = modValue;
            break;
          }
        }
      }//while (tokens)

      final Calendar calendar = GregorianCalendar.getInstance();
      for (int i = 0; i < agoValues.length; i++) {
        if (agoValues[i] == 0) {
          continue;
        }
        int calendarField = templateModifierIndexToCalendarField(i);
        calendar.add(calendarField, 0 - agoValues[i]);
      }

      return calendar.getTime();
    }

    private static int templateModifierIndexToCalendarField(int modifierIndex)
        throws IllegalArgumentException {
      switch (modifierIndex) {
      case 0:
        return Calendar.YEAR;
      case 1:
        return Calendar.MONTH;
      case 2:
        return Calendar.WEEK_OF_MONTH;
      case 3:
        return Calendar.DAY_OF_MONTH;
      case 4:
        return Calendar.HOUR_OF_DAY;
      case 5:
        return Calendar.MINUTE;
      case 6:
        return Calendar.SECOND;
      }
      throw new IllegalArgumentException("Invalid template modifier index: "
          + modifierIndex);
    }
}
