/*
** Module   : BinaryFileComparison.java
** Abstract : encodes data allowing two binary files to be compared to determine
**            if they are the same outside of excluded regions
**
** Copyright (c) 2009, Golden Code Development Corporation.
**
** -#- -I- --Date-- -----------------------Description------------------------
** 001 GES 20090218 Encodes data allowing two binary files to be compared to
**                  determine if they are the same outside of excluded regions.
*/

/*
** 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 java.io.*;
import com.goldencode.harness.transport.*;

/**
 * Encodes data allowing two binary files to be compared to determine if they
 * are the same outside of excluded regions.
 */
public class BinaryFileComparison
extends FileComparison
{
   /** Short name for the activity. */
   private static final String NAME = "BINARY-FILE-COMPARISON";
   
   /** Format specification for mismatching data. */
   private static final String FAIL_SPEC = "Mismatched data at index %d, " +
                                           "Expected '%c' (0x%02X) and "   +
                                           "found '%c' (0x%02X).";
                                             
   /** Format specification for an unexpected EOF. */
   private static final String EOF_SPEC = "Unexpected EOF (base 0x%08X, " +
                                          "actual 0x%08X).";
   
   /**
    * Create an instance.
    *
    * @param    baseline
    *           Identifies the file containing the expected data.
    * @param    actual
    *           Identifies the file containing the actual data generated during
    *           the test.
    * @param    downloadDir 
    *           The local directory in which a downloaded file will be placed
    *           or <code>null</code> if no downloading should occur.
    * @param    sftp
    *           Helper for downloading the comparison file via SFTP. The
    *           <code>actual</code> parameter must specify the absolute or
    *           relative filename of the file on the server system. The file
    *           will be downloaded into the download directory as specified
    *           in the project index.
    * @param    excludes
    *           A list of portions of the byte stream to be ignored for
    *           comparison purposes or <code>null</code> if there are no
    *           exclusion regions.
    */
   public BinaryFileComparison(String            baseline,
                               String            actual,
                               String            downloadDir,
                               SFTPSession       sftp,
                               ExclusionRegion[] excludes)
   {
      super(NAME, baseline, actual, downloadDir, sftp, excludes);
   }
   
   /**
    * Detect if the actual data file generated during testing matches the
    * given baseline file outside of any specified exclusion regions.
    *
    * @param    base
    *           Baseline file data.
    * @param    other
    *           Comparison file data.
    *
    * @return   <code>null</code> if the actual file is the same as the
    *           baseline file (after ignoring any exclusion regions) or an
    *           error string on any deviation.
    */
   public String compare(File base, File other)
   throws Exception
   {
      long baseLen  = base.length();
      long otherLen = other.length();
      
      if (baseLen != otherLen)
      {
         return String.format("File size mismatch (base = %d, actual = %d).",
                              baseLen,
                              otherLen);
      }
      
      FileInputStream     baseFis  = new FileInputStream(base);
      BufferedInputStream baseBis  = new BufferedInputStream(baseFis);
      FileInputStream     otherFis = new FileInputStream(other);
      BufferedInputStream otherBis = new BufferedInputStream(otherFis);
      
      for (long j = 0; j < baseLen; j++)
      {
         int nextBase  = baseBis.read();
         int nextOther = otherBis.read();
         
         if (nextBase == -1 || nextOther == -1)
         {
            // unexpected EOF
            return String.format(EOF_SPEC, nextBase, nextOther);
         }
         
         if (exclude(j))
         {
            // ignore the byte
            continue;
         }
         
         if (nextBase != nextOther)
         {
            // mismatching data
            return String.format(FAIL_SPEC,
                                 j,
                                 (char) nextBase,
                                 nextBase,
                                 (char) nextOther,
                                 nextOther);
         }
      }
      
      return null;
   }
   
   /**
    * Report if the given index position is contained within the list of
    * excluded regions.
    * 
    * @param    num
    *           Zero-based index position to check.
    *
    * @return   <code>true</code> if the given index is contained inside any
    *           of the excluded regions.
    */
   public boolean exclude(long num)
   {
      if (excludes != null)
      {
         for (int i = 0; i < excludes.length; i++)
         {
            if (excludes[i].contains(num))
            {
               return true;
            }
         }
      }
      
      return false;
   }
}
