package org.myrobotlab.document.workflow;

import java.util.concurrent.LinkedBlockingQueue;

import org.myrobotlab.document.Document;
import org.myrobotlab.document.transformer.WorkflowConfiguration;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.service.DocumentPipeline;
import org.slf4j.Logger;

/**
 * 
 * Workflow : top level workflow class that controls the thread that do the work
 * of processing documents on each stage.
 *
 */
public class Workflow {

  private final int numWorkerThreads;
  private final int queueLength;
  private final LinkedBlockingQueue<Document> queue;
  private String name = "defaultWorkflow";
  // The workflow has it's own copy of each stage. to avoid thread safety issues
  // when running
  // with more than 1 thread. (todo:review this design pattern for something
  // more thread poolesque?)
  private WorkflowWorker[] workers;
  private WorkflowConfiguration workflowConfig;
  public final static Logger log = LoggerFactory.getLogger(Workflow.class);

  // constructor
  public Workflow(WorkflowConfiguration workflowConfig) throws ClassNotFoundException {
    // create each of the worker threads. each with their own copy of the stages
    numWorkerThreads = workflowConfig.getNumWorkerThreads();
    queueLength = workflowConfig.getQueueLength();
    queue = new LinkedBlockingQueue<Document>(queueLength);
    this.workflowConfig = workflowConfig;
    // We need to load a config
    // then we need to create each of the stages for the config
    // and add those to our stage list.
    this.name = workflowConfig.getName();
  }

  // initialize the workflow
  public void initialize(DocumentPipeline pipeline) {
    workers = new WorkflowWorker[numWorkerThreads];
    for (int i = 0; i < numWorkerThreads; i++) {
      initializeWorkerThread(i, pipeline);
    }
  }

  // init the worker threads
  private void initializeWorkerThread(int threadNum, DocumentPipeline pipeline) {
    WorkflowWorker worker = null;
    try {
      worker = new WorkflowWorker(workflowConfig, queue, Integer.toString(threadNum), pipeline);
    } catch (ClassNotFoundException e) {
      // TODO: better handling?
      log.warn("Error starting the worker thread. {}", e.getLocalizedMessage());
      e.printStackTrace();
      return;
    }
    worker.start();
    workers[threadNum] = worker;
  }

  public void processDocument(Document doc) throws InterruptedException {
    // put the document on the processing queue.
    if (doc != null) {
      queue.put(doc);
    } else {
      queue.put(doc);
    }
  }

  public Document getDocToProcess() throws InterruptedException {
    Document doc = queue.take();
    return doc;
    // TODO: cleaner way to do this is have a queue of a new class, which
    // has the Item in it and meta information to tell us when to stop / etc
    // if (doc.getId() == stopDoc.getId()) {
    // // For now, we push it back on, so that in multi-worker environment
    // // all of them get the stop notification.
    // queue.put(stopDoc);
    // return null;
    // } else {
    // // System.out.println("Pulled doc to process : " + doc.getId());
    // return doc;
    // }
  }

  // flush all the stages on each worker thread.
  public void flush() {
    // TODO: Or make it block here.
    while (!queue.isEmpty()) {
      try {
        // TODO: give a logger object to this class.
        // TODO: review this for threading issues and concurrency
        log.info("Waiting for workflow flush.");
        Thread.sleep(500);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        log.info("Interrupted while waiting for queue to drain. {}", e.getMessage());
        e.printStackTrace();
      }
    }

    // now wait for the threads to no longer be running
    while (true) {
      boolean oneIsRunning = false;
      for (int i = 0; i < numWorkerThreads; i++) {
        oneIsRunning |= workers[i].isProcessing();
      }
      if (!oneIsRunning) {
        break;
      }
      try {
        log.info("Workers are still running...");
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }

    }

    // Each worker will get flushed.
    // (each worker flushes its stage)
    for (WorkflowWorker worker : workers) {
      worker.flush();
    }
    log.info("Workflow {} flushed.", name);

  }

  public String getName() {
    return name;
  }

}
