package org.jboss.resteasy.core.interception.jaxrs;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.ReaderInterceptor;
import jakarta.ws.rs.ext.ReaderInterceptorContext;

import org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages;
import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.tracing.InterceptorTimestampPair;
import org.jboss.resteasy.tracing.RESTEasyTracingLogger;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public abstract class AbstractReaderInterceptorContext implements ReaderInterceptorContext {
    protected RESTEasyTracingLogger tracingLogger;
    private InterceptorTimestampPair<ReaderInterceptor> lastTracedInterceptor;
    protected ReaderInterceptor[] interceptors;
    protected ResteasyProviderFactory providerFactory;
    protected Class type;
    protected Type genericType;
    protected Annotation[] annotations;
    protected MediaType mediaType;
    protected MultivaluedMap<String, String> headers;
    protected InputStream inputStream;
    protected int index = 0;

    @Deprecated
    public AbstractReaderInterceptorContext(final MediaType mediaType, final ResteasyProviderFactory providerFactory,
            final Annotation[] annotations, final ReaderInterceptor[] interceptors,
            final MultivaluedMap<String, String> headers, final Type genericType, final Class type,
            final InputStream inputStream) {
        this.mediaType = mediaType;
        this.annotations = annotations;
        this.interceptors = interceptors;
        this.headers = headers;
        this.genericType = genericType;
        this.type = type;
        this.inputStream = inputStream;
        this.providerFactory = providerFactory;
        this.tracingLogger = RESTEasyTracingLogger.empty();
    }

    public AbstractReaderInterceptorContext(final MediaType mediaType, final ResteasyProviderFactory providerFactory,
            final Annotation[] annotations, final ReaderInterceptor[] interceptors,
            final MultivaluedMap<String, String> headers, final Type genericType, final Class type,
            final InputStream inputStream, final RESTEasyTracingLogger logger) {
        this.mediaType = mediaType;
        this.annotations = annotations;
        this.interceptors = interceptors;
        this.headers = headers;
        this.genericType = genericType;
        this.type = type;
        this.inputStream = inputStream;
        this.providerFactory = providerFactory;
        this.tracingLogger = logger;
        if (logger == null) {
            this.tracingLogger = RESTEasyTracingLogger.empty();
        }
    }

    @Override
    public Object proceed() throws IOException {
        LogMessages.LOGGER.debugf("Interceptor Context: %s,  Method : proceed", getClass().getName());
        if (interceptors == null || index >= interceptors.length) {
            MessageBodyReader reader = getReader();
            if (reader != null) {
                tracingLogger.log("MBR_READ_FROM", reader.getClass().getName());
                LogMessages.LOGGER.debugf("MessageBodyReader: %s", reader.getClass().getName());
            }
            return readFrom(reader);
        }
        LogMessages.LOGGER.debugf("ReaderInterceptor: %s", interceptors[index].getClass().getName());

        int x = index;
        traceBefore(interceptors[x]);

        try {
            return interceptors[index++].aroundReadFrom(this);
        } finally {
            traceAfter(interceptors[x]);
        }

        // index--;  we used to pop the index, but the TCK does not like this
    }

    @SuppressWarnings(value = "unchecked")
    protected Object readFrom(MessageBodyReader reader) throws IOException {
        return reader.readFrom(type, genericType, annotations, mediaType, headers, inputStream);
    }

    protected final void traceBefore(final ReaderInterceptor interceptor) {
        if (tracingLogger.isLogEnabled("RI_BEFORE")) {
            if ((lastTracedInterceptor != null) && (interceptor != null)) {
                tracingLogger.logDuration("RI_BEFORE", lastTracedInterceptor.getTimestamp(),
                        lastTracedInterceptor.getInterceptor());
            }
            lastTracedInterceptor = new InterceptorTimestampPair<>(interceptor, System.nanoTime());
        }
    }

    protected final void traceAfter(final ReaderInterceptor interceptor) {
        if (tracingLogger.isLogEnabled("RI_AFTER")) {
            if ((lastTracedInterceptor != null) && (lastTracedInterceptor.getInterceptor() != null)) {
                tracingLogger.logDuration("RI_AFTER", lastTracedInterceptor.getTimestamp(), interceptor);
            }
            lastTracedInterceptor = new InterceptorTimestampPair<>(interceptor, System.nanoTime());
        }
    }

    protected MessageBodyReader getReader() {
        MediaType mediaType = this.mediaType;
        // spec says set to octet stream
        if (getHeaders() != null && getHeaders().getFirst(HttpHeaders.CONTENT_TYPE) == null && mediaType.isWildcardType()) {
            mediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE;
        }
        MessageBodyReader reader = resolveReader(mediaType);
        if (reader == null) {
            throwReaderNotFound();
        }
        return reader;
    }

    protected abstract MessageBodyReader resolveReader(MediaType mediaType);

    protected abstract void throwReaderNotFound();

    @Override
    public InputStream getInputStream() {
        return inputStream;
    }

    @Override
    public void setInputStream(InputStream is) {
        this.inputStream = is;
    }

    @Override
    public MultivaluedMap<String, String> getHeaders() {
        return headers;
    }

    @Override
    public Annotation[] getAnnotations() {
        return annotations;
    }

    @Override
    public void setAnnotations(Annotation[] annotations) {
        if (annotations == null)
            throw new NullPointerException(Messages.MESSAGES.annotationsParamNull());
        this.annotations = annotations;
    }

    @Override
    public Class getType() {
        return type;
    }

    @Override
    public void setType(Class type) {
        this.type = type;
    }

    @Override
    public Type getGenericType() {
        return genericType;
    }

    @Override
    public void setGenericType(Type genericType) {
        this.genericType = genericType;
    }

    @Override
    public MediaType getMediaType() {
        return mediaType;
    }

    @Override
    public void setMediaType(MediaType mediaType) {
        this.mediaType = mediaType;
    }

    public Object getProcessedInterceptorCount() {
        return index;
    }
}
