/*
** Module   : ScreenRegion.java
** Abstract : encodes a comparable portion of the screen including contents 
**
** Copyright (c) 2009-2015, Golden Code Development Corporation.
**
** -#- -I- --Date-- -----------------------Description------------------------
** 001 GES 20090126 Encodes a comparable portion of the screen including
**                  contents.
** 002 IAS 20150713 Added screen template file name to check-screen-buffer failure message
*/

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

/**
 * Encodes a comparable portion of the screen including contents.
 */
public class ScreenRegion
{
   /** Format specification for mismatching data. */
   private static final String FAIL_SPEC = "Mismatched data at line %d, "   +
                                           "column %d. Expected '%c' "      +
                                           "(0x%04X at relative Y %d, "     +
                                           "relative X %d) and found '%c' " +
                                           "(0x%04X at relative Y %d, "     +
                                           "relative X %d), template: %s.";
   
   /** Coordinates of the region. */
   private final Rectangle rect;
   
   /** Contents of the region. */
   private final char[][] contents;
   
   /** Sub-regions that should be excluded from content comparisons. */
   private final Rectangle[] exclude;

   /* The name of the template screen */
   public final String fname;
   
   /**
    * Create an instance.
    *
    * @param    rect
    *           Coordinates of the entire region. Must not be <code>null</code>.
    * @param    contents
    *           The data at every character position in the region. Must not be
    *           <code>null</code>.
    * @param    fname
    *           The name of the template screen
    * @param    exclude
    *           Sub-regions that should be excluded from content comparisons.
    *           This may be <code>null</code>.
    */
   public ScreenRegion(Rectangle rect, String fname, char[][] contents, Rectangle[] exclude)
   {
      this.rect     = rect;
      this.fname    = fname;
      this.contents = contents;
      this.exclude  = exclude;
   }
   
   /**
    * Determines if the given instance completely overlaps with this instance
    * AND if the contents of that overlapping area are identical. A complete
    * overlap means that the coordinates of the given instance are the same as
    * this instance OR they are completely contained within this instances
    * coordinates. The contents of any exclusion regions that are specified
    * in the given instance will be ignored during comparison. Note that any
    * exclusion regions in this instance are not honored in this comparison.
    *
    * @param    region
    *           The portion of the screen to compare against.
    *
    * @return   <code>null</code> if the given screen region is contained in
    *           this screen region or an error string on any failure.
    */
   public String contains(ScreenRegion region)
   {
      if (region != null && rect.contains(region.rect))
      {
         int rowOffset = region.rect.getTop();
         int rowLimit  = region.rect.getBottom();
         int colOffset = region.rect.getLeft();
         int colLimit  = region.rect.getRight();
         int baseRow   = rect.getTop();
         int baseCol   = rect.getLeft();
         int rowLen    = (rowLimit - rowOffset) + 1;
         int colLen    = (colLimit - colOffset) + 1;
         
         // iterate through each row of the overlapping region
         for (int i = 0; i < rowLen; i++)
         {
            // iterate through each column of the overlapping region
            for (int j = 0; j < colLen; j++)
            {
               // translate the relative (to the contained region) point to
               // the equivalent in the universal coordinate space
               int absX = j + colOffset;
               int absY = i + rowOffset;
               
               // ignore contents of any character position that is excluded
               // from the contained region
               if (!region.exclude(absX, absY))
               {
                  int y = absY - baseRow;
                  int x = absX - baseCol;
                  
                  char me  = contents[y][x];
                  char you = region.contents[i][j];
                  
                  // convert null bytes into spaces for comparison purposes
                  if (me == 0)
                     me = ' ';
                  
                  if (you == 0)
                     you = ' ';
                  
                  // compare contents at equivalent relative positions
                  if (me != you)
                  {
                     if (me < 0x20 && me > 0x7E)
                     {
                        me = '?';
                     }
                     if (you < 0x20 && you > 0x7E)
                     {
                        you = '?';
                     }
                     // contents mismatch is a failure
                     return String.format(FAIL_SPEC,
                                          absY,
                                          absX,
                                          you,
                                          (short) region.contents[i][j],
                                          i,
                                          j,
                                          me,
                                          (short) contents[y][x],
                                          y,
                                          x,
                                          fname);
                  }
               }
            }
         }
         
         // no failures means success
         return null;
      }
      
      // not fully contained
      return "Missing complete overlap in coordinates.";
   }
   
   /**
    * Calculate if any of the exclusion regions contain the given point.
    *
    * @param    x
    *           The X coordinate.
    * @param    y
    *           The Y coordinate.
    *
    * @return   <code>true</code> if there are exclusion regions defined and
    *           the given point is included in at least one of the exclusion
    *           regions.
    */
   public boolean exclude(int x, int y)
   {
      if (exclude != null)
      {
         for (int i = 0; i < exclude.length; i++)
         {
            if (exclude[i].isIncluded(x, y))
            {
               return true;
            }
         }
      }
      
      return false;
   }
   
   /**
    * Output the contents of the screen region as text.
    *
    * @return   The screen contents.
    */
   public String toString()
   {
      StringBuilder sb = new StringBuilder();
      
      for (int i = 0; i < contents.length; i++)
      {
         if (i > 0)
         {
            sb.append("\n");
         }
         
         sb.append(contents[i]);
      }
      
      return sb.toString();
   }
}
