/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2004 David Corbin <dcorbin@users.sourceforge.net>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby.test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.jruby.Ruby;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * Helper class, used for testing calls to java from ruby code.
 **/
public class TestHelper {
    private String privateField = "pfValue";
    public String localVariable1;

    // Function not used...but it gets rid of unused warnings in Eclipse (we do call those methods
    // from Ruby so they are not really unused).
    public static void removeWarningsFromEclipse() {
        TestHelper helper = new TestHelper("A");
        helper.privateMethod();
        TestHelper.staticPrivateMethod();
    }

    private TestHelper(String x) {
        privateField = x;
    }

    public TestHelper() {
    }

    private String privateMethod() {
        return privateField;
    }

    private static String staticPrivateMethod() {
        return "staticPM";
    }

    public String identityTest() {
        return "Original";
    }

    /**
     * used to test Java Arrays in Ruby.
     *  while we don't yet have a way to create them this can be used to test basic
     *  array functionalities
     */
    public static String[] createArray(int i) {
        return new String[i];
    }

    /**
     * used to test native exception handling.
     **/
    public static void throwException() {
        throw new RuntimeException("testException");
    }

    /**
     * @return object used to test casting
     */
    public static SomeInterface getInterfacedInstance() {
        return new SomeImplementation();
    }

    public static Object getLooslyCastedInstance() {
        return new SomeImplementation();
    }

    public static Object getNull() {
        return null;
    }

    public static interface SomeInterface {
        String doStuff();
        String dispatchObject(Object iObject);
    }

    private static class SomeImplementation implements SomeInterface {
        public String doStuff() {
            return "stuff done";
        }
        
        public String dispatchObject(Object iObject) {
            return iObject == null ? null : iObject.toString();
        }
    }


    public static Class loadAlternateClass() throws ClassNotFoundException {
        AlternateLoader loader = new AlternateLoader();
        Class klass = loader.loadClass("org.jruby.test.TestHelper");
        return klass;
    }

    /**
     * Used by JVM bytecode compiler tests to run compiled code
     */
    public static IRubyObject loadAndCall(IRubyObject self, String name, byte[] javaClass, String methodName)
            throws Throwable {
        Loader loader = new Loader();
        Class c = loader.loadClass(name, javaClass);
        Method method = c.getMethod(methodName, new Class[] { Ruby.class, IRubyObject.class });
        Ruby runtime = self.getRuntime();
        ThreadContext tc = runtime.getCurrentContext();
        
        tc.pushRubyClass(self.getType());

        try {
            return (IRubyObject) method.invoke(null, new Object[] { runtime, self });
        } catch (InvocationTargetException e) {
            throw unrollException(e);
        } finally {
            tc.popRubyClass();
        }
    }

    private static Throwable unrollException(InvocationTargetException e) {
        while (e.getCause() instanceof InvocationTargetException) {
            e = (InvocationTargetException) e.getCause();
        }
        return e.getCause();
    }

    public static String getClassName(Class klass) {
        return klass.getName();
    }

    public static void throwTestHelperException() {
        throw new TestHelperException();
    }

    private static class Loader extends ClassLoader {

        public Class loadClass(String name, byte[] javaClass) {
            Class cl = defineClass(name,
                                   javaClass,
                                   0,
                                   javaClass.length);
            resolveClass(cl);
            return cl;
        }

    }
    private static class AlternateLoader extends ClassLoader {

        protected Class findModClass(String name) throws ClassNotFoundException {
           byte[] classBytes = loadClassBytes(name);
           replace(classBytes, "Original", "ABCDEFGH");
           return defineClass(name, classBytes, 0, classBytes.length);
        }
        private void replace(byte[] classBytes, String find, String replaceWith) {
            byte[] findBytes = find.getBytes();
            byte[] replaceBytes = replaceWith.getBytes();
            for (int i=0; i<classBytes.length; i++) {
                boolean match = true;
                for (int j=0; j<findBytes.length; j++) {
                    if (classBytes[i+j] != findBytes[j]) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    for (int j=0; j<findBytes.length; j++)
                        classBytes[i+j] = replaceBytes[j];
                    return;
                }
            }
        }
        @Override
        public Class loadClass(String name) throws ClassNotFoundException {
            if (name.equals("org.jruby.test.TestHelper"))
                return findModClass(name);
            return super.loadClass(name);
        }
        private byte[] loadClassBytes(String name) throws ClassNotFoundException {
            InputStream stream = null;
            try {
                String fileName = name.replaceAll("\\.", "/");
                fileName += ".class";
                byte[] buf = new byte[1024];
                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                int bytesRead = 0;
                stream = getClass().getResourceAsStream("/" + fileName);
                while ((bytesRead = stream.read(buf)) != -1) {
                    bytes.write(buf, 0, bytesRead);
                }
                return bytes.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException(e.getMessage(),e);
            } finally {
                if (stream != null)
                    try {
                        stream.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
            }
        }
    }

    private static class TestHelperException extends RuntimeException {
        private static final long serialVersionUID = 3649034127816624007L;
    }
}
