/*
 * Copyright 2003, 2004  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.ws.jaxme.util;

import java.io.Serializable;


/** <p>Implementation of xs:duration.</p>
 */
public class Duration implements Serializable, Comparable {
	private static final long serialVersionUID = 3257001055736117303L;
	private final boolean isNegative;
    private final int years, months, days, hours, minutes, seconds;
    private final long millis;

	/** Creates a new instance with the given values.
	 */
	public Duration(boolean pNegative, int pYears, int pMonths, int pDays, int pHours, int pMinutes, int pSeconds, long pMillis) {
        isNegative = pNegative;
        years = pYears;
        months = pMonths;
        days = pDays;
        hours = pHours;
        minutes = pMinutes;
        seconds = pSeconds;
        millis = pMillis;
    }
  
    /** <p>Returns the number of years.</p>
     */
    public int getYears() {
        return years;
    }

    /** <p>Returns the number of months.</p>
     */
    public int getMonths() {
        return months;
    }

    /** <p>Returns the number of days.</p>
     */
    public int getDays() {
        return days;
    }

    /** <p>Returns the number of hours.</p>
     */
    public int getHours() {
        return hours;
    }

    /** <p>Returns the number of minutes.</p>
     */
    public int getMinutes() {
        return minutes;
    }

    /** <p>Returns the number of seconds.</p>
     */
    public int getSeconds() {
        return seconds;
    }

    /** <p>Returns the number of milliseconds.</p>
     */
    public long getMillis() {
        return millis;
    }

    /** <p>Returns a string representation of this Duration.</p>
     */
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append('P');
        sb.append(getYears());
        sb.append('Y');
        sb.append(getMonths());
        sb.append('M');
        sb.append(getDays());
        sb.append("DT");
        sb.append(getHours());
        sb.append('H');
        sb.append(getMinutes());
        sb.append('M');
        sb.append(getSeconds());
        long m = getMillis();
        if (m != 0) {
            sb.append('.');
            sb.append(m);
        }
        sb.append('S');
        return sb.toString();
    }

    /** <p>Converts the given String representation into an instance of
     * Duration.</p>
     * @throws IllegalArgumentException The String could not be parsed.
     */
    public static Duration valueOf(String pValue) {
        if (pValue == null) {
            throw new NullPointerException("The duration value must not be null.");
        }
        int len = pValue.length();
        int offset = 0;
        boolean isNegative;
        if (len > 0) {
            char c = pValue.charAt(0);
            if (c == '-') {
                isNegative = true;
                ++offset;
            } else if (c == '+') {
                isNegative = false;
                ++offset;
            } else {
                isNegative = false;
            }
        } else {
            throw new IllegalArgumentException("Invalid duration: Empty string");
        }
        
        if (len == 0  ||  pValue.charAt(offset) != 'P') {
            throw new IllegalArgumentException("Invalid duration: " + pValue + " (must start with P, +P, or -P)");
        } else {
            ++offset;
        }
        
        int years = -1, months = -1, daysOfMonth = -1, hours = -1, minutes = -1, seconds = -1;
        long millis = -1;
        int preDecimalPoint = -1;
        boolean separatorSeen = false;
        StringBuffer digits = new StringBuffer();
        while (offset < len) {
            char c = pValue.charAt(offset);
            if (Character.isDigit(c)) {
                digits.append(c);
            } else if (c == 'T') {
                if (separatorSeen) {
                    throw new IllegalArgumentException("Invalid duration: " + pValue
                            + " (date/time separator 'T' used twice)");
                } else {
                    separatorSeen = true;
                }
            } else {
                long l;
                if (digits.length() == 0) {
                    l = 0;
                } else {
                    try {
                        l = Long.parseLong(digits.toString());
                    } catch (NumberFormatException e) {
                        throw new IllegalArgumentException("Invalid duration: "  + pValue
                                + " (max long value exceeded by " + digits + ")");
                    }
                    digits.setLength(0);
                }
                if (preDecimalPoint >= 0) {
                    if (c == 'S') {
                        if (!separatorSeen) {
                            throw new IllegalArgumentException("Invalid duration: " + pValue
                                    + "(seconds specified before date/time separator 'T' seen)");
                        }
                        if (seconds != -1) {
                            throw new IllegalArgumentException("Invalid duration: " + pValue
                                    + " (seconds specified twice)");
                        }
                        seconds = preDecimalPoint;
                        millis = l;
                        preDecimalPoint = -1;
                    } else {
                        throw new IllegalArgumentException("Invalid duration: " + pValue
                                + " (decimal point not allowed here: "
                                + preDecimalPoint + "." + digits + c + ")");
                    }
                } else if (l > Integer.MAX_VALUE) {
                    throw new IllegalArgumentException("Invalid duration: " + pValue
                            + " (max integer value exceeded by " + digits + ")");
                } else {
                    int i = (int) l;
                    if (c == '.') {
                        preDecimalPoint = i;
                    } else if (separatorSeen) {
                        if (c == 'Y'  ||  c == 'D') {
                            throw new IllegalArgumentException("Invalid duration: " + pValue
                                    + " (years or days of month specified after date/time separator 'T' seen)");
                        } else if (c == 'S') {
                            if (seconds != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (seconds specified twice)");
                            }
                            seconds = i;
                            millis = 0;
                        } else if (c == 'M') {
                            if (minutes != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (minutes specified twice)");
                            } else if (seconds != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (minutes specified after seconds)");
                            }
                            minutes = i;
                        } else if (c == 'H') {
                            if (hours != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (hours specified twice)");
                           } else if (minutes != -1) {
                               throw new IllegalArgumentException("Invalid duration: " + pValue
                                       + " (hours specified after minutes)");
                           } else if (seconds != -1) {
                               throw new IllegalArgumentException("Invalid duration: " + pValue
                                       + " (seconds specified after minutes)");
                           }
                           hours = i;
                        }
                    } else {
                        if (c == 'H'  ||  c == 'S') {
                            throw new IllegalArgumentException("Invalid duration: " + pValue
                                    + " (hours or seconds specified before date/time separator 'T' seen)");
                        } else if (c == 'Y') {
                            if (years != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (years specified twice)");
                            } else if (months != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (years specified after months)");
                            } else if (daysOfMonth != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (years specified after days of month)");
                            }
                            years = i;
                        } else if (c == 'M') {
                            if (months != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (months specified twice)");
                            } else if (daysOfMonth != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                        + " (days of month specified after months)");
                            }
                            months = i;
                        } else if (c == 'D') {
                            if (daysOfMonth != -1) {
                                throw new IllegalArgumentException("Invalid duration: " + pValue
                                                                   + " (days of month specified twice)");
                            }
                            daysOfMonth = i;
                        }
                    }
                }
            }
            ++offset;
        }
        return new Duration(isNegative,
                			years == -1 ? 0 : years,
							months == -1 ? 0 : months,
							daysOfMonth == -1 ? 0 :daysOfMonth,
							hours == -1 ? 0 : hours,
							minutes == -1 ? 0 : minutes,
							seconds == -1 ? 0 : seconds,
							millis == -1 ? 0 : millis);
    }

    public boolean equals(Object o) {
        if (o == null  ||  !(o instanceof Duration)) {
            return false;
        }
        return compareTo((Duration) o) == 0;
    }

    public int compareTo(Object o) {
        return compareTo((Duration) o);
    }

	/** Actual implementation of {@link #compareTo(Object)}.
	 */
	public int compareTo(Duration d) {
        if (isNegative != d.isNegative) {
            return isNegative ? -1 : 1;
        }
        if (years != d.years) { return years - d.years; }
        if (months != d.months) { return months - d.months; }
        if (days != d.days) { return days - d.days; }
        if (hours != d.hours) { return hours - d.hours; }
        if (minutes != d.minutes) { return minutes - d.minutes; }
        if (seconds != d.seconds) { return seconds - d.seconds; }
        if (millis > d.millis) {
            return 1;
        } else if (millis < d.millis) {
            return -1;
        } else {
            return 0;
        }
    }

    public int hashCode() {
        return isNegative ? 1 : 0 + years + months + days + hours + minutes + seconds + (int) millis;
    }
}
