/**
 * JBoss, Home of Professional Open Source Copyright Red Hat, Inc., and
 * individual contributors.
 *
 * Licensed 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.jboss.aerogear.android.impl.pipeline;

import static org.junit.Assert.assertTrue;

import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.aerogear.android.Callback;
import org.jboss.aerogear.android.Pipeline;
import org.jboss.aerogear.android.Provider;
import org.jboss.aerogear.android.RecordId;
import org.jboss.aerogear.android.http.HeaderAndBody;
import org.jboss.aerogear.android.http.HttpException;
import org.jboss.aerogear.android.http.HttpProvider;
import org.jboss.aerogear.android.impl.helper.UnitTestUtils;
import org.jboss.aerogear.android.impl.http.HttpStubProvider;
import org.jboss.aerogear.android.pipeline.Pipe;
import org.junit.Assert;
import org.junit.Before;
import sun.rmi.runtime.Log;

public class MultipartRequestBuilderTest {

    private static final String STRING_DATA = "This is a String";
    public Queue<String> multiPartSerializedData;

    @Before
    public void resetSeriaializedData() {
        multiPartSerializedData = new ArrayDeque<String>(20);
        multiPartSerializedData.add("multipart/form-data; boundary=[\\w\\d-]+");

        multiPartSerializedData.add("--[\\w\\d-]+\r\n");
        multiPartSerializedData.add("Content-Disposition: form-data; name=\"string\"\r\n");
        multiPartSerializedData.add("Content-Type: text/plain; charset=US-ASCII\r\n");
        multiPartSerializedData.add("Content-Transfer-Encoding: 8bit\r\n");
        multiPartSerializedData.add("\r\n");
        multiPartSerializedData.add(STRING_DATA + "\r\n");
        multiPartSerializedData.add("--[\\w\\d-]+\r\n");
        multiPartSerializedData.add("Content-Disposition: form-data; name=\"files\"\r\n");
        multiPartSerializedData.add("Content-Type: multipart/mixed; boundary=[\\w\\d-]+\r\n");
        multiPartSerializedData.add("\r\n");
        multiPartSerializedData.add("--[\\w\\d-]+\r\n");
        multiPartSerializedData.add("Content-Disposition: file; filename=\"inputStream\"\r\n");
        multiPartSerializedData.add("Content-Type: application/octet-stream\r\n");
        multiPartSerializedData.add("Content-Transfer-Encoding: binary\r\n");
        multiPartSerializedData.add("\r\n");
        multiPartSerializedData.add("abcdef\r\n");
        multiPartSerializedData.add("--[\\w\\d-]+\r\n");
        multiPartSerializedData.add("Content-Disposition: file; filename=\"byteArray\"\r\n");
        multiPartSerializedData.add("Content-Type: application/octet-stream\r\n");
        multiPartSerializedData.add("Content-Transfer-Encoding: binary\r\n");
        multiPartSerializedData.add("\r\n");
        multiPartSerializedData.add("abcdef\r\n");
        multiPartSerializedData.add("--[\\w\\d-]+--\r\n");
        multiPartSerializedData.add("--[\\w\\d-]+--\r\n");
    }

    @Test
    public void testGetContentType() {
        MultipartRequestBuilder<Object> builder = new MultipartRequestBuilder();
        assertTrue(builder.getContentType().startsWith("multipart/form-data;"));
    }

    @Test
    public void testBuilder() {

        MultiPartData data = new MultiPartData();
        MultipartRequestBuilder<MultiPartData> dataBuilder = new MultipartRequestBuilder<MultiPartData>();
        assertTrue(dataBuilder.getContentType().matches(multiPartSerializedData.poll()));
        byte[] out = dataBuilder.getBody(data);
        ByteArrayInputStream stream = new ByteArrayInputStream(out);
        StringBuilder stringBuilder = new StringBuilder();
        int readByte;
        while ((readByte = stream.read()) != -1) {
            stringBuilder.append((char) readByte);
            if (((char) readByte) == '\n') {
                assertTrue(stringBuilder.toString().matches(multiPartSerializedData.poll()));
                stringBuilder = new StringBuilder();
            }
        }

    }

    @Test
    public void testMultipartBuilderCall() throws MalformedURLException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InterruptedException {
        URL url = new URL("http://example.com");
        final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        final HttpStubProvider provider = new HttpStubProvider(url, null) {
            
            @Override
            public HeaderAndBody put(String id, byte[] data) throws HttpException {
                byteBuffer.put(data);
                return new HeaderAndBody("{}".getBytes(), new HashMap<String, Object>());
            }
        };

        PipeConfig config = new PipeConfig(url, MultipartRequestBuilderTest.MultiPartData.class);
        config.setRequestBuilder(new MultipartRequestBuilder<MultipartRequestBuilderTest.MultiPartData>());

        Pipeline pipeline = new Pipeline(url);
        Pipe<MultipartRequestBuilderTest.MultiPartData> restPipe = pipeline.pipe(MultipartRequestBuilderTest.MultiPartData.class, config);
        org.junit.Assert.assertEquals(MultipartRequestBuilder.class, restPipe.getRequestBuilder().getClass());

        Object restRunner = UnitTestUtils.getPrivateField(restPipe, "restRunner");
        UnitTestUtils.setPrivateField(restRunner, "httpProviderFactory", new Provider<HttpProvider>() {
            @Override
            public HttpProvider get(Object... in) {
                return provider;
            }
        });

        final CountDownLatch latch = new CountDownLatch(1);


        restPipe.save(new MultipartRequestBuilderTest.MultiPartData(), new Callback<MultipartRequestBuilderTest.MultiPartData>() {
            @Override
            public void onSuccess(MultipartRequestBuilderTest.MultiPartData data) {
                latch.countDown();
            }

            @Override
            public void onFailure(Exception e) {
                Logger.getLogger(MultiPartData.class.getCanonicalName()).log(Level.SEVERE, e.getMessage(), e);
                latch.countDown();
            }
        });

        latch.await();

        
        //move data past the header
        multiPartSerializedData.poll();
        
        ByteArrayInputStream stream = new ByteArrayInputStream(byteBuffer.array());
        StringBuilder stringBuilder = new StringBuilder();
        int readByte;
        while ((readByte = stream.read()) != -1) {
            stringBuilder.append((char) readByte);
            if (((char) readByte) == '\n') {
                assertTrue(stringBuilder.toString().matches(multiPartSerializedData.poll()));
                stringBuilder = new StringBuilder();
            }
        }

    }

    @Test
    public void testBuilderUsage() throws MalformedURLException {
        URL baseUrl = new URL("http://example.com");
        PipeConfig config = new PipeConfig(baseUrl, MultiPartData.class);
        config.setRequestBuilder(new MultipartRequestBuilder<MultiPartData>());

        Pipeline pipeline = new Pipeline(baseUrl);
        Pipe<MultiPartData> pipe = pipeline.pipe(MultiPartData.class, config);
        Assert.assertEquals(MultipartRequestBuilder.class, pipe.getRequestBuilder().getClass());

    }

    public static class MultiPartData {

        private byte[] byteArray = {'a', 'b', 'c', 'd', 'e', 'f'};
        private InputStream inputStream = new ByteArrayInputStream(byteArray);
        
        @RecordId
        private String string = STRING_DATA;

        public byte[] getByteArray() {
            return byteArray;
        }

        public InputStream getInputStream() {
            return inputStream;
        }

        public String getString() {
            return string;
        }

        public void setByteArray(byte[] byteArray) {
            this.byteArray = byteArray;
        }

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

        public void setString(String string) {
            this.string = string;
        }
    }
}
