/**
 * 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.cxf.transport.http.asyncclient;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;

import org.apache.cxf.io.CachedOutputStream;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
import org.apache.http.protocol.HttpContext;

public class CXFHttpAsyncRequestProducer implements HttpAsyncRequestProducer {

    private final CXFHttpRequest request;
    private final SharedOutputBuffer buf;
    private volatile CachedOutputStream content;
    private volatile ByteBuffer buffer;
    private volatile InputStream fis;
    private volatile ReadableByteChannel chan;

    public CXFHttpAsyncRequestProducer(final CXFHttpRequest request, final SharedOutputBuffer buf) {
        super();
        this.buf = buf;
        this.request = request;
    }

    public HttpHost getTarget() {
        URI uri = request.getURI();
        if (uri == null) {
            throw new IllegalStateException("Request URI is null");
        }
        if (!uri.isAbsolute()) {
            throw new IllegalStateException("Request URI is not absolute");
        }
        return new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
    }

    public HttpRequest generateRequest() throws IOException, HttpException {
        return request;
    }

    public void produceContent(final ContentEncoder enc, final IOControl ioc) throws IOException {
        if (content != null) {
            if (buffer == null) {
                if (content.getTempFile() == null) {
                    buffer = ByteBuffer.wrap(content.getBytes());
                } else {
                    fis = content.getInputStream();
                    chan = (fis instanceof FileInputStream)
                        ? ((FileInputStream)fis).getChannel() : Channels.newChannel(fis);
                    buffer = ByteBuffer.allocate(8 * 1024);
                }
            }
            int i = -1;
            buffer.rewind();
            if (buffer.hasRemaining() && chan != null) {
                i = chan.read(buffer);
                buffer.flip();
            }
            enc.write(buffer);
            if (!buffer.hasRemaining() && i == -1) {
                enc.complete();
            }
        } else {
            buf.produceContent(enc, ioc);
        }
    }

    public void requestCompleted(final HttpContext context) {
        if (fis != null) {
            try {
                fis.close();
            } catch (IOException io) {
                //ignore
            }
            chan = null;
            fis = null;
        }
        buffer = null;
    }

    public void failed(final Exception ex) {
        buf.shutdown();
    }

    public boolean isRepeatable() {
        return request.getOutputStream().retransmitable();
    }

    public void resetRequest() throws IOException {
        if (request.getOutputStream().retransmitable()) {
            content = request.getOutputStream().getCachedStream();
        }
    }

    @Override
    public void close() throws IOException {
        buf.close();
        if (fis != null) {
            try {
                fis.close();
            } catch (IOException io) {
                //ignore
            }
            chan = null;
            fis = null;
        }
        buffer = null;
    }

}
