package net.bytebuddy.android.test;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.android.AndroidClassLoadingStrategy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.utility.RandomString;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.concurrent.Callable;

import static net.bytebuddy.matcher.ElementMatchers.named;

/**
 * This activity allows to run a code generation on an Android device.
 */
public class TestActivity extends Activity {

    /**
     * A sample String to be returned by an instrumented {@link Object#toString()} method.
     */
    private static final String FOO = "foo";

    /**
     * The tag to be used for Android's log messages.
     */
    private static final String BYTE_BUDDY_TAG = "net.bytebuddy";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView mavenInfo = (TextView) findViewById(R.id.maven_info);
        String version = "n/a";
        try {
            InputStream inputStream = TestActivity.class.getClassLoader().getResourceAsStream("maven.properties");
            if (inputStream != null) {
                try {
                    Properties properties = new Properties();
                    properties.load(inputStream);
                    version = properties.getProperty("version", version);
                } finally {
                    inputStream.close();
                }
            }
        } catch (Exception exception) {
            Log.i(BYTE_BUDDY_TAG, "Could not read version", exception);
            Toast.makeText(TestActivity.this, "Warning: Could not read version property. (" + exception.getMessage() + ")", Toast.LENGTH_SHORT).show();
        }
        mavenInfo.setText(getResources().getString(R.string.version_info, version));
        Button runTestWrapping = (Button) findViewById(R.id.run_test_wrapping);
        runTestWrapping.setOnClickListener(new TestRun(new StrategyCreator.Wrapping()));
        Button runTestInjecting = (Button) findViewById(R.id.run_test_injecting);
        runTestInjecting.setOnClickListener(new TestRun(new StrategyCreator.Injecting()));
    }

    /**
     * An interceptor to be used in the instrumentation of the {@link Object#toString()} method. Of course, this
     * could also be achieved by using a {@link net.bytebuddy.implementation.FixedValue} instrumentation. However,
     * the instrumentation should generate an {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType}
     * to validate their functionality.
     */
    public static class Interceptor {

        /**
         * The interception method to be applied.
         *
         * @param zuper A proxy to call the super method to validate the functioning og creating an auxiliary type.
         * @return The value to be returned by the instrumented {@link Object#toString()} method.
         * @throws Exception If an exception occurs.
         */
        public static String intercept(@SuperCall Callable<String> zuper) throws Exception {
            String toString = zuper.call();
            if (toString.equals(FOO)) {
                throw new IllegalStateException("Super call proxy invocation did not derive in its value");
            }
            return FOO;
        }
    }

    /**
     * A test run for the sample Android application.
     */
    private class TestRun implements View.OnClickListener {

        /**
         * The strategy creator to use.
         */
        private final StrategyCreator strategyCreator;

        /**
         * Creates a new test run listener.
         *
         * @param strategyCreator The strategy creator to use.
         */
        private TestRun(StrategyCreator strategyCreator) {
            this.strategyCreator = strategyCreator;
        }

        @Override
        public void onClick(View view) {
            ByteBuddy byteBuddy;
            try {
                byteBuddy = new ByteBuddy();
            } catch (Throwable throwable) {
                Log.w(BYTE_BUDDY_TAG, throwable);
                Toast.makeText(TestActivity.this, "Failure: Could not create Byte Buddy instance. (" + throwable.getMessage() + ")", Toast.LENGTH_LONG).show();
                return;
            }
            try {
                File file = TestActivity.this.getDir(RandomString.make(), Context.MODE_PRIVATE);
                if (!file.isDirectory()) {
                    throw new IOException("Not a directory: " + file);
                }
                DynamicType.Loaded<?> dynamicType;
                try {
                    dynamicType = byteBuddy.subclass(Object.class)
                            .method(named("toString")).intercept(MethodDelegation.to(Interceptor.class))
                            .make()
                            .load(TestActivity.class.getClassLoader(), strategyCreator.make(file));
                } catch (Throwable throwable) {
                    Log.w(BYTE_BUDDY_TAG, throwable);
                    Toast.makeText(TestActivity.this, "Failure: Could not load dynamic type. (" + throwable.getMessage() + ")", Toast.LENGTH_LONG).show();
                    return;
                }
                try {
                    String value = dynamicType.getLoaded().newInstance().toString();
                    Toast.makeText(TestActivity.this,
                            FOO.equals(value)
                                    ? "Success: Created type and verified instrumentation."
                                    : "Failure: Expected different value by instrumented method. (was: " + value + ")",
                            Toast.LENGTH_LONG).show();
                } catch (Throwable throwable) {
                    Log.w(BYTE_BUDDY_TAG, throwable);
                    Toast.makeText(TestActivity.this, "Failure: Could create dynamic instance. (" + throwable.getMessage() + ")", Toast.LENGTH_LONG).show();
                }
            } catch (Throwable throwable) {
                Log.w(BYTE_BUDDY_TAG, throwable);
                Toast.makeText(TestActivity.this, "Failure: Could not create temporary file. (" + throwable.getMessage() + ")", Toast.LENGTH_LONG).show();
            }
        }
    }

    /**
     * A strategy creator for Android.
     */
    private interface StrategyCreator {

        /**
         * Creates an Android class loading strategy.
         *
         * @param file The private folder to use.
         * @return The class loading strategy to use.
         */
        AndroidClassLoadingStrategy make(File file);

        /**
         * A creator for creating a wrapping strategy.
         */
        class Wrapping implements StrategyCreator {

            @Override
            public AndroidClassLoadingStrategy make(File file) {
                return new AndroidClassLoadingStrategy.Wrapping(file);
            }
        }

        /**
         * A creator for creating an injecting strategy.
         */
        class Injecting implements StrategyCreator {

            @Override
            public AndroidClassLoadingStrategy make(File file) {
                return new AndroidClassLoadingStrategy.Injecting(file);
            }
        }
    }
}

