/*
 * Copyright 2011 the original author or authors.
 *
 * 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.build.gradle.internal.test.report;

import static org.gradle.api.tasks.testing.TestResult.ResultType;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Custom ClassPageRenderer based on Gradle's ClassPageRenderer
 */
class ClassPageRenderer extends PageRenderer<ClassTestResults> {
    private final CodePanelRenderer codePanelRenderer = new CodePanelRenderer();

    ClassPageRenderer(ReportType reportType) {
        super(reportType);
    }

    @Override
    protected String getTitle() {
        return getModel().getTitle();
    }

    @Override
    protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException {
        htmlWriter.startElement("div").attribute("class", "breadcrumbs")
                .startElement("a").attribute("href", "index.html").characters("all").endElement()
                .characters(" > ")
                .startElement("a").attribute("href", String.format("%s.html", getResults().getPackageResults().getFilename(reportType))).characters(getResults().getPackageResults().getName()).endElement()
                .characters(String.format(" > %s", getResults().getSimpleName()))
        .endElement();
    }

    private void renderTests(SimpleHtmlWriter htmlWriter) throws IOException {
        htmlWriter.startElement("table")
                .startElement("thead")
                .startElement("tr")
                .startElement("th").characters("Test").endElement();

        // get all the results per device and per test name
        Map<String, Map<String, TestResult>> results = getResults().getTestResultsMap();

        // gather all devices.
        List<String> devices = Lists.newArrayList(results.keySet());
        Collections.sort(devices);

        for (String device : devices) {
            htmlWriter.startElement("th").characters(device).endElement();
        }
        htmlWriter.endElement().endElement(); // tr/thead

        // gather all tests
        Set<String> tests = Sets.newHashSet();
        for (Map<String, TestResult> deviceMap : results.values()) {
            tests.addAll(deviceMap.keySet());
        }
        List<String> sortedTests = Lists.newArrayList(tests);
        Collections.sort(sortedTests);

        for (String testName : sortedTests) {
            htmlWriter.startElement("tr").startElement("td").characters(testName).endElement();

            ResultType currentType = ResultType.SKIPPED;

            // loop for all devices to find this test and put its result
            for (String device : devices) {
                Map<String, TestResult> deviceMap = results.get(device);
                TestResult test = deviceMap.get(testName);
                if (test != null) {
                    htmlWriter.startElement("td").attribute("class", test.getStatusClass())
                            .characters(String.format("%s (%s)",
                                    test.getFormattedResultType(), test.getFormattedDuration()))
                            .endElement();

                    currentType = combineResultType(currentType, test.getResultType());
                } else {
                    htmlWriter.startElement("td").characters("not run ").endElement();
                }
            }

            // finally based on whether if a single test failed, set the class on the test name.
//todo            td.setAttribute("class", getStatusClass(currentType));

            htmlWriter.endElement(); //tr
        }
        htmlWriter.endElement(); // table
    }

    public static ResultType combineResultType(ResultType currentType, ResultType newType) {
        switch (currentType) {
            case SUCCESS:
                if (newType == ResultType.FAILURE) {
                    return newType;
                }

                return currentType;
            case FAILURE:
                return currentType;
            case SKIPPED:
                if (newType != ResultType.SKIPPED) {
                    return newType;
                }
                return currentType;
            default:
                throw new IllegalStateException();
        }
    }

    public String getStatusClass(ResultType resultType) {
        switch (resultType) {
            case SUCCESS:
                return "success";
            case FAILURE:
                return "failures";
            case SKIPPED:
                return "skipped";
            default:
                throw new IllegalStateException();
        }
    }

    private static final class TestPercent {
        int failed;
        int total;
        TestPercent(int failed, int total) {
            this.failed = failed;
            this.total = total;
        }

        boolean isFullFailure() {
            return failed == total;
        }
    }

    @Override
    protected void renderFailures(SimpleHtmlWriter htmlWriter) throws IOException {
        // get all the results per device and per test name
        Map<String, Map<String, TestResult>> results = getResults().getTestResultsMap();

        Map<String, TestPercent> testPassPercent = Maps.newHashMap();

        for (TestResult test : getResults().getFailures()) {
            String testName = test.getName();
            // compute the display name which will include the name of the device and how many
            // devices are impact so to not force counting.
            // If all devices, then we don't display all of them.
            // (The off chance that all devices fail the test with a different stack trace is slim)
            TestPercent percent = testPassPercent.get(testName);
            if (percent != null && percent.isFullFailure()) {
                continue;
            }

            if (percent == null) {
                int failed = 0;
                int total = 0;
                for (Map<String, TestResult> deviceMap : results.values()) {
                    TestResult testResult = deviceMap.get(testName);
                    if (testResult == null) {
                        continue;
                    }
                    ResultType resultType = testResult.getResultType();

                    if (resultType == ResultType.FAILURE) {
                        failed++;
                    }

                    if (resultType != ResultType.SKIPPED) {
                        total++;
                    }
                }

                percent = new TestPercent(failed, total);
                testPassPercent.put(testName, percent);
            }

            String name;
            if (percent.total == 1) {
                name = testName;
            } else if (percent.isFullFailure()) {
                name = testName + " [all devices]";
            } else {
                name = String.format("%s [%s] (on %d/%d devices)", testName, test.getDevice(),
                        percent.failed, percent.total);
            }

            htmlWriter.startElement("div").attribute("class", "test")
                .startElement("a").attribute("name", test.getId().toString()).characters("").endElement() //browsers dont understand <a name="..."/>
                    .startElement("h3").attribute("class", test.getStatusClass()).characters(name).endElement();
            for (TestResult.TestFailure failure : test.getFailures()) {
                codePanelRenderer.render(failure.getStackTrace(), htmlWriter);
            }
            htmlWriter.endElement();
        }
    }

    @Override
    protected void registerTabs() {
        addFailuresTab();
        addTab("Tests", new ErroringAction<SimpleHtmlWriter>() {
            @Override
            public void doExecute(SimpleHtmlWriter writer) throws IOException {
                renderTests(writer);
            }
        });
        addDeviceAndVariantTabs();
    }
}
