/*
** Module   : WaitForScreenBuffer.java
** Abstract : directive to wait for a screen buffer to be available
**
** Copyright (c) 2009-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 GES 20090126 Directive to wait for a screen buffer to be available.
** 002 CA  20130509 Fixed substitutions for WAIT-FOR-SCREEN-BUFFER.
** 003 CA  20210827 Refactored to allow other test types, beside terminal (which can be executed from the same 
**                  test suite).  Added SOAP and REST web service tests.
*/

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

import com.goldencode.harness.*;
import com.goldencode.harness.terminal.*;

/**
 * Directive to wait for a screen buffer to be available. If a timeout or
 * interruption occurs before a screen becomes available, this directive will
 * fail.
 * <p>
 * There are 2 modes of usage. The first is ANY mode. This means that there is
 * no specific text that must be matched in the screen buffer. In ANY mode, the
 * existence of a screen buffer (no matter what text it contains) is enough
 * to satisfy this directive, so long as that screen buffer is available before
 * the timeout occurs. The second mode is SEARCH mode. In SEARCH mode, only
 * screen buffers that contain specific text are matched. If that text does not
 * exist anywhere in the screen, then that screen buffer is not considered a
 * valid screen buffer for passing the directive. Note that there is no 
 * positioning information regarding this text. If the text appears anywhere in
 * the screen it is a match.
 * <p>
 * In ANY mode, this directive should be preceded at some point with a
 * <code>CLEAR-SCREEN-BUFFER</code> directive, which will erase any currently
 * cached screen and allow this directive to detect when a new screen is
 * available (see {@link ClearScreenBuffer}). If the cache is not cleared
 * first, then this directive will immediately return if there was EVER a
 * screen received from the target system.
 */
public class WaitForScreenBuffer
extends ScreenPoller
{
   /** Short name for the activity. */
   private static final String NAME = "WAIT-FOR-SCREEN-BUFFER";
   
   /** Format specification for creating a description of this step. */
   private static final String SPEC = "millis = '%d'; value = '%s'";
   
   /** Only match a screen that has this text (somewhere/anywhere). */
   private String value = null;

   /** Helper to process substitutions in WAIT-FOR-SCREEN-BUFFER nodes. */
   private final SubstitutionReport sr = new SubstitutionReport("bogus", "bogus")
   {
      @Override
      public Setter obtainSetter(String parm)
      {
         Setter setter = null;
         
         if ("value".equals(parm))
         {
            return getValueSetter();
         }

         return setter;
      }
      
      @Override
      protected void executeWorker(Terminal term)
      {
         // no-op
      }

      @Override
      protected void updateDescription()
      {
         WaitForScreenBuffer.this.setDescription(String.format(SPEC, getMillis(), value));
      }
   };
   
   /**
    * Construct an instance which waits for any screen buffer to be available.
    * This assumes the ANY mode of operation.
    *
    * @param    millis
    *           Number of milliseconds to wait for a screen buffer to become
    *           available. Use -1 for an indefinite wait and 0 for no wait.
    */
   public WaitForScreenBuffer(int millis)
   {
      this(millis, null);
   }
   
   /**
    * Construct an instance which waits for a screen buffer to be available
    * which contains the verbetim text. That text may appear at any location
    * in the screen and it will still be considered a match. If the given text
    * not <code>null</code> and it is not found before the number of
    * milliseconds elapses, then this directive will fail. ANY mode can be
    * forced by passing <code>null</code> for the search text.
    *
    * @param    millis
    *           Number of milliseconds to wait for the screen buffer to become
    *           available. Use -1 for an indefinite wait and 0 for no wait. In
    *           a no wait case, any value specified will be checked in any
    *           currently available screen buffer and the results will be
    *           immediately returned.
    * @param    value
    *           The text to search for in the screen buffer or <code>null</code>
    *           if no text search is to be done. Thus <code>null</code> has the
    *           affect of matching any screen that is available (the ANY mode
    *           of operation).
    */
   public WaitForScreenBuffer(int millis, String value)
   {
      super(NAME, String.format(SPEC, millis, value), millis);
      this.value = value;
   }

   /**
    * Get the {@link #sr SubstitutionReport} helper.
    * 
    * @return   See above.
    */
   public SubstitutionReport getSubstitutionReport()
   {
      return sr;
   }
   
   /**
    * Execute the directive of the test using the given terminal as the data
    * sink and/or source for the operation.
    *
    * @param    term
    *           The terminal that represents the system under test.
    */
   @Override
   public void execute(Terminal term)
   {
      // this will just process the substitutions
      sr.execute(term);

      // call the real execution code
      super.execute(term);
   }
   
   /**
    * Core logic to check the given screen.
    *
    * @param    term
    *           The terminal that represents the system under test.
    * @param    screen
    *           The current screen on the terminal.
    *
    * @return   The reason (if there was a failure) or <code>null</code> if
    *           the screens matched.
    */
   protected String tryTest(Terminal term, char[][] screen)
   {
      if (value == null)
      {
         // we are in ANY mode, so we pass
         return null;
      }
      
      // search mode
      return searchScreen(screen);
   }
   
   /**
    * Detect if the testing is complete when the first screen is tested.
    *
    * @return   <code>true</code> if the testable activity is complete when
    *           the first screen has been tested.
    */
   protected boolean firstScreenOnly()
   {
      return (value == null);
   }
   
   /**
    * Search the given screen for a match (at any location) for the known
    * search text.
    *
    * @param    screen
    *           Current screen contents.
    *
    * @return   <code>null</code> if a match is found or a specific failure
    *           reason otherwise.
    */
   private String searchScreen(char[][] screen)
   {
      String reason = "Search text not found.";
      
      for (int i = 0; i < screen.length; i++)
      {
         String next = new String(screen[i]);
         
         if (next.indexOf(value) != -1)
         {
            // found a match, exit
            reason = null;
            break;
         }
      }
      
      return reason;
   }
   
   /**
    * Obtain an instance that can delegate the assignment of the {@link #value} field.
    *
    * @return   The delegated instance.
    */
   private Setter getValueSetter()
   {
      return new Setter()
      {
         public void set(String value)
         {
            WaitForScreenBuffer.this.value = value;
         }
      };
   }
}
