/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.tests.sigtest;

import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.os.Bundle;

import org.xmlpull.v1.XmlPullParserException;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

/**
 * This class is used for Signature Test. It will started by the Instrumentation class,
 * and send back the test result via IBinder.
 */
public class SignatureTestActivity extends Activity {
    static final String BUNDLE_EXTRA_SIG_TEST = "sigtest";

    static final String BUNDLE_KEY_RESULT = "result";
    static final String BUNDLE_KEY_MISSING_CLASS = "missing_class";
    static final String BUNDLE_KEY_MISSING_INTERFACE = "missing_interface";
    static final String BUNDLE_KEY_MISSING_METHOD = "missing_method";
    static final String BUNDLE_KEY_MISSING_FIELD = "missing_field";
    static final String BUNDLE_KEY_MISMATCH_CLASS = "mismatch_class_signature";
    static final String BUNDLE_KEY_MISMATCH_INTERFACE = "mismatch_interface_signature";
    static final String BUNDLE_KEY_MISMATCH_METHOD = "mismatch_method_signature";
    static final String BUNDLE_KEY_MISMATCH_FIELD = "mismatch_field_signature";
    static final String BUNDLE_KEY_CAUGHT_EXCEPTION = "caught_exception";


    static final int GET_SIG_TEST_RESULT_TRANSACTION = 101;

    private DeviceResultObserver mResultObserver;

    /**
     * Define the type of the signature check failures.
     */
    public static enum FAILURE_TYPE {
        MISSING_CLASS,
        MISSING_INTERFACE,
        MISSING_METHOD,
        MISSING_FIELD,
        MISMATCH_CLASS,
        MISMATCH_INTERFACE,
        MISMATCH_METHOD,
        MISMATCH_FIELD,
        CAUGHT_EXCEPTION,
    }

    static final HashMap<FAILURE_TYPE, String> FAILURE_TYPE_TO_KEY =
            new HashMap<FAILURE_TYPE, String>();
    static {
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.MISSING_CLASS, BUNDLE_KEY_MISSING_CLASS);
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.MISSING_INTERFACE, BUNDLE_KEY_MISSING_INTERFACE);
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.MISSING_METHOD, BUNDLE_KEY_MISSING_METHOD);
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.MISSING_FIELD, BUNDLE_KEY_MISSING_FIELD);
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.MISMATCH_CLASS, BUNDLE_KEY_MISMATCH_CLASS);
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.MISMATCH_INTERFACE, BUNDLE_KEY_MISMATCH_INTERFACE);
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.MISMATCH_METHOD, BUNDLE_KEY_MISMATCH_METHOD);
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.MISMATCH_FIELD, BUNDLE_KEY_MISMATCH_FIELD);
        FAILURE_TYPE_TO_KEY.put(FAILURE_TYPE.CAUGHT_EXCEPTION, BUNDLE_KEY_CAUGHT_EXCEPTION);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mResultObserver = new DeviceResultObserver();
        start();
        mResultObserver.sendResult(getIntent());
    }

    /**
     * Start the signature test.
     */
    @SuppressWarnings("unchecked")
    private void start() {
        SignatureTest sigTest = new SignatureTest(mResultObserver);

        HashSet<String> excludeSet = getExcludedSet();

        Resources r = getResources();

        Class rClass = R.xml.class;
        Field[]fs = rClass.getFields();
        for (Field f : fs) {
            if (!excludeSet.contains(f.getName())) {
                try {
                    sigTest.start(r.getXml(f.getInt(rClass)));
                } catch (Exception e) {
                    mResultObserver.notifyFailure(FAILURE_TYPE.CAUGHT_EXCEPTION, e.getMessage(),
                            e.getMessage());
                }
            }
        }
    }

    /**
     * Get the excluded package set, which is defined by res/raw/excludepackages.txt.
     *
     * @return The excluded package set.
     */
    private HashSet<String> getExcludedSet() {
        HashSet<String> excludeSet = new HashSet<String>();

        Resources r = getResources();
        InputStream excludepackage = r.openRawResource(R.raw.excludepackages);
        BufferedReader reader = new BufferedReader(new InputStreamReader(excludepackage));
        try {
            String p = null;
            while (true) {
                p = reader.readLine();
                if (p == null || p.equals("")) {
                    break;
                }
                excludeSet.add(p);
            }
            reader.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return excludeSet;
    }

    Bundle mBundle;

    /**
     * This class is an implementation of the ResultObserver. And it aims to
     * record the result in the Bundle, and send back to the Instrumentation class
     * after all results has been recorded.
     */
    final class DeviceResultObserver implements ResultObserver {
        DeviceResultObserver() {
            mBundle = new Bundle();
            mBundle.putStringArrayList(BUNDLE_KEY_MISSING_FIELD, new ArrayList<String>());
            mBundle.putStringArrayList(BUNDLE_KEY_MISSING_METHOD, new ArrayList<String>());
            mBundle.putStringArrayList(BUNDLE_KEY_MISMATCH_CLASS,
                    new ArrayList<String>());
            mBundle.putStringArrayList(BUNDLE_KEY_MISMATCH_FIELD,
                    new ArrayList<String>());
            mBundle.putStringArrayList(BUNDLE_KEY_MISMATCH_METHOD,
                    new ArrayList<String>());
            mBundle.putStringArrayList(BUNDLE_KEY_MISSING_CLASS, new ArrayList<String>());
            mBundle.putStringArrayList(BUNDLE_KEY_MISSING_INTERFACE,
                    new ArrayList<String>());
            mBundle.putStringArrayList(BUNDLE_KEY_MISMATCH_INTERFACE,
                    new ArrayList<String>());
            mBundle.putStringArrayList(BUNDLE_KEY_CAUGHT_EXCEPTION,
                    new ArrayList<String>());
        }

        /**
         * This method is called when all the results has been recorded. And this method
         * will save the results in IBinder and send back to the Instrumentation class.
         *
         * @param i The intent to carry the result.
         */
        @SuppressWarnings("deprecation")
        public void sendResult(Intent i) {
            SignatureTestLog.d("Send result");
            if ((mBundle.getStringArrayList(BUNDLE_KEY_MISSING_FIELD).size() == 0)
                    && (mBundle.getStringArrayList(BUNDLE_KEY_MISSING_CLASS).size() == 0)
                    && (mBundle.getStringArrayList(BUNDLE_KEY_MISSING_METHOD).size() == 0)
                    && (mBundle.getStringArrayList(BUNDLE_KEY_MISSING_INTERFACE).size() == 0)
                    && (mBundle.getStringArrayList(
                            BUNDLE_KEY_MISMATCH_CLASS).size() == 0)
                    && (mBundle.getStringArrayList(
                            BUNDLE_KEY_MISMATCH_FIELD).size() == 0)
                    && (mBundle.getStringArrayList(
                            BUNDLE_KEY_MISMATCH_INTERFACE).size() == 0)
                    && (mBundle.getStringArrayList(
                            BUNDLE_KEY_MISMATCH_METHOD).size() == 0)
                    && (mBundle.getStringArrayList(
                            BUNDLE_KEY_CAUGHT_EXCEPTION).size() == 0)) {
                SignatureTestLog.d("PASS");
                mBundle.putBoolean(BUNDLE_KEY_RESULT, true);
            } else {
                SignatureTestLog.d("FAIL: " + mBundle.size());
                mBundle.putBoolean(BUNDLE_KEY_RESULT, false);
            }
        }

        public void notifyFailure(FAILURE_TYPE type,
                                  String name,
                                  String errorMessage) {
            SignatureTestLog.d("Failure: ");
            SignatureTestLog.d("   Type: " + type);
            SignatureTestLog.d("   Name: " + name);
            SignatureTestLog.d("   Why : " + errorMessage);
            mBundle.getStringArrayList(SignatureTestActivity.FAILURE_TYPE_TO_KEY.get(type))
                    .add(name);
        }
    }
}
