/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 *
 *   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.apache.streams.s3;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.transfer.TransferManager;
import com.amazonaws.services.s3.transfer.Upload;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

/**
 * This class uses ByteArrayOutputStreams to ensure files are written to S3 properly. The stream is written to the
 * in memory ByteArrayOutPutStream before it is finally written to Amazon S3. The size the file is allowed to become
 * is directly controlled by the S3PersistWriter.
 */
public class S3OutputStreamWrapper extends OutputStream {

  private static final Logger LOGGER = LoggerFactory.getLogger(S3OutputStreamWrapper.class);

  private final AmazonS3Client amazonS3Client;
  private final String bucketName;
  private final String path;
  private final String fileName;
  private ByteArrayOutputStream outputStream;
  private final Map<String, String> metaData;
  private boolean isClosed = false;

  /**
   * Create an OutputStream Wrapper
   * @param amazonS3Client
   * The Amazon S3 Client which will be handling the file
   * @param bucketName
   * The Bucket Name you are wishing to write to.
   * @param path
   * The path where the object will live
   * @param fileName
   * The fileName you ware wishing to write.
   * @param metaData
   * Any meta data that is to be written along with the object
   * @throws IOException
   * If there is an issue creating the stream, this
   */
  public S3OutputStreamWrapper(AmazonS3Client amazonS3Client, String bucketName, String path, String fileName, Map<String, String> metaData) throws IOException {
    this.amazonS3Client = amazonS3Client;
    this.bucketName = bucketName;
    this.path = path;
    this.fileName = fileName;
    this.metaData = metaData;
    this.outputStream = new ByteArrayOutputStream();
  }

  public void write(int byt) throws IOException {
    this.outputStream.write(byt);
  }

  public void write(byte[] byt) throws IOException {
    this.outputStream.write(byt);
  }

  public void write(byte[] byt, int off, int len) throws IOException {
    this.outputStream.write(byt, off, len);
  }

  public void flush() throws IOException {
    this.outputStream.flush();
  }

  /**
   * Whenever the output stream is closed we are going to kick the ByteArrayOutputStream off to Amazon S3.
   * @throws IOException
   * Exception thrown from the FileOutputStream
   */
  public void close() throws IOException {
    if (!isClosed) {
      try {
        this.addFile();
        this.outputStream.close();
        this.outputStream = null;
      } catch (Exception ex) {
        ex.printStackTrace();
        LOGGER.warn("There was an error adding the temporaryFile to S3");
      } finally {
        // we are done here.
        this.isClosed = true;
      }
    }
  }

  private void addFile() throws Exception {

    InputStream is = new ByteArrayInputStream(this.outputStream.toByteArray());
    int contentLength = outputStream.size();

    TransferManager transferManager = new TransferManager(amazonS3Client);
    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setExpirationTime(DateTime.now().plusDays(365 * 3).toDate());
    metadata.setContentLength(contentLength);

    metadata.addUserMetadata("writer", "org.apache.streams");

    for (String s : metaData.keySet()) {
      metadata.addUserMetadata(s, metaData.get(s));
    }

    String fileNameToWrite = path + fileName;
    Upload upload = transferManager.upload(bucketName, fileNameToWrite, is, metadata);
    try {
      upload.waitForUploadResult();

      is.close();
      transferManager.shutdownNow(false);
      LOGGER.info("S3 File Close[{} kb] - {}", contentLength / 1024, path + fileName);
    } catch (Exception ignored) {
      LOGGER.trace("Ignoring", ignored);
    }


  }


}
