package org.jboss.resteasy.specimpl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;

import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.ext.ReaderInterceptor;

import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.interception.jaxrs.AbstractReaderInterceptorContext;
import org.jboss.resteasy.core.interception.jaxrs.ClientReaderInterceptorContext;
import org.jboss.resteasy.plugins.providers.sse.EventInput;
import org.jboss.resteasy.resteasy_jaxrs.i18n.LogMessages;
import org.jboss.resteasy.resteasy_jaxrs.i18n.Messages;
import org.jboss.resteasy.spi.HttpResponseCodes;
import org.jboss.resteasy.spi.MarshalledEntity;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.util.Types;
import org.jboss.resteasy.util.InputStreamToByteArray;
import org.jboss.resteasy.util.ReadFromStream;
import org.reactivestreams.Publisher;

/**
 * A response object not attached to a client or server invocation.
 *
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public class BuiltResponse extends AbstractBuiltResponse {

    public BuiltResponse() {
        super();
    }

    public BuiltResponse(final int status, final Headers<Object> metadata,
            final Object entity, final Annotation[] entityAnnotations) {
        this(status, null, metadata, entity, entityAnnotations);
    }

    public BuiltResponse(final int status, final String reason,
            final Headers<Object> metadata, final Object entity,
            final Annotation[] entityAnnotations) {
        super(status, reason, metadata, entity, entityAnnotations);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T readEntity(Class<T> type, Type genericType, Annotation[] anns) {
        abortIfClosed();
        if (entity != null) {
            if (type.isInstance((this.entity))) {
                return (T) entity;
            } else if (entity instanceof InputStream) {
                setInputStream((InputStream) entity);
                entity = null;
            } else if (bufferedEntity == null) {
                throw new RuntimeException(Messages.MESSAGES.entityAlreadyRead(entity.getClass()));
            } else {
                entity = null;
            }
        }

        if (entity == null) {
            if (status == HttpResponseCodes.SC_NO_CONTENT)
                return null;

            try {
                entity = readFrom(type, genericType, getMediaType(), anns);
                if (entity == null || (entity != null
                        && !InputStream.class.isInstance(entity)
                        && !Reader.class.isInstance(entity)
                        && bufferedEntity == null)) {
                    try {
                        if (!Publisher.class.isInstance(entity) && !EventInput.class.isInstance(entity)) {
                            close();
                        }
                    } catch (Exception ignored) {
                    }
                }
            } catch (RuntimeException e) {
                try {
                    close();
                } catch (Exception ignored) {

                }
                LogMessages.LOGGER.clientReceiveProcessingFailure(e);
                throw e;
            }
        }
        return (T) entity;
    }

    // this is synchronized in conjunction with finalize to protect against premature finalize called by the GC
    protected synchronized <T> Object readFrom(Class<T> type, Type genericType,
            MediaType media, Annotation[] annotations) {
        Type useGeneric = genericType == null ? type : genericType;
        Class<?> useType = type;
        media = media == null ? MediaType.WILDCARD_TYPE : media;
        annotations = annotations == null ? this.annotations : annotations;
        boolean isMarshalledEntity = false;
        if (type.equals(MarshalledEntity.class)) {
            isMarshalledEntity = true;
            ParameterizedType param = (ParameterizedType) useGeneric;
            useGeneric = param.getActualTypeArguments()[0];
            useType = Types.getRawType(useGeneric);
        }

        try {
            InputStream is = getEntityStream();
            if (is == null) {
                throw new IllegalStateException(Messages.MESSAGES.inputStreamWasEmpty());
            }
            if (isMarshalledEntity) {
                is = new InputStreamToByteArray(is);

            }

            ResteasyProviderFactory providerFactory = ResteasyProviderFactory.getInstance();
            ReaderInterceptor[] readerInterceptors = providerFactory
                    .getServerReaderInterceptorRegistry()
                    .postMatch(null, null);

            final Object finalObj;
            AbstractReaderInterceptorContext context = new ClientReaderInterceptorContext(
                    readerInterceptors, providerFactory, useType,
                    useGeneric, annotations, media, getStringHeaders(), is,
                    new HashMap<String, Object>(), null);

            finalObj = context.proceed();

            if (isMarshalledEntity) {
                InputStreamToByteArray isba = (InputStreamToByteArray) is;
                final byte[] bytes = isba.toByteArray();
                return new MarshalledEntity<Object>() {
                    @Override
                    public byte[] getMarshalledBytes() {
                        return bytes;
                    }

                    @Override
                    public Object getEntity() {
                        return finalObj;
                    }
                };
            } else {
                return finalObj;
            }

        } catch (ProcessingException pe) {
            throw pe;
        } catch (Exception ex) {
            throw new ProcessingException(ex);
        }
    }

    protected InputStream getEntityStream() {
        if (bufferedEntity != null) {
            return new ByteArrayInputStream(bufferedEntity);
        }
        if (isClosed()) {
            throw new ProcessingException(Messages.MESSAGES.streamIsClosed());
        }
        InputStream is = getInputStream();
        return is != null ? new AbstractBuiltResponse.InputStreamWrapper<BuiltResponse>(is, this) : null;
    }

    protected void setInputStream(InputStream is) {
        this.is = is;
        resetEntity();
    }

    protected InputStream getInputStream() {
        if (is == null && entity != null && entity instanceof InputStream) {
            is = (InputStream) entity;
        }
        return is;
    }

    @Override
    public void releaseConnection() throws IOException {
        releaseConnection(false);
    }

    @Override
    public void releaseConnection(boolean consumeInputStream) throws IOException {
        try {
            if (is != null) {
                if (consumeInputStream) {
                    while (is.read() > 0) {
                    }
                }
                is.close();
                is = null;
            }
        } catch (IOException e) {

        }

    }

    @Override
    public boolean bufferEntity() {
        abortIfClosed();

        if (bufferedEntity != null)
            return true;
        if (streamRead)
            return false;
        InputStream is = getInputStream();
        if (is == null)
            return false;
        try {
            bufferedEntity = ReadFromStream.readFromStream(1024, is);
        } catch (IOException e) {
            throw new ProcessingException(e);
        } finally {
            try {
                releaseConnection();
            } catch (IOException e) {
                throw new ProcessingException(e);
            }
        }
        return true;
    }
}
