package com.zillabyte.motherbrain.flow.operations.multilang.builder;

import com.zillabyte.motherbrain.flow.App;
import com.zillabyte.motherbrain.flow.Component;
import com.zillabyte.motherbrain.flow.Flow;
import com.zillabyte.motherbrain.flow.FlowCompilationException;
import com.zillabyte.motherbrain.flow.components.ComponentInput;
import com.zillabyte.motherbrain.flow.components.ComponentOutput;
import com.zillabyte.motherbrain.flow.operations.Operation;
import com.zillabyte.motherbrain.flow.operations.Sink;
import com.zillabyte.motherbrain.flow.operations.Source;

public class FlowValidator {

    
  public static void validateComponent(Component flow) throws FlowCompilationException{
    validateNotEmpty(flow);
    validateTotallySunk(flow);   
  }
  
  public static void validateApp(App flow) throws FlowCompilationException {
    validateNotEmpty(flow);
    validateTotallySunk(flow);
  }
  
  
  
  public static void validateNotEmpty(Flow flow) throws FlowCompilationException {
    if (flow.getOperations().size() == 0) {
      throw (FlowCompilationException) new FlowCompilationException().setAllMessages("The app does not declare any operations.");
    }
  }
  
  public static void validateTotallySunk(Component flow) throws FlowCompilationException {
    
    // Init 
    int sources = 0;
    for(Operation op : flow.getOperations()) {
      
      // An unsunk branch is simply an operation that doesn't have any successors, and is not a sink, or a join
      if (op.nextOperations().size() == 0 && op instanceof ComponentOutput == false) {
        throw (FlowCompilationException) new FlowCompilationException().setAllMessages("The stream originating from '" + op.namespaceName() + "' does not end in an output.  All streams must end in output.");
      }
      if (op instanceof ComponentInput) {
        sources++;
      }
      if (op instanceof Source) {
        sources++;
      }
    }
    
    if (sources == 0) {
      throw (FlowCompilationException) new FlowCompilationException().setAllMessages("The component does not declare any sources.");
    }
      
  }
  
  public static void validateTotallySunk(App flow) throws FlowCompilationException {
    
    // Init 
    int sources = 0;
    for(Operation op : flow.getOperations()) {
      // An unsunk branch is simply an operation that doesn't have any successors, and is
      // not a sink, or a join
      if (op.nextOperations().size() == 0 && op instanceof Sink == false) {
        throw (FlowCompilationException) new FlowCompilationException().setAllMessages("The stream originating from " + op.namespaceName() + " does not end in a sink. All streams must be sunk.");
      }
      if (op instanceof Source) {
        sources++;
      }
    }
    
    if (sources == 0) {
      throw (FlowCompilationException) new FlowCompilationException().setAllMessages("The app does not declare any sources.");
    }
      
    
  }

  public void validate(Flow flow) throws FlowCompilationException {
    if (flow instanceof App) {
      validateApp((App) flow);
    } else {
      validateComponent((Component) flow);
    }
  }

}
