/*
** Module   : TestPlan.java
** Abstract : stores the data needed to execute a list of test sets
**
** Copyright (c) 2009, Golden Code Development Corporation.
**
** -#- -I- --Date-- -----------------------Description------------------------
** 001 GES 20090122 Stores the data needed to execute a list of test sets.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.goldencode.harness;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;

/**
 * Stores the data needed to execute a list of test sets.
 */
public class TestPlan
extends Report
{
   /** Default results directory name. */
   private static final String RESULTS_DIR = "results";
   
   /** Test sets to execute. */
   private List<TestSet> sets = null;
   
   /** Store shared, scoped resources. */
   private ResourcePool pool = new ResourcePool();
   
   /** Output directory for all result reports. */
   private String dir = null;
   
   /**
    * Construct a default instance. The contained data must be set subsequently
    * using the explicit setter methods.
    */                    
   public TestPlan()
   {
   }
   
   /**
    * Construct an instance containing the given data.
    *
    * @param    name
    *           Short name for the test plan.
    * @param    descr
    *           Verbose description of the test plan.
    * @param    sets
    *           The list of test sets to execute.
    */                    
   public TestPlan(String name, String descr, List<TestSet> sets)
   {
      super(name, descr);
      this.sets = sets;
      setOutputDirectory(RESULTS_DIR, false);
   }
   
   /**
    * Get the list of test sets to execute.
    *
    * @return   The test sets or <code>null</code> if none have been set.
    */
   public List<TestSet> getSets()
   {
      return sets;
   }
   
   /**
    * Set the list of test sets to execute.
    *
    * @param    sets
    *           The list of test sets.
    */
   public void setSets(List<TestSet> sets)
   {
      this.sets = sets;
   }
   
   /**
    * Get the scoped resource pool.
    *
    * @return   The pool.
    */
   public ResourcePool getResourcePool()
   {
      return pool;
   }
   
   /**
    * Set the scoped resource pool.
    *
    * @param    pool
    *           The new pool.
    */
   public void setResourcePool(ResourcePool pool)
   {
      this.pool = pool;
   }
   
   /**
    * Get the output directory.
    *
    * @return   The output directory or <code>null</code> if none have been set.
    */
   public String getOutputDirectory()
   {
      return dir;
   }
   
   /**
    * Set the output directory.
    *
    * @param    dir
    *           The output directory path.
    * @param    unique
    *           Create a unique subdirectory inside the given path such that
    *           the test plan can be run multiple times and each time will
    *           have a unique output directory.
    */
   public void setOutputDirectory(String dir, boolean unique)
   {
      StringBuilder sb = new StringBuilder(dir);
      
      if (!dir.endsWith(File.separator))
         sb.append(File.separator);
      
      if (unique)
      {
         // create a timestamp like 20090223_135650 which is (YYYYMMDD_HHMMSS)
         String ts = String.format("%1$TY%1$Tm%1$Td_%1$TH%1$TM%1$TS",
                                   System.currentTimeMillis());
         sb.append(ts).append(File.separator);
      }
      
      this.dir = sb.toString();
   }
   
   /**
    * Get the number of sub-elements of the testable activity.
    *
    * @return   If the testable activity is a single atomic activity, then
    *           this will return 1. Otherwise, it will return the number of
    *           individual sub-elements to be executed in this activity.
    */
   public int getNumberOfElements()
   {
      return sets.size();
   }
   
   /**
    * Iterate through each test set and execute each one (which in turn runs
    * all the tests in the test set). After all execution is complete, this
    * method will trigger {@link #postProcess} to calculate all needed
    * statistics on the results.
    *
    * @param    rc
    *           Object in which to store the details of non-testing failures.
    */
   public void execute(FailureNotification rc)
   {
      setThreadName(Thread.currentThread().getName());
      
      int num = 0;
      
      StringBuilder  sb  = new StringBuilder();
      
      // start test plan timer
      setStartTime(System.currentTimeMillis());
      
      num = processTestSets(sb, Dependency.PRE_CONDITION, rc);
      
      // if the pre-conditions failed, the rest of the test sets are aborted
      if (num == 0)
      {
         num  = processTestSets(sb, Dependency.NONE, rc);
         num += processTestSets(sb, Dependency.POST_CONDITION, rc);
      }
      
      // end test plan timer
      setEndTime(System.currentTimeMillis());
      
      setStatus((num > 0) ? Status.FAILED : Status.PASSED,
                (sb.length() > 0) ? sb.toString() : null);
      
      // this must happen after the status is set for the plan
      postProcess();
   }
   
   /**
    * Handle any post-execution tasks such as statistics or other summary
    * data calculation. 
    */
   public void postProcess()
   {
      calculateStatusCounter(sets);
      
      Iterator<TestSet> iter = sets.iterator();
      
      while (iter.hasNext())
      {
         TestSet set = iter.next();
         set.calculateStatusCounter(set.getTests());
      }
   }
   
   /**
    * Execute each of the test sets that belong to a specific dependency
    * type.
    *
    * @param    sb
    *           Buffer in which to accumulate error text from failures.
    * @param    type
    *           Dependency group to execute.
    * @param    rc
    *           Object in which to store the details of non-testing failures.
    *
    * @return   The number of failures.
    */
   private int processTestSets(StringBuilder       sb,
                               Dependency          type,
                               FailureNotification rc)
   {
      int num = 0;
      
      List<TestSet> list = new ArrayList<TestSet>();
      
      Iterator<TestSet> iter = sets.iterator();
      
      // subset the list of test-sets from the specified dependency group
      while (iter.hasNext())
      {
         TestSet set = iter.next();
         
         if (set.getDependency() == type)
            list.add(set);
      }
      
      // create a specific latch of the right size
      CountDownLatch sem = new CountDownLatch(list.size());
      
      iter = list.iterator();
      
      // execute each of the sets in the group
      while (iter.hasNext())
      {
         TestSet set = iter.next();
         set.execute(sem, rc);
      }
      
      try            
      {
         // wait for test set to finish
         sem.await();
      }
      
      catch (InterruptedException ie)
      {
         sb.append("test plan thread interrupted before driver ")
           .append(" threads finished");
         num = 1;
      }
      
      return checkResults(sets, sb, num);
   }
}
