/*
 * Copyright 2015 the original author or authors.
 *
 * 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 io.bosh.client.v2.internal;

import io.bosh.client.v2.DirectorException;

import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.util.UriComponentsBuilder;

import rx.Observable;

/**
 * @author David Ehringer
 */
public abstract class AbstractSpringOperations {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final int TASK_TRACKING_POLL_INTERVAL = 1000;
    private static final List<String> COMPLETED_STATES = Arrays
            .asList("done", "error", "cancelled");

    protected final RestOperations restOperations;
    protected final URI root;

    protected AbstractSpringOperations(RestOperations restOperations, URI root) {
        this.restOperations = restOperations;
        this.root = root;
    }

    protected final <T> Observable<T> get(Class<T> responseType,
            Consumer<UriComponentsBuilder> builderCallback) {
        return exchange(() -> {
            UriComponentsBuilder builder = UriComponentsBuilder.fromUri(this.root);
            builderCallback.accept(builder);
            URI uri = builder.build().toUri();

            this.logger.debug("GET {}", uri);
            return this.restOperations.getForObject(uri, responseType);
        });
    }

    protected final <T> Observable<ResponseEntity<T>> getEntity(Class<T> responseType,
            Consumer<UriComponentsBuilder> builderCallback) {
        return exchange(() -> {
            UriComponentsBuilder builder = UriComponentsBuilder.fromUri(this.root);
            builderCallback.accept(builder);
            URI uri = builder.build().toUri();

            this.logger.debug("GET {}", uri);
            return this.restOperations.getForEntity(uri, responseType);
        });
    }
    
    protected final <T> Observable<T> post(Class<T> responseType, Object request,
            Consumer<UriComponentsBuilder> builderCallback) {
        return exchange(() -> {
            UriComponentsBuilder builder = UriComponentsBuilder.fromUri(this.root);
            builderCallback.accept(builder);
            URI uri = builder.build().toUri();

            this.logger.debug("GET {}", uri);
            return this.restOperations.postForObject(uri, request, responseType);
        });
    }
    
    protected final <T> Observable<ResponseEntity<T>> postForEntity(Class<T> responseType, Object request,
            Consumer<UriComponentsBuilder> builderCallback) {
        return exchange(() -> {
            UriComponentsBuilder builder = UriComponentsBuilder.fromUri(this.root);
            builderCallback.accept(builder);
            URI uri = builder.build().toUri();

            this.logger.debug("POST {}", uri);
            return this.restOperations.postForEntity(uri, request, responseType);
        });
    }

    protected final <T> Observable<T> exchange(Supplier<T> exchange) {
        return Observable.create(subscriber -> {

            try {
                subscriber.onNext(exchange.get());
                subscriber.onCompleted();
            } catch (HttpStatusCodeException e) {
                 subscriber.onError(new DirectorException(e.getMessage(), e));
            }
        });
    }
    
    protected String getTaskId(ResponseEntity<?> response){
        // https://10.174.52.151/tasks/3307
        Pattern pattern = Pattern.compile(".*/tasks/(.*)$");
        Matcher matcher = pattern.matcher(response.getHeaders().getLocation().toString());
        if (matcher.matches()) {
            return matcher.group(1);
        }
        throw new IllegalArgumentException("Response does not have a redirect header for a task");
    }

    // TODO delete this after removing all uses of it
    @SuppressWarnings("unchecked")
    protected String trackTask(ResponseEntity<?> response) {
        // TODO assert redirect
        // https://10.174.52.151/tasks/3307
        Pattern pattern = Pattern.compile(".*/tasks/(.*)$");
        Matcher matcher = pattern.matcher(response.getHeaders().getLocation().toString());
        if (matcher.matches()) {
            String taskId = matcher.group(1);
            logger.debug("Tracking task {}", taskId);
            URI taskUri = UriComponentsBuilder.fromUri(this.root).pathSegment("tasks", taskId)
                    .build().toUri();
            String state = "unknown";
            while (inProgress(state)) {
                // TODO put in a max polls?
                try {
                    Thread.sleep(TASK_TRACKING_POLL_INTERVAL);
                } catch (InterruptedException e) {
                }
                Map<String, String> result = this.restOperations.getForObject(taskUri, Map.class);
                state = result.get("state");
                logger.debug("Task {}: state = {}", taskId, state);
            }
            // TODO how to handle error states?
            return taskId;
        }
        // TODO how to handle error states?
        return null;
    }

    private boolean inProgress(String state) {
        return !COMPLETED_STATES.contains(state);
    }
}

@@ -716,78 +716,8 @@
 RI;%0A
-import java.util.Arrays;%0Aimport java.util.List;%0Aimport java.util.Map;%0A
 impo
@@ -1318,189 +1318,8 @@
 );%0A%0A
-    private static final int TASK_TRACKING_POLL_INTERVAL = 1000;%0A    private static final List%3CString%3E COMPLETED_STATES = Arrays%0A            .asList(%22done%22, %22error%22, %22cancelled%22);%0A%0A
     
@@ -4401,1414 +4401,7 @@
    %7D
-%0A%0A    // TODO delete this after removing all uses of it%0A    @SuppressWarnings(%22unchecked%22)%0A    protected String trackTask(ResponseEntity%3C?%3E response) %7B%0A        // TODO assert redirect%0A        // https://10.174.52.151/tasks/3307%0A        Pattern pattern = Pattern.compile(%22.*/tasks/(.*)$%22);%0A        Matcher matcher = pattern.matcher(response.getHeaders().getLocation().toString());%0A        if (matcher.matches()) %7B%0A            String taskId = matcher.group(1);%0A            logger.debug(%22Tracking task %7B%7D%22, taskId);%0A            URI taskUri = UriComponentsBuilder.fromUri(this.root).pathSegment(%22tasks%22, taskId)%0A                    .build().toUri();%0A            String state = %22unknown%22;%0A            while (inProgress(state)) %7B%0A                // TODO put in a max polls?%0A                try %7B%0A                    Thread.sleep(TASK_TRACKING_POLL_INTERVAL);%0A                %7D catch (InterruptedException e) %7B%0A                %7D%0A                Map%3CString, String%3E result = this.restOperations.getForObject(taskUri, Map.class);%0A                state = result.get(%22state%22);%0A                logger.debug(%22Task %7B%7D: state = %7B%7D%22, taskId, state);%0A            %7D%0A            // TODO how to handle error states?%0A            return taskId;%0A        %7D%0A        // TODO how to handle error states?%0A        return null;%0A    %7D%0A%0A    private boolean inProgress(String state) %7B%0A        return !COMPLETED_STATES.contains(state);%0A    %7D
 %0A%7D%0A

Remove old task tracking code