/*
** Module   : Report.java
** Abstract : helpful base class for implementing a report
**
** Copyright (c) 2009-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- -----------------------Description------------------------
** 001 GES 20090113 Helpful base class for implementing a report.
** 002 CA  20170404 Add the failed test name in the result summary.
** 003 TJD 20230119 Timeout multiplier and trace mode support
*/

/*
** 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.util.*;

/**
 * Helpful base class for implementing a report about an activity. An activity
 * may be arbitrarily complex and may in fact be a set or list of other related
 * activities.
 */
public class Report
implements Reportable
{
   /** Short name for the activity being reported on. */
   private String name = "";
   
   /** Verbose description of the activity being reported on. */
   private String descr = "";

   /** Status of the activity's execution. */
   private Status status = Status.NOT_RUN;
   
   /** The relationship between this activity and other sibling activities. */
   private Dependency type = Dependency.NONE;
   
   /** Define how to respond to a failure of this activity. */ 
   private FailureMode failMode = FailureMode.IGNORE;
   
   /** Description of the reason for a failure. */
   private String reason = null;
   
   /** Number of milliseconds since the epoch at the start of execution. */
   private long start = 0;
   
   /** Number of milliseconds since the epoch at the end of execution. */
   private long end = 0;
   
   /** Status code frequency distribution. */
   private StatusCounter counter = null;
   
   /** Store the URL to the HTML results page for this report. */ 
   private String url = null;
   
   /** The thread on which this activity executed. */
   private String thread = null;
   
   /**
    * Construct a new (default) instance.
    */
   public Report()
   {
      // nothing to do
   }

   /**
    * Copy constructor.
    *
    * @param    copy
    *           The instance upon which to pattern the duplicate.
    */
   public Report(Report copy)
   {
      this.name     = copy.name;
      this.descr    = copy.descr;
      this.status   = copy.status;
      this.reason   = copy.reason;
      this.start    = copy.start;
      this.end      = copy.end;
      this.counter  = new StatusCounter(copy.counter);
      this.url      = copy.url;
      this.type     = copy.type;
      this.failMode = copy.failMode;
   }  

   /**
    * Construct a new instance with a known description.
    *
    * @param    descr
    *           Verbose description of the testable activity.
    */
   public Report(String descr)
   {
      this(null, descr);
   }

   /**
    * Construct a new instance with a known name and description.
    *
    * @param    name
    *           Short name of the testable activity.
    * @param    descr
    *           Verbose description of the testable activity.
    */
   public Report(String name, String descr)
   {
      if (name != null)
         this.name = name;
      
      if (descr != null)
         this.descr = descr;
   }

   /**
    * Get the current status of the testable activity.
    *
    * @return   The current status.
    */
   public Status getStatus()
   {
      return status;
   }
   
   /**
    * Set the new status of the testable activity.
    *
    * @param    status
    *           New status for the testable activity.
    */
   public void setStatus(Status status)
   {
      this.status = status;
   }
   
   /**
    * Set the status for the testable activity and if there was a failure, set
    * the reason for that failure.
    *
    * @param    status
    *           New status for the testable activity.
    * @param    reason
    *           If <code>status</code> is {@link Status#FAILED} then any
    *           value for this will be stored as the cause.
    */
   public void setStatus(Status status, String reason)
   {
      if (status != Status.PASSED && reason != null)
      {
         this.reason = reason;
      }
      
      setStatus(status);
   }
   
   /**
    * Get the current dependency type of the testable activity.
    *
    * @return   The current dependency type.
    */
   public Dependency getDependency()
   {
      return type;
   }
   
   /**
    * Set the dependency type of the testable activity.
    *
    * @param    type
    *           New dependency type for the testable activity.
    */
   public void setDependency(Dependency type)
   {
      this.type = type;
   }
   
   /**
    * Get the reason for the testable activity failure, if a failure has
    * occurred.
    *
    * @return   A description of the reason for a failure or <code>null</code>
    *           if the testable activity has not completed running or if there
    *           was no failure.
    */
   public String getReason()
   {
      return reason;
   }
   
   /**
    * Set the reason for the testable activity failure, if a failure has
    * occurred.
    *
    * @param    reason
    *           A description of the reason for a failure or <code>null</code>
    *           if the testable activity has not completed running or if there
    *           was no failure.
    */
   public void setReason(String reason)
   {
      this.reason = reason;
   }
   
   /**
    * Get the current time (in milliseconds since the epoch) at the start of
    * this testable activity.
    *
    * @return   The number of milliseconds.
    */
   public long getStartTime()
   {
      return start;
   }
   
   /**
    * Set the current time (in milliseconds since the epoch) at the start of
    * this testable activity.
    *
    * @param    start
    *           The number of milliseconds.
    */
   public void setStartTime(long start)
   {
      this.start = start;
   }
   
   /**
    * Get the current time (in milliseconds since the epoch) at the end of
    * this testable activity.
    *
    * @return   The number of milliseconds.
    */
   public long getEndTime()
   {
      return end;
   }
   
   /**
    * Set the current time (in milliseconds since the epoch) at the end of
    * this testable activity.
    *
    * @param    end
    *           The number of milliseconds.
    */
   public void setEndTime(long end)
   {
      this.end = end;
   }
   
   /**
    * Get the number of milliseconds that elapsed during this testable activity.
    *
    * @return   The number of milliseconds.
    */
   public long getElapsed()
   {
      return (end - start);
   }
   
   /**
    * Get the short name for the testable activity.
    *
    * @return   A name of the testable activity.
    */
   public String getName()
   {
      return name;
   }
   
   /**
    * Set the short name for the testable activity.
    *
    * @param    name
    *           A name of the testable activity.
    */
   public void setName(String name)
   {
      this.name = name;
   }
   
   /**
    * Get the verbose description of the testable activity.
    *
    * @return   A description of the testable activity.
    */
   public String getDescription()
   {
      return descr;
   }
   
   /**
    * Set the verbose description of the testable activity.
    *
    * @param    descr
    *           A description of the testable activity.
    */
   public void setDescription(String descr)
   {
      this.descr = (descr == null) ? "" : descr;
   }
   
   /**
    * Get the frequency distribution of the status codes in the list of
    * results.
    *
    * @return   The current status code statistics or <code>null</code> if
    *           the statistics have not been calculated yet.
    */
   public StatusCounter getStatusCounter()
   {
      return counter;
   }
   
   /**
    * Calculate the frequency distribution of the status codes in the given
    * list of results.
    *
    * @param    list
    *           The results list.
    */
   public void calculateStatusCounter(List<? extends Reportable> list)
   {
      counter = new StatusCounter();
      counter.calculate(list);
   }
   
   /**
    * Get the URL to the HTML report for the testable activity.
    *
    * @return   The URL or <code>null</code> if the testable activity has not
    *           completed running or if the report has not yet been generated.
    */
   public String getURL()
   {
      return url;
   }
   
   /**
    * Set the URL to the HTML report for the testable activity.
    *
    * @param    url
    *           The URL or <code>null</code> if the testable activity has not
    *           completed running or if the report has not yet been generated.
    */
   public void setURL(String url)
   {
      this.url = url;
   }
   
   /**
    * Get the failure mode.
    *
    * @return   The definition of how to respond if/when this activity fails.
    */
   public FailureMode getFailureMode()
   {
      return failMode;
   }
   
   /**
    * Set the failure mode.
    *
    * @param    failMode
    *           The definition of how to respond if/when this activity fails.
    */
   public void setFailureMode(FailureMode failMode)
   {
      this.failMode = failMode;
   }
   
   /**
    * Get the short name for the thread on which the testable activity
    * executed.
    *
    * @return   The thread name.
    */
   public String getThreadName()
   {
      return (thread == null) ? "" : thread;
   }
   
   /**
    * Set the short name for the thread on which the testable activity
    * executed.
    *
    * @param    thread
    *           The thread name.
    */
   public void setThreadName(String thread)
   {
      this.thread = thread;
   }
   
   /**
    * 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 1;
   }
   
   /**
    * Read the results of each test and aggregate any failing test causes
    * into a single string for display.
    *
    * @param    list
    *           The test results.
    * @param    sb
    *           Buffer in which to aggregate the failure reasons.
    * @param    num
    *           The number of failure reasons that are already in the buffer.
    *
    * @return   The total number of failure reasons in the buffer.
    */
   public int checkResults(List<? extends Reportable> list,
                           StringBuilder              sb,
                           int                        num)
   {
      Iterator<? extends Reportable> iter = list.iterator();
      
      while (iter.hasNext())
      {
         Reportable report = iter.next();
         
         Status next = report.getStatus();
         
         if (next != Status.PASSED)
         {
            String why = report.getReason();
            
            if (why != null)
            {
               why = report.getName() + ": " + why;

               // update the reason counter
               num++;
               
               // 2nd reason causes us to rewrite things with numbers
               if (num == 2)
               {
                  String temp = sb.toString();
                  
                  sb.delete(0, sb.length());
                  sb.append(String.format("1. %s", temp));
               }
               
               if (num > 1)
               {
                  // each subsequent reason
                  sb.append('\n').append(String.format("%d. %s", num, why));
               }
               else
               {
                  // the first reason may be the only reason so we don't number
                  // it
                  sb.append(why);
               }
            }
         }
      }
      
      return num;
   }

   /**
    * Get the maximum number of milliseconds that this testable activity is allowed to run.
    * Valid for some of Tests only, -1 if not applicable
    * @return   The maximum number of milliseconds.
    */
   @Override
   public long getMaxElapsed()
   {
      return -1;
   }
}
