/*
 * Copyright (C) 2007-2014, GoodData(R) Corporation. All rights reserved.
 */
package com.gooddata;

import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.client.HttpMessageConverterExtractor;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;

import static com.gooddata.Validate.notNull;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static org.springframework.http.HttpMethod.GET;


/**
 * Parent for GoodData services providing helpers for REST API calls and polling.
 */
public abstract class AbstractService {

    public static Integer WAIT_BEFORE_RETRY_IN_MILLIS = 5 * 1000;

    protected final RestTemplate restTemplate;

    protected final ObjectMapper mapper = new ObjectMapper();

    protected final RequestCallback noopRequestCallback = new RequestCallback() {
        @Override
        public void doWithRequest(final ClientHttpRequest request) throws IOException {
        }
    };

    private final ResponseExtractor<ClientHttpResponse> reusableResponseExtractor = new ResponseExtractor<ClientHttpResponse>() {
        @Override
        public ClientHttpResponse extractData(final ClientHttpResponse response) throws IOException {
            return new ReusableClientHttpResponse(response);
        }
    };


    /**
     * Sets RESTful HTTP Spring template. Should be called from constructor of concrete service extending
     * this abstract one.
     *
     * @param restTemplate RESTful HTTP Spring template
     */
    public AbstractService(RestTemplate restTemplate) {
        this.restTemplate = notNull(restTemplate, "restTemplate");
    }

    final <R> R poll(final PollHandler<?,R> handler, long timeout, final TimeUnit unit) {
        notNull(handler, "handler");
        final long start = System.currentTimeMillis();
        while (true) {
            if (pollOnce(handler)) {
                return handler.getResult();
            }
            if (unit != null && start + unit.toMillis(timeout) > System.currentTimeMillis()) {
                throw new GoodDataException("timeout");
            }

            try {
                Thread.sleep(WAIT_BEFORE_RETRY_IN_MILLIS);
            } catch (InterruptedException e) {
                throw new GoodDataException("interrupted");
            }
        }
    }

    final <P> boolean pollOnce(final PollHandler<P,?> handler) {
        notNull(handler, "handler");
        final ClientHttpResponse response = restTemplate.execute(handler.getPollingUri(), GET, noopRequestCallback,
                reusableResponseExtractor);

        try {
            if (handler.isFinished(response)) {
                final P data = extractData(response, handler.getPollClass());
                handler.handlePollResult(data);
            } else if (HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())) {
                throw new GoodDataException(
                        format("Polling returned client error HTTP status %s", response.getStatusCode().value())
                );
            }
        } catch (IOException e) {
            throw new GoodDataException("I/O error occurred during HTTP response extraction", e);
        }
        return handler.isDone();
    }

    protected final <T> T extractData(ClientHttpResponse response, Class<T> cls) throws IOException {
        notNull(response, "response");
        notNull(cls, "cls");
        if (Void.class.isAssignableFrom(cls)) {
            return null;
        }
        return new HttpMessageConverterExtractor<>(cls, restTemplate.getMessageConverters()).extractData(response);
    }

    private class ReusableClientHttpResponse implements ClientHttpResponse {

        private final byte[] body;
        private final HttpStatus statusCode;
        private final int rawStatusCode;
        private final String statusText;
        private final HttpHeaders headers;

        public ReusableClientHttpResponse(ClientHttpResponse response) {
            try {
                body = FileCopyUtils.copyToByteArray(response.getBody());
                statusCode = response.getStatusCode();
                rawStatusCode = response.getRawStatusCode();
                statusText = response.getStatusText();
                headers = response.getHeaders();
            } catch (IOException e) {
                throw new RuntimeException("Unable to read from HTTP response", e);
            } finally {
                if (response != null) {
                    response.close();
                }
            }
        }

        @Override
        public HttpStatus getStatusCode() throws IOException {
            return statusCode;
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return rawStatusCode;
        }

        @Override
        public String getStatusText() throws IOException {
            return statusText;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }

        @Override
        public InputStream getBody() throws IOException {
            return new ByteArrayInputStream(body);
        }

        @Override
        public void close() {
            //already closed
        }
    }

    protected static class OutputStreamResponseExtractor implements ResponseExtractor<Integer> {
        private final OutputStream output;

        public OutputStreamResponseExtractor(OutputStream output) {
            this.output = output;
        }

        @Override
        public Integer extractData(ClientHttpResponse response) throws IOException {
            return FileCopyUtils.copy(response.getBody(), output);
        }
    }

}

@@ -97,16 +97,156 @@
 ddata;%0A%0A
+import static com.gooddata.Validate.notNull;%0Aimport static java.lang.String.format;%0Aimport static org.springframework.http.HttpMethod.GET;%0A%0A
 import o
@@ -721,67 +721,8 @@
 or;%0A
-import org.springframework.web.client.RestClientException;%0A
 impo
@@ -935,195 +935,8 @@
 t;%0A%0A
-import static com.gooddata.Validate.notNull;%0Aimport static java.lang.String.format;%0Aimport static java.util.Collections.emptyList;%0Aimport static org.springframework.http.HttpMethod.GET;%0A%0A
 %0A/**
@@ -4192,22 +4192,16 @@
 private 
-final 
 byte%5B%5D b
@@ -4459,32 +4459,145 @@
           try %7B%0A
+                final InputStream bodyStream = response.getBody();%0A                if (bodyStream != null) %7B%0A    
                 
@@ -4637,28 +4637,38 @@
 ray(
-response.getBody());
+bodyStream);%0A                %7D
 %0A   
@@ -5704,24 +5704,39 @@
       return
+ body != null ?
  new ByteArr
@@ -5754,16 +5754,23 @@
 am(body)
+ : null
 ;%0A      

fix polling on response without body