package com.neverwinterdp.scribengin.storage.s3;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.event.ProgressEvent;
import com.amazonaws.event.ProgressEventType;
import com.amazonaws.event.ProgressListener;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;

public class S3ObjectWriter {
  private S3Client s3Client;
  private String bucketName;
  private String key;
  private ObjectMetadata metadata ;
  private ByteArrayOutputStream bos ;
  private ObjectOutputStream    objOs;
  
  public S3ObjectWriter(S3Client s3Client, String bucketName, String key, ObjectMetadata metadata) throws IOException {
    this.s3Client   = s3Client;
    this.bucketName = bucketName;
    this.key        = key;
    this.metadata   = metadata;
    this.bos        = new ByteArrayOutputStream(4 * 1024 * 1024) ;
    this.objOs      = new ObjectOutputStream(bos);
  }

  public ObjectMetadata getObjectMetadata() { return metadata; }

  public void write(byte[] data) throws IOException {
    objOs.writeInt(data.length);
    objOs.write(data);
  }

  public void waitAndClose(long timeout) throws Exception, IOException, InterruptedException {
    objOs.close();
    byte[] data = bos.toByteArray();
    if(data.length == 0) return;
    Throwable error = null;
    for(int i = 0; i < 3; i++) {
      try {
        tryWaitAndClose(data, timeout);
        return;
      } catch (AmazonServiceException ase) {
        System.out.println("Error Message:    " + ase.getMessage());
        System.out.println("HTTP Status Code: " + ase.getStatusCode());
        System.out.println("AWS Error Code:   " + ase.getErrorCode());
        System.out.println("Error Type:       " + ase.getErrorType());
        System.out.println("Request ID:       " + ase.getRequestId());
        error = ase;
        continue ;
      } catch (AmazonClientException ace) {
        error = ace ;
        break;
      }
    }
    throw new IOException(error);
  }
  
  public void forceClose() throws IOException, InterruptedException {
    objOs.close();
  }
  
  private void tryWaitAndClose(byte[] data, long timeout) throws AmazonServiceException, AmazonClientException, InterruptedException {
    ByteArrayInputStream input = new ByteArrayInputStream(data);
    metadata.setContentLength(data.length);
    PutObjectRequest request = new PutObjectRequest(bucketName, key, input, metadata);
    request.getRequestClientOptions().setReadLimit(256 * 1024);
    UploadProgressListener uploadListener = new UploadProgressListener();
    request.setGeneralProgressListener(uploadListener);
    PutObjectResult result = s3Client.getAmazonS3Client().putObject(request);
    uploadListener.waitForUploadComplete(timeout);
    if(uploadListener.getComleteProgressEvent() == null) {
      String mesg = 
          "Cannot get the complete event after " + timeout + "ms\n" + 
          uploadListener.getProgressEventInfo();
      throw new AmazonServiceException(mesg);
    }
  }
  
  static public class UploadProgressListener implements ProgressListener {
    private int requestByteTransferEvent = 0;
    private ProgressEvent lastRequestByteTransferEvent  ;
    private StringBuilder progressEvents = new StringBuilder();
    private ProgressEvent completeEvent ;
    
    @Override
    synchronized public void progressChanged(ProgressEvent progressEvent) {
      if(progressEvent.getEventType() == ProgressEventType.REQUEST_BYTE_TRANSFER_EVENT) {
        requestByteTransferEvent++;
        lastRequestByteTransferEvent = progressEvent;
      } else {
        progressEvents.append(progressEvent).append("\n");
      }
      if(progressEvent.getEventType() == ProgressEventType.TRANSFER_COMPLETED_EVENT) {
        completeEvent = progressEvent;
        notifyComplete();
      }
    }
    
    public ProgressEvent getComleteProgressEvent() { return  completeEvent; }
    
    public String getProgressEventInfo() {
      String info = 
          "lastRequestByteTransferEvent = " + lastRequestByteTransferEvent + "\n" +
          "REQUEST_BYTE_TRANSFER_EVENT = " + requestByteTransferEvent + "\n" +
          progressEvents.toString();
      return info;
    }
    
    synchronized void notifyComplete() {
       notifyAll();
    }
    
    synchronized void waitForUploadComplete(long timeout) throws InterruptedException {
      if(completeEvent != null) return;
      wait(timeout);
   }
  }
}

@@ -1713,35 +1713,92 @@
 %0A        System.
-out
+err.println(%22waitAndClose: try = %22  + i);%0A        System.err
 .println(%22Error 
@@ -1839,35 +1839,35 @@
 %0A        System.
-out
+err
 .println(%22HTTP S
@@ -1911,35 +1911,35 @@
 %0A        System.
-out
+err
 .println(%22AWS Er
@@ -1982,35 +1982,35 @@
 %0A        System.
-out
+err
 .println(%22Error 
@@ -2061,19 +2061,19 @@
  System.
-out
+err
 .println
@@ -2174,35 +2174,20 @@
 atch
- (AmazonClientException ace
+(Throwable t
 ) %7B%0A
@@ -2206,11 +2206,9 @@
 r = 
-ace
+t
  ;%0A 

add more catch for s3 commit