/*******************************************************************************
 * Copyright (c) 2010 Robert "Unlogic" Olofsson (unlogic@unlogic.se).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-3.0-standalone.html
 ******************************************************************************/
package se.unlogic.standardutils.threads;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import se.unlogic.standardutils.xml.Elementable;
import se.unlogic.standardutils.xml.XMLAttribute;
import se.unlogic.standardutils.xml.XMLElement;
import se.unlogic.standardutils.xml.XMLGenerator;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@XMLElement(name="ExecutionController")
public class SimpleExecutionController<T extends TaskGroup> implements ExecutionController<T>, Elementable{

   private final ReentrantLock globalLock = new ReentrantLock();
   private Condition finishedCondition = globalLock.newCondition();

   private T taskGroup;

   private BlockingQueue<? extends Runnable> taskQueue;
   private ThreadPoolTaskGroupHandler<T> threadPoolTaskHandler;

   @XMLAttribute
   private boolean started;

   @XMLAttribute
   private boolean aborted;

   @XMLAttribute
   private int initialTaskCount;

   @XMLAttribute
   private AtomicInteger completedTaskCount = new AtomicInteger();

   public SimpleExecutionController(T taskGroup, ThreadPoolTaskGroupHandler<T> threadPoolTaskHandler) {

      super();
      this.taskGroup = taskGroup;
      this.taskQueue = taskGroup.getTasks();
      this.threadPoolTaskHandler = threadPoolTaskHandler;
      initialTaskCount = taskQueue.size();
   }

   public void abort() {

      globalLock.lock();

      try{

         if(this.threadPoolTaskHandler != null){

            if(started){
               threadPoolTaskHandler.remove(this);
            }

            this.aborted = true;

            this.executionComplete();
         }

      }finally{

         globalLock.unlock();
      }
   }

   public void awaitExecution(long timeout) throws InterruptedException {

      globalLock.lock();

      try{

         if(finishedCondition != null){

            finishedCondition.await(timeout, TimeUnit.MILLISECONDS);
         }

      }finally{

         globalLock.unlock();
      }

   }

   public void awaitExecution() throws InterruptedException {

      globalLock.lock();

      try{

         if(finishedCondition != null){

            finishedCondition.await();
         }

      }finally{

         globalLock.unlock();
      }

   }

   void executionComplete(){

      globalLock.lock();

      try{

         if(finishedCondition != null){

            finishedCondition.signalAll();

            threadPoolTaskHandler = null;
            finishedCondition = null;
         }

      }finally{

         globalLock.unlock();
      }
   }

   public int getRemainingTaskCount() {

      return taskQueue.size();
   }


   BlockingQueue<? extends Runnable> getTaskQueue() {

      return taskQueue;
   }

   public void start() {

      globalLock.lock();

      try{
         if(!started && !aborted){

            this.threadPoolTaskHandler.add(this);
            this.started = true;
         }

      }finally{

         globalLock.unlock();
      }
   }


   public int getInitialTaskCount() {

      return initialTaskCount;
   }


   public int getCompletedTaskCount() {

      return completedTaskCount.get();
   }

   void incrementCompletedTaskCount(){

      this.completedTaskCount.incrementAndGet();
   }

   public boolean isStarted(){

      return started;
   }

   public boolean isAborted(){

      return aborted;
   }

   public boolean isFinished(){

      return started && !aborted && this.threadPoolTaskHandler == null;
   }


   public T getTaskGroup() {

      return taskGroup;
   }

   public Element toXML(Document doc) {

      return XMLGenerator.toXML(this, doc);
   }
}
