package junit.runner;

import junit.framework.*;
import java.lang.reflect.*;
import java.text.NumberFormat;
import java.io.*;
import java.util.*;

/**
 * Base class for all test runners.
 * This class was born live on stage in Sardinia during XP2000.
 */
public abstract class BaseTestRunner implements TestListener {
    public static final String SUITE_METHODNAME= "suite";

    private static Properties fPreferences;
    static int fgMaxMessageLength= 500;
    static boolean fgFilterStack= true;
    boolean fLoading= true;

    /*
    * Implementation of TestListener
    */
    public synchronized void startTest(Test test) {
        testStarted(test.toString());
    }

    protected static void setPreferences(Properties preferences) {
        fPreferences= preferences;
    }

    protected static Properties getPreferences() {
        if (fPreferences == null) {
            fPreferences= new Properties();
             fPreferences.put("loading", "true");
             fPreferences.put("filterstack", "true");
              readPreferences();
        }
        return fPreferences;
    }

    public static void savePreferences() throws IOException {
        FileOutputStream fos= new FileOutputStream(getPreferencesFile());
        try {
            getPreferences().store(fos, "");
        } finally {
            fos.close();
        }
    }

    public void setPreference(String key, String value) {
        getPreferences().setProperty(key, value);
    }

    public synchronized void endTest(Test test) {
        testEnded(test.toString());
    }

    public synchronized void addError(final Test test, final Throwable t) {
        testFailed(TestRunListener.STATUS_ERROR, test, t);
    }

    public synchronized void addFailure(final Test test, final AssertionFailedError t) {
        testFailed(TestRunListener.STATUS_FAILURE, test, t);
    }

    // TestRunListener implementation

    public abstract void testStarted(String testName);

    public abstract void testEnded(String testName);

    public abstract void testFailed(int status, Test test, Throwable t);

    /**
     * Returns the Test corresponding to the given suite. This is
     * a template method, subclasses override runFailed(), clearStatus().
     */
    public Test getTest(String suiteClassName) {
        if (suiteClassName.length() <= 0) {
            clearStatus();
            return null;
        }
        Class testClass= null;
        try {
            testClass= loadSuiteClass(suiteClassName);
        } catch (ClassNotFoundException e) {
            String clazz= e.getMessage();
            if (clazz == null)
                clazz= suiteClassName;
            runFailed("Class not found \""+clazz+"\"");
            return null;
        } catch(Exception e) {
            runFailed("Error: "+e.toString());
            return null;
        }
        Method suiteMethod= null;
        try {
            suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]);
         } catch(Exception e) {
             // try to extract a test suite automatically
            clearStatus();
            return new TestSuite(testClass);
        }
        if (! Modifier.isStatic(suiteMethod.getModifiers())) {
            runFailed("Suite() method must be static");
            return null;
        }
        Test test= null;
        try {
            test= (Test)suiteMethod.invoke(null); // static method
            if (test == null)
                return test;
        }
        catch (InvocationTargetException e) {
            runFailed("Failed to invoke suite():" + e.getTargetException().toString());
            return null;
        }
        catch (IllegalAccessException e) {
            runFailed("Failed to invoke suite():" + e.toString());
            return null;
        }

        clearStatus();
        return test;
    }

    /**
     * Returns the formatted string of the elapsed time.
     */
    public String elapsedTimeAsString(long runTime) {
        return NumberFormat.getInstance().format((double)runTime/1000);
    }

    /**
     * Processes the command line arguments and
     * returns the name of the suite class to run or null
     */
    protected String processArguments(String[] args) {
        String suiteName= null;
        for (int i= 0; i < args.length; i++) {
            if (args[i].equals("-noloading")) {
                setLoading(false);
            } else if (args[i].equals("-nofilterstack")) {
                fgFilterStack= false;
            } else if (args[i].equals("-c")) {
                if (args.length > i+1)
                    suiteName= extractClassName(args[i+1]);
                else
                    System.out.println("Missing Test class name");
                i++;
            } else {
                suiteName= args[i];
            }
        }
        return suiteName;
    }

    /**
     * Sets the loading behaviour of the test runner
     */
    public void setLoading(boolean enable) {
        fLoading= enable;
    }
    /**
     * Extract the class name from a String
     */
    public String extractClassName(String className) {
        if(className.startsWith("Default package for"))
            return className.substring(className.lastIndexOf(".")+1);
        return className;
    }

    /**
     * Truncates a String to the maximum length.
     */
    public static String truncate(String s) {
        if (fgMaxMessageLength != -1 && s.length() > fgMaxMessageLength)
            s= s.substring(0, fgMaxMessageLength)+"...";
        return s;
    }

    /**
     * Override to define how to handle a failed loading of
     * a test suite.
     */
    protected abstract void runFailed(String message);

    /**
     * Returns the loaded Class for a suite name.
     */
    protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
        return getLoader().load(suiteClassName);
    }

    /**
     * Clears the status message.
     */
    protected void clearStatus() { // Belongs in the GUI TestRunner class
    }

    /**
     * Returns the loader to be used.
     */
    public TestSuiteLoader getLoader() {
        if (useReloadingTestSuiteLoader())
            return new ReloadingTestSuiteLoader();
        return new StandardTestSuiteLoader();
    }

    protected boolean useReloadingTestSuiteLoader() {
        return getPreference("loading").equals("true") && !inVAJava() && fLoading;
    }

    private static File getPreferencesFile() {
         String home= System.getProperty("user.home");
         return new File(home, "junit.properties");
     }

     private static void readPreferences() {
         InputStream is= null;
         try {
             is= new FileInputStream(getPreferencesFile());
             setPreferences(new Properties(getPreferences()));
            getPreferences().load(is);
        } catch (IOException e) {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e1) {
            }
        }
     }

     public static String getPreference(String key) {
         return getPreferences().getProperty(key);
     }

     public static int getPreference(String key, int dflt) {
         String value= getPreference(key);
         int intValue= dflt;
         if (value == null)
             return intValue;
         try {
             intValue= Integer.parseInt(value);
          } catch (NumberFormatException ne) {
         }
         return intValue;
     }

     public static boolean inVAJava() {
        try {
            Class.forName("com.ibm.uvm.tools.DebugSupport");
        }
        catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * Returns a filtered stack trace
     */
    public static String getFilteredTrace(Throwable t) {
        StringWriter stringWriter= new StringWriter();
        PrintWriter writer= new PrintWriter(stringWriter);
        t.printStackTrace(writer);
        StringBuffer buffer= stringWriter.getBuffer();
        String trace= buffer.toString();
        return BaseTestRunner.getFilteredTrace(trace);
    }

    /**
     * Filters stack frames from internal JUnit classes
     */
    public static String getFilteredTrace(String stack) {
        if (showStackRaw())
            return stack;

        StringWriter sw= new StringWriter();
        PrintWriter pw= new PrintWriter(sw);
        StringReader sr= new StringReader(stack);
        // BEGIN android-changed
        // Use a sensible default buffer size
        BufferedReader br= new BufferedReader(sr, 1000);
        // END android-changed

        String line;
        try {
            while ((line= br.readLine()) != null) {
                if (!filterLine(line))
                    pw.println(line);
            }
        } catch (Exception IOException) {
            return stack; // return the stack unfiltered
        }
        return sw.toString();
    }

    protected static boolean showStackRaw() {
        return !getPreference("filterstack").equals("true") || fgFilterStack == false;
    }

    static boolean filterLine(String line) {
        String[] patterns= new String[] {
            "junit.framework.TestCase",
            "junit.framework.TestResult",
            "junit.framework.TestSuite",
            "junit.framework.Assert.", // don't filter AssertionFailure
            "junit.swingui.TestRunner",
            "junit.awtui.TestRunner",
            "junit.textui.TestRunner",
            "java.lang.reflect.Method.invoke("
        };
        for (int i= 0; i < patterns.length; i++) {
            if (line.indexOf(patterns[i]) > 0)
                return true;
        }
        return false;
    }

     static {
         fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
     }

}
