/*
 * 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.xmlrpc.util;


import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Calendar;
import java.util.TimeZone;


/** <p>An instance of {@link java.text.Format}, which may be used
 * to parse and format <code>dateTime</code> values, as specified
 * by the XML-RPC specification. The specification doesn't precisely
 * describe the format, it only gives an example:</p>
 * <pre>
 *   19980717T14:08:55
 * </pre>
 * This class accepts and creates instances of {@link Calendar}.
 */
public abstract class XmlRpcDateTimeFormat extends Format {
    private static final long serialVersionUID = -8008230377361175138L;

    /** Returns the time zone, which is used to interpret date/time
     * values.
     */
    protected abstract TimeZone getTimeZone();

    private int parseInt(String pString, int pOffset, StringBuffer pDigits, int pMaxDigits) {
        int length = pString.length();
        pDigits.setLength(0);
        while (pMaxDigits-- > 0  &&  pOffset < length) {
            char c = pString.charAt(pOffset);
            if (Character.isDigit(c)) {
                pDigits.append(c);
                ++pOffset;
            } else {
                break;
            }
        }
        return pOffset;
    }

    public Object parseObject(String pString, ParsePosition pParsePosition) {
        if (pString == null) {
            throw new NullPointerException("The String argument must not be null.");
        }
        if (pParsePosition == null) {
            throw new NullPointerException("The ParsePosition argument must not be null.");
        }
        int offset = pParsePosition.getIndex();
        int length = pString.length();

        StringBuffer digits = new StringBuffer();
        int year, month, mday;

        offset = parseInt(pString, offset, digits, 4);
        if (digits.length() < 4) {
            pParsePosition.setErrorIndex(offset);
            return null;
        }
        year = Integer.parseInt(digits.toString());
	
        offset = parseInt(pString, offset, digits, 2);
        if (digits.length() != 2) {
            pParsePosition.setErrorIndex(offset);
            return null;
        }
        month = Integer.parseInt(digits.toString());
	
        offset = parseInt(pString, offset, digits, 2);
        if (digits.length() != 2) {
            pParsePosition.setErrorIndex(offset);
            return null;
        }
        mday = Integer.parseInt(digits.toString());

        if (offset < length  &&  pString.charAt(offset) == 'T') {
            ++offset;
        } else {
            pParsePosition.setErrorIndex(offset);
            return null;
        }

        int hour, minute, second;
        offset = parseInt(pString, offset, digits, 2);
        if (digits.length() != 2) {
            pParsePosition.setErrorIndex(offset);
            return null;
        }
        hour = Integer.parseInt(digits.toString());
	
        if (offset < length  &&  pString.charAt(offset) == ':') {
            ++offset;
        } else {
            pParsePosition.setErrorIndex(offset);
            return null;
        }
	
        offset = parseInt(pString, offset, digits, 2);
        if (digits.length() != 2) {
            pParsePosition.setErrorIndex(offset);
            return null;
        }
        minute = Integer.parseInt(digits.toString());
	
        if (offset < length  &&  pString.charAt(offset) == ':') {
            ++offset;
        } else {
            pParsePosition.setErrorIndex(offset);
            return null;
        }
	
        offset = parseInt(pString, offset, digits, 2);
        if (digits.length() != 2) {
            pParsePosition.setErrorIndex(offset);
            return null;
        }
        second = Integer.parseInt(digits.toString());
	
        Calendar cal = Calendar.getInstance(getTimeZone());
        cal.set(year, month-1, mday, hour, minute, second);
        cal.set(Calendar.MILLISECOND, 0);
        pParsePosition.setIndex(offset);
        return cal;
    }

    private void append(StringBuffer pBuffer, int pNum, int pMinLen) {
        String s = Integer.toString(pNum);
        for (int i = s.length();  i < pMinLen;  i++) {
            pBuffer.append('0');
        }
        pBuffer.append(s);
    }

    public StringBuffer format(Object pCalendar, StringBuffer pBuffer, FieldPosition pPos) {
        if (pCalendar == null) {
            throw new NullPointerException("The Calendar argument must not be null.");
        }
        if (pBuffer == null) {
            throw new NullPointerException("The StringBuffer argument must not be null.");
        }
        if (pPos == null) {
            throw new NullPointerException("The FieldPosition argument must not be null.");
        }

        Calendar cal = (Calendar) pCalendar;
        int year = cal.get(Calendar.YEAR);
        append(pBuffer, year, 4);
        append(pBuffer, cal.get(Calendar.MONTH)+1, 2);
        append(pBuffer, cal.get(Calendar.DAY_OF_MONTH), 2);
        pBuffer.append('T');
        append(pBuffer, cal.get(Calendar.HOUR_OF_DAY), 2);
        pBuffer.append(':');
        append(pBuffer, cal.get(Calendar.MINUTE), 2);
        pBuffer.append(':');
        append(pBuffer, cal.get(Calendar.SECOND), 2);
        return pBuffer;
    }
}
