/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2006, GeoTools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 */
package org.geotools.filter.text.cql2;

import java.util.Calendar;
import java.util.Date;


/**
 * Utility class for filter builder
 *
 * @since 2.4
 * @author Mauricio Pazos (Axios Engineering)
 * @author Gabriel Roldan (Axios Engineering)
 * @version $Id: Util.java 28415 2007-12-18 09:54:31Z vmpazos $
 * @source $URL:
 *         http://svn.geotools.org/geotools/trunk/gt/modules/library/cql/src/main/java/org/geotools/text/filter/Util.java $
 */
final class Util {
    private static final Calendar CALENDAR = Calendar.getInstance();
    private static final int YEARS = 0;
    private static final int MONTHS = 1;
    private static final int DAYS = 2;

    /** Y,M,D */
    private static int[] DURATION_DATE = new int[3];
    private static final int HOURS = 0;
    private static final int MINUTES = 1;
    private static final int SECONDS = 2;

    /** H,M,S */
    private static int[] DURATION_TIME = new int[3];

    private Util() {
        // utility class
    }

    /**
     * Extract from duration string the values of years, month and days
     *
     * @param duration
     * @return int[3] with years,months,days, if some value are not present -1
     *         will be returned.
     */
    private static int[] extractDurationDate(final String duration) {
        // initializa duration date container
        for (int i = 0; i < DURATION_DATE.length; i++) {
            DURATION_DATE[i] = -1;
        }

        // if has not duration date return array with -1 values
        int cursor = duration.indexOf("P");

        if (cursor == -1) {
            return DURATION_DATE;
        }

        // extracts duration date and set duration array
        cursor++;

        // years
        int endYears = duration.indexOf("Y", cursor);

        if (endYears >= 0) {
            String strYears = duration.substring(cursor, endYears);
            int years = Integer.parseInt(strYears);
            DURATION_DATE[YEARS] = years;

            cursor = endYears + 1;
        }

        // months
        int endMonths = duration.indexOf("M", cursor);

        if (endMonths >= 0) {
            String strMonths = duration.substring(cursor, endMonths);
            int months = Integer.parseInt(strMonths);
            DURATION_DATE[MONTHS] = months;

            cursor = endMonths + 1;
        }

        // days
        int endDays = duration.indexOf("D", cursor);

        if (endDays >= 0) {
            String strDays = duration.substring(cursor, endDays);
            int days = Integer.parseInt(strDays);
            DURATION_DATE[DAYS] = days;
        }

        return DURATION_DATE;
    }

    /**
     * Extract from duration string the values of hours, minutes and seconds
     *
     * @param duration
     * @return int[3] with hours, minutes and seconds if some value are not
     *         present -1 will be returned.
     */
    private static int[] extractDurationTime(final String duration) {
        for (int i = 0; i < DURATION_TIME.length; i++) {
            DURATION_TIME[i] = -1;
        }

        int cursor = duration.indexOf("T");

        if (cursor == -1) {
            return DURATION_TIME;
        }

        cursor++;

        // hours
        int endHours = duration.indexOf("H", cursor);

        if (endHours >= 0) {
            String strHours = duration.substring(cursor, endHours);
            int hours = Integer.parseInt(strHours);
            DURATION_TIME[HOURS] = hours;

            cursor = endHours + 1;
        }

        // minute
        int endMinutes = duration.indexOf("M", cursor);

        if (endMinutes >= 0) {
            String strMinutes = duration.substring(cursor, endMinutes);
            int minutes = Integer.parseInt(strMinutes);
            DURATION_TIME[MINUTES] = minutes;

            cursor = endMinutes + 1;
        }

        // seconds
        int endSeconds = duration.indexOf("S", cursor);

        if (endSeconds >= 0) {
            String strSeconds = duration.substring(cursor, endSeconds);
            int seconds = Integer.parseInt(strSeconds);
            DURATION_TIME[SECONDS] = seconds;
        }

        return DURATION_TIME;
    }

    /**
     * Add duration to date
     *
     * @param date
     *            a Date
     * @param duration
     *            a String formated like "P##Y##M##D"
     *
     * @return a Date
     *
     */
    public static Date addDurationToDate(final Date date, final String duration)
        throws NumberFormatException {
        final int positive = 1;

        Date computedDate = null;

        computedDate = computeDateFromDurationDate(date, duration, positive);

        computedDate = computeDateFromDurationTime(computedDate, duration, positive);

        return computedDate;
    }

    /**
     * Adds years, month and days (duration) to initial date.
     *
     * @param date
     *            initial date
     * @param duration
     *            a String with format: PddYddMddD
     * @return Date a computed date. if duration have not got duration "P"
     *         return date value.
     *
     */
    private static Date computeDateFromDurationDate(final Date date, final String duration, int sign) {
        DURATION_DATE = extractDurationDate(duration);

        if (isNull(DURATION_DATE)) {
            return date;
        }

        CALENDAR.setTime(date);

        // years
        if (DURATION_DATE[YEARS] >= 0) {
            CALENDAR.add(Calendar.YEAR, sign * DURATION_DATE[YEARS]);
        }

        // months
        if (DURATION_DATE[MONTHS] >= 0) {
            CALENDAR.add(Calendar.MONTH, sign * DURATION_DATE[MONTHS]);
        }

        // days
        if (DURATION_DATE[DAYS] >= 0) {
            CALENDAR.add(Calendar.DATE, sign * DURATION_DATE[DAYS]);
        }

        Date lastDate = CALENDAR.getTime();

        return lastDate;
    }

    /**
     * durDate is null if all his values are -1
     *
     * @param durDate
     * @return true if has some greater than or equal 0
     */
    private static boolean isNull(int[] durDate) {
        for (int i = 0; i < durDate.length; i++) {
            if (durDate[i] >= 0) {
                return false;
            }
        }

        return true;
    }

    /**
     * Add or subtract time duration to initial date.
     *
     * @param date
     *            initial date
     * @param duration
     *            a String with format: TddHddMddS
     * @param sign
     *            1 or -1 (add or subract)
     * @return Date a computed date. if duration have not got duration "T"
     *         return date value.
     */
    private static Date computeDateFromDurationTime(final Date date, final String duration,
        final int sign) {
        DURATION_TIME = extractDurationTime(duration);

        if (isNull(DURATION_TIME)) {
            return date;
        }

        CALENDAR.setTime(date);

        // hours
        if (DURATION_TIME[HOURS] >= 0) {
            CALENDAR.add(Calendar.HOUR, sign * DURATION_TIME[HOURS]);
        }

        // minute
        if (DURATION_TIME[MINUTES] >= 0) {
            CALENDAR.add(Calendar.MINUTE, sign * DURATION_TIME[MINUTES]);
        }

        // seconds
        if (DURATION_TIME[SECONDS] >= 0) {
            CALENDAR.add(Calendar.SECOND, sign * DURATION_TIME[SECONDS]);
        }

        Date lastDate = CALENDAR.getTime();

        return lastDate;
    }

    /**
     * Subtracts duration to date
     *
     * @param date
     *            a Date
     * @param duration
     *            a String formated like "P##Y##M##D"
     *
     * @return a Date
     */
    public static Date subtractDurationToDate(Date date, String duration) {
        final int negative = -1;

        Date computedDate = null;

        computedDate = computeDateFromDurationDate(date, duration, negative);

        computedDate = computeDateFromDurationTime(computedDate, duration, negative);

        return computedDate;
    }
}
