/*
 * Copyright (C) 2019 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.compatibility.common.util;

import org.xmlpull.v1.XmlSerializer;

import java.io.IOException;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;

/**
 * Utility class to add test case result history to the report. This class records per-case history
 * for CTS Verifier. If this field is used for large test suites like CTS, it may cause performance
 * issues in APFE. Thus please do not use this class in other test suites.
 */
public class TestResultHistory implements Serializable {

    private static final long serialVersionUID = 10L;

    private static final String ENCODING = "UTF-8";
    private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";

    // XML constants
    private static final String SUB_TEST_ATTR = "subtest";
    private static final String RUN_HISTORY_TAG = "RunHistory";
    private static final String RUN_TAG = "Run";
    private static final String START_TIME_ATTR = "start";
    private static final String END_TIME_ATTR = "end";
    private static final String IS_AUTOMATED_ATTR = "isAutomated";

    private final String mTestName;
    private final Set<ExecutionRecord> mExecutionRecords;

    /**
     * Constructor of test result history.
     *
     * @param testName a string of test name.
     * @param executionRecords a Set of ExecutionRecords.
     */
    public TestResultHistory(String testName, Set<ExecutionRecord> executionRecords) {
        this.mTestName = testName;
        this.mExecutionRecords = executionRecords;
    }

    /** Get test name */
    public String getTestName() {
        return mTestName;
    }

    /** Get a set of ExecutionRecords. */
    public Set<ExecutionRecord> getExecutionRecords() {
        return mExecutionRecords;
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        TestResultHistory that = (TestResultHistory) o;
        return Objects.equals(mTestName, that.mTestName)
                && Objects.equals(mExecutionRecords, that.mExecutionRecords);
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        return Objects.hash(mTestName, mExecutionRecords);
    }

    /**
     * Serializes a given {@link TestResultHistory} to XML.
     *
     * @param serializer given serializer.
     * @param resultHistory test result history with test name and execution record.
     * @param testName top-level test name.
     * @throws IOException
     */
    public static void serialize(
            XmlSerializer serializer, TestResultHistory resultHistory, String testName)
            throws IOException {
        if (resultHistory == null) {
            throw new IllegalArgumentException("Test result history was null");
        }

        serializer.startTag(null, RUN_HISTORY_TAG);
        // Only show sub-test names in test attribute in run history node.
        String name = getSubTestName(testName, resultHistory.getTestName());
        if (!name.isEmpty() && !name.equalsIgnoreCase(testName)) {
            serializer.attribute(null, SUB_TEST_ATTR, name);
        }

        for (ExecutionRecord execRecord : resultHistory.getExecutionRecords()) {
            serializer.startTag(null, RUN_TAG);
            serializer.attribute(null, START_TIME_ATTR, String.valueOf(execRecord.getStartTime()));
            serializer.attribute(null, END_TIME_ATTR, String.valueOf(execRecord.getEndTime()));
            serializer.attribute(
                    null, IS_AUTOMATED_ATTR, String.valueOf(execRecord.getIsAutomated()));
            serializer.endTag(null, RUN_TAG);
        }
        serializer.endTag(null, RUN_HISTORY_TAG);
    }

    /**
     * Get subtest name by replacing top-level test name.
     *
     * @param testName top-level test name.
     * @param fullTestName test name with the combination of top-level and subtest name.
     * @return subtest name without top-level test name
     */
    protected static String getSubTestName(String testName, String fullTestName) {
        // Handle test name with brackets, like [folded] as the suffix for foldable test plan.
        testName = testName.replace("[", "\\[").replace("]", "\\]");
        String subTestName = fullTestName.replaceFirst(testName + ":", "");
        return subTestName;
    }

    /** Execution Record about start time, end time and isAutomated */
    public static class ExecutionRecord implements Serializable {

        private static final long serialVersionUID = 0L;
        // Start time of test case.
        private final long startTime;
        // End time of test case.
        private final long endTime;
        // Whether test case was executed through automation.
        private final boolean isAutomated;

        public ExecutionRecord(long startTime, long endTime, boolean isAutomated) {
            this.startTime = startTime;
            this.endTime = endTime;
            this.isAutomated = isAutomated;
        }

        public long getStartTime() {
            return startTime;
        }

        public long getEndTime() {
            return endTime;
        }

        public boolean getIsAutomated() {
            return isAutomated;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            ExecutionRecord that = (ExecutionRecord) o;
            return startTime == that.startTime
                    && endTime == that.endTime
                    && isAutomated == that.isAutomated;
        }

        @Override
        public int hashCode() {
            return Objects.hash(startTime, endTime, isAutomated);
        }
    }
}
