/*
 * Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */
package com.ning.http.client.async;

import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.ByteArrayPart;
import com.ning.http.client.FilePart;
import com.ning.http.client.RequestBuilder;
import com.ning.http.client.Response;
import com.ning.http.client.StringPart;
import com.ning.http.util.AsyncHttpProviderUtils;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.zip.GZIPInputStream;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

/**
 * @author dominict
 */
public abstract class MultipartUploadTest extends AbstractBasicTest {
    private String BASE_URL;

    private String servletEndpointRedirectUrl;
    public static byte GZIPTEXT[] = new byte[]{31, -117, 8, 8, 11, 43, 79, 75, 0, 3, 104, 101, 108, 108, 111, 46, 116, 120, 116, 0, -53, 72, -51, -55, -55, -25, 2, 0, 32, 48, 58, 54, 6, 0, 0, 0};

    @BeforeClass
    public void setUp() throws Exception {
        server = new Server();

        port1 = findFreePort();

        Connector listener = new SelectChannelConnector();
        listener.setHost("localhost");
        listener.setPort(port1);

        server.addConnector(listener);

        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        context.addServlet(new ServletHolder(new MockMultipartUploadServlet()), "/upload/*");

        server.setHandler(context);
        server.start();

        servletEndpointRedirectUrl = "http://localhost" + ":" + port1;
    }

    @AfterClass
    public void stop() {
        try {

            if (server != null) {
                server.stop();
            }

        } catch (Exception e) {
            System.err.print("Error stopping servlet tester");
            e.printStackTrace();
        }

    }

    private File getClasspathFile(String file) throws FileNotFoundException {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Throwable ex) {
        }
        if (cl == null) {
            cl = MultipartUploadTest.class.getClassLoader();
        }
        URL resourceUrl = cl.getResource(file);

        try {
            return new File(new URI(resourceUrl.toString()).getSchemeSpecificPart());
        } catch (URISyntaxException e) {
            throw new FileNotFoundException(file);
        }
    }

    /**
     * Tests that the streaming of a file works.
     */
    @Test (enabled = true)
    public void testSendingSmallFilesAndByteArray() {
        String expectedContents = "filecontent: hello";
        String expectedContents2 = "gzipcontent: hello";
        String expectedContents3 = "filecontent: hello2";
        String testResource1 = "textfile.txt";
        String testResource2 = "gzip.txt.gz";
        String testResource3 = "textfile2.txt";

        File testResource1File = null;
        try {
            testResource1File = getClasspathFile(testResource1);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            fail("unable to find " + testResource1);
        }

        File testResource2File = null;
        try {
            testResource2File = getClasspathFile(testResource2);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            fail("unable to find " + testResource2);
        }


        File testResource3File = null;
        try {
            testResource3File = getClasspathFile(testResource3);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            fail("unable to find " + testResource3);
        }

        List<File> testFiles = new ArrayList<File>();
        testFiles.add(testResource1File);
        testFiles.add(testResource2File);
        testFiles.add(testResource3File);

        List<String> expected = new ArrayList<String>();
        expected.add(expectedContents);
        expected.add(expectedContents2);
        expected.add(expectedContents3);

        List<Boolean> gzipped = new ArrayList<Boolean>();
        gzipped.add(false);
        gzipped.add(true);
        gzipped.add(false);


        boolean tmpFileCreated = false;
        File tmpFile = null;
        FileOutputStream os = null;
        try {
            tmpFile = File.createTempFile("textbytearray", ".txt");
            os = new FileOutputStream(tmpFile);
            IOUtils.write(expectedContents.getBytes("UTF-8"), os);
            tmpFileCreated = true;

            testFiles.add(tmpFile);
            expected.add(expectedContents);
            gzipped.add(false);


        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } finally {
            if (os != null) {
                IOUtils.closeQuietly(os);
            }
        }

        if (!tmpFileCreated) {
            fail("Unable to test ByteArrayMultiPart, as unable to write to filesystem the tmp test content");
        }


        AsyncHttpClientConfig.Builder bc =
                new AsyncHttpClientConfig.Builder();

        bc.setFollowRedirects(true);


        AsyncHttpClient c = new AsyncHttpClient(bc.build());

        try {

            RequestBuilder builder = new RequestBuilder("POST");
            builder.setUrl(servletEndpointRedirectUrl + "/upload/bob");
            builder.addBodyPart(new FilePart("file1", testResource1File, "text/plain", "UTF-8"));
            builder.addBodyPart(new FilePart("file2", testResource2File, "application/x-gzip", null));
            builder.addBodyPart(new StringPart("Name", "Dominic"));
            builder.addBodyPart(new FilePart("file3", testResource3File, "text/plain", "UTF-8"));

            builder.addBodyPart(new StringPart("Age", "3", AsyncHttpProviderUtils.DEFAULT_CHARSET));
            builder.addBodyPart(new StringPart("Height", "shrimplike", AsyncHttpProviderUtils.DEFAULT_CHARSET));
            builder.addBodyPart(new StringPart("Hair", "ridiculous", AsyncHttpProviderUtils.DEFAULT_CHARSET));

            builder.addBodyPart(new ByteArrayPart("file4", "bytearray.txt", expectedContents.getBytes("UTF-8") ,"text/plain", "UTF-8"));


            com.ning.http.client.Request r = builder.build();

            Response res = c.executeRequest(r).get();

            assertEquals(200, res.getStatusCode());

            testSentFile(expected, testFiles, res, gzipped);

            c.close();
        } catch (Exception e) {
            e.printStackTrace();
            fail("Download Exception");
        } finally {
            FileUtils.deleteQuietly(tmpFile);
        }
    }


    /**
     * Test that the files were sent, based on the response from the servlet
     *
     * @param expectedContents
     * @param sourceFiles
     * @param r
     * @param deflate
     */
    private void testSentFile(List<String> expectedContents, List<File> sourceFiles,
                              Response r, List<Boolean> deflate) {
        String content = null;
        try {
            content = r.getResponseBody();
            assertNotNull("===>" + content);
            System.out.println(content);
        } catch (IOException e) {
            fail("Unable to obtain content");
        }

        String[] contentArray = content.split("\\|\\|");
        // TODO: this fail on win32
        assertEquals(2, contentArray.length);

        String tmpFiles = contentArray[1];
        assertNotNull(tmpFiles);
        assertTrue(tmpFiles.trim().length() > 2);
        tmpFiles = tmpFiles.substring(1, tmpFiles.length() - 1);

        String[] responseFiles = tmpFiles.split(",");
        assertNotNull(responseFiles);
        assertEquals( sourceFiles.size(), responseFiles.length);


        System.out.println(Arrays.toString(responseFiles));
        //assertTrue("File should exist: " + tmpFile.getAbsolutePath(),tmpFile.exists());

        int i = 0;
        for (File sourceFile : sourceFiles) {

            FileInputStream instream = null;
            File tmp = null;
            try {

                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] sourceBytes = null;
                try {
                    instream = new FileInputStream(sourceFile);
                    byte[] buf = new byte[8092];
                    int len = 0;
                    while ((len = instream.read(buf)) > 0) {
                        baos.write(buf, 0, len);
                    }
                    System.out.println("================");
                    System.out.println("Length of file: " + baos.toByteArray().length);
                    System.out.println("Contents: " + Arrays.toString(baos.toByteArray()));
                    System.out.println("================");
                    System.out.flush();
                    sourceBytes = baos.toByteArray();
                } finally {
                    IOUtils.closeQuietly(instream);
                }


                tmp = new File(responseFiles[i].trim());
                System.out.println("==============================");
                System.out.println(tmp.getAbsolutePath());
                System.out.println("==============================");
                System.out.flush();
                assertTrue(tmp.exists());


                instream = new FileInputStream(tmp);
                ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
                byte[] buf = new byte[8092];
                int len = 0;
                while ((len = instream.read(buf)) > 0) {
                    baos2.write(buf, 0, len);
                }
                IOUtils.closeQuietly(instream);

                assertEquals(sourceBytes, baos2.toByteArray());

                if (!deflate.get(i)) {

                    String helloString = new String(baos2.toByteArray());
                    assertEquals(expectedContents.get(i), helloString);
                } else {
                    instream = new FileInputStream(tmp);

                    GZIPInputStream deflater = new GZIPInputStream(instream);
                    ByteArrayOutputStream baos3 = new ByteArrayOutputStream();
                    byte[] buf3 = new byte[8092];
                    int len3 = 0;
                    while ((len3 = deflater.read(buf3)) > 0) {
                        baos3.write(buf3, 0, len3);
                    }

                    String helloString = new String(baos3.toByteArray());

                    assertEquals(expectedContents.get(i), helloString);

                }
            } catch (Exception e) {
                e.printStackTrace();
                fail("Download Exception");
            } finally {
                if (tmp != null) FileUtils.deleteQuietly(tmp);
                IOUtils.closeQuietly(instream);
                i++;
            }
        }
    }

    /**
     * Takes the content that is being passed to it, and streams to a file on disk
     *
     * @author dominict
     */
    public static class MockMultipartUploadServlet extends HttpServlet {
        /**
         *
         */
        private static final long serialVersionUID = 1L;
        private int filesProcessed = 0;
        private int stringsProcessed = 0;


        public MockMultipartUploadServlet() {

        }

        public synchronized void resetFilesProcessed() {
            filesProcessed = 0;
        }

        private synchronized int incrementFilesProcessed() {
            return ++filesProcessed;

        }

        public int getFilesProcessed() {
            return filesProcessed;
        }

        public synchronized void resetStringsProcessed() {
            stringsProcessed = 0;
        }

        private synchronized int incrementStringsProcessed() {
            return ++stringsProcessed;

        }

        public int getStringsProcessed() {
            return stringsProcessed;
        }

        @Override
        public void service(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            // Check that we have a file upload request
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            if (isMultipart) {
                List<String> files = new ArrayList<String>();
                ServletFileUpload upload = new ServletFileUpload();
                // Parse the request
                FileItemIterator iter = null;
                try {
                    iter = upload.getItemIterator(request);
                    while (iter.hasNext()) {
                        FileItemStream item = iter.next();
                        String name = item.getFieldName();
                        InputStream stream = null;
                        try {
                            stream = item.openStream();

                            if (item.isFormField()) {
                                System.out.println("Form field " + name + " with value "
                                        + Streams.asString(stream) + " detected.");
                                incrementStringsProcessed();
                            } else {
                                System.out.println("File field " + name + " with file name "
                                        + item.getName() + " detected.");
                                // Process the input stream
                                OutputStream os = null;
                                try {
                                    File tmpFile = File.createTempFile(UUID.randomUUID().toString() + "_MockUploadServlet", ".tmp");
                                    tmpFile.deleteOnExit();
                                    os = new FileOutputStream(tmpFile);
                                    byte[] buffer = new byte[4096];
                                    int bytesRead;
                                    while ((bytesRead = stream.read(buffer)) != -1) {
                                        os.write(buffer, 0, bytesRead);
                                    }
                                    incrementFilesProcessed();
                                    files.add(tmpFile.getAbsolutePath());
                                } finally {
                                    IOUtils.closeQuietly(os);
                                }
                            }
                        } finally {
                            IOUtils.closeQuietly(stream);
                        }
                    }
                } catch (FileUploadException e) {

                }
                Writer w = response.getWriter();
                w.write(Integer.toString(getFilesProcessed()));
                resetFilesProcessed();
                resetStringsProcessed();
                w.write("||");
                w.write(files.toString());
                w.close();
            } else {
                Writer w = response.getWriter();
                w.write(Integer.toString(getFilesProcessed()));
                resetFilesProcessed();
                resetStringsProcessed();
                w.write("||");
                w.close();
            }

        }

    }


}
