/*
 * ====================================================================
 * 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package org.apache.http.nio.client.methods;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.concurrent.Future;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.io.output.FileWriterWithEncoding;
import org.apache.http.Consts;
import org.apache.http.localserver.HttpAsyncTestBase;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NFileEntity;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.nio.protocol.BasicAsyncRequestHandler;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

public class TestZeroCopy extends HttpAsyncTestBase {

    private static final String[] TEXT = {
        "blah blah blah blah blah blah blah blah blah blah blah blah blah blah",
        "yada yada yada yada yada yada yada yada yada yada yada yada yada yada",
        "da da da da da da da da da da da da da da da da da da da da da da da da",
        "nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet nyet"
    };

    private static final Charset ASCII = Charset.forName("ascii");
    private static File TEST_FILE;
    private File tmpfile;

    @BeforeClass
    public static void createSrcFile() throws Exception {
        final File tmpdir = FileUtils.getTempDirectory();
        TEST_FILE = new File(tmpdir, "src.test");
        final FileWriterWithEncoding out = new FileWriterWithEncoding(TEST_FILE, ASCII);
        try {
            for (int i = 0; i < 500; i++) {
                for (final String line: TEXT) {
                    out.write(line);
                    out.write("\r\n");
                }
            }
        } finally {
            out.close();
        }
    }

    @AfterClass
    public static void deleteSrcFile() throws Exception {
        if (TEST_FILE != null) {
            TEST_FILE.delete();
            TEST_FILE = null;
        }
    }

    @After
    public void cleanUp() throws Exception {
        if (this.tmpfile != null && this.tmpfile.exists()) {
            this.tmpfile.delete();
        }
    }

    static class TestZeroCopyPost extends BaseZeroCopyRequestProducer {

        private final boolean forceChunking;

        protected TestZeroCopyPost(
                final String requestURI,
                final boolean forceChunking) throws FileNotFoundException {
            super(URI.create(requestURI), TEST_FILE, ContentType.create("text/plain"));
            this.forceChunking = forceChunking;
        }

        @Override
        protected HttpEntityEnclosingRequest createRequest(final URI requestURI, final HttpEntity entity) {
            final HttpPost httppost = new HttpPost(requestURI);
            if (this.forceChunking) {
                final BasicHttpEntity chunkedEntity = new BasicHttpEntity();
                chunkedEntity.setChunked(true);
                httppost.setEntity(chunkedEntity);
            } else {
                httppost.setEntity(entity);
            }
            return httppost;
        }

    }

    static class TestZeroCopyConsumer extends ZeroCopyConsumer<Integer> {

        public TestZeroCopyConsumer(final File file) throws FileNotFoundException {
            super(file);
        }

        @Override
        protected Integer process(
                final HttpResponse response,
                final File file,
                final ContentType contentType) {
            return response.getStatusLine().getStatusCode();
        }

    }

    static class TestHandler implements HttpRequestHandler {

        private final boolean forceChunking;

        TestHandler(final boolean forceChunking) {
            super();
            this.forceChunking = forceChunking;
        }

        @Override
        public void handle(
                final HttpRequest request,
                final HttpResponse response,
                final HttpContext context) throws HttpException, IOException {
            HttpEntity requestEntity = null;
            if (request instanceof HttpEntityEnclosingRequest) {
                requestEntity = ((HttpEntityEnclosingRequest) request).getEntity();
            }
            if (requestEntity == null) {
                response.setEntity(new NStringEntity("Empty content"));
                return;
            }

            boolean ok = true;

            final InputStream inStream = requestEntity.getContent();
            try {
                final ContentType contentType = ContentType.getOrDefault(requestEntity);
                Charset charset = contentType.getCharset();
                if (charset == null) {
                    charset = Consts.ISO_8859_1;
                }
                final LineIterator it = IOUtils.lineIterator(inStream, charset.name());
                int count = 0;
                while (it.hasNext()) {
                    final String line = it.next();
                    final int i = count % TEXT.length;
                    final String expected = TEXT[i];
                    if (!line.equals(expected)) {
                        ok = false;
                        break;
                    }
                    count++;
                }
            } finally {
                inStream.close();
            }
            if (ok) {
                final NFileEntity responseEntity = new NFileEntity(TEST_FILE,
                        ContentType.create("text/plian"));
                if (this.forceChunking) {
                    responseEntity.setChunked(true);
                }
                response.setEntity(responseEntity);
            } else {
                response.setEntity(new NStringEntity("Invalid content"));
            }
        }
    }

    @Test
    public void testTwoWayZeroCopy() throws Exception {
        this.serverBootstrap.registerHandler("*", new BasicAsyncRequestHandler(new TestHandler(false)));
        final HttpHost target = start();

        final File tmpdir = FileUtils.getTempDirectory();
        this.tmpfile = new File(tmpdir, "dst.test");
        final TestZeroCopyPost httppost = new TestZeroCopyPost(target.toURI() + "/bounce", false);
        final TestZeroCopyConsumer consumer = new TestZeroCopyConsumer(this.tmpfile);
        final Future<Integer> future = this.httpclient.execute(httppost, consumer, null);
        final Integer status = future.get();
        Assert.assertNotNull(status);
        Assert.assertEquals(HttpStatus.SC_OK, status.intValue());
        final InputStream inStream = new FileInputStream(this.tmpfile);
        try {
            final LineIterator it = IOUtils.lineIterator(inStream, ASCII.name());
            int count = 0;
            while (it.hasNext()) {
                final String line = it.next();
                final int i = count % TEXT.length;
                final String expected = TEXT[i];
                Assert.assertEquals(expected, line);
                count++;
            }
        } finally {
            inStream.close();
        }
    }

    @Test
    public void testZeroCopyFallback() throws Exception {
        this.serverBootstrap.registerHandler("*", new BasicAsyncRequestHandler(new TestHandler(true)));
        final HttpHost target = start();
        final File tmpdir = FileUtils.getTempDirectory();
        this.tmpfile = new File(tmpdir, "dst.test");
        final TestZeroCopyPost httppost = new TestZeroCopyPost(target.toURI() + "/bounce", true);
        final TestZeroCopyConsumer consumer = new TestZeroCopyConsumer(this.tmpfile);
        final Future<Integer> future = this.httpclient.execute(httppost, consumer, null);
        final Integer status = future.get();
        Assert.assertNotNull(status);
        Assert.assertEquals(HttpStatus.SC_OK, status.intValue());
        final InputStream inStream = new FileInputStream(this.tmpfile);
        try {
            final LineIterator it = IOUtils.lineIterator(inStream, ASCII.name());
            int count = 0;
            while (it.hasNext()) {
                final String line = it.next();
                final int i = count % TEXT.length;
                final String expected = TEXT[i];
                Assert.assertEquals(expected, line);
                count++;
            }
        } finally {
            inStream.close();
        }
    }

}
