package org.togglz.s3;

import com.amazonaws.AmazonClientException;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.services.s3.model.S3Object;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.togglz.core.Feature;
import org.togglz.core.repository.FeatureState;
import org.togglz.core.repository.StateRepository;
import org.togglz.core.util.FeatureStateStorageWrapper;

import java.io.IOException;

/**
 * A state repository that uses Amazon S3.
 * <p>
 * The repository is configured using the {@link Builder}
 * <p>
 * You must already have a bucket provisioned before you create this repository.
 *
 * @author Mark Richardson
 * @date 12/27/16
 */
public class S3StateRepository implements StateRepository {

    // http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
    private static final String ERR_NO_SUCH_KEY = "NoSuchKey";

    private final ObjectMapper objectMapper = new ObjectMapper();

    private final AmazonS3Client client;
    private final String bucketName;
    private final String keyPrefix;

    private S3StateRepository(Builder builder) {
        this.client = builder.client;
        this.bucketName = builder.bucketName;
        this.keyPrefix = builder.keyPrefix;
    }

    @Override
    public FeatureState getFeatureState(Feature feature) {
        try (S3Object object = client.getObject(bucketName, keyPrefix + feature.name())) {
            if (object != null) {
                FeatureStateStorageWrapper wrapper = objectMapper.reader()
                    .forType(FeatureStateStorageWrapper.class)
                    .readValue(object.getObjectContent());
                return FeatureStateStorageWrapper.featureStateForWrapper(feature, wrapper);
            }
        } catch (AmazonS3Exception ae) {
            if (ERR_NO_SUCH_KEY.equals(ae.getErrorCode())) {
                return null;
            }
            throw ae;
        } catch (IOException e) {
            throw new RuntimeException("Failed to set the feature state", e);
        }
        return null;
    }

    @Override
    public void setFeatureState(FeatureState featureState) {
        try {
            FeatureStateStorageWrapper storageWrapper = FeatureStateStorageWrapper.wrapperForFeatureState(featureState);
            String json = objectMapper.writeValueAsString(storageWrapper);
            client.putObject(bucketName, keyPrefix + featureState.getFeature().name(), json);
        } catch (AmazonClientException | JsonProcessingException e) {
            throw new RuntimeException("Failed to set the feature state", e);
        }
    }

    /**
     * Creates a new builder for creating a {@link S3StateRepository}.
     *
     * @param client     the client instance to use for connecting to amazon s3
     * @param bucketName The name of the bucket to use
     * @return a Builder
     */
    public static Builder newBuilder(AmazonS3Client client, String bucketName) {
        return new Builder(client, bucketName);
    }

    /**
     * Builder for a {@link S3StateRepository}.
     */
    public static class Builder {

        private final AmazonS3Client client;
        private final String bucketName;
        private String keyPrefix = "togglz/";

        /**
         * Creates a new builder for a {@link S3StateRepository}.
         *
         * @param client     the client instance to use for connecting to amazon s3
         * @param bucketName The name of the bucket to use
         */
        public Builder(AmazonS3Client client, String bucketName) {
            this.client = client;
            this.bucketName = bucketName;
        }

        /**
         * Optional prefixes to prepend on to each key
         *
         * @param keyPrefix The prefix to use
         * @return
         */
        public Builder prefix(String keyPrefix) {
            this.keyPrefix = keyPrefix == null ? "" : keyPrefix;
            return this;
        }

        /**
         * Creates a new {@link S3StateRepository} using the current settings.
         */
        public S3StateRepository build() {
            return new S3StateRepository(this);
        }
    }
}

@@ -105,22 +105,16 @@
 AmazonS3
-Client
 ;%0D%0Aimpor
@@ -1107,38 +1107,32 @@
 e final AmazonS3
-Client
  client;%0D%0A    pr
@@ -3083,38 +3083,32 @@
 Builder(AmazonS3
-Client
  client, String 
@@ -3310,30 +3310,24 @@
 nal AmazonS3
-Client
  client;%0D%0A  
@@ -3696,22 +3696,16 @@
 AmazonS3
-Client
  client,

Use the more basic AmazonS3 interface instead of AmazonS3Client to be more flexble in general usage.