/*******************************************************************************
 * Copyright (c) 2010 Bruno Medeiros and other Contributors.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Bruno Medeiros - initial implementation
 *******************************************************************************/
package melnorme.utilbox.core;

import static melnorme.utilbox.core.CoreUtil.areEqual;


/**
 * Contains utility methods for assertion contract checking .
 * 
 * Each method in this class, such as 'isTrue', has an identical method
 * with the 'assert' prefix, (ie, assertTrue). These later methods are named this way so as to be
 * use with Java's static imports, and are actually the preferred usage. 
 * They are designed to be placed as a favorite in JDT's Content Assist preferences.
 * 
 * This class can be used without OSGi running.
 */
public class Assert {
	
	protected static final AssertHandler assertHandler; // Poor mans dependency injection
	
	public static boolean assertionFailureExpected = false; // Only tests should modify this field
	
	static {
		String assertHandlerStr = System.getProperty(Assert.class.getName() + ".handler");
		if(assertHandlerStr == null) {
			assertHandler = new AssertHandler();
		} else if(assertHandlerStr.equals("disable") || assertHandlerStr.equals("null")) {
			assertHandler = null; // No op handler
		} else {
			try {
				final Class<?> klass = Class.forName(assertHandlerStr);
				Object handler = klass.newInstance();
				if(handler instanceof Runnable) {
					final Runnable runnableHandler = (Runnable) handler;
					assertHandler = new AssertHandler() {
						@Override 
						protected void handleAssert(String message) {
							runnableHandler.run();
						};
					};
				} else {
					assertHandler = CoreUtil.downCast(handler);
				}
			} catch(Exception e) {
				throw new RuntimeException(e); // Yes, let our classloading fail.
			}
		}
	}
	
	/** Default implementation of the assert handler. */
	public static class AssertHandler {
		protected void handleAssert(String message) {
			throw new AssertFailedException(message);
		}
	}
	
	/** 
	 * The class thrown by the default assertion failure handler.
	 * Clients should not expect this to be thrown.
	 */
	@SuppressWarnings("serial")
	public static class AssertFailedException extends RuntimeException {
		
		public AssertFailedException(String message) {
			super(message);
		}
		
		@Override
		public String toString() {
			String message = getLocalizedMessage();
			return AssertFailedException.class.getSimpleName() 
				+ ((message == null) ? "" : (": " + message));  //$NON-NLS-1$
		}
		
	}
	
	
	protected static void checkAssertion(boolean condition, String message) {
		if(assertHandler != null && condition == false) {
			if(assertionFailureExpected) {
				// This does same thing as else branch, but code is separated 
				// so that this case won't trigger the IDE breakpoint
				assertHandler.handleAssert(message);
			} else {
				assertHandler.handleAssert(message);  // USEFUL TIP: place IDE breakpoint here
			}
		}
	}
	
	
	/** Asserts if given condition is true or not. If it is not, call assert handler.
	 * Default handler behavior is to throw an {@link AssertFailedException} with given message.
	 */
	public static void isTrue(boolean condition, String message) {
		checkAssertion(condition, message);
	}
	/** Like {@link Assert#isTrue(boolean, String)} with no message */
	public static void isTrue(boolean expression) {
		checkAssertion(expression, null);
	}
	
	
	/** Asserts that the given object is not null, with given message */
	public static void isNotNull(Object object, String message) {
		checkAssertion(!(object == null), message);
	}
	/** Like {@link Assert#isNotNull(Object, String)} with no message. */
	public static void isNotNull(Object object) {
		checkAssertion(!(object == null), null);
	}
	
	
	/** Asserts that given object1 equals object2. */
	public static void equals(Object object1, Object object2) {
		checkAssertion(object1.equals(object2), null);
	}
	
	
	/** Causes an inconditional assertion failure, with given message. 
	 * Will always call the assertion handler, and return an RuntimeException if nothing is thrown. */
	public static RuntimeException fail(String message) {
		checkAssertion(false, message);
		return new AssertFailedException(message);
	}
	/** Like {@link Assert#fail(String)} with no message. */
	public static RuntimeException fail() {
		checkAssertion(false, null);
		return new AssertFailedException(null);
	}
	
	
	/** Like {@link Assert#fail(String)}, but specifically signals unreachable code */
	public static RuntimeException unreachable() {
		return fail("Unreachable code."); //$NON-NLS-1$
	}
	
	
	/** A namespace class for holding assert methods to use as static imports. */
	public static class AssertNamespace {
		/** Asserts if given condition is true or not. If it is not, call assert handler.
		 * Default handler behavior is to throw an {@link AssertFailedException} with given message.
		 */
		public static void assertTrue(boolean condition, String message) {
			checkAssertion(condition, message);
		}
		/** Like {@link Assert#isTrue(boolean, String)} with no message */
		public static void assertTrue(boolean expression) {
			checkAssertion(expression, null);
		}
		
		
		/** Asserts that the given object is not null, with given message */
		public static void assertNotNull(Object object, String message) {
			checkAssertion(!(object == null), message);
		}
		/** Like {@link Assert#isNotNull(Object, String)} with no message. */
		public static <T> T assertNotNull(T object) {
			checkAssertion(!(object == null), null);
			return object;
		}
		
		
		/** Asserts that given object1 equals object2. */
		public static void assertEquals(Object object1, Object object2) {
			checkAssertion(object1.equals(object2), null);
		}
		
		public static void assertAreEqual(Object object1, Object object2) {
			checkAssertion(areEqual(object1, object2), null);
		}
		
		
		/** Causes an inconditional assertion failure, with given message. 
		 * Will always call the assertion handler, and return an RuntimeException if nothing is thrown. */
		public static RuntimeException assertFail(String message) {
			checkAssertion(false, message);
			return new AssertFailedException(message);
		}
		/** Like {@link Assert#fail(String)} with no message. */
		public static RuntimeException assertFail() {
			checkAssertion(false, null);
			return new AssertFailedException(null);
		}
		
		
		/** Like {@link Assert#fail(String)}, but specifically signals unreachable code */
		public static RuntimeException assertUnreachable() {
			return fail("Unreachable code."); //$NON-NLS-1$
		}
	}
	
}
