/*
 * Copyright 2014-2017 Andrew Gaul <andrew@gaul.org>
 *
 * 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.gaul.s3proxy;

import static java.util.Objects.requireNonNull;

import static com.google.common.base.Preconditions.checkArgument;

import java.net.URI;

import com.google.common.base.Optional;
import com.google.common.base.Strings;

import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.jclouds.blobstore.BlobStore;

/**
 * S3Proxy translates S3 HTTP operations into jclouds provider-agnostic
 * operations.  This allows applications using the S3 API to interface with any
 * provider that jclouds supports, e.g., EMC Atmos, Microsoft Azure,
 * OpenStack Swift.
 */
public final class S3Proxy {
    private final Server server;
    private final S3ProxyHandlerJetty handler;
    private final boolean listenHTTP;
    private final boolean listenHTTPS;

    static {
        // Prevent Jetty from rewriting headers:
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=414449
        System.setProperty("org.eclipse.jetty.http.HttpParser.STRICT", "true");
    }

    S3Proxy(Builder builder) {
        checkArgument(builder.endpoint != null ||
                        builder.secureEndpoint != null,
                "Must provide endpoint or secure-endpoint");
        // TODO: allow service paths?
        if (builder.endpoint != null) {
            checkArgument(builder.endpoint.getPath().isEmpty(),
                    "endpoint path must be empty, was: %s",
                    builder.endpoint.getPath());
        }
        if (builder.secureEndpoint != null) {
            checkArgument(builder.secureEndpoint.getPath().isEmpty(),
                    "secure-endpoint path must be empty, was: %s",
                    builder.secureEndpoint.getPath());
            requireNonNull(builder.keyStorePath,
                    "Must provide keyStorePath with HTTPS endpoint");
            requireNonNull(builder.keyStorePassword,
                    "Must provide keyStorePassword with HTTPS endpoint");
        }
        checkArgument(Strings.isNullOrEmpty(builder.identity) ^
                !Strings.isNullOrEmpty(builder.credential),
                "Must provide both identity and credential");

        server = new Server();

        if (builder.servicePath != null && !builder.servicePath.isEmpty()) {
            ContextHandler context = new ContextHandler();
            context.setContextPath(builder.servicePath);
        }

        ((QueuedThreadPool) server.getThreadPool()).setName("S3Proxy");
        HttpConnectionFactory httpConnectionFactory =
                new HttpConnectionFactory();
        ServerConnector connector;
        if (builder.endpoint != null) {
            connector = new ServerConnector(server, httpConnectionFactory);
            connector.setHost(builder.endpoint.getHost());
            connector.setPort(builder.endpoint.getPort());
            server.addConnector(connector);
            listenHTTP = true;
        } else {
            listenHTTP = false;
        }

        if (builder.secureEndpoint != null) {
            SslContextFactory sslContextFactory = new SslContextFactory();
            sslContextFactory.setKeyStorePath(builder.keyStorePath);
            sslContextFactory.setKeyStorePassword(builder.keyStorePassword);
            connector = new ServerConnector(server, sslContextFactory,
                    httpConnectionFactory);
            connector.setHost(builder.secureEndpoint.getHost());
            connector.setPort(builder.secureEndpoint.getPort());
            server.addConnector(connector);
            listenHTTPS = true;
        } else {
            listenHTTPS = false;
        }
        handler = new S3ProxyHandlerJetty(builder.blobStore,
                builder.authenticationType, builder.identity,
                builder.credential, Optional.fromNullable(builder.virtualHost),
                builder.v4MaxNonChunkedRequestSize,
                builder.ignoreUnknownHeaders, builder.ignoreUnknownParameters,
                builder.corsAllowAll, builder.servicePath);
        server.setHandler(handler);
    }

    public static final class Builder {
        private BlobStore blobStore;
        private URI endpoint;
        private URI secureEndpoint;
        private AuthenticationType authenticationType =
                AuthenticationType.NONE;
        private String identity;
        private String credential;
        private String keyStorePath;
        private String keyStorePassword;
        private String virtualHost;
        private String servicePath;
        private long v4MaxNonChunkedRequestSize = 32 * 1024 * 1024;
        private boolean ignoreUnknownHeaders;
        private boolean ignoreUnknownParameters;
        private boolean corsAllowAll;

        Builder() {
        }

        public S3Proxy build() {
            return new S3Proxy(this);
        }

        public Builder blobStore(BlobStore blobStore) {
            this.blobStore = requireNonNull(blobStore);
            return this;
        }

        public Builder endpoint(URI endpoint) {
            this.endpoint = requireNonNull(endpoint);
            return this;
        }

        public Builder secureEndpoint(URI secureEndpoint) {
            this.secureEndpoint = requireNonNull(secureEndpoint);
            return this;
        }

        public Builder awsAuthentication(AuthenticationType authenticationType,
                String identity, String credential) {
            this.authenticationType = authenticationType;
            this.identity = requireNonNull(identity);
            this.credential = requireNonNull(credential);
            return this;
        }

        public Builder keyStore(String keyStorePath, String keyStorePassword) {
            this.keyStorePath = requireNonNull(keyStorePath);
            this.keyStorePassword = requireNonNull(keyStorePassword);
            return this;
        }

        public Builder virtualHost(String virtualHost) {
            this.virtualHost = requireNonNull(virtualHost);
            return this;
        }

        public Builder v4MaxNonChunkedRequestSize(
                long v4MaxNonChunkedRequestSize) {
            if (v4MaxNonChunkedRequestSize <= 0) {
                throw new IllegalArgumentException(
                        "must be greater than zero, was: " +
                        v4MaxNonChunkedRequestSize);
            }
            this.v4MaxNonChunkedRequestSize = v4MaxNonChunkedRequestSize;
            return this;
        }

        public Builder ignoreUnknownHeaders(boolean ignoreUnknownHeaders) {
            this.ignoreUnknownHeaders = ignoreUnknownHeaders;
            return this;
        }

        public Builder ignoreUnknownParameters(
                boolean ignoreUnknownParameters) {
            this.ignoreUnknownParameters = ignoreUnknownParameters;
            return this;
        }

        public Builder corsAllowAll(boolean corsAllowAll) {
            this.corsAllowAll = corsAllowAll;
            return this;
        }

        public void servicePath(String s3ProxyServicePath) {
            String path = Strings.nullToEmpty(s3ProxyServicePath);

            if (!path.isEmpty()) {
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
            }

            this.servicePath = path;
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    public void start() throws Exception {
        server.start();
    }

    public void stop() throws Exception {
        server.stop();
    }

    public int getPort() {
        if (listenHTTP) {
            return ((ServerConnector) server.getConnectors()[0]).getLocalPort();
        } else {
            return -1;
        }
    }

    public int getSecurePort() {
        if (listenHTTPS) {
            ServerConnector connector;
            if (listenHTTP) {
                connector = (ServerConnector) server.getConnectors()[1];
            } else {
                connector = (ServerConnector) server.getConnectors()[0];
            }
            return connector.getLocalPort();
        }

        return -1;
    }

    public String getState() {
        return server.getState();
    }

    public void setBlobStoreLocator(BlobStoreLocator lookup) {
        handler.getHandler().setBlobStoreLocator(lookup);
    }
}

@@ -2053,46 +2053,8 @@
 %22);%0A
-        // TODO: allow service paths?%0A
     

Remove stale TODO