/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 io.undertow.attribute;

import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;

import java.util.concurrent.TimeUnit;

/**
 * The response time
 *
 * This will only work if {@link io.undertow.UndertowOptions#RECORD_REQUEST_START_TIME} has been set
 */
public class ResponseTimeAttribute implements ExchangeAttribute {

    private static final AttachmentKey<Long> FIRST_RESPONSE_TIME_NANOS = AttachmentKey.create(Long.class);

    public static final String RESPONSE_TIME_MILLIS_SHORT = "%D";
    public static final String RESPONSE_TIME_SECONDS_SHORT = "%T";
    public static final String RESPONSE_TIME_MILLIS = "%{RESPONSE_TIME}";
    public static final String RESPONSE_TIME_MICROS = "%{RESPONSE_TIME_MICROS}";
    public static final String RESPONSE_TIME_NANOS = "%{RESPONSE_TIME_NANOS}";

    private final TimeUnit timeUnit;

    public ResponseTimeAttribute(TimeUnit timeUnit) {
        this.timeUnit = timeUnit;
    }

    @Override
    public String readAttribute(HttpServerExchange exchange) {
        long requestStartTime = exchange.getRequestStartTime();
        if(requestStartTime == -1) {
            return null;
        }
        final long nanos;
        Long first = exchange.getAttachment(FIRST_RESPONSE_TIME_NANOS);
        if(first != null) {
            nanos = first;
        } else {
            nanos = System.nanoTime() - requestStartTime;
            if(exchange.isResponseComplete()) {
                //save the response time so it is consistent
                exchange.putAttachment(FIRST_RESPONSE_TIME_NANOS, nanos);
            }
        }
        if(timeUnit == TimeUnit.SECONDS) {
            StringBuilder buf = new StringBuilder();
            long milis = TimeUnit.MILLISECONDS.convert(nanos, TimeUnit.NANOSECONDS);
            buf.append(Long.toString(milis / 1000));
            buf.append('.');
            int remains = (int) (milis % 1000);
            buf.append(Long.toString(remains / 100));
            remains = remains % 100;
            buf.append(Long.toString(remains / 10));
            buf.append(Long.toString(remains % 10));
            return buf.toString();
        } else {
            return String.valueOf(timeUnit.convert(nanos, TimeUnit.NANOSECONDS));
        }
    }

    @Override
    public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException {
        throw new ReadOnlyAttributeException("Response Time", newValue);
    }

    public static final class Builder implements ExchangeAttributeBuilder {

        @Override
        public String name() {
            return "Response Time";
        }

        @Override
        public ExchangeAttribute build(String token) {
            if (token.equals(RESPONSE_TIME_MILLIS) || token.equals(RESPONSE_TIME_MILLIS_SHORT)) {
                return new ResponseTimeAttribute(TimeUnit.MILLISECONDS);
            }
            if (token.equals(RESPONSE_TIME_SECONDS_SHORT)) {
                return new ResponseTimeAttribute(TimeUnit.SECONDS);
            }
            if(token.equals(RESPONSE_TIME_MICROS)) {
                return new ResponseTimeAttribute(TimeUnit.MICROSECONDS);
            }
            if(token.equals(RESPONSE_TIME_NANOS)) {
                return new ResponseTimeAttribute(TimeUnit.NANOSECONDS);
            }
            return null;
        }

        @Override
        public int priority() {
            return 0;
        }
    }

}
