/*
 * Copyright 2014 Effektif GmbH.
 *
 * 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 com.effektif.workflow.impl.activity.types;

import static javax.swing.text.html.HTML.Tag.HEAD;

import com.effektif.workflow.api.Configuration;
import com.effektif.workflow.api.WorkflowEngine;
import com.effektif.workflow.api.activities.SubProcess;
import com.effektif.workflow.api.model.Message;
import com.effektif.workflow.api.model.TriggerInstance;
import com.effektif.workflow.api.model.WorkflowId;
import com.effektif.workflow.api.query.WorkflowQuery;
import com.effektif.workflow.api.workflow.Binding;
import com.effektif.workflow.api.workflow.ExecutableWorkflow;
import com.effektif.workflow.api.workflow.Variable;
import com.effektif.workflow.impl.WorkflowEngineImpl;
import com.effektif.workflow.impl.WorkflowParser;
import com.effektif.workflow.impl.WorkflowStore;
import com.effektif.workflow.impl.data.TypedValueImpl;
import com.effektif.workflow.impl.workflow.ActivityImpl;
import com.effektif.workflow.impl.workflow.BindingImpl;
import com.effektif.workflow.impl.workflowinstance.ActivityInstanceImpl;
import com.effektif.workflow.impl.workflowinstance.WorkflowInstanceImpl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Tom Baeyens
 */
public class SubProcessImpl extends AbstractBindableActivityImpl<SubProcess> {

  // IDEA Boolean waitTillSubWorkflowEnds; add a configuration property to specify if this is fire-and-forget or wait-till-subworkflow-ends
  protected WorkflowId subWorkflowId;
  protected String subWorkflowSourceId;

  public SubProcessImpl() {
    super(SubProcess.class);
  }

  public SubProcessImpl(Class<SubProcess> activityApiClass) {
    super(activityApiClass);
  }

  @Override
  public void parse(ActivityImpl activityImpl, SubProcess subProcess, WorkflowParser parser) {
    super.parse(activityImpl, subProcess, parser);

    this.subWorkflowId = subProcess.getSubWorkflowId();
    this.subWorkflowSourceId = subProcess.getSubWorkflowSourceId();
    
    WorkflowQuery workflowQuery = null;
    if (subWorkflowId!=null) {
      workflowQuery = new WorkflowQuery().workflowId(subWorkflowId);
    } else if (subWorkflowSourceId !=null) {
      workflowQuery = new WorkflowQuery().workflowSource(subWorkflowSourceId);
    }
    ExecutableWorkflow subWorkflow = null;
    if (workflowQuery!=null) {
      WorkflowEngine workflowEngine = parser.configuration.getWorkflowEngine();
      List<ExecutableWorkflow> workflows = workflowEngine.findWorkflows(workflowQuery);
      if (workflows!=null && !workflows.isEmpty()) {
        subWorkflow = workflows.get(0);
      }
    }

    if (subWorkflow != null) {
      if (subWorkflowId == null) {
        subWorkflowId = subWorkflow.getId();
      }
      if (subWorkflowSourceId == null) {
        subWorkflowSourceId = subWorkflow.getSourceWorkflowId();
      }

      List<Variable> subWorkflowVariables = subWorkflow.getVariables();
      Map<String, Binding> inputBindingsApi = subProcess.getSubWorkflowInputs();
      if (subWorkflowVariables != null && inputBindingsApi != null) {
        for (Variable subWorkflowVariable : subWorkflowVariables) {
          String subWorkflowVariableId = subWorkflowVariable.getId();
          Binding inputBindingApi = inputBindingsApi.get(subWorkflowVariableId);
          parser.pushContext("inputBindings[" + subWorkflowVariableId + "]", inputBindingApi, null, null);
          BindingImpl<?> bindingImpl = parser
            .parseBinding(inputBindingApi, subWorkflowVariableId, false, subWorkflowVariable.getType());
          if (bindingImpl != null) {
            if (inputBindings == null) {
              inputBindings = new HashMap<>();
            }
            inputBindings.put(subWorkflowVariableId, bindingImpl);
          }
          parser.popContext();
        }
      }
    }
  }


  @Override
  public void execute(ActivityInstanceImpl activityInstance) {
    Configuration configuration = activityInstance.getConfiguration();

    WorkflowId actualSubWorkflowId = null;
    if (this.subWorkflowId != null) {
      actualSubWorkflowId = this.subWorkflowId;
    }
    else if (subWorkflowSourceId != null) {
      WorkflowStore workflowStore = configuration.get(WorkflowStore.class);
      actualSubWorkflowId = workflowStore.findLatestWorkflowIdBySource(subWorkflowSourceId);
    }

    if (actualSubWorkflowId != null) {
      TriggerInstance triggerInstance = new TriggerInstance()
        .sourceWorkflowId(subWorkflowSourceId)
        .workflowId(actualSubWorkflowId);

      triggerInstance.setCallingWorkflowInstanceId(activityInstance.workflowInstance.id);
      triggerInstance.setCallingActivityInstanceId(activityInstance.id);

      if (inputBindings != null) {
        for (String subWorkflowVariableId: inputBindings.keySet()) {
          BindingImpl<?> subWorkflowBinding = inputBindings.get(subWorkflowVariableId);
          Object value = activityInstance.getValue(subWorkflowBinding);
          triggerInstance.data(subWorkflowVariableId, value);
        }
      }

      startWorkflowInstance(activityInstance, triggerInstance);
    } else {
      reportError(activityInstance, "Cannot execute sub-process action because no sub-process was configured.");
      activityInstance.onwards();
    }
  }

  /**
   * Starts the subworkflow instance.
   */
  protected void startWorkflowInstance(ActivityInstanceImpl activityInstance, TriggerInstance triggerInstance) {
    Configuration configuration = activityInstance.getConfiguration();
    WorkflowEngineImpl workflowEngine = configuration.get(WorkflowEngineImpl.class);
    WorkflowInstanceImpl calledWorkflowInstance = workflowEngine.startInitialize(triggerInstance);
    calledWorkflowInstance.addLockedWorkflowInstance(activityInstance.workflowInstance);
    activityInstance.setCalledWorkflowInstanceId(calledWorkflowInstance.getId());
    workflowEngine.startExecute(calledWorkflowInstance);
  }

  public void calledWorkflowInstanceEnded(final ActivityInstanceImpl callingActivityInstance, WorkflowInstanceImpl calledWorkflowInstance) {
    mapOutputVariables(callingActivityInstance, calledWorkflowInstance);

    calledWorkflowInstance.workflow.getWorkflowEngine()
            .send(new Message()
                    .workflowInstanceId(callingActivityInstance.workflowInstance.getId())
                    .activityInstanceId(callingActivityInstance.getId()),
                callingActivityInstance.workflowInstance);


    //    // after the called workflow instance is completely done and flushed and unlocked,
//    calledWorkflowInstance.addUnlockListener(new UnlockListener() {
//      @Override
//      public void unlocked(WorkflowInstanceImpl workflowInstanceImpl) {
//        // Continue the calling activity instance
//        workflowInstanceImpl.workflow.workflowEngine
//            .send(new Message()
//                .workflowInstanceId(callingActivityInstance.workflowInstance.getId())
//                .activityInstanceId(callingActivityInstance.getId()),
//            callingActivityInstance.workflowInstance);
//        //        callingActivityInstance.onwards();
//        //        callingWorkflowInstance.executeWork();
//      }
//    });
  }

  protected void mapOutputVariables(ActivityInstanceImpl callerActivityInstance, WorkflowInstanceImpl calledProcessInstance) {
    if (outputBindings!=null) {
      for (String subWorkflowVariableId: outputBindings.keySet()) {
        String variableId = outputBindings.get(subWorkflowVariableId);
        TypedValueImpl typedValue = calledProcessInstance.getTypedValue(subWorkflowVariableId);
        callerActivityInstance.setVariableValue(variableId, typedValue);
      }
    }
  }

  protected void reportError(ActivityInstanceImpl activityInstance, String message) {
    log.warn(message);
  }
}

@@ -999,16 +999,71 @@
 flowId;%0A
+import com.effektif.workflow.api.query.OrderDirection;%0A
 import c
@@ -2865,32 +2865,89 @@
 orkflowSourceId)
+%0A        .orderByCreateTime(OrderDirection.desc).limit(1)
 ;%0A    %7D%0A    Exec

fix workflow query to fetch latest version