/*
 Copyright (c) 2006 - 2009  Eric J. Feminella  <eric@ericfeminella.com>
 All rights reserved.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is 
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in 
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 THE SOFTWARE.

 @internal
 */

package com.ericfeminella.tests
{
    import com.ericfeminella.collections.ArrayList;
    import com.ericfeminella.collections.Collection;
    import com.ericfeminella.collections.Iterator;
    import com.ericfeminella.utils.AbstractStaticType;
    import flash.utils.describeType;
    import flash.utils.getQualifiedClassName;
    import flexunit.framework.TestSuite;
    
    /**
     * 
     * All static utility class which provides a mechanism for adding
     * a <code>TestCase</code> to a <code>TestSuite</code> for each 
     * method defined by a specific class
     * 
     * @example The following example demonstrates how a class which 
     * extends TestCase can utilize <code>TestSuiteBuilder</code> to
     * automate the creation of the associated <code>TestSuite</code>
     * 
     * <listing version="3.0">
     * 
     * package
     * {
     *    public class ExampleTest extends TestCase 
     *    {
     *        public function testX() : void
     *        {
     *            // test implementation...
     *        }
     * 
     *        public function testY() : void
     *        {
     *            // test implementation...
     *        }
     * 
     *        public function testZ() : void
     *        {
     *            // test implementation...
     *        }
     *    }
     * }
     * 
     * // TestRunner.mxml on creationComplete Event
     * private function onCreationComplete() : void
     * {
     *     testRunner.test = TestSuiteFactory.createSuite( ExampleTest );
     *     testRunner.startTest();
     * }
     * 
     * // creates a new TestSuite and adds each method defined by ExampleTest 
     * // to the suite
     * 
     * </listing>
     * 
     * @see flexunit.framework.TestSuite
     * @see flexunit.framework.TestCase
     * 
     */    
    public final class TestSuiteFactory extends AbstractStaticType
    {
        /**
         *
         * Specifies all inherited test fixtures which are not to be included in 
         * the tests.
         *  
         */        
        protected static const FIXTURES:Array = [ "addAsync",
                                                  "countTestCases",
                                                  "getNextAsync",
                                                  "getTestMethodNames",
                                                  "hasAsync",
                                                  "run",
                                                  "runFinish",
                                                  "runMiddle",
                                                  "runStart",
                                                  "runWithResult",
                                                  "setTestResult",
                                                  "setUp",
                                                  "startAsync",
                                                  "tearDown",
                                                  "toString" ];
        
        /**
         *
         * Creates a <code>TestSuite</code> and, through introspection,
         * determines all methods defined by a specific class which 
         * extends <code>TestCase</code> and adds each method to the
         * <code>TestSuite</code>
         *  
         * @param   the class in which to locate all tests
         * @return  a <code>TestSuite</code> containing all of the tests
         * 
         */        
        public static function createSuite(Type:Class) : TestSuite
        {
            var tests:XMLList = getTests( Type );

            var testSuite:TestSuite = new TestSuite();
            
            for each ( var methodName:String in tests.@name )
            {
                if ( !isTestFixture( methodName ) )
                {
                    testSuite.addTest( new Type( methodName ) );
                }
            }
            return testSuite;
        }
        
        /**
         *
         * Creates a <code>TestSuite</code> which contains all test classes
         * to be run as part of the <code>TestRunner</code>.
         * 
         * <p>
         * <code>createSuites</code> requires an <code>Array</code> of Class
         * objects which extend <code>TestCase</code>. Each Class in the Array
         * will have all public methods added to a suite, which in turn will
         * run in the test runner. By creating an <code>Array</code> containing 
         * each test class to be tested, and invoking <code>createSuites</code> 
         * the process of creating <code>TestSuites</code> for every test
         * method can be completely automated.
         * </p>
         * 
         */        
        public static function createSuites(client:TestSuiteFactoryClient) : TestSuite
        {
            var suite:TestSuite = new TestSuite();
            
            var allTests:Collection = client.allTests;
            var it:Iterator = allTests.iterator;
            
            while ( it.hasNext() )
            {
                var TestClass:Class = it.next() as Class;
                suite.addTest( createSuite( TestClass ) );                
            }
            return suite;
        }
        
        /**
         * 
         * Retrieves all tests which have been defined for a specific 
         * <code>TestCase</code> sub class
         * 
         * @param   the class or type in which to locate tests
         * @return  an <code>XMLList</code> containing all test names
         * 
         */        
        protected static function getTests(Type:Class) : XMLList
        {
            var type:String = getQualifiedClassName( Type );
            var definedMethods:XMLList = describeType(Type)..method.(@declaredBy == type)

            return definedMethods;
        }
        
        /**
         *
         * @private
         * 
         * <code>TestSuiteFactory.isTestFixture</code> determines if the specified 
         * method is an override of a defined test fixture, if so this operation 
         * returns true, otherwise false.
         *  
         * @param  specified method in which to validate
         * @return true if method name is not a test fixture, otherwise false
         * 
         */        
        private static function isTestFixture(methodName:String) : Boolean
        {
            var list:ArrayList = ArrayList.createArrayList( FIXTURES );
            var it:Iterator = list.iterator;
            
            var valid:Boolean = false;
            
            while ( it.hasNext() )
            {
                if ( it.next() == methodName )
                {
                    valid = true;
                }            
            }
            return valid;
        }
    }
}