/*
 * Copyright (C) 2010 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 com.android.cts.tradefed.testtype;

import com.android.ddmlib.Log;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.util.xml.AbstractXmlParser;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.Iterator;
import java.util.Stack;

/**
 * Parser for CTS test case XML.
 * <p/>
 * Dumb parser that just retrieves data from in the test case xml and stuff it into a
 * {@link TestPackageDef}. Currently performs limited error checking.
 */
public class TestPackageXmlParser extends AbstractXmlParser {

    private static final String LOG_TAG = "TestPackageXmlParser";

    private TestPackageDef mPackageDef;

    /**
     * SAX callback object. Handles parsing data from the xml tags.
     * <p/>
     * Expected structure:
     * <TestPackage>
     *     <TestSuite ...>
     *        <TestCase>
     *           <Test>
     */
    private class TestPackageHandler extends DefaultHandler {

        private static final String TEST_PACKAGE_TAG = "TestPackage";
        private static final String TEST_SUITE_TAG = "TestSuite";
        private static final String TEST_CASE_TAG = "TestCase";
        private static final String TEST_TAG = "Test";

        // holds current class name segments
        private Stack<String> mClassNameStack = new Stack<String>();

        @Override
        public void startElement(String uri, String localName, String name, Attributes attributes)
                throws SAXException {
            if (TEST_PACKAGE_TAG.equals(localName)) {
                // appPackageName is used as the uri
                final String entryUriValue = attributes.getValue("appPackageName");
                final String testPackageNameSpace = attributes.getValue("appNameSpace");
                final String packageName = attributes.getValue("name");
                final String runnerName = attributes.getValue("runner");
                final String hostSideTest = attributes.getValue("hostSideOnly");
                final String jarPath = attributes.getValue("jarPath");
                final String signatureCheck = attributes.getValue("signatureCheck");
                final String referenceApp = attributes.getValue("referenceAppTest");

                mPackageDef = new TestPackageDef();
                mPackageDef.setUri(entryUriValue);
                mPackageDef.setAppNameSpace(testPackageNameSpace);
                mPackageDef.setName(packageName);
                mPackageDef.setRunner(runnerName);
                mPackageDef.setIsHostSideTest(parseBoolean(hostSideTest));
                mPackageDef.setJarPath(jarPath);
                mPackageDef.setIsSignatureCheck(parseBoolean(signatureCheck));
                mPackageDef.setIsReferenceApp(parseBoolean(referenceApp));

                // reset the class name
                mClassNameStack = new Stack<String>();
            } else if (TEST_SUITE_TAG.equals(localName)) {
                String packageSegment = attributes.getValue("name");
                if (packageSegment != null) {
                    mClassNameStack.push(packageSegment);
                } else {
                    Log.e(LOG_TAG, String.format("Invalid XML: missing 'name' attribute for '%s'",
                            TEST_SUITE_TAG));
                }
            } else if (TEST_CASE_TAG.equals(localName)) {
                String classSegment = attributes.getValue("name");
                if (classSegment != null) {
                    mClassNameStack.push(classSegment);
                } else {
                    Log.e(LOG_TAG, String.format("Invalid XML: missing 'name' attribute for '%s'",
                            TEST_CASE_TAG));
                }
            } else if (TEST_TAG.equals(localName)) {
                String methodName = attributes.getValue("name");
                if (mPackageDef == null) {
                    Log.e(LOG_TAG, String.format(
                            "Invalid XML: encountered a '%s' tag not enclosed within a '%s' tag",
                            TEST_TAG, TEST_PACKAGE_TAG));
                } else if (methodName == null) {
                    Log.e(LOG_TAG, String.format("Invalid XML: missing 'name' attribute for '%s'",
                            TEST_TAG));
                } else {
                    // build class name from package segments
                    StringBuilder classNameBuilder = new StringBuilder();
                    for (Iterator<String> iter = mClassNameStack.iterator(); iter.hasNext(); ) {
                        classNameBuilder.append(iter.next());
                        if (iter.hasNext()) {
                            classNameBuilder.append(".");
                        }
                    }
                    TestIdentifier testdef = new TestIdentifier(classNameBuilder.toString(),
                            methodName);
                    mPackageDef.addTest(testdef);
                }
            }

        }

        @Override
        public void endElement (String uri, String localName, String qName) throws SAXException {
            if (TEST_SUITE_TAG.equals(localName) || TEST_CASE_TAG.equals(localName)) {
                mClassNameStack.pop();
            }
        }

        /**
         * Parse a boolean attribute value
         */
        private boolean parseBoolean(final String stringValue) {
            return stringValue != null &&
                    Boolean.parseBoolean(stringValue);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected DefaultHandler createXmlHandler() {
        return new TestPackageHandler();
    }

    /**
     * @returns the {@link TestPackageDef} containing data parsed from xml or <code>null</code> if
     *          xml did not contain the correct information.
     */
    public TestPackageDef getTestPackageDef() {
        return mPackageDef;
    }
}
