/*
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 com.amazonaws.services.simpleworkflow.flow.examples.fileprocessing;

import java.io.File;
import java.io.IOException;

import com.amazonaws.services.simpleworkflow.flow.ActivitySchedulingOptions;
import com.amazonaws.services.simpleworkflow.flow.DecisionContextProviderImpl;
import com.amazonaws.services.simpleworkflow.flow.WorkflowContext;
import com.amazonaws.services.simpleworkflow.flow.annotations.Asynchronous;
import com.amazonaws.services.simpleworkflow.flow.core.Promise;
import com.amazonaws.services.simpleworkflow.flow.core.Settable;
import com.amazonaws.services.simpleworkflow.flow.core.TryCatchFinally;

/**
 * This implementation of FileProcessingWorkflow downloads the file, zips it and
 * uploads it back to S3
 */
public class FileProcessingWorkflowZipImpl implements FileProcessingWorkflow {

    private final SimpleStoreActivitiesClient store;

    private final FileProcessingActivitiesClient processor;

    private final WorkflowContext workflowContext;

    private String state = "Started";

    public FileProcessingWorkflowZipImpl() {
        // Create activity clients
        this.store = new SimpleStoreActivitiesClientImpl();
        processor = new FileProcessingActivitiesClientImpl();
        workflowContext = (new DecisionContextProviderImpl()).getDecisionContext().getWorkflowContext();
    }

    /**
     * Constructor used for unit testing or when Spring is used to configure
     * workflow
     */
    public FileProcessingWorkflowZipImpl(SimpleStoreActivitiesClient store, FileProcessingActivitiesClient processor,
            WorkflowContext workflowContext) {
        this.store = store;
        this.processor = processor;
        this.workflowContext = workflowContext;
    }

    @Override
    public void processFile(final String sourceBucketName, final String sourceFilename, final String targetBucketName,
            final String targetFilename) throws IOException {
        // Settable to store the worker specific task list returned by the activity
        final Settable<String> taskList = new Settable<String>();

        // Use runId as a way to ensure that downloaded files do not get name collisions
        String workflowRunId = workflowContext.getWorkflowExecution().getRunId();
        File localSource = new File(sourceFilename);
        final String localSourceFilename = workflowRunId + "_" + localSource.getName();
        File localTarget = new File(targetFilename);
        final String localTargetFilename = workflowRunId + "_" + localTarget.getName();
        new TryCatchFinally() {

            @Override
            protected void doTry() throws Throwable {
                Promise<String> activityWorkerTaskList = store.download(sourceBucketName, sourceFilename, localSourceFilename);
                // chaining is a way for one promise get assigned value of another 
                taskList.chain(activityWorkerTaskList);
                // Call processFile activity to zip the file
                Promise<Void> fileProcessed = processFileOnHost(localSourceFilename, localTargetFilename, activityWorkerTaskList);
                // Call upload activity to upload zipped file
                upload(targetBucketName, targetFilename, localTargetFilename, taskList, fileProcessed);
            }

            @Override
            protected void doCatch(Throwable e) throws Throwable {
                state = "Failed: " + e.getMessage();
                throw e;
            }

            @Override
            protected void doFinally() throws Throwable {
                if (taskList.isReady()) { // File was downloaded

                    // Set option to schedule activity in worker specific task list
                    ActivitySchedulingOptions options = new ActivitySchedulingOptions().withTaskList(taskList.get());

                    // Call deleteLocalFile activity using the host specific task list
                    store.deleteLocalFile(localSourceFilename, options);
                    store.deleteLocalFile(localTargetFilename, options);
                }
                if (!state.startsWith("Failed:")) {
                    state = "Completed";
                }
            }

        };
    }

    @Asynchronous
    private Promise<Void> processFileOnHost(String fileToProcess, String fileToUpload, Promise<String> taskList) {
        state = "Downloaded to " + taskList.get();
        // Call the activity to process the file using worker specific task list
        ActivitySchedulingOptions options = new ActivitySchedulingOptions().withTaskList(taskList.get());
        return processor.processFile(fileToProcess, fileToUpload, options);
    }

    @Asynchronous
    private void upload(final String targetBucketName, final String targetFilename, final String localTargetFilename,
            Promise<String> taskList, Promise<Void> fileProcessed) {
        state = "Processed at " + taskList.get();
        ActivitySchedulingOptions options = new ActivitySchedulingOptions().withTaskList(taskList.get());
        store.upload(targetBucketName, localTargetFilename, targetFilename, options);
    }

    @Override
    public String getState() {
        return state;
    }

}
