package com.tomsquest.marklogic.fluent;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class Client implements AutoCloseable {

    private static final Logger LOG = LoggerFactory.getLogger(Client.class);
    private final String serverUrl;
    private final CloseableHttpClient httpClient;
    private final TextWriter textWriter;

    public Client(Config config) {
        this.serverUrl = config.getAuth().getServerUrl();
        this.httpClient = config.buildHttpClient();
        this.textWriter = config.getTextWriterBuilder().build(this);
    }

    @Override
    public void close() {
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
                throw new FluentClientException("Unable to close http client: " + httpClient, e);
            }
        }
    }

    public String getServerUrl() {
        return serverUrl;
    }

    public CloseableHttpClient getHttpClient() {
        return httpClient;
    }

    public Transaction openTransaction() {
        return new Transaction(this).open();
    }

    public Client inTransaction(Consumer<Transaction> consumer) {
        Transaction tran = openTransaction();
        try {
            consumer.accept(tran);
        } catch (Exception e) {
            LOG.error("Exception thrown by consumer: " + e.getMessage(), e);
            tran.rollback();
        }
        tran.commit();
        return this;
    }

    public boolean exists(String uri) {
        try {
            URIBuilder uriBuilder = new URIBuilder(serverUrl + "/v1/documents");
            uriBuilder.addParameter("uri", uri);

            HttpResponse response = Executor
                    .newInstance(httpClient)
                    .execute(Request.Head(uriBuilder.build()))
                    .returnResponse();

            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                return true;
            }

        } catch (Exception e) {
            throw new FluentClientException(e);
        }

        return false;
    }

    public Client delete(String... uris) {
        try {
            URIBuilder uriBuilder = new URIBuilder(serverUrl + "/v1/documents");
            Arrays.asList(uris).forEach(uri -> uriBuilder.addParameter("uri", uri));

            HttpResponse response = Executor
                    .newInstance(httpClient)
                    .execute(Request.Delete(uriBuilder.build()))
                    .returnResponse();

            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) {
                LOG.info("Deleted uris {}", Arrays.toString(uris));
            }

        } catch (Exception e) {
            throw new FluentClientException(e);
        }

        return this;
    }

    public WriteToUri write(Object value) {
        return new WriteExecution(this, value);
    }

    public interface WriteToUri {
        WriteInTransaction toUri(String uri);
    }

    public interface WriteInTransaction extends WriteWithCollections {
        WriteWithCollections inTransaction(Transaction transaction);
    }

    public interface WriteWithCollections extends WriteWithTransform {
        WriteWithTransform inCollections(String... names);
    }

    public interface WriteWithTransform extends WriteWithTriples {
        WriteWithTriples transformedBy(Transform transform);
    }

    public interface WriteWithTriples extends WriteMethod {
        WriteMethod withTriples(Triples triples);
    }

    public interface WriteMethod {
        void asText();

        void asJson();

        void asXml();
    }

    public static class WriteExecution implements WriteToUri {
        private final Client client;
        private final Object value;

        public WriteExecution(Client client, Object value) {
            this.client = client;
            this.value = value;
        }

        @Override
        public WriteInTransaction toUri(String uri) {
            return new WriteOperation(client, value, uri);
        }
    }

    /* package */ static class WriteOperation implements WriteInTransaction {
        private final Client client;
        private final Object value;
        private final String uri;
        private Transaction transaction;
        private List<String> collections;
        private Transform transform;
        private Triples triples;

        public WriteOperation(Client client, Object value, String uri) {
            this.client = client;
            this.value = value;
            this.uri = uri;
        }

        @Override
        public WriteWithCollections inTransaction(Transaction transaction) {
            this.transaction = transaction;
            return this;
        }

        @Override
        public WriteWithTransform inCollections(String... names) {
            this.collections = Arrays.asList(names);
            return this;
        }

        @Override
        public WriteWithTriples transformedBy(Transform transform) {
            this.transform = transform;
            return this;
        }

        @Override
        public WriteMethod withTriples(Triples triples) {
            this.triples = triples;
            return this;
        }

        @Override
        public void asText() {
            client.textWriter.write(this);
        }

        @Override
        public void asJson() {
            // TODO impl
            throw new UnsupportedOperationException("not implemented");
        }

        @Override
        public void asXml() {
            // TODO impl
            throw new UnsupportedOperationException("not implemented");
        }

        @Override
        public String toString() {
            return "UriWriteDestination{" +
                    "collections=" + collections +
                    ", value=" + value +
                    ", uri='" + uri + '\'' +
                    ", transaction=" + transaction +
                    ", transform=" + transform +
                    ", triples=" + triples +
                    '}';
        }

        public List<String> getCollections() {
            return collections;
        }

        public Transaction getTransaction() {
            return transaction;
        }

        public Transform getTransform() {
            return transform;
        }

        public Triples getTriples() {
            return triples;
        }

        public String getUri() {
            return uri;
        }

        public Object getValue() {
            return value;
        }
    }
}

@@ -2327,35 +2327,187 @@
     
-return true;%0A            %7D%0A
+LOG.info(%22Document exists at uri: %7B%7D%22, uri);%0A                return true;%0A            %7D%0A%0A            LOG.info(%22Document does not exist at uri: %7B%7D%22, uri);%0A            return false;
 %0A   
@@ -2596,31 +2596,8 @@
    %7D
-%0A%0A        return false;
 %0A   
@@ -3024,35 +3024,52 @@
 ;%0A%0A            i
-f (
+nt receivedStatus = 
 response.getStat
@@ -3084,32 +3084,64 @@
 .getStatusCode()
+;%0A            if (receivedStatus
  == HttpStatus.S
@@ -3228,34 +3228,255 @@
 ));%0A            
-%7D%0A
+    return this;%0A            %7D%0A%0A            throw new FluentClientException(%22Invalid server response: %22 + response.getStatusLine() + %22. %22%0A                    + %22Expected %22 + HttpStatus.SC_OK + %22 and got %22 + receivedStatus);
 %0A        %7D catch
@@ -3553,30 +3553,8 @@
    %7D
-%0A%0A        return this;
 %0A   

Throw an exception when deleting given error sent by server