/*
 * Copyright 2006 the original author or authors.
 *
 * 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.springframework.ws.soap.security.xwss.callback;

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

import com.sun.xml.wss.impl.callback.TimestampValidationCallback;

/**
 * A default implementation of a <code>TimestampValidationCallback.TimestampValidator</code>. Based on a version found
 * in the JWSDP samples.
 *
 * @author Arjen Poutsma
 * @since 1.0.0
 */
public class DefaultTimestampValidator implements TimestampValidationCallback.TimestampValidator {

    public void validate(TimestampValidationCallback.Request request)
            throws TimestampValidationCallback.TimestampValidationException {
        if (request instanceof TimestampValidationCallback.UTCTimestampRequest) {
            TimestampValidationCallback.UTCTimestampRequest utcRequest =
                    (TimestampValidationCallback.UTCTimestampRequest) request;
            Date created = parseDate(utcRequest.getCreated());

            validateCreationTime(created, utcRequest.getMaxClockSkew(), utcRequest.getTimestampFreshnessLimit());

            if (utcRequest.getExpired() != null) {
                Date expired = parseDate(utcRequest.getExpired());
                validateExpirationTime(expired, utcRequest.getMaxClockSkew());
            }
        }
        else {
            throw new TimestampValidationCallback.TimestampValidationException("Unsupport request: [" + request + "]");
        }
    }

    private Date getFreshnessAndSkewAdjustedDate(long maxClockSkew, long timestampFreshnessLimit) {
        Calendar c = new GregorianCalendar();
        long offset = c.get(Calendar.ZONE_OFFSET);
        if (c.getTimeZone().inDaylightTime(c.getTime())) {
            offset += c.getTimeZone().getDSTSavings();
        }
        long beforeTime = c.getTimeInMillis();
        long currentTime = beforeTime - offset;

        long adjustedTime = currentTime - maxClockSkew - timestampFreshnessLimit;
        c.setTimeInMillis(adjustedTime);

        return c.getTime();
    }

    private Date getGMTDateWithSkewAdjusted(Calendar calendar, long maxClockSkew, boolean addSkew) {
        long offset = calendar.get(Calendar.ZONE_OFFSET);
        if (calendar.getTimeZone().inDaylightTime(calendar.getTime())) {
            offset += calendar.getTimeZone().getDSTSavings();
        }
        long beforeTime = calendar.getTimeInMillis();
        long currentTime = beforeTime - offset;

        if (addSkew) {
            currentTime = currentTime + maxClockSkew;
        }
        else {
            currentTime = currentTime - maxClockSkew;
        }

        calendar.setTimeInMillis(currentTime);
        return calendar.getTime();
    }

    private Date parseDate(String date) throws TimestampValidationCallback.TimestampValidationException {
        SimpleDateFormat calendarFormatter1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        SimpleDateFormat calendarFormatter2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'SSS'Z'");

        try {
            try {
                return calendarFormatter1.parse(date);
            }
            catch (ParseException ignored) {
                return calendarFormatter2.parse(date);
            }
        }
        catch (ParseException ex) {
            throw new TimestampValidationCallback.TimestampValidationException("Could not parse request date: " + date,
                    ex);
        }
    }

    private void validateCreationTime(Date created, long maxClockSkew, long timestampFreshnessLimit)
            throws TimestampValidationCallback.TimestampValidationException {
        Date current = getFreshnessAndSkewAdjustedDate(maxClockSkew, timestampFreshnessLimit);

        if (created.before(current)) {
            throw new TimestampValidationCallback.TimestampValidationException(
                    "The creation time is older than  currenttime - timestamp-freshness-limit - max-clock-skew");
        }

        Date currentTime = getGMTDateWithSkewAdjusted(new GregorianCalendar(), maxClockSkew, true);
        if (currentTime.before(created)) {
            throw new TimestampValidationCallback.TimestampValidationException(
                    "The creation time is ahead of the current time.");
        }
    }

    private void validateExpirationTime(Date expires, long maxClockSkew)
            throws TimestampValidationCallback.TimestampValidationException {
        Date currentTime = getGMTDateWithSkewAdjusted(new GregorianCalendar(), maxClockSkew, false);
        if (expires.before(currentTime)) {
            throw new TimestampValidationCallback.TimestampValidationException(
                    "The current time is ahead of the expiration time in Timestamp");
        }
    }

}
