package org.testng;

import java.io.File;
import java.io.IOException;

/**
 * Assertion tool for File centric assertions.
 * Conceptually this is an extension of {@link Assert}
 * Presents assertion methods with a more natural parameter order.
 * The order is always <B>actualValue</B>, <B>expectedValue</B> [, message].
 *
 * @author <a href='mailto:pmendelson@trueoutcomes.com'>Paul Mendelon</a>
 * @since 5.6
 * @version $Revision: 650 $, $Date: 2009-01-05 03:51:54 -0800 (Mon, 05 Jan 2009) $
 */
public class FileAssert {

  /**
     * Protect constructor since it is a static only class
     */
  private FileAssert() {
  	// hide constructor
  }
  
  /**
     * Asserts that a {@code tstvalue} is a proper directory. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param tstvalue the file to evaluate
     * @param message the assertion error message
     */
  static public void assertDirectory(File tstvalue, String message) {
  	boolean condition=false;
  	try {
  	condition=tstvalue.isDirectory();
  	} catch(SecurityException e) {
  	failSecurity(e,tstvalue,fileType(tstvalue),"Directory", message);
  	}
  	if(!condition) {
  	failFile(tstvalue,fileType(tstvalue),"Directory", message);
  	}
  }
  
  static public void assertDirectory(File tstvalue) {
  	assertDirectory(tstvalue, null);
  }
  
  /**
     * Asserts that a {@code tstvalue} is a proper directory. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param tstvalue the file to evaluate
     * @param message the assertion error message
     */
  static public void assertFile(File tstvalue, String message) {
  	boolean condition=false;
  	try {
  	condition=tstvalue.isFile();
  	} catch(SecurityException e) {
  	failSecurity(e,tstvalue,fileType(tstvalue),"File", message);
  	}
  	if(!condition) {
  	failFile(tstvalue,fileType(tstvalue),"File", message);
  	}
  }
  
  /**
     * @see #assertFile(File, String)
     */
  static public void assertFile(File tstvalue) {
  	assertFile(tstvalue, null);
  }
  
  /**
     * Asserts that a {@code tstvalue} is a file of exactly {@code expected} characters
     * or a directory of exactly {@code expected} entries. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param tstvalue the file to evaluate
     * @param message the assertion error message
     */
  static public void assertLength(File tstvalue, long expected, String message) {
  	long actual=-1L;
  	try {
  	actual=tstvalue.isDirectory()?tstvalue.list().length:tstvalue.length();
  	} catch(SecurityException e) {
  	failSecurity(e,tstvalue,String.valueOf(actual),String.valueOf(expected), message);
  	}
  	if(actual!=expected) {
  	failFile(tstvalue,String.valueOf(actual),String.valueOf(expected), message);
  	}
  }
  
  /**
     * @see #assertLength(File, long, String)
     */
  static public void assertLength(File tstvalue, long expected) {
  	assertLength(tstvalue, expected, null);
  }
  
  /**
     * Asserts that a {@code tstvalue} is a file of at least {@code expected} characters
     * or a directory of at least {@code expected} entries. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param tstvalue the file to evaluate
     * @param message the assertion error message
     */
  static public void assertMinLength(File tstvalue, long expected, String message) {
  	long actual=-1L;
  	try {
  	actual=tstvalue.isDirectory()?tstvalue.list().length:tstvalue.length();
  	} catch(SecurityException e) {
  	failSecurity(e,tstvalue,String.valueOf(actual),"at least "+String.valueOf(expected), message);
  	}
  	if(actual<expected) {
  	failFile(tstvalue,String.valueOf(actual),"at least "+String.valueOf(expected), message);
  	}
  }
  
  /**
     * @see #assertMinLength(File, long, String)
     */
  static public void assertMinLength(File tstvalue, long expected) {
  	assertMinLength(tstvalue, expected, null);
  }
  
  /**
     * Asserts that a {@code tstvalue} is a file of at most {@code expected} characters
     * or a directory of at most {@code expected} entries. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param tstvalue the file to evaluate
     * @param message the assertion error message
     */
  static public void assertMaxLength(File tstvalue, long expected, String message) {
  	long actual=-1L;
  	try {
  	actual=tstvalue.isDirectory()?tstvalue.list().length:tstvalue.length();
  	} catch(SecurityException e) {
  	failSecurity(e,tstvalue,String.valueOf(actual),"at most "+String.valueOf(expected), message);
  	}
  	if(actual>expected) {
  	failFile(tstvalue,String.valueOf(actual),"at most "+String.valueOf(expected), message);
  	}
  }
  
  /**
     * @see #assertMaxLength(File, long, String)
     */
  static public void assertMaxLength(File tstvalue, long expected) {
  	assertMaxLength(tstvalue, expected, null);
  }
  
  /**
     * Asserts that a {@code tstvalue} is readable. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param tstvalue the file to evaluate
     * @param message the assertion error message
     */
  static public void assertReadable(File tstvalue, String message) {
  	boolean condition=false;
  	try {
  	condition=tstvalue.canRead();
  	} catch(SecurityException e) {
  	failSecurity(e,tstvalue,fileAccess(tstvalue),"Read Access", message);
  	}
  	if(!condition) {
  	failFile(tstvalue,fileAccess(tstvalue),"Read Access", message);
  	}
  }
  
  /**
     * @see #assertReadable(File, String)
     */
  static public void assertReadable(File tstvalue) {
  	assertReadable(tstvalue, null);
  }
  
  /**
     * Asserts that a {@code tstvalue} is writeable. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param tstvalue the file to evaluate
     * @param message the assertion error message
     */
  static public void assertWriteable(File tstvalue, String message) {
  	boolean condition=false;
  	try {
  	condition=tstvalue.canWrite();
  	} catch(SecurityException e) {
  	failSecurity(e,tstvalue,fileAccess(tstvalue),"Write Access", message);
  	}
  	if(!condition) {
  	failFile(tstvalue,fileAccess(tstvalue),"Write Access", message);
  	}
  }
  
  /**
     * @see #assertWriteable(File, String)
     */
  static public void assertWriteable(File tstvalue) {
  	assertReadable(tstvalue, null);
  }
  
  /**
     * Asserts that a {@code tstvalue} is readable and writeable. If it isn't,
     * an AssertionError, with the given message, is thrown.
     * @param tstvalue the file to evaluate
     * @param message the assertion error message
     */
  static public void assertReadWrite(File tstvalue, String message) {
  	boolean condition=false;
  	try {
  	condition=tstvalue.canRead() && tstvalue.canWrite();
  	} catch(SecurityException e) {
  	failSecurity(e,tstvalue,fileAccess(tstvalue),"Read/Write Access", message);
  	}
  	if(!condition) {
  	failFile(tstvalue,fileAccess(tstvalue),"Read/Write Access", message);
  	}
  }
  
  /**
     * @see #assertReadWrite(File, String)
     */
  static public void assertReadWrite(File tstvalue) {
  	assertReadWrite(tstvalue, null);
  }
  
  /**
     * Fails a test with the given message and wrapping the original exception.
     *
     * @param message the assertion error message
     * @param realCause the original exception
     */
  static public void fail(String message, Throwable realCause) {
  	AssertionError ae = new AssertionError(message);
  	ae.initCause(realCause);
  
  	throw ae;
  }
  
  /**
     * Fails a test with the given message.
     * @param message the assertion error message
     */
  static public void fail(String message) {
  	throw new AssertionError(message);
  }
  
  /**
     * Fails a test with no message.
     */
  static public void fail() {
  	fail(null);
  }
  
  /**
     * Formats failure for file assertions
     */
  private static void failFile(File path, String actual, String expected, String message) {
  	String formatted = "";
  	if(message != null) {
  	formatted = message + " ";
  	}
  	fail(formatted + "expected <" + expected +"> but was <" + toString(path) + ">"
  		+(expected!=null?"<" + expected +">":""));
  }
  
  /**
     * @param tstvalue
     * @param string
     * @param string2
     * @param message
     */
  private static void failSecurity(Exception e, File path, String actual, String expected, String message) {
  	String formatted = "";
  	if(message != null) {
  	formatted = message + " ";
  	}
  	fail(formatted + "expected <" + expected +"> but was <" + toString(path) + ">"
  		+"<"
  		+ (e!=null && e.getMessage()!=null && e.getMessage().length()>0
  			?e.getMessage()
  			:"not authorized by JVM")
  		+ ">");
  }
  
  /**
     * String representation of what sort of file {@code path} is.
     */
  private static String fileType(File path) {
  	try {
  	if(!path.exists())
  		return "Non existant";
  	else if (path.isDirectory())
  		return "Directory";
  	else if (path.isFile())
  		return "File";
  	else
  		return "Special File";
  	} catch (SecurityException e) {
  	return "Unauthorized";
  	}
  }
  
  /**
     * String representation of what sort of file {@code path} is.
     */
  private static String fileAccess(File path) {
  	try {
  	if(!path.exists())
  		return "Non existant";
  	else if (path.canWrite() && path.canRead())
  		return "Read/Write Access";
  	else if (path.canRead())
  		return "Read only Access";
  	else if (path.canWrite())
  		return "Write only Access";
  	else
  		return "No Access";
  	} catch (SecurityException e) {
  	return "Unauthorized";
  	}
  }
  
  private static String toString(File path) {
  	try {
  	return path.getCanonicalPath();
  	} catch(IOException e) {
  	return path.getAbsolutePath();
  	}
  }
}
